Skip to content

Workspaces #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 26, 2023
95 changes: 91 additions & 4 deletions mergin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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.

Expand All @@ -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

Expand All @@ -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,
)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand Down