Skip to content

Commit 002e038

Browse files
authored
Merge pull request #296 from MerginMaps/v2-pull-integration
V2 pull integration
2 parents 52ce00f + 7f4497d commit 002e038

9 files changed

Lines changed: 1342 additions & 325 deletions

File tree

mergin/client.py

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
from enum import Enum
2121
from typing import Optional, Type, Union
2222

23+
from .models import (
24+
ProjectDelta,
25+
ProjectDeltaItemDiff,
26+
ProjectDeltaChange,
27+
ProjectInfo,
28+
ProjectFile,
29+
ProjectWorkspace,
30+
)
31+
2332
from .common import (
2433
SYNC_ATTEMPT_WAIT,
2534
SYNC_ATTEMPTS,
@@ -732,6 +741,93 @@ def project_info(self, project_path_or_id, since=None, version=None):
732741
resp = self.get("/v1/project/{}".format(project_path_or_id), params)
733742
return json.load(resp)
734743

744+
def project_info_v2(self, project_id: str, files_at_version=None) -> ProjectInfo:
745+
"""
746+
Fetch info about project.
747+
748+
:param project_id: Project's id
749+
:type project_id: String
750+
:param files_at_version: Version to track files at given version
751+
:type files_at_version: String
752+
"""
753+
self.check_v2_project_info_support()
754+
755+
params = {}
756+
if files_at_version:
757+
params = {"files_at_version": files_at_version}
758+
resp = self.get(f"/v2/projects/{project_id}", params)
759+
resp_json = json.load(resp)
760+
project_workspace = resp_json.get("workspace")
761+
762+
# make sure we are making project info 1:1 with files_at_version parameter.
763+
# we have files prepared to next sync with proper version pulled from server
764+
version = resp_json.get("version") if files_at_version is None else files_at_version
765+
return ProjectInfo(
766+
id=resp_json.get("id"),
767+
name=resp_json.get("name"),
768+
created_at=resp_json.get("created_at"),
769+
updated_at=resp_json.get("updated_at"),
770+
version=version,
771+
public=resp_json.get("public"),
772+
role=resp_json.get("role"),
773+
size=resp_json.get("size"),
774+
workspace=ProjectWorkspace(
775+
id=project_workspace.get("id"),
776+
name=project_workspace.get("name"),
777+
),
778+
files=[
779+
ProjectFile(
780+
checksum=f.get("checksum"),
781+
mtime=f.get("mtime"),
782+
path=f.get("path"),
783+
size=f.get("size"),
784+
)
785+
for f in resp_json.get("files", [])
786+
],
787+
)
788+
789+
def get_project_delta(self, project_id: str, since: str, to: typing.Optional[str] = None) -> ProjectDelta:
790+
"""
791+
Fetch info about project delta since given version.
792+
793+
:param project_id: Project's id
794+
:type project_id: String
795+
:param since: Version to track history of files from
796+
:type since: String (e.g. v1)
797+
:param to: Optional version to track history of files to, if not given latest version is used
798+
:type to: String (e.g. v2)
799+
"""
800+
# If it is not enabled on the server, raise error
801+
if not self.server_features().get("v2_pull_enabled", False):
802+
raise ClientError("Project delta is not supported by the server")
803+
804+
params = {"since": since}
805+
if to:
806+
params["to"] = to
807+
resp = self.get(f"/v2/projects/{project_id}/delta", params)
808+
resp_parsed = json.load(resp)
809+
return ProjectDelta(
810+
to_version=resp_parsed.get("to_version"),
811+
changes=[
812+
ProjectDeltaChange(
813+
path=item["path"],
814+
size=item.get("size"),
815+
checksum=item.get("checksum"),
816+
version=item.get("version"),
817+
type=item.get("change"),
818+
diffs=(
819+
[
820+
ProjectDeltaItemDiff(
821+
id=diff.get("id"),
822+
)
823+
for diff in item.get("diffs", [])
824+
]
825+
),
826+
)
827+
for item in resp_parsed.get("items", [])
828+
],
829+
)
830+
735831
def paginated_project_versions(self, project_path, page, per_page=100, descending=False):
736832
"""
737833
Get records of project's versions (history) using calculated pagination.
@@ -822,11 +918,11 @@ def download_project(self, project_path, directory, version=None):
822918
:param project_path: Project's full name (<namespace>/<name>)
823919
:type project_path: String
824920
825-
:param version: Project version to download, e.g. v42
826-
:type version: String
827-
828921
:param directory: Target directory
829922
:type directory: String
923+
924+
:param version: Project version to download, e.g. v42
925+
:type version: String
830926
"""
831927
job = download_project_async(self, project_path, directory, version)
832928
download_project_wait(job)
@@ -1070,7 +1166,7 @@ def project_status(self, directory):
10701166
mp = MerginProject(directory)
10711167
server_info = self.project_info(mp.project_full_name(), since=mp.version())
10721168

1073-
pull_changes = mp.get_pull_changes(server_info["files"])
1169+
pull_changes = mp.get_pull_changes(server_info.get("files", []), server_info.get("version"))
10741170

10751171
push_changes = mp.get_push_changes()
10761172
push_changes_summary = mp.get_list_of_push_changes(push_changes)
@@ -1341,13 +1437,21 @@ def check_collaborators_members_support(self):
13411437
if not is_version_acceptable(self.server_version(), f"{min_version}"):
13421438
raise NotImplementedError(f"This needs server at version {min_version} or later")
13431439

1440+
def check_v2_project_info_support(self):
1441+
"""
1442+
Check if the server is compatible with v2 endpoint for project info
1443+
"""
1444+
min_version = "2025.8.2"
1445+
if not is_version_acceptable(self.server_version(), f"{min_version}"):
1446+
raise NotImplementedError(f"This needs server at version {min_version} or later")
1447+
13441448
def create_user(
13451449
self,
13461450
email: str,
13471451
password: str,
13481452
workspace_id: int,
13491453
workspace_role: Union[str, WorkspaceRole],
1350-
username: str = None,
1454+
username: Optional[str] = None,
13511455
notify_user: bool = False,
13521456
) -> dict:
13531457
"""

0 commit comments

Comments
 (0)