diff --git a/mergin/client.py b/mergin/client.py index 4a497f74..5df33023 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -12,6 +12,7 @@ from datetime import datetime, timezone import dateutil.parser import ssl +from enum import Enum, auto import re from .common import ClientError, LoginError, InvalidProject @@ -37,6 +38,13 @@ class TokenError(Exception): pass +class ServerType(Enum): + OLD = auto() # Server is old and does not support workspaces + CE = auto() # Server is Community Edition + EE = auto() # Server is Enterprise Edition + SAAS = auto() # Server is SaaS + + def decode_token_data(token): token_prefix = "Bearer ." if not token.startswith(token_prefix): @@ -70,6 +78,7 @@ def __init__(self, url=None, auth_token=None, login=None, password=None, plugin_ self._auth_params = None self._auth_session = None self._user_info = None + self._server_type = None self.client_version = "Python-client/" + __version__ if plugin_version is not None: # this could be e.g. "Plugin/2020.1 QGIS/3.14" self.client_version += " " + plugin_version @@ -310,6 +319,7 @@ def user_service(self): Requests information about user from /user/service endpoint if such exists in self.url server. Returns response from server as JSON dict or None if endpoint is not found + This can be removed once our SaaS server is upgraded to support workspaces """ try: @@ -322,6 +332,58 @@ def user_service(self): return response + def workspace_service(self, workspace_id): + """ + This Requests information about a workspace service from /workspace/{id}/service endpoint, + if such exists in self.url server. + + Returns response from server as JSON dict or None if endpoint is not found + """ + + try: + response = self.get(f"/v1/workspace/{workspace_id}/service") + except ClientError as e: + self.log.debug(f"Unable to query for /workspace/{workspace_id}/service endpoint") + return + + response = json.loads(response.read()) + + return response + + def server_type(self): + """ + Returns the deployment type of the server + + The value is cached for self's lifetime + + :returns: ServerType of server deployment + :rtype: ServerType + """ + if not self._server_type: + try: + resp = self.get("/config") + config = json.load(resp) + if config["server_type"] == "ce": + self._server_type = ServerType.CE + elif config["server_type"] == "ee": + self._server_type = ServerType.EE + elif config["server_type"] == "saas": + self._server_type = ServerType.SAAS + except (ClientError, KeyError): + self._server_type = ServerType.OLD + + return self._server_type + + def workspaces_list(self): + """ + Find all available workspaces + + :rtype: List[Dict] + """ + resp = self.get("/v1/workspaces") + workspaces = json.load(resp) + return workspaces + def create_project(self, project_name, is_public=False, namespace=None): """ Create new project repository in user namespace on Mergin Maps server. @@ -366,7 +428,16 @@ def create_project_and_push(self, project_name, directory, is_public=False, name self.push_project(directory) def paginated_projects_list( - self, page=1, per_page=50, tags=None, user=None, flag=None, name=None, namespace=None, order_params=None + self, + page=1, + per_page=50, + tags=None, + user=None, + flag=None, + name=None, + only_namespace=None, + namespace=None, + order_params=None, ): """ Find all available Mergin Maps projects. @@ -383,6 +454,9 @@ def paginated_projects_list( :param name: Filter projects with name like name :type name: String + :param only_namespace: Filter projects with namespace exactly equal to namespace + :type namespace: String + :param namespace: Filter projects with namespace like namespace :type namespace: String @@ -408,7 +482,9 @@ def paginated_projects_list( params["flag"] = flag if name: params["name"] = name - if namespace: + if only_namespace: + params["only_namespace"] = only_namespace + elif namespace: params["namespace"] = namespace params["page"] = page params["per_page"] = per_page @@ -418,7 +494,9 @@ def paginated_projects_list( projects = json.load(resp) return projects - def projects_list(self, tags=None, user=None, flag=None, name=None, namespace=None, order_params=None): + def projects_list( + self, tags=None, user=None, flag=None, name=None, only_namespace=None, namespace=None, order_params=None + ): """ Find all available Mergin Maps projects. @@ -436,6 +514,9 @@ def projects_list(self, tags=None, user=None, flag=None, name=None, namespace=No :param name: Filter projects with name like name :type name: String + :param only_namespace: Filter projects with namespace exactly equal to namespace + :type namespace: String + :param namespace: Filter projects with namespace like namespace :type namespace: String @@ -457,6 +538,7 @@ def projects_list(self, tags=None, user=None, flag=None, name=None, namespace=No user=user, flag=flag, name=name, + only_namespace=only_namespace, namespace=namespace, order_params=order_params, ) @@ -557,7 +639,11 @@ def enough_storage_available(self, data): return True, free_space def user_info(self): - resp = self.get("/v1/user/" + self.username()) + server_type = self.server_type() + if server_type == ServerType.OLD: + resp = self.get("/v1/user/" + self.username()) + else: + resp = self.get("/v1/user/profile") return json.load(resp) def set_project_access(self, project_path, access): @@ -708,6 +794,7 @@ def project_status(self, directory): project_path = mp.metadata["name"] local_version = mp.metadata["version"] server_info = self.project_info(project_path, since=local_version) + pull_changes = mp.get_pull_changes(server_info["files"]) push_changes = mp.get_push_changes()