Add support for RTD subprojects 54/61954/1 v0.27.1
authorDW Talton <dtalton@contractor.linuxfoundation.org>
Wed, 9 Oct 2019 18:26:31 +0000 (11:26 -0700)
committerDW Talton <dtalton@contractor.linuxfoundation.org>
Wed, 9 Oct 2019 18:26:31 +0000 (11:26 -0700)
Add new rtd subprojects commands to allow for subproject CRUD operations.

Signed-off-by: DW Talton <dtalton@contractor.linuxfoundation.org>
Change-Id: I9004a1e9a38dd939d3752dfb9b2efb3ba366d113

lftools/api/endpoints/readthedocs.py
lftools/cli/rtd.py
releasenotes/notes/readthedocs-f718039153d37377.yaml [new file with mode: 0644]
tests/fixtures/rtd/subproject_create.json [new file with mode: 0644]
tests/fixtures/rtd/subproject_details.json [new file with mode: 0644]
tests/fixtures/rtd/subproject_list.json [new file with mode: 0644]
tests/test_rtd.py

index da66325..bc501fc 100644 (file)
@@ -212,3 +212,87 @@ class ReadTheDocs(client.RestApi):
         result = self.post('projects/{}/versions/{}/builds/'
                            .format(project, version))[1]
         return result
+
+    def subproject_list(self, project):
+        """Return a list of subprojects.
+
+        This returns the list of subprojects by their slug name ['slug'],
+        not their pretty name ['name'].
+
+        :param kwargs:
+        :return: [subprojects]
+        """
+        result = self.get('projects/{}/subprojects/'.format(project))[1]  # NOQA
+        more_results = None
+        data = result['results']
+        subproject_list = []
+
+        if result['next']:
+            more_results = result['next'].rsplit('/', 1)[-1]
+
+        if more_results:
+            while more_results is not None:
+                get_more_results = self.get('projects/{}/subprojects/'
+                                            .format(project) + more_results)[1]
+                data.append(get_more_results['results'])
+                more_results = get_more_results['next']
+
+                if more_results is not None:
+                    more_results = more_results.rsplit('/', 1)[-1]
+
+        for subproject in data:
+            subproject_list.append(subproject['child']['slug'])
+
+        return subproject_list
+
+    def subproject_details(self, project, subproject):
+        """Retrieve the details of a specific subproject.
+
+        :param project:
+        :param subproject:
+        :return:
+        """
+        result = self.get('projects/{}/subprojects/{}/'
+                          .format(project, subproject))[1]
+        return result
+
+    def subproject_create(self, project, subproject, alias=None):
+        """Create a subproject.
+
+        Subprojects are actually just top-level projects that
+        get subordinated to another project. Create the subproject
+        using project_create, then make it a subproject with
+        this function.
+
+        :param project: The top-level project's slug
+        :param subproject: The other project's slug that is to be subordinated
+        :param alias: An alias (not required). (user-defined slug)
+        :return:
+        """
+
+        data = {
+            'child': subproject,
+            'alias': alias
+        }
+        json_data = json.dumps(data)
+        result = self.post('projects/{}/subprojects/'
+                           .format(project), data=json_data)
+        return result
+
+    def subproject_delete(self, project, subproject):
+        """Deletes the project/sub relationship.
+
+        :param project:
+        :param subproject:
+        :return:
+        """
+        result = self.delete('projects/{}/subprojects/{}/'
+                             .format(project, subproject))
+
+        if hasattr(result, 'status_code'):
+            if result.status_code == 204:
+                return True
+            else:
+                return False, result.status_code
+        else:
+            return False
index e50a9c6..cd9579e 100644 (file)
@@ -124,6 +124,57 @@ def project_build_trigger(ctx, project_slug, version_slug):
     log.info(pformat(data))
 
 
+@click.command(name='subproject-list')
+@click.argument('project-slug')
+@click.pass_context
+def subproject_list(ctx, project_slug):
+    """Get a list of Read the Docs subprojects for a project.
+
+    Returns a list of RTD subprojects for a given
+    project.
+    """
+    r = readthedocs.ReadTheDocs()
+    for subproject in r.subproject_list(project_slug):
+        log.info(subproject)
+
+
+@click.command(name='subproject-details')
+@click.argument('project-slug')
+@click.argument('subproject-slug')
+@click.pass_context
+def subproject_details(ctx, project_slug, subproject_slug):
+    """Retrieve subproject's details."""
+    r = readthedocs.ReadTheDocs()
+    data = r.subproject_details(project_slug, subproject_slug)
+    log.info(pformat(data))
+
+
+@click.command(name='subproject-create')
+@click.argument('project-slug')
+@click.argument('subproject-slug')
+@click.pass_context
+def subproject_create(ctx, project_slug, subproject_slug):
+    """Creates a project-subproject relationship."""
+    r = readthedocs.ReadTheDocs()
+    data = r.subproject_create(project_slug, subproject_slug)
+    log.info(pformat(data))
+
+
+@click.command(name='subproject-delete')
+@click.argument('project-slug')
+@click.argument('subproject-slug')
+@click.pass_context
+def subproject_delete(ctx, project_slug, subproject_slug):
+    """Deletes a project-subproject relationship."""
+    r = readthedocs.ReadTheDocs()
+    data = r.subproject_delete(project_slug, subproject_slug)
+    if data:
+        log.info("Successfully removed the {} {} relationship"
+                 .format(project_slug, subproject_slug))
+    else:
+        log.error("Request failed. Is there a subproject relationship?")
+
+
 rtd.add_command(project_list)
 rtd.add_command(project_details)
 rtd.add_command(project_version_list)
@@ -132,3 +183,7 @@ rtd.add_command(project_create)
 rtd.add_command(project_build_list)
 rtd.add_command(project_build_details)
 rtd.add_command(project_build_trigger)
+rtd.add_command(subproject_list)
+rtd.add_command(subproject_details)
+rtd.add_command(subproject_create)
+rtd.add_command(subproject_delete)
diff --git a/releasenotes/notes/readthedocs-f718039153d37377.yaml b/releasenotes/notes/readthedocs-f718039153d37377.yaml
new file mode 100644 (file)
index 0000000..7e9268c
--- /dev/null
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add support for RTD subprojects, including list, details, create, delete.
diff --git a/tests/fixtures/rtd/subproject_create.json b/tests/fixtures/rtd/subproject_create.json
new file mode 100644 (file)
index 0000000..ffbc0cb
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "child":"testproject2"
+}
diff --git a/tests/fixtures/rtd/subproject_details.json b/tests/fixtures/rtd/subproject_details.json
new file mode 100644 (file)
index 0000000..539721d
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "child": {
+        "slug": "testproject2"
+    }
+}
diff --git a/tests/fixtures/rtd/subproject_list.json b/tests/fixtures/rtd/subproject_list.json
new file mode 100644 (file)
index 0000000..a114d1d
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "next": null,
+    "results": [
+        {
+            "child": {
+                "slug": "testproject2"
+            }
+        }
+    ]
+}
index df04c38..cf4b51a 100644 (file)
@@ -138,3 +138,39 @@ def test_project_build_trigger():
                   url='https://readthedocs.org/api/v3/projects/testproject1/versions/latest/builds/', # noqa
                   json=data, status=201)
     assert rtd.project_build_trigger('testproject1', 'latest')
+
+
+@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'rtd'),)
+@responses.activate
+def test_subproject_list(datafiles):
+    os.chdir(str(datafiles))
+    json_file = open('subproject_list.json', 'r')
+    json_data = json.loads(json_file.read())
+    responses.add(responses.GET,
+                  url='https://readthedocs.org/api/v3/projects/TestProject1/subprojects/',  # noqa
+                  json=json_data, status=200, match_querystring=True)
+    assert 'testproject2' in rtd.subproject_list('TestProject1')
+
+
+@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'rtd'),)
+@responses.activate
+def test_subproject_details(datafiles):
+    os.chdir(str(datafiles))
+    json_file = open('subproject_details.json', 'r')
+    json_data = json.loads(json_file.read())
+    responses.add(responses.GET,
+                  url='https://readthedocs.org/api/v3/projects/TestProject1/subprojects/testproject2/',  # NOQA
+                  json=json_data, status=200)
+    assert 'child' in rtd.subproject_details('TestProject1', 'testproject2')
+
+
+@responses.activate
+def test_subproject_create():
+    responses.add(responses.POST,
+                  url='https://readthedocs.org/api/v3/projects/TestProject1/subprojects/',  # NOQA
+                  status=201)
+    assert rtd.subproject_create('TestProject1', 'testproject2')
+
+
+def test_subproject_delete():
+    assert "untested because responses doesn't have DELETE support"