From 8b5cf6d373f57d723e20287ba7f205ec6f597679 Mon Sep 17 00:00:00 2001 From: DW Talton Date: Wed, 9 Oct 2019 11:26:31 -0700 Subject: [PATCH] Add support for RTD subprojects Add new rtd subprojects commands to allow for subproject CRUD operations. Signed-off-by: DW Talton Change-Id: I9004a1e9a38dd939d3752dfb9b2efb3ba366d113 --- lftools/api/endpoints/readthedocs.py | 84 ++++++++++++++++++++++ lftools/cli/rtd.py | 55 ++++++++++++++ .../notes/readthedocs-f718039153d37377.yaml | 4 ++ tests/fixtures/rtd/subproject_create.json | 3 + tests/fixtures/rtd/subproject_details.json | 5 ++ tests/fixtures/rtd/subproject_list.json | 10 +++ tests/test_rtd.py | 36 ++++++++++ 7 files changed, 197 insertions(+) create mode 100644 releasenotes/notes/readthedocs-f718039153d37377.yaml create mode 100644 tests/fixtures/rtd/subproject_create.json create mode 100644 tests/fixtures/rtd/subproject_details.json create mode 100644 tests/fixtures/rtd/subproject_list.json diff --git a/lftools/api/endpoints/readthedocs.py b/lftools/api/endpoints/readthedocs.py index da663254..bc501fc3 100644 --- a/lftools/api/endpoints/readthedocs.py +++ b/lftools/api/endpoints/readthedocs.py @@ -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 diff --git a/lftools/cli/rtd.py b/lftools/cli/rtd.py index e50a9c6f..cd9579e8 100644 --- a/lftools/cli/rtd.py +++ b/lftools/cli/rtd.py @@ -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 index 00000000..7e9268c0 --- /dev/null +++ b/releasenotes/notes/readthedocs-f718039153d37377.yaml @@ -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 index 00000000..ffbc0cbd --- /dev/null +++ b/tests/fixtures/rtd/subproject_create.json @@ -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 index 00000000..539721d2 --- /dev/null +++ b/tests/fixtures/rtd/subproject_details.json @@ -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 index 00000000..a114d1d5 --- /dev/null +++ b/tests/fixtures/rtd/subproject_list.json @@ -0,0 +1,10 @@ +{ + "next": null, + "results": [ + { + "child": { + "slug": "testproject2" + } + } + ] +} diff --git a/tests/test_rtd.py b/tests/test_rtd.py index df04c381..cf4b51a9 100644 --- a/tests/test_rtd.py +++ b/tests/test_rtd.py @@ -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" -- 2.16.6