Skip to content

Commit bb0ad10

Browse files
committed
Merge remote-tracking branch 'origin/master' into push-v2-integration
2 parents 2d6c72f + 70a75b5 commit bb0ad10

File tree

4 files changed

+89
-11
lines changed

4 files changed

+89
-11
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ htmlcov
1212
.pytest_cache
1313
deps
1414
venv
15-
.vscode/settings.json
1615
debug.py
16+
.vscode/

mergin/client.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import typing
1818
import warnings
1919
from time import sleep
20+
from enum import Enum
21+
from typing import Optional, Type, Union
2022

2123
from .common import (
2224
SYNC_ATTEMPT_WAIT,
@@ -51,6 +53,13 @@
5153
UploadChunksCache,
5254
)
5355
from .utils import DateTimeEncoder, get_versions_with_file_changes, int_version, is_version_acceptable
56+
from .utils import (
57+
DateTimeEncoder,
58+
get_versions_with_file_changes,
59+
int_version,
60+
is_version_acceptable,
61+
normalize_role,
62+
)
5463
from .version import __version__
5564

5665
this_dir = os.path.dirname(os.path.realpath(__file__))
@@ -1337,7 +1346,7 @@ def create_user(
13371346
email: str,
13381347
password: str,
13391348
workspace_id: int,
1340-
workspace_role: WorkspaceRole,
1349+
workspace_role: Union[str, WorkspaceRole],
13411350
username: str = None,
13421351
notify_user: bool = False,
13431352
) -> dict:
@@ -1352,11 +1361,15 @@ def create_user(
13521361
param notify_user: flag for email notifications - confirmation email will be sent
13531362
"""
13541363
self.check_collaborators_members_support()
1364+
role_enum = normalize_role(workspace_role, WorkspaceRole)
1365+
if role_enum is None:
1366+
raise ValueError(f"Invalid role: {workspace_role}")
1367+
13551368
params = {
13561369
"email": email,
13571370
"password": password,
13581371
"workspace_id": workspace_id,
1359-
"role": workspace_role.value,
1372+
"role": role_enum.value,
13601373
"notify_user": notify_user,
13611374
}
13621375
if username:
@@ -1381,17 +1394,26 @@ def list_workspace_members(self, workspace_id: int) -> typing.List[dict]:
13811394
return json.load(resp)
13821395

13831396
def update_workspace_member(
1384-
self, workspace_id: int, user_id: int, workspace_role: WorkspaceRole, reset_projects_roles: bool = False
1397+
self,
1398+
workspace_id: int,
1399+
user_id: int,
1400+
workspace_role: Union[str, WorkspaceRole],
1401+
reset_projects_roles: bool = False,
13851402
) -> dict:
13861403
"""
13871404
Update workspace role of a workspace member, optionally resets the projects role
13881405
13891406
param reset_projects_roles: all project specific roles will be removed
13901407
"""
13911408
self.check_collaborators_members_support()
1409+
1410+
role_enum = normalize_role(workspace_role, WorkspaceRole)
1411+
if role_enum is None:
1412+
raise ValueError(f"Invalid role: {workspace_role}")
1413+
13921414
params = {
13931415
"reset_projects_roles": reset_projects_roles,
1394-
"workspace_role": workspace_role.value,
1416+
"workspace_role": role_enum.value,
13951417
}
13961418
workspace_member = self.patch(f"v2/workspaces/{workspace_id}/members/{user_id}", params, json_headers)
13971419
return json.load(workspace_member)
@@ -1411,25 +1433,35 @@ def list_project_collaborators(self, project_id: str) -> typing.List[dict]:
14111433
project_collaborators = self.get(f"v2/projects/{project_id}/collaborators")
14121434
return json.load(project_collaborators)
14131435

1414-
def add_project_collaborator(self, project_id: str, user: str, project_role: ProjectRole) -> dict:
1436+
def add_project_collaborator(self, project_id: str, user: str, project_role: Union[str, ProjectRole]) -> dict:
14151437
"""
14161438
Add a user to project collaborators and grant them a project role.
14171439
Fails if user is already a member of the project.
14181440
14191441
param user: login (username or email) of the user
14201442
"""
14211443
self.check_collaborators_members_support()
1444+
1445+
role_enum = normalize_role(project_role, ProjectRole)
1446+
if role_enum is None:
1447+
raise ValueError(f"Invalid role: {project_role}")
1448+
14221449
params = {"role": project_role.value, "user": user}
14231450
project_collaborator = self.post(f"v2/projects/{project_id}/collaborators", params, json_headers)
14241451
return json.load(project_collaborator)
14251452

1426-
def update_project_collaborator(self, project_id: str, user_id: int, project_role: ProjectRole) -> dict:
1453+
def update_project_collaborator(self, project_id: str, user_id: int, project_role: Union[str, ProjectRole]) -> dict:
14271454
"""
14281455
Update project role of the existing project collaborator.
14291456
Fails if user is not a member of the project yet.
14301457
"""
14311458
self.check_collaborators_members_support()
1459+
1460+
role_enum = normalize_role(project_role, ProjectRole)
1461+
if role_enum is None:
1462+
raise ValueError(f"Invalid role: {project_role}")
14321463
params = {"role": project_role.value}
1464+
14331465
project_collaborator = self.patch(f"v2/projects/{project_id}/collaborators/{user_id}", params, json_headers)
14341466
return json.load(project_collaborator)
14351467

@@ -1505,14 +1537,19 @@ def send_logs(
15051537
request = urllib.request.Request(url, data=payload, headers=header)
15061538
return self._do_request(request)
15071539

1508-
def create_invitation(self, workspace_id: int, email: str, workspace_role: WorkspaceRole):
1540+
def create_invitation(self, workspace_id: int, email: str, workspace_role: Union[str, WorkspaceRole]):
15091541
"""
15101542
Create invitation to workspace for specific role
15111543
"""
15121544
min_version = "2025.6.1"
15131545
if not is_version_acceptable(self.server_version(), min_version):
15141546
raise NotImplementedError(f"This needs server at version {min_version} or later")
1515-
params = {"email": email, "role": workspace_role.value}
1547+
1548+
role_enum = normalize_role(workspace_role, WorkspaceRole)
1549+
if role_enum is None:
1550+
raise ValueError(f"Invalid role: {workspace_role}")
1551+
1552+
params = {"email": email, "role": role_enum.value}
15161553
ws_inv = self.post(f"v2/workspaces/{workspace_id}/invitations", params, json_headers)
15171554
return json.load(ws_inv)
15181555

mergin/test/test_client.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
unique_path_name,
4444
conflicted_copy_file_name,
4545
edit_conflict_file_name,
46+
normalize_role,
4647
)
4748
from ..merginproject import pygeodiff
4849
from ..report import create_report
@@ -3254,3 +3255,25 @@ def test_server_type(mc):
32543255
mock_client_get.side_effect = ClientError(detail="Service unavailable", http_error=503)
32553256
with pytest.raises(ClientError, match="Service unavailable"):
32563257
mc.server_type()
3258+
3259+
3260+
@pytest.mark.parametrize(
3261+
"value, role_enum, expected",
3262+
[
3263+
("guest", WorkspaceRole, WorkspaceRole.GUEST),
3264+
(" GuEsT ", WorkspaceRole, WorkspaceRole.GUEST),
3265+
("writer", ProjectRole, ProjectRole.WRITER),
3266+
(" WRITER ", ProjectRole, ProjectRole.WRITER),
3267+
("guuuest", WorkspaceRole, None),
3268+
("ownerr", ProjectRole, None),
3269+
("", WorkspaceRole, None),
3270+
(None, WorkspaceRole, None),
3271+
(123, WorkspaceRole, None),
3272+
],
3273+
)
3274+
def test_normalize_role_parametrized(value, role_enum, expected):
3275+
result = normalize_role(value, role_enum)
3276+
if expected is None:
3277+
assert result is None
3278+
else:
3279+
assert result == expected

mergin/utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from datetime import datetime
88
from pathlib import Path
99
import tempfile
10-
from .common import ClientError
11-
from typing import ByteString
10+
from enum import Enum
11+
from typing import Optional, Type, Union, ByteString
12+
from .common import ClientError, WorkspaceRole
1213

1314

1415
def generate_checksum(file, chunk_size=4096):
@@ -322,3 +323,20 @@ def cleanup_tmp_dir(mp, tmp_dir: tempfile.TemporaryDirectory):
322323
mp.log.warning(f"Permission error during tmp dir cleanup: {tmp_dir.name}")
323324
except Exception as e:
324325
mp.log.error(f"Error during tmp dir cleanup: {tmp_dir.name}: {e}")
326+
327+
328+
def normalize_role(role: Union[str, Enum], enum_cls: Type[Enum]) -> Optional[Enum]:
329+
"""
330+
Takes a role as a string or an Enum member and returns the corresponding Enum member
331+
from the given enum class. Returns None if the input is invalid or no match is found.
332+
"""
333+
if isinstance(role, enum_cls):
334+
return role
335+
336+
if isinstance(role, str):
337+
try:
338+
return enum_cls(role.strip().lower())
339+
except ValueError:
340+
return None
341+
342+
return None

0 commit comments

Comments
 (0)