From bcb92277117d0b2a2d1dba69081d908212f7cb0f Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 12:58:12 +0200 Subject: [PATCH 01/12] add rename_project() --- mergin/client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mergin/client.py b/mergin/client.py index 14ebe033..e3a07db9 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -1127,3 +1127,28 @@ def has_writing_permissions(self, project_path): """ info = self.project_info(project_path) return info["permissions"]["upload"] + + def rename_project(self, project_path, new_project_name): + """ + Rename project on server. + + :param project_path: Project's full name (/) + :type project_path: String + :param new_project_name: Project's name () + :type new_project_name: String + """ + # TODO: this version check should be replaced by checking against the list + # of endpoints that server publishes in /config (once implemented) + if not is_version_acceptable(self.server_version(), "2023.5.4"): + raise NotImplementedError("This needs server at version 2023.5.4 or later") + + project_info = self.project_info(project_path) + project_id = project_info["id"] + path = "/v2/projects/" + project_id + url = urllib.parse.urljoin(self.url, urllib.parse.quote(path)) + json_headers = {"Content-Type": "application/json"} + data = {"name": new_project_name} + request = urllib.request.Request( + url, data=json.dumps(data).encode("utf-8"), headers=json_headers, method="PATCH" + ) + self._do_request(request) From 36961f8e4e6d1569576f771bfb96df327f240c7a Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 12:58:22 +0200 Subject: [PATCH 02/12] test for rename_project --- mergin/test/test_client.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 36cbd0d2..f0902d9d 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -2038,3 +2038,51 @@ def test_project_metadata(mc): assert mp.project_name() == test_project assert mp.workspace_name() == API_USER assert mp.version() == "v0" + + +def test_project_rename(mc: MerginClient): + """Check project can be renamed""" + + test_project = "test_project_rename" + test_project_renamed = "test_project_renamed" + project = API_USER + "/" + test_project + project_renamed = API_USER + "/" + test_project_renamed + + project_dir = os.path.join(TMP_DIR, test_project) # primary project dir + + cleanup(mc, project, [project_dir]) + cleanup(mc, project_renamed, []) + + shutil.copytree(TEST_DATA_DIR, project_dir) + mc.create_project_and_push(project, project_dir) + + # renamed project does not exist + with pytest.raises(ClientError, match="The requested URL was not found on the server"): + info = mc.project_info(project_renamed) + + # rename + mc.rename_project(project, test_project_renamed) + + # validate project info + project_info = mc.project_info(project_renamed) + assert project_info["version"] == "v1" + assert project_info["name"] == test_project_renamed + assert project_info["namespace"] == API_USER + + # recreate project + cleanup(mc, project, [project_dir]) + shutil.copytree(TEST_DATA_DIR, project_dir) + mc.create_project_and_push(project, project_dir) + + # rename to existing name - created previously + mc.project_info(project_renamed) + with pytest.raises(ClientError, match="Entered project name is invalid"): + mc.rename_project(project, project_renamed) + + # cannot rename project that does not exist + with pytest.raises(ClientError, match="The requested URL was not found on the server."): + mc.rename_project(API_USER + "/" + "non_existing_project", API_USER + "/" + "new_project") + + # cannot rename with full project name + with pytest.raises(ClientError, match="Entered project name is invalid"): + mc.rename_project(project, "workspace" + "/" + test_project_renamed) From fcc1d0961e43bc28c78c9c76cc2f1fd3b2dc0240 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 14:13:57 +0200 Subject: [PATCH 03/12] raise error if new name contains "/" --- mergin/client.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index e3a07db9..9af10470 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -1128,13 +1128,13 @@ def has_writing_permissions(self, project_path): info = self.project_info(project_path) return info["permissions"]["upload"] - def rename_project(self, project_path, new_project_name): + def rename_project(self, project_path: str, new_project_name: str) -> None: """ Rename project on server. :param project_path: Project's full name (/) :type project_path: String - :param new_project_name: Project's name () + :param new_project_name: Project's new name () :type new_project_name: String """ # TODO: this version check should be replaced by checking against the list @@ -1142,6 +1142,11 @@ def rename_project(self, project_path, new_project_name): if not is_version_acceptable(self.server_version(), "2023.5.4"): raise NotImplementedError("This needs server at version 2023.5.4 or later") + if "/" in new_project_name: + raise ClientError( + "Project's new name should be without workspace specification (). Project can only be renamed within its current workspace." + ) + project_info = self.project_info(project_path) project_id = project_info["id"] path = "/v2/projects/" + project_id From 006a783ec12de8ab9f42db847836669a6e775bdb Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 14:14:14 +0200 Subject: [PATCH 04/12] fix tests - error messages --- mergin/test/test_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index f0902d9d..c3398051 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -2076,13 +2076,13 @@ def test_project_rename(mc: MerginClient): # rename to existing name - created previously mc.project_info(project_renamed) - with pytest.raises(ClientError, match="Entered project name is invalid"): - mc.rename_project(project, project_renamed) + with pytest.raises(ClientError, match="Name already exist within workspace"): + mc.rename_project(project, test_project_renamed) # cannot rename project that does not exist with pytest.raises(ClientError, match="The requested URL was not found on the server."): - mc.rename_project(API_USER + "/" + "non_existing_project", API_USER + "/" + "new_project") + mc.rename_project(API_USER + "/" + "non_existing_project", "new_project") # cannot rename with full project name - with pytest.raises(ClientError, match="Entered project name is invalid"): + with pytest.raises(ClientError, match="Project's new name should be without workspace specification"): mc.rename_project(project, "workspace" + "/" + test_project_renamed) From 8a4b4ba35718d196097734e85aac7b11d0e74b87 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 14:50:58 +0200 Subject: [PATCH 05/12] add cli rename_project --- mergin/cli.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/mergin/cli.py b/mergin/cli.py index c127fb3b..7ad7b7df 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -602,5 +602,40 @@ def resolve_unfinished_pull(ctx): _print_unhandled_exception() +@cli.command() +@click.argument("project_path") +@click.argument("new_project_name") +@click.pass_context +def rename_project(ctx, project_path: str, new_project_name: str): + """Rename project in Mergin Maps repository.""" + mc = ctx.obj["client"] + if mc is None: + return + + if "/" not in project_path: + click.secho(f"Specify `project_path` as full name (/) instead.", fg="red") + return + + if "/" in new_project_name: + old_workspace, old_project_name = project_path.split("/") + new_workspace, new_project_name = new_project_name.split("/") + + if old_workspace != new_workspace: + click.secho( + "`new_project_name` should not contain namespace, project can only be rename within their namespace.\nTo move project to another workspace use web dashboard.", + fg="red", + ) + return + + try: + mc.rename_project(project_path, new_project_name) + click.echo("Project renamed") + except ClientError as e: + click.secho("Error: " + str(e), fg="red") + except Exception as e: + _print_unhandled_exception() + + + if __name__ == "__main__": cli() From 6d9d37c8eb8cce1e74354468d9671ceebaf9cc14 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 15:08:13 +0200 Subject: [PATCH 06/12] add reset_local_changes --- mergin/cli.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mergin/cli.py b/mergin/cli.py index 7ad7b7df..faebc82c 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -636,6 +636,21 @@ def rename_project(ctx, project_path: str, new_project_name: str): _print_unhandled_exception() +@cli.command() +@click.pass_context +def reset_local_changes(ctx): + """Rename project in Mergin Maps repository.""" + directory = os.getcwd() + mc: MerginClient = ctx.obj["client"] + if mc is None: + return + try: + mc.reset_local_changes(directory) + except InvalidProject as e: + click.secho("Invalid project directory ({})".format(str(e)), fg="red") + except ClientError as e: + click.secho("Error: " + str(e), fg="red") + if __name__ == "__main__": cli() From b4956b4baf187c146f8ead64c61f6562340a336c Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 26 Oct 2023 15:22:17 +0200 Subject: [PATCH 07/12] add general exception --- mergin/cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mergin/cli.py b/mergin/cli.py index faebc82c..d8bc5aeb 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -650,6 +650,8 @@ def reset_local_changes(ctx): click.secho("Invalid project directory ({})".format(str(e)), fg="red") except ClientError as e: click.secho("Error: " + str(e), fg="red") + except Exception as e: + _print_unhandled_exception() if __name__ == "__main__": From 79aa5828843478bdb7498740133c59d4ecbb4c66 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 27 Oct 2023 14:14:36 +0200 Subject: [PATCH 08/12] rename --- mergin/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mergin/cli.py b/mergin/cli.py index d8bc5aeb..0956acb2 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -606,7 +606,7 @@ def resolve_unfinished_pull(ctx): @click.argument("project_path") @click.argument("new_project_name") @click.pass_context -def rename_project(ctx, project_path: str, new_project_name: str): +def rename(ctx, project_path: str, new_project_name: str): """Rename project in Mergin Maps repository.""" mc = ctx.obj["client"] if mc is None: From 7857381708eb690d3115ae97adc90a159d8b5f41 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 27 Oct 2023 14:16:01 +0200 Subject: [PATCH 09/12] test that original project does not exist --- mergin/test/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index da3d4335..37a37d2f 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -2189,6 +2189,8 @@ def test_project_rename(mc: MerginClient): assert project_info["version"] == "v1" assert project_info["name"] == test_project_renamed assert project_info["namespace"] == API_USER + with pytest.raises(ClientError, match="The requested URL was not found on the server"): + mc.project_info(project) # recreate project cleanup(mc, project, [project_dir]) From a601b0827825bc8346ddd0ea1213917c01c8bcea Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 27 Oct 2023 14:17:08 +0200 Subject: [PATCH 10/12] rename project --- mergin/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mergin/cli.py b/mergin/cli.py index 0956acb2..2d54b2b8 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -638,8 +638,8 @@ def rename(ctx, project_path: str, new_project_name: str): @cli.command() @click.pass_context -def reset_local_changes(ctx): - """Rename project in Mergin Maps repository.""" +def reset(ctx): + """Reset local changes Mergin Maps repository.""" directory = os.getcwd() mc: MerginClient = ctx.obj["client"] if mc is None: From 6c42c77d6778d47867cb9e01aed43f2632d501b3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 27 Oct 2023 14:19:13 +0200 Subject: [PATCH 11/12] fix docstring --- mergin/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mergin/cli.py b/mergin/cli.py index 2d54b2b8..5607b51a 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -639,7 +639,7 @@ def rename(ctx, project_path: str, new_project_name: str): @cli.command() @click.pass_context def reset(ctx): - """Reset local changes Mergin Maps repository.""" + """Reset local changes in project.""" directory = os.getcwd() mc: MerginClient = ctx.obj["client"] if mc is None: From 3faaa444435ed997536a8e956a36eabd2564fc04 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 27 Oct 2023 14:20:27 +0200 Subject: [PATCH 12/12] add cli commands --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4a714625..5cfda22c 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ Commands: pull Fetch changes from Mergin Maps repository push Upload local changes into Mergin Maps repository remove Remove project from server. + rename Rename project in Mergin Maps repository. + reset Reset local changes in project. share Fetch permissions to project share-add Add permissions to [users] to project share-remove Remove [users] permissions from project