Skip to content

Commit 3041e75

Browse files
committed
Move validation to post_init
- change version of python in tests
1 parent 85387a5 commit 3041e75

File tree

5 files changed

+63
-66
lines changed

5 files changed

+63
-66
lines changed

.github/workflows/autotests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
- uses: actions/setup-python@v2
2121
with:
22-
python-version: '3.x'
22+
python-version: '3.8'
2323

2424
- name: Install python package dependencies
2525
run: |

mergin/client_push.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import time
2323
from typing import List, Tuple, Optional, ByteString
2424

25-
from .local_changes import LocalChange, LocalChanges
25+
from .local_changes import ChangesValidationError, LocalChange, LocalChanges
2626

2727
from .common import (
2828
MAX_UPLOAD_VERSIONED_SIZE,
@@ -481,22 +481,14 @@ def get_push_changes_batch(mc, mp: MerginProject) -> Tuple[LocalChanges, int]:
481481
project_role = mp.project_role()
482482
changes = filter_changes(mc, project_role, changes)
483483

484-
local_changes = LocalChanges(
485-
added=[LocalChange(**change) for change in changes["added"]],
486-
updated=[LocalChange(**change) for change in changes["updated"]],
487-
removed=[LocalChange(**change) for change in changes["removed"]],
488-
)
489-
490-
over_limit_media = local_changes.get_media_upload_over_size(MAX_UPLOAD_MEDIA_SIZE)
491-
if over_limit_media:
492-
raise ClientError(
493-
f"File {over_limit_media.path} to upload exceeds the maximum allowed size of {MAX_UPLOAD_MEDIA_SIZE / (1024**3)} GB."
484+
try:
485+
local_changes = LocalChanges(
486+
added=[LocalChange(**change) for change in changes["added"]],
487+
updated=[LocalChange(**change) for change in changes["updated"]],
488+
removed=[LocalChange(**change) for change in changes["removed"]],
494489
)
495-
496-
over_limit_gpkg = local_changes.get_gpgk_upload_over_size(MAX_UPLOAD_VERSIONED_SIZE)
497-
if over_limit_gpkg:
490+
except ChangesValidationError as e:
498491
raise ClientError(
499-
f"Geopackage {over_limit_gpkg.path} to upload exceeds the maximum allowed size of {MAX_UPLOAD_VERSIONED_SIZE / (1024**3)} GB."
492+
f"Some files exceeded maximum upload size. Files: {', '.join([c.path for c in e.invalid_changes])}. Maximum size for media files is {e.max_media_upload_size / (1024**3)} GB and for geopackage files {e.max_versioned_upload_size / (1024**3)} GB."
500493
)
501-
502494
return local_changes, sum(len(v) for v in changes.values())

mergin/local_changes.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@
33
from typing import Optional, List, Tuple
44

55
from .utils import is_versioned_file
6+
from .common import MAX_UPLOAD_MEDIA_SIZE, MAX_UPLOAD_VERSIONED_SIZE
67

78
MAX_UPLOAD_CHANGES = 100
89

910

11+
# The custom exception
12+
class ChangesValidationError(Exception):
13+
def __init__(self, message, invalid_changes=[], max_media_upload_size=None, max_versioned_upload_size=None):
14+
super().__init__(message)
15+
self.invalid_changes = invalid_changes if invalid_changes is not None else []
16+
self.max_media_upload_size = max_media_upload_size
17+
self.max_versioned_upload_size = max_versioned_upload_size
18+
19+
1020
@dataclass
1121
class BaseLocalChange:
1222
path: str
@@ -63,7 +73,20 @@ def __post_init__(self):
6373
"""
6474
Enforce a limit of changes combined from `added` and `updated`.
6575
"""
66-
total_changes = len(self.get_upload_changes())
76+
upload_changes = self.get_upload_changes()
77+
total_changes = len(upload_changes)
78+
oversize_changes = []
79+
for change in upload_changes:
80+
if not is_versioned_file(change.path) and change.size > MAX_UPLOAD_MEDIA_SIZE:
81+
oversize_changes.append(change)
82+
elif not change.diff and change.size > MAX_UPLOAD_VERSIONED_SIZE:
83+
oversize_changes.append(change)
84+
if oversize_changes:
85+
error = ChangesValidationError("Some files exceed the maximum upload size", oversize_changes)
86+
error.max_media_upload_size = MAX_UPLOAD_MEDIA_SIZE
87+
error.max_versioned_upload_size = MAX_UPLOAD_VERSIONED_SIZE
88+
raise error
89+
6790
if total_changes > MAX_UPLOAD_CHANGES:
6891
# Calculate how many changes to keep from `added` and `updated`
6992
added_limit = min(len(self.added), MAX_UPLOAD_CHANGES)
@@ -112,23 +135,3 @@ def update_chunks(self, server_chunks: List[Tuple[str, str]]) -> None:
112135

113136
for change in self.updated:
114137
change.chunks = self._map_unique_chunks(change.chunks, server_chunks)
115-
116-
def get_media_upload_over_size(self, size_limit: int) -> Optional[LocalChange]:
117-
"""
118-
Find the first media file in added and updated changes that exceeds the size limit.
119-
:return: The first LocalChange that exceeds the size limit, or None if no such file exists.
120-
"""
121-
for change in self.get_upload_changes():
122-
if not is_versioned_file(change.path) and change.size > size_limit:
123-
return change
124-
125-
def get_gpgk_upload_over_size(self, size_limit: int) -> Optional[LocalChange]:
126-
"""
127-
Find the first GPKG file in added and updated changes that exceeds the size limit.
128-
Do not include diffs (only new or overwritten files).
129-
:param size_limit: The size limit in bytes.
130-
:return: The first LocalChange that exceeds the size limit, or None if no such file exists.
131-
"""
132-
for change in self.get_upload_changes():
133-
if is_versioned_file(change.path) and not change.diff and change.size > size_limit:
134-
return change

mergin/test/test_client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3225,11 +3225,11 @@ def test_push_file_limits(mc):
32253225
mc.download_project(project, project_dir)
32263226
shutil.copy(os.path.join(TEST_DATA_DIR, "base.gpkg"), project_dir)
32273227
# setting to some minimal value to mock limit hit
3228-
with patch("mergin.client_push.MAX_UPLOAD_VERSIONED_SIZE", 1):
3229-
with pytest.raises(ClientError, match=f"base.gpkg to upload exceeds the maximum allowed size of {1/1024**3}"):
3228+
with patch("mergin.local_changes.MAX_UPLOAD_VERSIONED_SIZE", 1):
3229+
with pytest.raises(ClientError, match=f"Some files exceeded maximum upload size. Files: base.gpkg."):
32303230
mc.push_project(project_dir)
32313231

32323232
shutil.copy(os.path.join(TEST_DATA_DIR, "test.txt"), project_dir)
3233-
with patch("mergin.client_push.MAX_UPLOAD_MEDIA_SIZE", 1):
3234-
with pytest.raises(ClientError, match=f"test.txt to upload exceeds the maximum allowed size of {1/1024**3}"):
3233+
with patch("mergin.local_changes.MAX_UPLOAD_MEDIA_SIZE", 1):
3234+
with pytest.raises(ClientError, match=f"Some files exceeded maximum upload size. Files: test.txt."):
32353235
mc.push_project(project_dir)

mergin/test/test_local_changes.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from datetime import datetime
2+
import pytest
3+
from unittest.mock import patch
24

3-
from ..local_changes import LocalChange, LocalChanges, MAX_UPLOAD_CHANGES
5+
from ..local_changes import ChangesValidationError, LocalChange, LocalChanges, MAX_UPLOAD_CHANGES
46

57

68
def test_local_changes_from_dict():
@@ -120,10 +122,10 @@ def test_local_changes_get_upload_changes():
120122
assert upload_changes[1].path == "file2.txt" # Second change is from updated
121123

122124

123-
def test_local_changes_get_media_upload_over_size():
125+
def test_local_changes_post_init_validation_media():
124126
"""Test the get_media_upload_file method of LocalChanges."""
125127
# Define constants
126-
SIZE_LIMIT_MB = 10
128+
SIZE_LIMIT_MB = 5
127129
SIZE_LIMIT_BYTES = SIZE_LIMIT_MB * 1024 * 1024
128130
SMALL_FILE_SIZE = 1024
129131
LARGE_FILE_SIZE = 15 * 1024 * 1024
@@ -139,18 +141,16 @@ def test_local_changes_get_media_upload_over_size():
139141
]
140142

141143
# Initialize LocalChanges
142-
local_changes = LocalChanges(added=added, updated=updated)
143-
144-
# Call get_media_upload_file with a size limit
145-
media_file = local_changes.get_media_upload_over_size(SIZE_LIMIT_BYTES)
146-
147-
# Assertions
148-
assert media_file is not None
149-
assert media_file.path == "file2.jpg" # The first file over the limit
150-
assert media_file.size == LARGE_FILE_SIZE
144+
with patch("mergin.local_changes.MAX_UPLOAD_MEDIA_SIZE", SIZE_LIMIT_BYTES):
145+
with pytest.raises(ChangesValidationError, match="Some files exceed") as err:
146+
LocalChanges(added=added, updated=updated)
147+
print(err.value.invalid_changes)
148+
assert len(err.value.invalid_changes) == 1
149+
assert "file2.jpg" == err.value.invalid_changes[0].path
150+
assert err.value.invalid_changes[0].size == LARGE_FILE_SIZE
151151

152152

153-
def test_local_changes_get_gpgk_upload_over_size():
153+
def test_local_changes_post_init_validation_media():
154154
"""Test the get_gpgk_upload_file method of LocalChanges."""
155155
# Define constants
156156
SIZE_LIMIT_MB = 10
@@ -166,21 +166,23 @@ def test_local_changes_get_gpgk_upload_over_size():
166166
), # Over limit
167167
]
168168
updated = [
169-
LocalChange(path="file3.gpkg", checksum="lmn456", size=5 * 1024 * 1024, mtime=datetime.now()),
169+
LocalChange(
170+
path="file3.gpkg",
171+
checksum="lmn456",
172+
size=SIZE_LIMIT_BYTES + 1,
173+
mtime=datetime.now(),
174+
diff={"path": "file3-diff.gpkg", "checksum": "diff123", "size": 1024, "mtime": datetime.now()},
175+
),
170176
LocalChange(path="file4.txt", checksum="opq123", size=SMALL_FILE_SIZE, mtime=datetime.now()),
171177
]
172178

173179
# Initialize LocalChanges
174-
local_changes = LocalChanges(added=added, updated=updated)
175-
176-
# Call get_gpgk_upload_file with a size limit
177-
gpkg_file = local_changes.get_gpgk_upload_over_size(SIZE_LIMIT_BYTES)
178-
179-
# Assertions
180-
assert gpkg_file is not None
181-
assert gpkg_file.path == "file2.gpkg" # The first GPKG file over the limit
182-
assert gpkg_file.size == LARGE_FILE_SIZE
183-
assert gpkg_file.diff is None # Ensure it doesn't include diffs
180+
with patch("mergin.local_changes.MAX_UPLOAD_VERSIONED_SIZE", SIZE_LIMIT_BYTES):
181+
with pytest.raises(ChangesValidationError) as err:
182+
LocalChanges(added=added, updated=updated)
183+
assert len(err.value.invalid_changes) == 1
184+
assert "file2.gpkg" == err.value.invalid_changes[0].path
185+
assert err.value.invalid_changes[0].size == LARGE_FILE_SIZE
184186

185187

186188
def test_local_changes_post_init():

0 commit comments

Comments
 (0)