1212from datetime import datetime , timezone
1313import dateutil .parser
1414import ssl
15+ from enum import Enum , auto
1516import re
1617
1718from .common import ClientError , LoginError , InvalidProject
@@ -37,6 +38,13 @@ class TokenError(Exception):
3738 pass
3839
3940
41+ class ServerType (Enum ):
42+ OLD = auto () # Server is old and does not support workspaces
43+ CE = auto () # Server is Community Edition
44+ EE = auto () # Server is Enterprise Edition
45+ SAAS = auto () # Server is SaaS
46+
47+
4048def decode_token_data (token ):
4149 token_prefix = "Bearer ."
4250 if not token .startswith (token_prefix ):
@@ -70,6 +78,7 @@ def __init__(self, url=None, auth_token=None, login=None, password=None, plugin_
7078 self ._auth_params = None
7179 self ._auth_session = None
7280 self ._user_info = None
81+ self ._server_type = None
7382 self .client_version = "Python-client/" + __version__
7483 if plugin_version is not None : # this could be e.g. "Plugin/2020.1 QGIS/3.14"
7584 self .client_version += " " + plugin_version
@@ -310,6 +319,7 @@ def user_service(self):
310319 Requests information about user from /user/service endpoint if such exists in self.url server.
311320
312321 Returns response from server as JSON dict or None if endpoint is not found
322+ This can be removed once our SaaS server is upgraded to support workspaces
313323 """
314324
315325 try :
@@ -322,6 +332,58 @@ def user_service(self):
322332
323333 return response
324334
335+ def workspace_service (self , workspace_id ):
336+ """
337+ This Requests information about a workspace service from /workspace/{id}/service endpoint,
338+ if such exists in self.url server.
339+
340+ Returns response from server as JSON dict or None if endpoint is not found
341+ """
342+
343+ try :
344+ response = self .get (f"/v1/workspace/{ workspace_id } /service" )
345+ except ClientError as e :
346+ self .log .debug (f"Unable to query for /workspace/{ workspace_id } /service endpoint" )
347+ return
348+
349+ response = json .loads (response .read ())
350+
351+ return response
352+
353+ def server_type (self ):
354+ """
355+ Returns the deployment type of the server
356+
357+ The value is cached for self's lifetime
358+
359+ :returns: ServerType of server deployment
360+ :rtype: ServerType
361+ """
362+ if not self ._server_type :
363+ try :
364+ resp = self .get ("/config" )
365+ config = json .load (resp )
366+ if config ["server_type" ] == "ce" :
367+ self ._server_type = ServerType .CE
368+ elif config ["server_type" ] == "ee" :
369+ self ._server_type = ServerType .EE
370+ elif config ["server_type" ] == "saas" :
371+ self ._server_type = ServerType .SAAS
372+ except (ClientError , KeyError ):
373+ self ._server_type = ServerType .OLD
374+
375+ return self ._server_type
376+
377+ def workspaces_list (self ):
378+ """
379+ Find all available workspaces
380+
381+ :rtype: List[Dict]
382+ """
383+ resp = self .get ("/v1/workspaces" )
384+ workspaces = json .load (resp )
385+ return workspaces
386+
325387 def create_project (self , project_name , is_public = False , namespace = None ):
326388 """
327389 Create new project repository in user namespace on Mergin Maps server.
@@ -367,7 +429,16 @@ def create_project_and_push(self, project_name, directory, is_public=False, name
367429 self .push_project (directory )
368430
369431 def paginated_projects_list (
370- self , page = 1 , per_page = 50 , tags = None , user = None , flag = None , name = None , namespace = None , order_params = None
432+ self ,
433+ page = 1 ,
434+ per_page = 50 ,
435+ tags = None ,
436+ user = None ,
437+ flag = None ,
438+ name = None ,
439+ only_namespace = None ,
440+ namespace = None ,
441+ order_params = None ,
371442 ):
372443 """
373444 Find all available Mergin Maps projects.
@@ -384,6 +455,9 @@ def paginated_projects_list(
384455 :param name: Filter projects with name like name
385456 :type name: String
386457
458+ :param only_namespace: Filter projects with namespace exactly equal to namespace
459+ :type namespace: String
460+
387461 :param namespace: Filter projects with namespace like namespace
388462 :type namespace: String
389463
@@ -409,7 +483,9 @@ def paginated_projects_list(
409483 params ["flag" ] = flag
410484 if name :
411485 params ["name" ] = name
412- if namespace :
486+ if only_namespace :
487+ params ["only_namespace" ] = only_namespace
488+ elif namespace :
413489 params ["namespace" ] = namespace
414490 params ["page" ] = page
415491 params ["per_page" ] = per_page
@@ -419,7 +495,9 @@ def paginated_projects_list(
419495 projects = json .load (resp )
420496 return projects
421497
422- def projects_list (self , tags = None , user = None , flag = None , name = None , namespace = None , order_params = None ):
498+ def projects_list (
499+ self , tags = None , user = None , flag = None , name = None , only_namespace = None , namespace = None , order_params = None
500+ ):
423501 """
424502 Find all available Mergin Maps projects.
425503
@@ -437,6 +515,9 @@ def projects_list(self, tags=None, user=None, flag=None, name=None, namespace=No
437515 :param name: Filter projects with name like name
438516 :type name: String
439517
518+ :param only_namespace: Filter projects with namespace exactly equal to namespace
519+ :type namespace: String
520+
440521 :param namespace: Filter projects with namespace like namespace
441522 :type namespace: String
442523
@@ -458,6 +539,7 @@ def projects_list(self, tags=None, user=None, flag=None, name=None, namespace=No
458539 user = user ,
459540 flag = flag ,
460541 name = name ,
542+ only_namespace = only_namespace ,
461543 namespace = namespace ,
462544 order_params = order_params ,
463545 )
@@ -558,7 +640,11 @@ def enough_storage_available(self, data):
558640 return True , free_space
559641
560642 def user_info (self ):
561- resp = self .get ("/v1/user/" + self .username ())
643+ server_type = self .server_type ()
644+ if server_type == ServerType .OLD :
645+ resp = self .get ("/v1/user/" + self .username ())
646+ else :
647+ resp = self .get ("/v1/user/profile" )
562648 return json .load (resp )
563649
564650 def set_project_access (self , project_path , access ):
@@ -709,6 +795,7 @@ def project_status(self, directory):
709795 project_path = mp .metadata ["name" ]
710796 local_version = mp .metadata ["version" ]
711797 server_info = self .project_info (project_path , since = local_version )
798+
712799 pull_changes = mp .get_pull_changes (server_info ["files" ])
713800
714801 push_changes = mp .get_push_changes ()
0 commit comments