From 143f7b011e1f711ece85a663641cd7f69323aea3 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 20 May 2024 10:31:04 +0200 Subject: [PATCH 1/9] Added editor project permission - unsupported operation set_project_access --- mergin/cli.py | 5 ++++- mergin/client.py | 23 ++++++++++++++++------- mergin/test/test_client.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/mergin/cli.py b/mergin/cli.py index 5607b51a..5e9054fa 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -320,8 +320,11 @@ def share(ctx, project): for username in access_list.get("writers"): if username not in access_list.get("owners"): click.echo("{:20}\t{:20}".format(username, "writer")) - for username in access_list.get("readers"): + for username in access_list.get("editors"): if username not in access_list.get("writers"): + click.echo("{:20}\t{:20}".format(username, "editor")) + for username in access_list.get("readers"): + if username not in access_list.get("editors"): click.echo("{:20}\t{:20}".format(username, "reader")) diff --git a/mergin/client.py b/mergin/client.py index 4acf8ffe..3045f559 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -761,8 +761,11 @@ def set_project_access(self, project_path, access): """ Updates access for given project. :param project_path: project full name (/) - :param access: dict -> list of str username we want to give access to + :param access: dict -> list of str username we want to give access to """ + if not is_version_acceptable(self.server_version(), "2024.4"): + raise NotImplementedError("This needs server at version 2024.4.0 or later") + if not self._user_info: raise Exception("Authentication required") @@ -784,7 +787,7 @@ def add_user_permissions_to_project(self, project_path, usernames, permission_le :param usernames: list of usernames to be granted specified permission level :param permission_level: string (reader, writer, owner) """ - if permission_level not in ["owner", "reader", "writer"]: + if permission_level not in ["owner", "reader", "writer", "editor"]: raise ClientError("Unsupported permission level") project_info = self.project_info(project_path) @@ -792,9 +795,11 @@ def add_user_permissions_to_project(self, project_path, usernames, permission_le for name in usernames: if permission_level == "owner": access.get("ownersnames").append(name) - if permission_level == "writer" or permission_level == "owner": + if permission_level in ("writer", "owner"): access.get("writersnames").append(name) - if permission_level == "writer" or permission_level == "owner" or permission_level == "reader": + if permission_level in ("writer", "owner", "editor"): + access.get("editorsnames").append(name) + if permission_level in ("writer", "owner", "editor", "reader"): access.get("readersnames").append(name) self.set_project_access(project_path, access) @@ -811,6 +816,8 @@ def remove_user_permissions_from_project(self, project_path, usernames): access.get("ownersnames").remove(name) if name in access.get("writersnames"): access.get("writersnames").remove(name) + if name in access.get("editorsnames"): + access.get("editorsnames").remove(name) if name in access.get("readersnames"): access.get("readersnames").remove(name) self.set_project_access(project_path, access) @@ -821,14 +828,16 @@ def project_user_permissions(self, project_path): :param project_path: project full name (/) :return dict("owners": list(usernames), "writers": list(usernames), + "editors": list(usernames), "readers": list(usernames)) """ project_info = self.project_info(project_path) access = project_info.get("access") result = {} - result["owners"] = access.get("ownersnames") - result["writers"] = access.get("writersnames") - result["readers"] = access.get("readersnames") + result["owners"] = access.get("ownersnames", []) + result["writers"] = access.get("writersnames", []) + result["editors"] = access.get("editorsnames", []) + result["readers"] = access.get("readersnames", []) return result def push_project(self, directory): diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 087c1b19..06932ee7 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -657,6 +657,7 @@ def test_set_read_write_access(mc): project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] access["writersnames"].append(API_USER2) + access["editorsnames"].append(API_USER2) access["readersnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) @@ -664,8 +665,34 @@ def test_set_read_write_access(mc): project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] assert API_USER2 in access["writersnames"] + assert API_USER2 in access["editorsnames"] assert API_USER2 in access["readersnames"] +def test_set_editor_access(mc): + test_project = "test_set_editor_access" + test_project_fullname = API_USER + "/" + test_project + + # cleanups + project_dir = os.path.join(TMP_DIR, test_project, API_USER) + cleanup(mc, test_project_fullname, [project_dir]) + + # create new (empty) project on server + mc.create_project(test_project) + + # Add writer access to another client + project_info = get_project_info(mc, API_USER, test_project) + access = project_info["access"] + access["editorsnames"].append(API_USER2) + access["readersnames"].append(API_USER2) + mc.set_project_access(test_project_fullname, access) + + # check access + project_info = get_project_info(mc, API_USER, test_project) + access = project_info["access"] + assert API_USER2 in access["editorsnames"] + assert API_USER2 in access["readersnames"] + assert API_USER2 not in access["writersnames"] + def test_available_storage_validation(mc): """ @@ -1102,18 +1129,21 @@ def test_modify_project_permissions(mc): permissions = mc.project_user_permissions(project) assert permissions["owners"] == [API_USER] assert permissions["writers"] == [API_USER] + assert permissions["editors"] == [API_USER] assert permissions["readers"] == [API_USER] mc.add_user_permissions_to_project(project, [API_USER2], "writer") permissions = mc.project_user_permissions(project) assert set(permissions["owners"]) == {API_USER} assert set(permissions["writers"]) == {API_USER, API_USER2} + assert set(permissions["editors"]) == {API_USER, API_USER2} assert set(permissions["readers"]) == {API_USER, API_USER2} mc.remove_user_permissions_from_project(project, [API_USER2]) permissions = mc.project_user_permissions(project) assert permissions["owners"] == [API_USER] assert permissions["writers"] == [API_USER] + assert permissions["editors"] == [API_USER] assert permissions["readers"] == [API_USER] @@ -1839,7 +1869,7 @@ def test_report(mc): create_report(mc, directory, since, to, report_file) -def test_project_versions_list(mc, mc2): +def test_user_permissions(mc, mc2): """ Test retrieving user permissions """ From f103ab973d8e5712cd3666f760573d66fc9dd0f2 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 20 May 2024 12:41:27 +0200 Subject: [PATCH 2/9] Deal with editors compatibility with older servers --- mergin/client.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 3045f559..5d3a752a 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -761,11 +761,13 @@ def set_project_access(self, project_path, access): """ Updates access for given project. :param project_path: project full name (/) - :param access: dict -> list of str username we want to give access to + :param access: dict -> list of str username we want to give access to (editorsnames are only supported on servers at version 2024.4.0 or later) """ - if not is_version_acceptable(self.server_version(), "2024.4"): - raise NotImplementedError("This needs server at version 2024.4.0 or later") - + if "editorsnames" in access and not is_version_acceptable( + self.server_version(), "2024.4" + ): + raise NotImplementedError("Editors are only supported on servers at version 2024.4.0 or later") + if not self._user_info: raise Exception("Authentication required") @@ -785,9 +787,14 @@ def add_user_permissions_to_project(self, project_path, usernames, permission_le Add specified permissions to specified users to project :param project_path: project full name (/) :param usernames: list of usernames to be granted specified permission level - :param permission_level: string (reader, writer, owner) + :param permission_level: string (reader, editor, writer, owner) + + Editor permission_level is only supported on servers at version 2024.4.0 or later. """ - if permission_level not in ["owner", "reader", "writer", "editor"]: + if permission_level not in ["owner", "reader", "writer", "editor"] or ( + permission_level == "editor" + and not is_version_acceptable(self.server_version(), "2024.4") + ): raise ClientError("Unsupported permission level") project_info = self.project_info(project_path) @@ -797,7 +804,7 @@ def add_user_permissions_to_project(self, project_path, usernames, permission_le access.get("ownersnames").append(name) if permission_level in ("writer", "owner"): access.get("writersnames").append(name) - if permission_level in ("writer", "owner", "editor"): + if permission_level in ("writer", "owner", "editor") and "editorsnames" in access: access.get("editorsnames").append(name) if permission_level in ("writer", "owner", "editor", "reader"): access.get("readersnames").append(name) @@ -828,15 +835,17 @@ def project_user_permissions(self, project_path): :param project_path: project full name (/) :return dict("owners": list(usernames), "writers": list(usernames), - "editors": list(usernames), + "editors": list(usernames) - only on servers at version 2024.4.0 or later, "readers": list(usernames)) """ project_info = self.project_info(project_path) access = project_info.get("access") result = {} + if ("editorsnames" in access): + result["editors"] = access.get("editorsnames", []) + result["owners"] = access.get("ownersnames", []) result["writers"] = access.get("writersnames", []) - result["editors"] = access.get("editorsnames", []) result["readers"] = access.get("readersnames", []) return result From df7d0f601d3e2bb0fe7e9ce903cd97e0a1a15456 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Tue, 21 May 2024 10:07:07 +0200 Subject: [PATCH 3/9] FIx tests with <2024.4.0 servers (editor project access is ignored) --- mergin/client.py | 8 ++++---- mergin/test/test_client.py | 40 +++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 5d3a752a..139d25bb 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -819,13 +819,13 @@ def remove_user_permissions_from_project(self, project_path, usernames): project_info = self.project_info(project_path) access = project_info.get("access") for name in usernames: - if name in access.get("ownersnames"): + if name in access.get("ownersnames", []): access.get("ownersnames").remove(name) - if name in access.get("writersnames"): + if name in access.get("writersnames", []): access.get("writersnames").remove(name) - if name in access.get("editorsnames"): + if name in access.get("editorsnames", []): access.get("editorsnames").remove(name) - if name in access.get("readersnames"): + if name in access.get("readersnames", []): access.get("readersnames").remove(name) self.set_project_access(project_path, access) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 06932ee7..d0993318 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -24,6 +24,7 @@ from ..utils import ( generate_checksum, get_versions_with_file_changes, + is_version_acceptable, unique_path_name, conflicted_copy_file_name, edit_conflict_file_name, @@ -89,6 +90,17 @@ def sudo_works(): return sudo_res.returncode != 0 +def server_has_editor_support(access): + """ + Checks if the server has editor support based on the provided access information. + + Returns: + bool: True if the server has editor support, False otherwise. + """ + return "editorsnames" in access and not is_version_acceptable( + mc.server_version(), "2024.4" + ) + def test_login(mc): token = mc._auth_session["token"] assert MerginClient(mc.url, auth_token=token) @@ -657,16 +669,18 @@ def test_set_read_write_access(mc): project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] access["writersnames"].append(API_USER2) - access["editorsnames"].append(API_USER2) access["readersnames"].append(API_USER2) + editor_support = server_has_editor_support(access) + if editor_support: + access["editorsnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) - # check access project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] assert API_USER2 in access["writersnames"] - assert API_USER2 in access["editorsnames"] assert API_USER2 in access["readersnames"] + if editor_support: + assert API_USER2 in access["editorsnames"] def test_set_editor_access(mc): test_project = "test_set_editor_access" @@ -679,13 +693,14 @@ def test_set_editor_access(mc): # create new (empty) project on server mc.create_project(test_project) - # Add writer access to another client project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] - access["editorsnames"].append(API_USER2) + # Stop test if server does not support editor access + if not server_has_editor_support(access): + return + access["readersnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) - # check access project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] @@ -1129,23 +1144,30 @@ def test_modify_project_permissions(mc): permissions = mc.project_user_permissions(project) assert permissions["owners"] == [API_USER] assert permissions["writers"] == [API_USER] - assert permissions["editors"] == [API_USER] assert permissions["readers"] == [API_USER] + editor_support = server_has_editor_support(permissions) + if editor_support: + assert permissions["editors"] == [API_USER] mc.add_user_permissions_to_project(project, [API_USER2], "writer") permissions = mc.project_user_permissions(project) assert set(permissions["owners"]) == {API_USER} assert set(permissions["writers"]) == {API_USER, API_USER2} - assert set(permissions["editors"]) == {API_USER, API_USER2} assert set(permissions["readers"]) == {API_USER, API_USER2} + editor_support = server_has_editor_support(permissions) + if editor_support: + assert set(permissions["editors"]) == {API_USER, API_USER2} mc.remove_user_permissions_from_project(project, [API_USER2]) permissions = mc.project_user_permissions(project) assert permissions["owners"] == [API_USER] assert permissions["writers"] == [API_USER] - assert permissions["editors"] == [API_USER] assert permissions["readers"] == [API_USER] + editor_support = server_has_editor_support(permissions) + if editor_support: + assert permissions["editors"] == [API_USER] + def _use_wal(db_file): """Ensures that sqlite database is using WAL journal mode""" From 8e936954274da0fc98a74fa0c3e1b45e019fb1ba Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Tue, 21 May 2024 10:26:49 +0200 Subject: [PATCH 4/9] Fix condition for is_version_acceptable --- mergin/test/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index d0993318..8d7c6ae3 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -97,7 +97,7 @@ def server_has_editor_support(access): Returns: bool: True if the server has editor support, False otherwise. """ - return "editorsnames" in access and not is_version_acceptable( + return "editorsnames" in access and is_version_acceptable( mc.server_version(), "2024.4" ) From 7034f8058dee6b2dc26a4121c9e1883bd568b7bb Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Tue, 21 May 2024 11:14:50 +0200 Subject: [PATCH 5/9] Tests: add mc as param to server version --- mergin/test/test_client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 8d7c6ae3..c112cb45 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -90,7 +90,7 @@ def sudo_works(): return sudo_res.returncode != 0 -def server_has_editor_support(access): +def server_has_editor_support(mc, access): """ Checks if the server has editor support based on the provided access information. @@ -670,7 +670,7 @@ def test_set_read_write_access(mc): access = project_info["access"] access["writersnames"].append(API_USER2) access["readersnames"].append(API_USER2) - editor_support = server_has_editor_support(access) + editor_support = server_has_editor_support(mc, access) if editor_support: access["editorsnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) @@ -696,10 +696,11 @@ def test_set_editor_access(mc): project_info = get_project_info(mc, API_USER, test_project) access = project_info["access"] # Stop test if server does not support editor access - if not server_has_editor_support(access): + if not server_has_editor_support(mc, access): return access["readersnames"].append(API_USER2) + access["editorsnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) # check access project_info = get_project_info(mc, API_USER, test_project) @@ -1145,7 +1146,7 @@ def test_modify_project_permissions(mc): assert permissions["owners"] == [API_USER] assert permissions["writers"] == [API_USER] assert permissions["readers"] == [API_USER] - editor_support = server_has_editor_support(permissions) + editor_support = server_has_editor_support(mc, permissions) if editor_support: assert permissions["editors"] == [API_USER] @@ -1154,7 +1155,7 @@ def test_modify_project_permissions(mc): assert set(permissions["owners"]) == {API_USER} assert set(permissions["writers"]) == {API_USER, API_USER2} assert set(permissions["readers"]) == {API_USER, API_USER2} - editor_support = server_has_editor_support(permissions) + editor_support = server_has_editor_support(mc, permissions) if editor_support: assert set(permissions["editors"]) == {API_USER, API_USER2} @@ -1164,7 +1165,7 @@ def test_modify_project_permissions(mc): assert permissions["writers"] == [API_USER] assert permissions["readers"] == [API_USER] - editor_support = server_has_editor_support(permissions) + editor_support = server_has_editor_support(mc, permissions) if editor_support: assert permissions["editors"] == [API_USER] From 26acf3e8bb6df8d96e8893086fc025f8fa1f94a3 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Tue, 21 May 2024 11:30:37 +0200 Subject: [PATCH 6/9] Fix: add default values for share command --- mergin/cli.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/mergin/cli.py b/mergin/cli.py index 5e9054fa..2bb24164 100755 --- a/mergin/cli.py +++ b/mergin/cli.py @@ -315,16 +315,21 @@ def share(ctx, project): return access_list = mc.project_user_permissions(project) - for username in access_list.get("owners"): + owners = access_list.get("owners", []) + writers = access_list.get("writers", []) + editors = access_list.get("editors", []) + readers = access_list.get("readers", []) + + for username in owners: click.echo("{:20}\t{:20}".format(username, "owner")) - for username in access_list.get("writers"): - if username not in access_list.get("owners"): + for username in writers: + if username not in owners: click.echo("{:20}\t{:20}".format(username, "writer")) - for username in access_list.get("editors"): - if username not in access_list.get("writers"): + for username in editors: + if username not in writers: click.echo("{:20}\t{:20}".format(username, "editor")) - for username in access_list.get("readers"): - if username not in access_list.get("editors"): + for username in readers: + if username not in editors: click.echo("{:20}\t{:20}".format(username, "reader")) From 9d384759e792cfb535b2faf7c289bf616a190057 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Tue, 21 May 2024 14:33:25 +0200 Subject: [PATCH 7/9] Try black formating --- mergin/client.py | 9 +++------ mergin/test/test_client.py | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 139d25bb..50c9b6f1 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -763,9 +763,7 @@ def set_project_access(self, project_path, access): :param project_path: project full name (/) :param access: dict -> list of str username we want to give access to (editorsnames are only supported on servers at version 2024.4.0 or later) """ - if "editorsnames" in access and not is_version_acceptable( - self.server_version(), "2024.4" - ): + if "editorsnames" in access and not is_version_acceptable(self.server_version(), "2024.4"): raise NotImplementedError("Editors are only supported on servers at version 2024.4.0 or later") if not self._user_info: @@ -792,8 +790,7 @@ def add_user_permissions_to_project(self, project_path, usernames, permission_le Editor permission_level is only supported on servers at version 2024.4.0 or later. """ if permission_level not in ["owner", "reader", "writer", "editor"] or ( - permission_level == "editor" - and not is_version_acceptable(self.server_version(), "2024.4") + permission_level == "editor" and not is_version_acceptable(self.server_version(), "2024.4") ): raise ClientError("Unsupported permission level") @@ -841,7 +838,7 @@ def project_user_permissions(self, project_path): project_info = self.project_info(project_path) access = project_info.get("access") result = {} - if ("editorsnames" in access): + if "editorsnames" in access: result["editors"] = access.get("editorsnames", []) result["owners"] = access.get("ownersnames", []) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index c112cb45..b988ea1f 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -97,9 +97,7 @@ def server_has_editor_support(mc, access): Returns: bool: True if the server has editor support, False otherwise. """ - return "editorsnames" in access and is_version_acceptable( - mc.server_version(), "2024.4" - ) + return "editorsnames" in access and is_version_acceptable(mc.server_version(), "2024.4") def test_login(mc): token = mc._auth_session["token"] @@ -682,6 +680,7 @@ def test_set_read_write_access(mc): if editor_support: assert API_USER2 in access["editorsnames"] + def test_set_editor_access(mc): test_project = "test_set_editor_access" test_project_fullname = API_USER + "/" + test_project @@ -698,7 +697,8 @@ def test_set_editor_access(mc): # Stop test if server does not support editor access if not server_has_editor_support(mc, access): return - + + access["readersnames"].append(API_USER2) access["editorsnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) From beffab8b209563405a476b11bed33b649357dd8a Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 27 May 2024 15:21:39 +0200 Subject: [PATCH 8/9] Black with 2024.4.2 version --- .gitignore | 1 + mergin/test/test_client.py | 544 +++++++++++++++++++++++++++++-------- 2 files changed, 434 insertions(+), 111 deletions(-) diff --git a/.gitignore b/.gitignore index b46dd1cb..6b483114 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ htmlcov .pytest_cache deps venv +.vscode/settings.json diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index b988ea1f..9cd2d16d 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -12,7 +12,15 @@ import time from .. import InvalidProject -from ..client import MerginClient, ClientError, MerginProject, LoginError, decode_token_data, TokenError, ServerType +from ..client import ( + MerginClient, + ClientError, + MerginProject, + LoginError, + decode_token_data, + TokenError, + ServerType, +) from ..client_push import push_project_async, push_project_cancel from ..client_pull import ( download_project_async, @@ -40,7 +48,9 @@ USER_PWD2 = os.environ.get("TEST_API_PASSWORD2") TMP_DIR = tempfile.gettempdir() TEST_DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data") -CHANGED_SCHEMA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "modified_schema") +CHANGED_SCHEMA_DIR = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "modified_schema" +) @pytest.fixture(scope="function") @@ -58,7 +68,12 @@ def mc2(): def create_client(user, pwd): - assert SERVER_URL and SERVER_URL.rstrip("/") != "https://app.merginmaps.com" and user and pwd + assert ( + SERVER_URL + and SERVER_URL.rstrip("/") != "https://app.merginmaps.com" + and user + and pwd + ) return MerginClient(SERVER_URL, login=user, password=pwd) @@ -93,18 +108,23 @@ def sudo_works(): def server_has_editor_support(mc, access): """ Checks if the server has editor support based on the provided access information. - + Returns: bool: True if the server has editor support, False otherwise. """ - return "editorsnames" in access and is_version_acceptable(mc.server_version(), "2024.4") + return "editorsnames" in access and is_version_acceptable( + mc.server_version(), "2024.4" + ) + def test_login(mc): token = mc._auth_session["token"] assert MerginClient(mc.url, auth_token=token) invalid_token = "Completely invalid token...." - with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}"): + with pytest.raises( + TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}" + ): decode_token_data(invalid_token) invalid_token = "Bearer .jas646kgfa" @@ -125,7 +145,9 @@ def test_create_delete_project(mc: MerginClient): # create new (empty) project on server mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) # try again with pytest.raises(ClientError, match=f"already exists"): @@ -134,37 +156,53 @@ def test_create_delete_project(mc: MerginClient): # remove project mc.delete_project_now(API_USER + "/" + test_project) projects = mc.projects_list(flag="created") - assert not any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert not any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) # try again, nothing to delete with pytest.raises(ClientError): mc.delete_project_now(API_USER + "/" + test_project) # test that using namespace triggers deprecate warning, but creates project correctly - with pytest.deprecated_call(match=r"The usage of `namespace` parameter in `create_project\(\)` is deprecated."): + with pytest.deprecated_call( + match=r"The usage of `namespace` parameter in `create_project\(\)` is deprecated." + ): mc.create_project(test_project, namespace=API_USER) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) mc.delete_project_now(project) # test that using only project name triggers deprecate warning, but creates project correctly - with pytest.deprecated_call(match=r"The use of only project name in `create_project\(\)` is deprecated"): + with pytest.deprecated_call( + match=r"The use of only project name in `create_project\(\)` is deprecated" + ): mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) mc.delete_project_now(project) # test that even if project is specified with full name and namespace is specified a warning is raised, but still create project correctly - with pytest.warns(UserWarning, match="Parameter `namespace` specified with full project name"): + with pytest.warns( + UserWarning, match="Parameter `namespace` specified with full project name" + ): mc.create_project(project, namespace=API_USER) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) mc.delete_project_now(project) # test that create project with full name works mc.create_project(project) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) mc.delete_project_now(project) @@ -248,7 +286,10 @@ def test_push_pull_changes(mc): f_removed = "test.txt" os.remove(os.path.join(project_dir, f_removed)) f_renamed = "test_dir/test2.txt" - shutil.move(os.path.normpath(os.path.join(project_dir, f_renamed)), os.path.join(project_dir, "renamed.txt")) + shutil.move( + os.path.normpath(os.path.join(project_dir, f_renamed)), + os.path.join(project_dir, "renamed.txt"), + ) f_updated = "test3.txt" with open(os.path.join(project_dir, f_updated), "w") as f: f.write("Modified") @@ -276,13 +317,22 @@ def test_push_pull_changes(mc): assert not next((f for f in project_info["files"] if f["path"] == f_renamed), None) assert next((f for f in project_info["files"] if f["path"] == "renamed.txt"), None) assert next((f for f in project_info["files"] if f["path"] == f_added), None) - f_remote_checksum = next((f["checksum"] for f in project_info["files"] if f["path"] == f_updated), None) + f_remote_checksum = next( + (f["checksum"] for f in project_info["files"] if f["path"] == f_updated), None + ) assert generate_checksum(os.path.join(project_dir, f_updated)) == f_remote_checksum assert project_info["id"] == mp.project_id() assert len(project_info["files"]) == len(mp.inspect_files()) project_versions = mc.project_versions(project) assert len(project_versions) == 2 - f_change = next((f for f in project_versions[-1]["changes"]["updated"] if f["path"] == f_updated), None) + f_change = next( + ( + f + for f in project_versions[-1]["changes"]["updated"] + if f["path"] == f_updated + ), + None, + ) assert "origin_checksum" not in f_change # internal client info # test parallel changes @@ -307,12 +357,20 @@ def test_push_pull_changes(mc): assert not os.path.exists(os.path.join(project_dir_2, f_removed)) assert not os.path.exists(os.path.join(project_dir_2, f_renamed)) assert os.path.exists(os.path.join(project_dir_2, "renamed.txt")) - assert os.path.exists(os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1))) + assert os.path.exists( + os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1)) + ) assert ( - generate_checksum(os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1))) + generate_checksum( + os.path.join( + project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1) + ) + ) == f_conflict_checksum ) - assert generate_checksum(os.path.join(project_dir_2, f_updated)) == f_remote_checksum + assert ( + generate_checksum(os.path.join(project_dir_2, f_updated)) == f_remote_checksum + ) def test_cancel_push(mc): @@ -322,7 +380,9 @@ def test_cancel_push(mc): """ test_project = "test_cancel_push" project = API_USER + "/" + test_project - project_dir = os.path.join(TMP_DIR, test_project + "_3") # primary project dir for updates + project_dir = os.path.join( + TMP_DIR, test_project + "_3" + ) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_4") cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -368,23 +428,33 @@ def test_ignore_files(mc): cleanup(mc, project, [project_dir]) # create remote project shutil.copytree(TEST_DATA_DIR, project_dir) - shutil.copy(os.path.join(project_dir, "test.qgs"), os.path.join(project_dir, "test.qgs~")) + shutil.copy( + os.path.join(project_dir, "test.qgs"), os.path.join(project_dir, "test.qgs~") + ) mc.create_project_and_push(project, project_dir) project_info = mc.project_info(project) - assert not next((f for f in project_info["files"] if f["path"] == "test.qgs~"), None) + assert not next( + (f for f in project_info["files"] if f["path"] == "test.qgs~"), None + ) with open(os.path.join(project_dir, ".directory"), "w") as f: f.write("test") mc.push_project(project_dir) - assert not next((f for f in project_info["files"] if f["path"] == ".directory"), None) + assert not next( + (f for f in project_info["files"] if f["path"] == ".directory"), None + ) def test_sync_diff(mc): test_project = f"test_sync_diff" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates - project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir with no changes - project_dir_3 = os.path.join(TMP_DIR, test_project + "_3") # concurrent project dir with local changes + project_dir_2 = os.path.join( + TMP_DIR, test_project + "_2" + ) # concurrent project dir with no changes + project_dir_3 = os.path.join( + TMP_DIR, test_project + "_3" + ) # concurrent project dir with local changes cleanup(mc, project, [project_dir, project_dir_2, project_dir_3]) # create remote project @@ -399,10 +469,14 @@ def test_sync_diff(mc): mp = MerginProject(project_dir) f_updated = "base.gpkg" # step 1) base.gpkg updated to inserted_1_A (inserted A feature) - shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) # make local copy for changeset calculation + shutil.move( + mp.fpath(f_updated), mp.fpath_meta(f_updated) + ) # make local copy for changeset calculation shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) mc.push_project(project_dir) - mp.geodiff.create_changeset(mp.fpath(f_updated), mp.fpath_meta(f_updated), mp.fpath_meta("push_diff")) + mp.geodiff.create_changeset( + mp.fpath(f_updated), mp.fpath_meta(f_updated), mp.fpath_meta("push_diff") + ) assert not mp.geodiff.has_changes(mp.fpath_meta("push_diff")) # step 2) base.gpkg updated to inserted_1_A_mod (modified 2 features) shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) @@ -428,7 +502,9 @@ def test_sync_diff(mc): # pull project in different directory mp2 = MerginProject(project_dir_2) mc.pull_project(project_dir_2) - mp2.geodiff.create_changeset(mp.fpath(f_updated), mp2.fpath(f_updated), mp2.fpath_meta("diff")) + mp2.geodiff.create_changeset( + mp.fpath(f_updated), mp2.fpath(f_updated), mp2.fpath_meta("diff") + ) assert not mp2.geodiff.has_changes(mp2.fpath_meta("diff")) # introduce conflict local change (inserted B feature to base) @@ -441,13 +517,19 @@ def test_sync_diff(mc): # push new changes from project_3 and pull in original project mc.push_project(project_dir_3) mc.pull_project(project_dir) - mp3.geodiff.create_changeset(mp.fpath(f_updated), mp3.fpath(f_updated), mp.fpath_meta("diff")) + mp3.geodiff.create_changeset( + mp.fpath(f_updated), mp3.fpath(f_updated), mp.fpath_meta("diff") + ) assert not mp3.geodiff.has_changes(mp.fpath_meta("diff")) def test_list_of_push_changes(mc): PUSH_CHANGES_SUMMARY = { - "base.gpkg": {"geodiff_summary": [{"table": "simple", "insert": 1, "update": 0, "delete": 0}]} + "base.gpkg": { + "geodiff_summary": [ + {"table": "simple", "insert": 1, "update": 0, "delete": 0} + ] + } } test_project = "test_list_of_push_changes" @@ -462,7 +544,9 @@ def test_list_of_push_changes(mc): mp = MerginProject(project_dir) shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) - mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1) + mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta( + days=1 + ) pull_changes, push_changes, push_changes_summary = mc.project_status(project_dir) assert push_changes_summary == PUSH_CHANGES_SUMMARY @@ -477,7 +561,9 @@ def test_token_renewal(mc): shutil.copytree(TEST_DATA_DIR, project_dir) mc.create_project_and_push(project, project_dir) - mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1) + mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta( + days=1 + ) pull_changes, push_changes, _ = mc.project_status(project_dir) to_expire = mc._auth_session["expire"] - datetime.now().replace(tzinfo=pytz.utc) assert to_expire.total_seconds() > (9 * 3600) @@ -502,8 +588,13 @@ def test_force_gpkg_update(mc): shutil.move( mp.fpath(f_updated), mp.fpath_meta(f_updated) ) # make local copy for changeset calculation (which will fail) - shutil.copy(os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), mp.fpath(f_updated)) - shutil.copy(os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg-wal"), mp.fpath(f_updated + "-wal")) + shutil.copy( + os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), mp.fpath(f_updated) + ) + shutil.copy( + os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg-wal"), + mp.fpath(f_updated + "-wal"), + ) mc.push_project(project_dir) # by this point local file has been updated (changes committed from wal) updated_checksum = generate_checksum(mp.fpath(f_updated)) @@ -540,7 +631,11 @@ def test_new_project_sync(mc): # make sure everything is up-to-date mp = MerginProject(project_dir) local_changes = mp.get_push_changes() - assert not local_changes["added"] and not local_changes["removed"] and not local_changes["updated"] + assert ( + not local_changes["added"] + and not local_changes["removed"] + and not local_changes["updated"] + ) def test_missing_basefile_pull(mc): @@ -552,7 +647,9 @@ def test_missing_basefile_pull(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir - test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_project) + test_data_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), test_project + ) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -561,12 +658,18 @@ def test_missing_basefile_pull(mc): # update our gpkg in a different directory mc.download_project(project, project_dir_2) - shutil.copy(os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), os.path.join(project_dir_2, "base.gpkg")) + shutil.copy( + os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), + os.path.join(project_dir_2, "base.gpkg"), + ) mc.pull_project(project_dir_2) mc.push_project(project_dir_2) # make some other local change - shutil.copy(os.path.join(TEST_DATA_DIR, "inserted_1_B.gpkg"), os.path.join(project_dir, "base.gpkg")) + shutil.copy( + os.path.join(TEST_DATA_DIR, "inserted_1_B.gpkg"), + os.path.join(project_dir, "base.gpkg"), + ) # remove the basefile to simulate the issue os.remove(os.path.join(project_dir, ".mergin", "base.gpkg")) @@ -583,7 +686,9 @@ def test_empty_file_in_subdir(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir - test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_project) + test_data_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), test_project + ) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -596,7 +701,10 @@ def test_empty_file_in_subdir(mc): # add another empty file in a different subdir os.mkdir(os.path.join(project_dir, "subdir2")) - shutil.copy(os.path.join(project_dir, "subdir", "empty.txt"), os.path.join(project_dir, "subdir2", "empty2.txt")) + shutil.copy( + os.path.join(project_dir, "subdir", "empty.txt"), + os.path.join(project_dir, "subdir2", "empty2.txt"), + ) mc.push_project(project_dir) # check that pull works fine @@ -615,7 +723,9 @@ def test_clone_project(mc: MerginClient): # create new (empty) project on server mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) + assert any( + p for p in projects if p["name"] == test_project and p["namespace"] == API_USER + ) cloned_project_name = test_project + "_cloned" test_cloned_project_fullname = API_USER + "/" + cloned_project_name @@ -625,30 +735,52 @@ def test_clone_project(mc: MerginClient): cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone specifying cloned_project_namespace, does clone but raises deprecation warning - with pytest.deprecated_call(match=r"The usage of `cloned_project_namespace` parameter in `clone_project\(\)`"): + with pytest.deprecated_call( + match=r"The usage of `cloned_project_namespace` parameter in `clone_project\(\)`" + ): mc.clone_project(test_project_fullname, cloned_project_name, API_USER) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) + assert any( + p + for p in projects + if p["name"] == cloned_project_name and p["namespace"] == API_USER + ) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone without specifying cloned_project_namespace relies on workspace with user name, does clone but raises deprecation warning - with pytest.deprecated_call(match=r"The use of only project name as `cloned_project_name` in `clone_project\(\)`"): + with pytest.deprecated_call( + match=r"The use of only project name as `cloned_project_name` in `clone_project\(\)`" + ): mc.clone_project(test_project_fullname, cloned_project_name) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) + assert any( + p + for p in projects + if p["name"] == cloned_project_name and p["namespace"] == API_USER + ) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone project with full cloned project name with specification of `cloned_project_namespace` raises warning - with pytest.warns(match=r"Parameter `cloned_project_namespace` specified with full cloned project name"): + with pytest.warns( + match=r"Parameter `cloned_project_namespace` specified with full cloned project name" + ): mc.clone_project(test_project_fullname, test_cloned_project_fullname, API_USER) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) + assert any( + p + for p in projects + if p["name"] == cloned_project_name and p["namespace"] == API_USER + ) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone project using project full name mc.clone_project(test_project_fullname, test_cloned_project_fullname) projects = mc.projects_list(flag="created") - assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) + assert any( + p + for p in projects + if p["name"] == cloned_project_name and p["namespace"] == API_USER + ) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) @@ -698,7 +830,6 @@ def test_set_editor_access(mc): if not server_has_editor_support(mc, access): return - access["readersnames"].append(API_USER2) access["editorsnames"].append(API_USER2) mc.set_project_access(test_project_fullname, access) @@ -831,7 +962,9 @@ def get_project_info(mc, namespace, project_name): :return: dict with project info """ projects = mc.projects_list(flag="created") - test_project_list = [p for p in projects if p["name"] == project_name and p["namespace"] == namespace] + test_project_list = [ + p for p in projects if p["name"] == project_name and p["namespace"] == namespace + ] assert len(test_project_list) == 1 return test_project_list[0] @@ -848,7 +981,10 @@ def _generate_big_file(filepath, size): def test_get_projects_by_name(mc): """Test server 'bulk' endpoint for projects' info""" - test_projects = {"projectA": f"{API_USER}/projectA", "projectB": f"{API_USER}/projectB"} + test_projects = { + "projectA": f"{API_USER}/projectA", + "projectB": f"{API_USER}/projectB", + } for name, full_name in test_projects.items(): cleanup(mc, full_name, []) @@ -891,7 +1027,9 @@ def test_download_versions(mc): mc.download_project(project, project_dir_v2, "v2") assert os.path.exists(os.path.join(project_dir_v2, f_added)) - assert os.path.exists(os.path.join(project_dir_v1, "base.gpkg")) # added in v1 but still present in v2 + assert os.path.exists( + os.path.join(project_dir_v1, "base.gpkg") + ) # added in v1 but still present in v2 # try to download not-existing version with pytest.raises(ClientError): @@ -912,7 +1050,11 @@ def test_paginated_project_list(mc): sorted_test_names = [n for n in sorted(test_projects.keys())] resp = mc.paginated_projects_list( - flag="created", name="test_paginated", page=1, per_page=10, order_params="name_asc" + flag="created", + name="test_paginated", + page=1, + per_page=10, + order_params="name_asc", ) projects = resp["projects"] count = resp["count"] @@ -922,7 +1064,11 @@ def test_paginated_project_list(mc): assert project["name"] == sorted_test_names[i] resp = mc.paginated_projects_list( - flag="created", name="test_paginated", page=2, per_page=2, order_params="name_asc" + flag="created", + name="test_paginated", + page=2, + per_page=2, + order_params="name_asc", ) projects = resp["projects"] assert len(projects) == 2 @@ -936,9 +1082,13 @@ def test_missing_local_file_pull(mc): test_project = "test_dir" file_to_remove = "test2.txt" project = API_USER + "/" + test_project - project_dir = os.path.join(TMP_DIR, test_project + "_5") # primary project dir for updates + project_dir = os.path.join( + TMP_DIR, test_project + "_5" + ) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_6") # concurrent project dir - test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data", test_project) + test_data_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "test_data", test_project + ) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -980,7 +1130,9 @@ def test_server_compatibility(mc): assert mc.is_server_compatible() -def create_versioned_project(mc, project_name, project_dir, updated_file, remove=True, overwrite=False): +def create_versioned_project( + mc, project_name, project_dir, updated_file, remove=True, overwrite=False +): project = API_USER + "/" + project_name cleanup(mc, project, [project_dir]) @@ -1007,8 +1159,14 @@ def create_versioned_project(mc, project_name, project_dir, updated_file, remove # create version with forced overwrite (broken history) if overwrite: shutil.move(mp.fpath(updated_file), mp.fpath_meta(updated_file)) - shutil.copy(os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), mp.fpath(updated_file)) - shutil.copy(os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg-wal"), mp.fpath(updated_file + "-wal")) + shutil.copy( + os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), + mp.fpath(updated_file), + ) + shutil.copy( + os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg-wal"), + mp.fpath(updated_file + "-wal"), + ) mc.push_project(project_dir) return mp @@ -1020,7 +1178,9 @@ def test_get_versions_with_file_changes(mc): project_dir = os.path.join(TMP_DIR, test_project) f_updated = "base.gpkg" - mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False) + mp = create_versioned_project( + mc, test_project, project_dir, f_updated, remove=False + ) project_info = mc.project_info(project) assert project_info["version"] == "v4" @@ -1029,13 +1189,23 @@ def test_get_versions_with_file_changes(mc): with pytest.raises(ClientError) as e: mod_versions = get_versions_with_file_changes( - mc, project, f_updated, version_from="v1", version_to="v5", file_history=file_history + mc, + project, + f_updated, + version_from="v1", + version_to="v5", + file_history=file_history, ) assert "Wrong version parameters: 1-5" in str(e.value) assert "Available versions: [1, 2, 3, 4]" in str(e.value) mod_versions = get_versions_with_file_changes( - mc, project, f_updated, version_from="v2", version_to="v4", file_history=file_history + mc, + project, + f_updated, + version_from="v2", + version_to="v4", + file_history=file_history, ) assert mod_versions == [f"v{i}" for i in range(2, 5)] @@ -1062,13 +1232,19 @@ def test_download_file(mc): assert project_info["id"] == mp.project_id() # Versioned file should have the following content at versions 2-4 - expected_content = ("inserted_1_A.gpkg", "inserted_1_A_mod.gpkg", "inserted_1_B.gpkg") + expected_content = ( + "inserted_1_A.gpkg", + "inserted_1_A_mod.gpkg", + "inserted_1_B.gpkg", + ) # Download the base file at versions 2-4 and check the changes f_downloaded = os.path.join(project_dir, f_updated) for ver in range(2, 5): mc.download_file(project_dir, f_updated, f_downloaded, version=f"v{ver}") - expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content + expected = os.path.join( + TEST_DATA_DIR, expected_content[ver - 2] + ) # GeoPackage with expected content assert check_gpkg_same_content(mp, f_downloaded, expected) # make sure there will be exception raised if a file doesn't exist in the version @@ -1081,11 +1257,15 @@ def test_download_diffs(mc): test_project = "test_download_diffs" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) - download_dir = os.path.join(project_dir, "diffs") # project for downloading files at various versions + download_dir = os.path.join( + project_dir, "diffs" + ) # project for downloading files at various versions f_updated = "base.gpkg" diff_file = os.path.join(download_dir, f_updated + ".diff") - mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False) + mp = create_versioned_project( + mc, test_project, project_dir, f_updated, remove=False + ) project_info = mc.project_info(project) assert project_info["version"] == "v4" @@ -1192,11 +1372,15 @@ def _create_spatial_table(db_file): """Creates a spatial table called 'test' in sqlite database. Useful to simulate change of database schema.""" con = sqlite3.connect(db_file) cursor = con.cursor() - cursor.execute("CREATE TABLE geo_test (fid SERIAL, geometry POINT NOT NULL, txt TEXT);") + cursor.execute( + "CREATE TABLE geo_test (fid SERIAL, geometry POINT NOT NULL, txt TEXT);" + ) cursor.execute( "INSERT INTO gpkg_contents VALUES ('geo_test', 'features','description','geo_test','2019-06-18T14:52:50.928Z',-1.08892,0.0424077,-0.363885,0.562244,4326);" ) - cursor.execute("INSERT INTO gpkg_geometry_columns VALUES ('geo_test','geometry','POINT',4326, 0, 0 )") + cursor.execute( + "INSERT INTO gpkg_geometry_columns VALUES ('geo_test','geometry','POINT',4326, 0, 0 )" + ) cursor.execute("COMMIT;") @@ -1247,7 +1431,11 @@ class AnotherSqliteConn: def __init__(self, filename): self.proc = subprocess.Popen( - ["python3", os.path.join(os.path.dirname(__file__), "sqlite_con.py"), filename], + [ + "python3", + os.path.join(os.path.dirname(__file__), "sqlite_con.py"), + filename, + ], stdin=subprocess.PIPE, stderr=subprocess.PIPE, ) @@ -1286,7 +1474,9 @@ def test_push_gpkg_schema_change(mc): mp.log.info(" // create changeset") - mp.geodiff.create_changeset(mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-0")) + mp.geodiff.create_changeset( + mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-0") + ) mp.log.info(" // use wal") @@ -1311,7 +1501,9 @@ def test_push_gpkg_schema_change(mc): # why already here there is wal recovery - it could be because of two sqlite libs linked in one executable # INDEED THAT WAS THE PROBLEM, now running geodiff 1.0 with shared sqlite lib seems to work fine. with pytest.raises(pygeodiff.geodifflib.GeoDiffLibError): - mp.geodiff.create_changeset(mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-1")) + mp.geodiff.create_changeset( + mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-1") + ) _check_test_table(test_gpkg) with pytest.raises(sqlite3.OperationalError): @@ -1358,7 +1550,9 @@ def test_rebase_local_schema_change(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal( + test_gpkg + ) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) if extra_connection: @@ -1370,7 +1564,9 @@ def test_rebase_local_schema_change(mc, extra_connection): # Download project to the concurrent dir + add a feature + push a new version mc.download_project(project, project_dir_2) # download project to concurrent dir mp_2 = MerginProject(project_dir_2) - shutil.copy(os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), mp_2.fpath("test.gpkg")) + shutil.copy( + os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), mp_2.fpath("test.gpkg") + ) mc.push_project(project_dir_2) # Change schema in the primary project dir @@ -1424,7 +1620,9 @@ def test_rebase_remote_schema_change(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal( + test_gpkg + ) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -1489,7 +1687,9 @@ def test_rebase_success(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal( + test_gpkg + ) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + add a row + push a new version @@ -1530,18 +1730,48 @@ def test_conflict_file_names(): """ data = [ - ("/home/test/geo.gpkg", "jack", 10, "/home/test/geo (conflicted copy, jack v10).gpkg"), + ( + "/home/test/geo.gpkg", + "jack", + 10, + "/home/test/geo (conflicted copy, jack v10).gpkg", + ), ("/home/test/g.pkg", "j", 0, "/home/test/g (conflicted copy, j v0).pkg"), - ("home/test/geo.gpkg", "jack", 10, "home/test/geo (conflicted copy, jack v10).gpkg"), + ( + "home/test/geo.gpkg", + "jack", + 10, + "home/test/geo (conflicted copy, jack v10).gpkg", + ), ("geo.gpkg", "jack", 10, "geo (conflicted copy, jack v10).gpkg"), ("/home/../geo.gpkg", "jack", 10, "/geo (conflicted copy, jack v10).gpkg"), ("/home/./geo.gpkg", "jack", 10, "/home/geo (conflicted copy, jack v10).gpkg"), ("/home/test/geo.gpkg", "", 10, "/home/test/geo (conflicted copy, v10).gpkg"), - ("/home/test/geo.gpkg", "jack", -1, "/home/test/geo (conflicted copy, jack v-1).gpkg"), - ("/home/test/geo.tar.gz", "jack", 100, "/home/test/geo (conflicted copy, jack v100).tar.gz"), + ( + "/home/test/geo.gpkg", + "jack", + -1, + "/home/test/geo (conflicted copy, jack v-1).gpkg", + ), + ( + "/home/test/geo.tar.gz", + "jack", + 100, + "/home/test/geo (conflicted copy, jack v100).tar.gz", + ), ("", "jack", 1, ""), - ("/home/test/survey.qgs", "jack", 10, "/home/test/survey (conflicted copy, jack v10).qgs~"), - ("/home/test/survey.QGZ", "jack", 10, "/home/test/survey (conflicted copy, jack v10).QGZ~"), + ( + "/home/test/survey.qgs", + "jack", + 10, + "/home/test/survey (conflicted copy, jack v10).qgs~", + ), + ( + "/home/test/survey.QGZ", + "jack", + 10, + "/home/test/survey (conflicted copy, jack v10).QGZ~", + ), ] for i in data: @@ -1549,16 +1779,41 @@ def test_conflict_file_names(): assert file_name == i[3] data = [ - ("/home/test/geo.json", "jack", 10, "/home/test/geo (edit conflict, jack v10).json"), + ( + "/home/test/geo.json", + "jack", + 10, + "/home/test/geo (edit conflict, jack v10).json", + ), ("/home/test/g.jsn", "j", 0, "/home/test/g (edit conflict, j v0).json"), - ("home/test/geo.json", "jack", 10, "home/test/geo (edit conflict, jack v10).json"), + ( + "home/test/geo.json", + "jack", + 10, + "home/test/geo (edit conflict, jack v10).json", + ), ("geo.json", "jack", 10, "geo (edit conflict, jack v10).json"), ("/home/../geo.json", "jack", 10, "/geo (edit conflict, jack v10).json"), ("/home/./geo.json", "jack", 10, "/home/geo (edit conflict, jack v10).json"), ("/home/test/geo.json", "", 10, "/home/test/geo (edit conflict, v10).json"), - ("/home/test/geo.json", "jack", -1, "/home/test/geo (edit conflict, jack v-1).json"), - ("/home/test/geo.gpkg", "jack", 10, "/home/test/geo (edit conflict, jack v10).json"), - ("/home/test/geo.tar.gz", "jack", 100, "/home/test/geo (edit conflict, jack v100).json"), + ( + "/home/test/geo.json", + "jack", + -1, + "/home/test/geo (edit conflict, jack v-1).json", + ), + ( + "/home/test/geo.gpkg", + "jack", + 10, + "/home/test/geo (edit conflict, jack v10).json", + ), + ( + "/home/test/geo.tar.gz", + "jack", + 100, + "/home/test/geo (edit conflict, jack v100).json", + ), ("", "jack", 1, ""), ] @@ -1592,8 +1847,18 @@ def test_unique_path_names(): # - another (2).txt # - arch.tar.gz data = { - "folderA": {"files": ["fileA.txt", "fileA (1).txt", "fileB.txt"], "folderAB": {}, "folderAB (1)": {}}, - "files": ["file.txt", "another.txt", "another (1).txt", "another (2).txt", "arch.tar.gz"], + "folderA": { + "files": ["fileA.txt", "fileA (1).txt", "fileB.txt"], + "folderAB": {}, + "folderAB (1)": {}, + }, + "files": [ + "file.txt", + "another.txt", + "another (1).txt", + "another (2).txt", + "arch.tar.gz", + ], } create_directory(project_dir, data) @@ -1645,12 +1910,16 @@ def test_unfinished_pull(mc): test_gpkg_2 = os.path.join(project_dir_2, "test.gpkg") test_gpkg_basefile = os.path.join(project_dir, ".mergin", "test.gpkg") test_gpkg_conflict = conflicted_copy_file_name(test_gpkg, API_USER, 2) - test_gpkg_unfinished_pull = os.path.join(project_dir, ".mergin", "unfinished_pull", "test.gpkg") + test_gpkg_unfinished_pull = os.path.join( + project_dir, ".mergin", "unfinished_pull", "test.gpkg" + ) cleanup(mc, project, [project_dir, project_dir_2]) os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal( + test_gpkg + ) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -1733,12 +2002,16 @@ def test_unfinished_pull_push(mc): test_gpkg_2 = os.path.join(project_dir_2, "test.gpkg") test_gpkg_basefile = os.path.join(project_dir, ".mergin", "test.gpkg") test_gpkg_conflict = conflicted_copy_file_name(test_gpkg, API_USER, 2) - test_gpkg_unfinished_pull = os.path.join(project_dir, ".mergin", "unfinished_pull", "test.gpkg") + test_gpkg_unfinished_pull = os.path.join( + project_dir, ".mergin", "unfinished_pull", "test.gpkg" + ) cleanup(mc, project, [project_dir, project_dir_2]) os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal( + test_gpkg + ) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -1853,7 +2126,9 @@ def test_report(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) f_updated = "base.gpkg" - mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False, overwrite=True) + mp = create_versioned_project( + mc, test_project, project_dir, f_updated, remove=False, overwrite=True + ) # create report for between versions 2 and 4 directory = mp.dir @@ -1870,7 +2145,18 @@ def test_report(mc): with open(report_file, "r") as rf: content = rf.read() headers = ",".join( - ["file", "table", "author", "date", "time", "version", "operation", "length", "area", "count"] + [ + "file", + "table", + "author", + "date", + "time", + "version", + "operation", + "length", + "area", + "count", + ] ) assert headers in content assert "base.gpkg,simple,test_plugin" in content @@ -2057,11 +2343,15 @@ def test_clean_diff_files(mc): mp = MerginProject(project_dir) f_updated = "base.gpkg" # step 1) base.gpkg updated to inserted_1_A (inserted A feature) - shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) # make local copy for changeset calculation + shutil.move( + mp.fpath(f_updated), mp.fpath_meta(f_updated) + ) # make local copy for changeset calculation shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) mc.push_project(project_dir) - diff_files = glob.glob("*-diff-*", root_dir=os.path.split(mp.fpath_meta("inserted_1_A.gpkg"))[0]) + diff_files = glob.glob( + "*-diff-*", root_dir=os.path.split(mp.fpath_meta("inserted_1_A.gpkg"))[0] + ) assert diff_files == [] @@ -2070,7 +2360,9 @@ def test_reset_local_changes(mc: MerginClient): test_project = f"test_reset_local_changes" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates - project_dir_2 = os.path.join(TMP_DIR, test_project + "_v2") # primary project dir for updates + project_dir_2 = os.path.join( + TMP_DIR, test_project + "_v2" + ) # primary project dir for updates cleanup(mc, project, [project_dir]) # create remote project @@ -2134,7 +2426,9 @@ def test_reset_local_changes(mc: MerginClient): assert len(push_changes["updated"]) == 1 # reset local changes only to certain files, one added and one removed - mc.reset_local_changes(project_dir, files_to_reset=["new_test.txt", "test_dir/test2.txt"]) + mc.reset_local_changes( + project_dir, files_to_reset=["new_test.txt", "test_dir/test2.txt"] + ) # push changes after the reset mp = MerginProject(project_dir) @@ -2239,7 +2533,9 @@ def test_project_rename(mc: MerginClient): 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"): + with pytest.raises( + ClientError, match="The requested URL was not found on the server" + ): info = mc.project_info(project_renamed) # rename @@ -2250,7 +2546,9 @@ 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"): + with pytest.raises( + ClientError, match="The requested URL was not found on the server" + ): mc.project_info(project) # recreate project @@ -2264,11 +2562,16 @@ def test_project_rename(mc: MerginClient): 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."): + with pytest.raises( + ClientError, match="The requested URL was not found on the server." + ): mc.rename_project(API_USER + "/" + "non_existing_project", "new_project") # cannot rename with full project name - with pytest.raises(ClientError, match="Project's new name should be without workspace specification"): + with pytest.raises( + ClientError, + match="Project's new name should be without workspace specification", + ): mc.rename_project(project, "workspace" + "/" + test_project_renamed) @@ -2289,28 +2592,45 @@ def test_download_files(mc: MerginClient): assert project_info["id"] == mp.project_id() # Versioned file should have the following content at versions 2-4 - expected_content = ("inserted_1_A.gpkg", "inserted_1_A_mod.gpkg", "inserted_1_B.gpkg") + expected_content = ( + "inserted_1_A.gpkg", + "inserted_1_A_mod.gpkg", + "inserted_1_B.gpkg", + ) downloaded_file = os.path.join(download_dir, f_updated) # if output_paths is specified look at that location for ver in range(2, 5): - mc.download_files(project_dir, [f_updated], [downloaded_file], version=f"v{ver}") - expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content + mc.download_files( + project_dir, [f_updated], [downloaded_file], version=f"v{ver}" + ) + expected = os.path.join( + TEST_DATA_DIR, expected_content[ver - 2] + ) # GeoPackage with expected content assert check_gpkg_same_content(mp, downloaded_file, expected) # if output_paths is not specified look in the mergin project folder for ver in range(2, 5): mc.download_files(project_dir, [f_updated], version=f"v{ver}") - expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content + expected = os.path.join( + TEST_DATA_DIR, expected_content[ver - 2] + ) # GeoPackage with expected content assert check_gpkg_same_content(mp, mp.fpath(f_updated), expected) # download two files from v1 and check their content file_2 = "test.txt" downloaded_file_2 = os.path.join(download_dir, file_2) - mc.download_files(project_dir, [f_updated, file_2], [downloaded_file, downloaded_file_2], version="v1") - assert check_gpkg_same_content(mp, downloaded_file, os.path.join(TEST_DATA_DIR, f_updated)) + mc.download_files( + project_dir, + [f_updated, file_2], + [downloaded_file, downloaded_file_2], + version="v1", + ) + assert check_gpkg_same_content( + mp, downloaded_file, os.path.join(TEST_DATA_DIR, f_updated) + ) with open(os.path.join(TEST_DATA_DIR, file_2), mode="r", encoding="utf-8") as file: content_exp = file.read() @@ -2323,7 +2643,9 @@ def test_download_files(mc: MerginClient): with pytest.raises(ClientError, match=f"No \\[{f_updated}\\] exists at version v5"): mc.download_files(project_dir, [f_updated], version="v5") - with pytest.raises(ClientError, match=f"No \\[non_existing\\.file\\] exists at version v3"): + with pytest.raises( + ClientError, match=f"No \\[non_existing\\.file\\] exists at version v3" + ): mc.download_files(project_dir, [f_updated, "non_existing.file"], version="v3") From 402de230ea79bbb6fa47de7c0b7407b9762452c3 Mon Sep 17 00:00:00 2001 From: "marcel.kocisek" Date: Mon, 27 May 2024 15:44:01 +0200 Subject: [PATCH 9/9] ine width back to the 120 --- mergin/test/test_client.py | 327 +++++++++---------------------------- 1 file changed, 77 insertions(+), 250 deletions(-) diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 9cd2d16d..849e1910 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -48,9 +48,7 @@ USER_PWD2 = os.environ.get("TEST_API_PASSWORD2") TMP_DIR = tempfile.gettempdir() TEST_DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data") -CHANGED_SCHEMA_DIR = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "modified_schema" -) +CHANGED_SCHEMA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "modified_schema") @pytest.fixture(scope="function") @@ -68,12 +66,7 @@ def mc2(): def create_client(user, pwd): - assert ( - SERVER_URL - and SERVER_URL.rstrip("/") != "https://app.merginmaps.com" - and user - and pwd - ) + assert SERVER_URL and SERVER_URL.rstrip("/") != "https://app.merginmaps.com" and user and pwd return MerginClient(SERVER_URL, login=user, password=pwd) @@ -112,9 +105,7 @@ def server_has_editor_support(mc, access): Returns: bool: True if the server has editor support, False otherwise. """ - return "editorsnames" in access and is_version_acceptable( - mc.server_version(), "2024.4" - ) + return "editorsnames" in access and is_version_acceptable(mc.server_version(), "2024.4") def test_login(mc): @@ -122,9 +113,7 @@ def test_login(mc): assert MerginClient(mc.url, auth_token=token) invalid_token = "Completely invalid token...." - with pytest.raises( - TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}" - ): + with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}"): decode_token_data(invalid_token) invalid_token = "Bearer .jas646kgfa" @@ -145,9 +134,7 @@ def test_create_delete_project(mc: MerginClient): # create new (empty) project on server mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) # try again with pytest.raises(ClientError, match=f"already exists"): @@ -156,53 +143,37 @@ def test_create_delete_project(mc: MerginClient): # remove project mc.delete_project_now(API_USER + "/" + test_project) projects = mc.projects_list(flag="created") - assert not any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert not any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) # try again, nothing to delete with pytest.raises(ClientError): mc.delete_project_now(API_USER + "/" + test_project) # test that using namespace triggers deprecate warning, but creates project correctly - with pytest.deprecated_call( - match=r"The usage of `namespace` parameter in `create_project\(\)` is deprecated." - ): + with pytest.deprecated_call(match=r"The usage of `namespace` parameter in `create_project\(\)` is deprecated."): mc.create_project(test_project, namespace=API_USER) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) mc.delete_project_now(project) # test that using only project name triggers deprecate warning, but creates project correctly - with pytest.deprecated_call( - match=r"The use of only project name in `create_project\(\)` is deprecated" - ): + with pytest.deprecated_call(match=r"The use of only project name in `create_project\(\)` is deprecated"): mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) mc.delete_project_now(project) # test that even if project is specified with full name and namespace is specified a warning is raised, but still create project correctly - with pytest.warns( - UserWarning, match="Parameter `namespace` specified with full project name" - ): + with pytest.warns(UserWarning, match="Parameter `namespace` specified with full project name"): mc.create_project(project, namespace=API_USER) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) mc.delete_project_now(project) # test that create project with full name works mc.create_project(project) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) mc.delete_project_now(project) @@ -317,20 +288,14 @@ def test_push_pull_changes(mc): assert not next((f for f in project_info["files"] if f["path"] == f_renamed), None) assert next((f for f in project_info["files"] if f["path"] == "renamed.txt"), None) assert next((f for f in project_info["files"] if f["path"] == f_added), None) - f_remote_checksum = next( - (f["checksum"] for f in project_info["files"] if f["path"] == f_updated), None - ) + f_remote_checksum = next((f["checksum"] for f in project_info["files"] if f["path"] == f_updated), None) assert generate_checksum(os.path.join(project_dir, f_updated)) == f_remote_checksum assert project_info["id"] == mp.project_id() assert len(project_info["files"]) == len(mp.inspect_files()) project_versions = mc.project_versions(project) assert len(project_versions) == 2 f_change = next( - ( - f - for f in project_versions[-1]["changes"]["updated"] - if f["path"] == f_updated - ), + (f for f in project_versions[-1]["changes"]["updated"] if f["path"] == f_updated), None, ) assert "origin_checksum" not in f_change # internal client info @@ -357,20 +322,12 @@ def test_push_pull_changes(mc): assert not os.path.exists(os.path.join(project_dir_2, f_removed)) assert not os.path.exists(os.path.join(project_dir_2, f_renamed)) assert os.path.exists(os.path.join(project_dir_2, "renamed.txt")) - assert os.path.exists( - os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1)) - ) + assert os.path.exists(os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1))) assert ( - generate_checksum( - os.path.join( - project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1) - ) - ) + generate_checksum(os.path.join(project_dir_2, conflicted_copy_file_name(f_updated, API_USER, 1))) == f_conflict_checksum ) - assert ( - generate_checksum(os.path.join(project_dir_2, f_updated)) == f_remote_checksum - ) + assert generate_checksum(os.path.join(project_dir_2, f_updated)) == f_remote_checksum def test_cancel_push(mc): @@ -380,9 +337,7 @@ def test_cancel_push(mc): """ test_project = "test_cancel_push" project = API_USER + "/" + test_project - project_dir = os.path.join( - TMP_DIR, test_project + "_3" - ) # primary project dir for updates + project_dir = os.path.join(TMP_DIR, test_project + "_3") # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_4") cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -428,33 +383,23 @@ def test_ignore_files(mc): cleanup(mc, project, [project_dir]) # create remote project shutil.copytree(TEST_DATA_DIR, project_dir) - shutil.copy( - os.path.join(project_dir, "test.qgs"), os.path.join(project_dir, "test.qgs~") - ) + shutil.copy(os.path.join(project_dir, "test.qgs"), os.path.join(project_dir, "test.qgs~")) mc.create_project_and_push(project, project_dir) project_info = mc.project_info(project) - assert not next( - (f for f in project_info["files"] if f["path"] == "test.qgs~"), None - ) + assert not next((f for f in project_info["files"] if f["path"] == "test.qgs~"), None) with open(os.path.join(project_dir, ".directory"), "w") as f: f.write("test") mc.push_project(project_dir) - assert not next( - (f for f in project_info["files"] if f["path"] == ".directory"), None - ) + assert not next((f for f in project_info["files"] if f["path"] == ".directory"), None) def test_sync_diff(mc): test_project = f"test_sync_diff" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates - project_dir_2 = os.path.join( - TMP_DIR, test_project + "_2" - ) # concurrent project dir with no changes - project_dir_3 = os.path.join( - TMP_DIR, test_project + "_3" - ) # concurrent project dir with local changes + project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir with no changes + project_dir_3 = os.path.join(TMP_DIR, test_project + "_3") # concurrent project dir with local changes cleanup(mc, project, [project_dir, project_dir_2, project_dir_3]) # create remote project @@ -469,14 +414,10 @@ def test_sync_diff(mc): mp = MerginProject(project_dir) f_updated = "base.gpkg" # step 1) base.gpkg updated to inserted_1_A (inserted A feature) - shutil.move( - mp.fpath(f_updated), mp.fpath_meta(f_updated) - ) # make local copy for changeset calculation + shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) # make local copy for changeset calculation shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) mc.push_project(project_dir) - mp.geodiff.create_changeset( - mp.fpath(f_updated), mp.fpath_meta(f_updated), mp.fpath_meta("push_diff") - ) + mp.geodiff.create_changeset(mp.fpath(f_updated), mp.fpath_meta(f_updated), mp.fpath_meta("push_diff")) assert not mp.geodiff.has_changes(mp.fpath_meta("push_diff")) # step 2) base.gpkg updated to inserted_1_A_mod (modified 2 features) shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) @@ -502,9 +443,7 @@ def test_sync_diff(mc): # pull project in different directory mp2 = MerginProject(project_dir_2) mc.pull_project(project_dir_2) - mp2.geodiff.create_changeset( - mp.fpath(f_updated), mp2.fpath(f_updated), mp2.fpath_meta("diff") - ) + mp2.geodiff.create_changeset(mp.fpath(f_updated), mp2.fpath(f_updated), mp2.fpath_meta("diff")) assert not mp2.geodiff.has_changes(mp2.fpath_meta("diff")) # introduce conflict local change (inserted B feature to base) @@ -517,19 +456,13 @@ def test_sync_diff(mc): # push new changes from project_3 and pull in original project mc.push_project(project_dir_3) mc.pull_project(project_dir) - mp3.geodiff.create_changeset( - mp.fpath(f_updated), mp3.fpath(f_updated), mp.fpath_meta("diff") - ) + mp3.geodiff.create_changeset(mp.fpath(f_updated), mp3.fpath(f_updated), mp.fpath_meta("diff")) assert not mp3.geodiff.has_changes(mp.fpath_meta("diff")) def test_list_of_push_changes(mc): PUSH_CHANGES_SUMMARY = { - "base.gpkg": { - "geodiff_summary": [ - {"table": "simple", "insert": 1, "update": 0, "delete": 0} - ] - } + "base.gpkg": {"geodiff_summary": [{"table": "simple", "insert": 1, "update": 0, "delete": 0}]} } test_project = "test_list_of_push_changes" @@ -544,9 +477,7 @@ def test_list_of_push_changes(mc): mp = MerginProject(project_dir) shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) - mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta( - days=1 - ) + mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1) pull_changes, push_changes, push_changes_summary = mc.project_status(project_dir) assert push_changes_summary == PUSH_CHANGES_SUMMARY @@ -561,9 +492,7 @@ def test_token_renewal(mc): shutil.copytree(TEST_DATA_DIR, project_dir) mc.create_project_and_push(project, project_dir) - mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta( - days=1 - ) + mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1) pull_changes, push_changes, _ = mc.project_status(project_dir) to_expire = mc._auth_session["expire"] - datetime.now().replace(tzinfo=pytz.utc) assert to_expire.total_seconds() > (9 * 3600) @@ -588,9 +517,7 @@ def test_force_gpkg_update(mc): shutil.move( mp.fpath(f_updated), mp.fpath_meta(f_updated) ) # make local copy for changeset calculation (which will fail) - shutil.copy( - os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), mp.fpath(f_updated) - ) + shutil.copy(os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg"), mp.fpath(f_updated)) shutil.copy( os.path.join(CHANGED_SCHEMA_DIR, "modified_schema.gpkg-wal"), mp.fpath(f_updated + "-wal"), @@ -631,11 +558,7 @@ def test_new_project_sync(mc): # make sure everything is up-to-date mp = MerginProject(project_dir) local_changes = mp.get_push_changes() - assert ( - not local_changes["added"] - and not local_changes["removed"] - and not local_changes["updated"] - ) + assert not local_changes["added"] and not local_changes["removed"] and not local_changes["updated"] def test_missing_basefile_pull(mc): @@ -647,9 +570,7 @@ def test_missing_basefile_pull(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir - test_data_dir = os.path.join( - os.path.dirname(os.path.realpath(__file__)), test_project - ) + test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_project) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -686,9 +607,7 @@ def test_empty_file_in_subdir(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_2") # concurrent project dir - test_data_dir = os.path.join( - os.path.dirname(os.path.realpath(__file__)), test_project - ) + test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_project) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -723,9 +642,7 @@ def test_clone_project(mc: MerginClient): # create new (empty) project on server mc.create_project(test_project) projects = mc.projects_list(flag="created") - assert any( - p for p in projects if p["name"] == test_project and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == test_project and p["namespace"] == API_USER) cloned_project_name = test_project + "_cloned" test_cloned_project_fullname = API_USER + "/" + cloned_project_name @@ -735,52 +652,30 @@ def test_clone_project(mc: MerginClient): cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone specifying cloned_project_namespace, does clone but raises deprecation warning - with pytest.deprecated_call( - match=r"The usage of `cloned_project_namespace` parameter in `clone_project\(\)`" - ): + with pytest.deprecated_call(match=r"The usage of `cloned_project_namespace` parameter in `clone_project\(\)`"): mc.clone_project(test_project_fullname, cloned_project_name, API_USER) projects = mc.projects_list(flag="created") - assert any( - p - for p in projects - if p["name"] == cloned_project_name and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone without specifying cloned_project_namespace relies on workspace with user name, does clone but raises deprecation warning - with pytest.deprecated_call( - match=r"The use of only project name as `cloned_project_name` in `clone_project\(\)`" - ): + with pytest.deprecated_call(match=r"The use of only project name as `cloned_project_name` in `clone_project\(\)`"): mc.clone_project(test_project_fullname, cloned_project_name) projects = mc.projects_list(flag="created") - assert any( - p - for p in projects - if p["name"] == cloned_project_name and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone project with full cloned project name with specification of `cloned_project_namespace` raises warning - with pytest.warns( - match=r"Parameter `cloned_project_namespace` specified with full cloned project name" - ): + with pytest.warns(match=r"Parameter `cloned_project_namespace` specified with full cloned project name"): mc.clone_project(test_project_fullname, test_cloned_project_fullname, API_USER) projects = mc.projects_list(flag="created") - assert any( - p - for p in projects - if p["name"] == cloned_project_name and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) # clone project using project full name mc.clone_project(test_project_fullname, test_cloned_project_fullname) projects = mc.projects_list(flag="created") - assert any( - p - for p in projects - if p["name"] == cloned_project_name and p["namespace"] == API_USER - ) + assert any(p for p in projects if p["name"] == cloned_project_name and p["namespace"] == API_USER) cleanup(mc, API_USER + "/" + cloned_project_name, [cloned_project_dir]) @@ -962,9 +857,7 @@ def get_project_info(mc, namespace, project_name): :return: dict with project info """ projects = mc.projects_list(flag="created") - test_project_list = [ - p for p in projects if p["name"] == project_name and p["namespace"] == namespace - ] + test_project_list = [p for p in projects if p["name"] == project_name and p["namespace"] == namespace] assert len(test_project_list) == 1 return test_project_list[0] @@ -1027,9 +920,7 @@ def test_download_versions(mc): mc.download_project(project, project_dir_v2, "v2") assert os.path.exists(os.path.join(project_dir_v2, f_added)) - assert os.path.exists( - os.path.join(project_dir_v1, "base.gpkg") - ) # added in v1 but still present in v2 + assert os.path.exists(os.path.join(project_dir_v1, "base.gpkg")) # added in v1 but still present in v2 # try to download not-existing version with pytest.raises(ClientError): @@ -1082,13 +973,9 @@ def test_missing_local_file_pull(mc): test_project = "test_dir" file_to_remove = "test2.txt" project = API_USER + "/" + test_project - project_dir = os.path.join( - TMP_DIR, test_project + "_5" - ) # primary project dir for updates + project_dir = os.path.join(TMP_DIR, test_project + "_5") # primary project dir for updates project_dir_2 = os.path.join(TMP_DIR, test_project + "_6") # concurrent project dir - test_data_dir = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "test_data", test_project - ) + test_data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data", test_project) cleanup(mc, project, [project_dir, project_dir_2]) # create remote project @@ -1130,9 +1017,7 @@ def test_server_compatibility(mc): assert mc.is_server_compatible() -def create_versioned_project( - mc, project_name, project_dir, updated_file, remove=True, overwrite=False -): +def create_versioned_project(mc, project_name, project_dir, updated_file, remove=True, overwrite=False): project = API_USER + "/" + project_name cleanup(mc, project, [project_dir]) @@ -1178,9 +1063,7 @@ def test_get_versions_with_file_changes(mc): project_dir = os.path.join(TMP_DIR, test_project) f_updated = "base.gpkg" - mp = create_versioned_project( - mc, test_project, project_dir, f_updated, remove=False - ) + mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False) project_info = mc.project_info(project) assert project_info["version"] == "v4" @@ -1242,9 +1125,7 @@ def test_download_file(mc): f_downloaded = os.path.join(project_dir, f_updated) for ver in range(2, 5): mc.download_file(project_dir, f_updated, f_downloaded, version=f"v{ver}") - expected = os.path.join( - TEST_DATA_DIR, expected_content[ver - 2] - ) # GeoPackage with expected content + expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content assert check_gpkg_same_content(mp, f_downloaded, expected) # make sure there will be exception raised if a file doesn't exist in the version @@ -1257,15 +1138,11 @@ def test_download_diffs(mc): test_project = "test_download_diffs" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) - download_dir = os.path.join( - project_dir, "diffs" - ) # project for downloading files at various versions + download_dir = os.path.join(project_dir, "diffs") # project for downloading files at various versions f_updated = "base.gpkg" diff_file = os.path.join(download_dir, f_updated + ".diff") - mp = create_versioned_project( - mc, test_project, project_dir, f_updated, remove=False - ) + mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False) project_info = mc.project_info(project) assert project_info["version"] == "v4" @@ -1372,15 +1249,11 @@ def _create_spatial_table(db_file): """Creates a spatial table called 'test' in sqlite database. Useful to simulate change of database schema.""" con = sqlite3.connect(db_file) cursor = con.cursor() - cursor.execute( - "CREATE TABLE geo_test (fid SERIAL, geometry POINT NOT NULL, txt TEXT);" - ) + cursor.execute("CREATE TABLE geo_test (fid SERIAL, geometry POINT NOT NULL, txt TEXT);") cursor.execute( "INSERT INTO gpkg_contents VALUES ('geo_test', 'features','description','geo_test','2019-06-18T14:52:50.928Z',-1.08892,0.0424077,-0.363885,0.562244,4326);" ) - cursor.execute( - "INSERT INTO gpkg_geometry_columns VALUES ('geo_test','geometry','POINT',4326, 0, 0 )" - ) + cursor.execute("INSERT INTO gpkg_geometry_columns VALUES ('geo_test','geometry','POINT',4326, 0, 0 )") cursor.execute("COMMIT;") @@ -1474,9 +1347,7 @@ def test_push_gpkg_schema_change(mc): mp.log.info(" // create changeset") - mp.geodiff.create_changeset( - mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-0") - ) + mp.geodiff.create_changeset(mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-0")) mp.log.info(" // use wal") @@ -1501,9 +1372,7 @@ def test_push_gpkg_schema_change(mc): # why already here there is wal recovery - it could be because of two sqlite libs linked in one executable # INDEED THAT WAS THE PROBLEM, now running geodiff 1.0 with shared sqlite lib seems to work fine. with pytest.raises(pygeodiff.geodifflib.GeoDiffLibError): - mp.geodiff.create_changeset( - mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-1") - ) + mp.geodiff.create_changeset(mp.fpath("test.gpkg"), mp.fpath_meta("test.gpkg"), mp.fpath_meta("diff-1")) _check_test_table(test_gpkg) with pytest.raises(sqlite3.OperationalError): @@ -1550,9 +1419,7 @@ def test_rebase_local_schema_change(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal( - test_gpkg - ) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) if extra_connection: @@ -1564,9 +1431,7 @@ def test_rebase_local_schema_change(mc, extra_connection): # Download project to the concurrent dir + add a feature + push a new version mc.download_project(project, project_dir_2) # download project to concurrent dir mp_2 = MerginProject(project_dir_2) - shutil.copy( - os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), mp_2.fpath("test.gpkg") - ) + shutil.copy(os.path.join(TEST_DATA_DIR, "inserted_1_A.gpkg"), mp_2.fpath("test.gpkg")) mc.push_project(project_dir_2) # Change schema in the primary project dir @@ -1620,9 +1485,7 @@ def test_rebase_remote_schema_change(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal( - test_gpkg - ) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -1687,9 +1550,7 @@ def test_rebase_success(mc, extra_connection): os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal( - test_gpkg - ) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + add a row + push a new version @@ -1910,16 +1771,12 @@ def test_unfinished_pull(mc): test_gpkg_2 = os.path.join(project_dir_2, "test.gpkg") test_gpkg_basefile = os.path.join(project_dir, ".mergin", "test.gpkg") test_gpkg_conflict = conflicted_copy_file_name(test_gpkg, API_USER, 2) - test_gpkg_unfinished_pull = os.path.join( - project_dir, ".mergin", "unfinished_pull", "test.gpkg" - ) + test_gpkg_unfinished_pull = os.path.join(project_dir, ".mergin", "unfinished_pull", "test.gpkg") cleanup(mc, project, [project_dir, project_dir_2]) os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal( - test_gpkg - ) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -2002,16 +1859,12 @@ def test_unfinished_pull_push(mc): test_gpkg_2 = os.path.join(project_dir_2, "test.gpkg") test_gpkg_basefile = os.path.join(project_dir, ".mergin", "test.gpkg") test_gpkg_conflict = conflicted_copy_file_name(test_gpkg, API_USER, 2) - test_gpkg_unfinished_pull = os.path.join( - project_dir, ".mergin", "unfinished_pull", "test.gpkg" - ) + test_gpkg_unfinished_pull = os.path.join(project_dir, ".mergin", "unfinished_pull", "test.gpkg") cleanup(mc, project, [project_dir, project_dir_2]) os.makedirs(project_dir) shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), test_gpkg) - _use_wal( - test_gpkg - ) # make sure we use WAL, that's the more common and more difficult scenario + _use_wal(test_gpkg) # make sure we use WAL, that's the more common and more difficult scenario mc.create_project_and_push(project, project_dir) # Download project to the concurrent dir + change DB schema + push a new version @@ -2126,9 +1979,7 @@ def test_report(mc): project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) f_updated = "base.gpkg" - mp = create_versioned_project( - mc, test_project, project_dir, f_updated, remove=False, overwrite=True - ) + mp = create_versioned_project(mc, test_project, project_dir, f_updated, remove=False, overwrite=True) # create report for between versions 2 and 4 directory = mp.dir @@ -2343,15 +2194,11 @@ def test_clean_diff_files(mc): mp = MerginProject(project_dir) f_updated = "base.gpkg" # step 1) base.gpkg updated to inserted_1_A (inserted A feature) - shutil.move( - mp.fpath(f_updated), mp.fpath_meta(f_updated) - ) # make local copy for changeset calculation + shutil.move(mp.fpath(f_updated), mp.fpath_meta(f_updated)) # make local copy for changeset calculation shutil.copy(mp.fpath("inserted_1_A.gpkg"), mp.fpath(f_updated)) mc.push_project(project_dir) - diff_files = glob.glob( - "*-diff-*", root_dir=os.path.split(mp.fpath_meta("inserted_1_A.gpkg"))[0] - ) + diff_files = glob.glob("*-diff-*", root_dir=os.path.split(mp.fpath_meta("inserted_1_A.gpkg"))[0]) assert diff_files == [] @@ -2360,9 +2207,7 @@ def test_reset_local_changes(mc: MerginClient): test_project = f"test_reset_local_changes" project = API_USER + "/" + test_project project_dir = os.path.join(TMP_DIR, test_project) # primary project dir for updates - project_dir_2 = os.path.join( - TMP_DIR, test_project + "_v2" - ) # primary project dir for updates + project_dir_2 = os.path.join(TMP_DIR, test_project + "_v2") # primary project dir for updates cleanup(mc, project, [project_dir]) # create remote project @@ -2426,9 +2271,7 @@ def test_reset_local_changes(mc: MerginClient): assert len(push_changes["updated"]) == 1 # reset local changes only to certain files, one added and one removed - mc.reset_local_changes( - project_dir, files_to_reset=["new_test.txt", "test_dir/test2.txt"] - ) + mc.reset_local_changes(project_dir, files_to_reset=["new_test.txt", "test_dir/test2.txt"]) # push changes after the reset mp = MerginProject(project_dir) @@ -2533,9 +2376,7 @@ def test_project_rename(mc: MerginClient): 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" - ): + with pytest.raises(ClientError, match="The requested URL was not found on the server"): info = mc.project_info(project_renamed) # rename @@ -2546,9 +2387,7 @@ 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" - ): + with pytest.raises(ClientError, match="The requested URL was not found on the server"): mc.project_info(project) # recreate project @@ -2562,9 +2401,7 @@ def test_project_rename(mc: MerginClient): 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." - ): + with pytest.raises(ClientError, match="The requested URL was not found on the server."): mc.rename_project(API_USER + "/" + "non_existing_project", "new_project") # cannot rename with full project name @@ -2602,20 +2439,14 @@ def test_download_files(mc: MerginClient): # if output_paths is specified look at that location for ver in range(2, 5): - mc.download_files( - project_dir, [f_updated], [downloaded_file], version=f"v{ver}" - ) - expected = os.path.join( - TEST_DATA_DIR, expected_content[ver - 2] - ) # GeoPackage with expected content + mc.download_files(project_dir, [f_updated], [downloaded_file], version=f"v{ver}") + expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content assert check_gpkg_same_content(mp, downloaded_file, expected) # if output_paths is not specified look in the mergin project folder for ver in range(2, 5): mc.download_files(project_dir, [f_updated], version=f"v{ver}") - expected = os.path.join( - TEST_DATA_DIR, expected_content[ver - 2] - ) # GeoPackage with expected content + expected = os.path.join(TEST_DATA_DIR, expected_content[ver - 2]) # GeoPackage with expected content assert check_gpkg_same_content(mp, mp.fpath(f_updated), expected) # download two files from v1 and check their content @@ -2628,9 +2459,7 @@ def test_download_files(mc: MerginClient): [downloaded_file, downloaded_file_2], version="v1", ) - assert check_gpkg_same_content( - mp, downloaded_file, os.path.join(TEST_DATA_DIR, f_updated) - ) + assert check_gpkg_same_content(mp, downloaded_file, os.path.join(TEST_DATA_DIR, f_updated)) with open(os.path.join(TEST_DATA_DIR, file_2), mode="r", encoding="utf-8") as file: content_exp = file.read() @@ -2643,9 +2472,7 @@ def test_download_files(mc: MerginClient): with pytest.raises(ClientError, match=f"No \\[{f_updated}\\] exists at version v5"): mc.download_files(project_dir, [f_updated], version="v5") - with pytest.raises( - ClientError, match=f"No \\[non_existing\\.file\\] exists at version v3" - ): + with pytest.raises(ClientError, match=f"No \\[non_existing\\.file\\] exists at version v3"): mc.download_files(project_dir, [f_updated, "non_existing.file"], version="v3")