Skip to content

Commit 45d40b3

Browse files
authored
Merge pull request #568 from MerginMaps/master
Backport master -> develop
2 parents fe057e4 + 41c4241 commit 45d40b3

9 files changed

Lines changed: 78 additions & 25 deletions

File tree

deployment/community/docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ services:
2323
networks:
2424
- merginmaps
2525
server:
26-
image: lutraconsulting/merginmaps-backend:2025.2.2
26+
image: lutraconsulting/merginmaps-backend:2025.7.3
2727
container_name: merginmaps-server
2828
restart: always
2929
user: 901:999
@@ -39,7 +39,7 @@ services:
3939
networks:
4040
- merginmaps
4141
celery-beat:
42-
image: lutraconsulting/merginmaps-backend:2025.2.2
42+
image: lutraconsulting/merginmaps-backend:2025.7.3
4343
container_name: celery-beat
4444
restart: always
4545
env_file:
@@ -54,7 +54,7 @@ services:
5454
networks:
5555
- merginmaps
5656
celery-worker:
57-
image: lutraconsulting/merginmaps-backend:2025.2.2
57+
image: lutraconsulting/merginmaps-backend:2025.7.3
5858
container_name: celery-worker
5959
restart: always
6060
user: 901:999
@@ -73,7 +73,7 @@ services:
7373
networks:
7474
- merginmaps
7575
web:
76-
image: lutraconsulting/merginmaps-frontend:2025.2.2
76+
image: lutraconsulting/merginmaps-frontend:2025.7.3
7777
container_name: merginmaps-web
7878
restart: always
7979
depends_on:

deployment/enterprise/.env.template

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ LOCAL_PROJECTS=/data
117117

118118
# data download
119119

120-
#MAX_DOWNLOAD_ARCHIVE_SIZE=1024 * 1024 * 1024 # max total files size in bytes for archive download
120+
#MAX_DOWNLOAD_ARCHIVE_SIZE=1024 * 1024 * 1024 * 10 # max total files size in bytes for archive download
121121

122122
#USE_X_ACCEL=False # use nginx (in front of gunicorn) to serve files (https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/)
123123
USE_X_ACCEL=1
@@ -214,12 +214,8 @@ VECTOR_TILES_URL=https://tiles-ee.merginmaps.com/data/default/{z}/{x}/{y}.pbf
214214

215215
VECTOR_TILES_STYLE_URL=https://tiles-ee.merginmaps.com//styles/default.json
216216

217-
#QGIS_EXTRACTOR_API_URL=http://mergin-qgis-extractor:8000
218-
219217
#WMTS_SERVER_URL=http://mergin-qgis-nginx:80
220218

221-
#QGIS_EXTRACTOR_TIMEOUT=60
222-
223219
#OVERVIEW_MAX_FILE_SIZE=1048576 # 1MB
224220

225221
### Diagnostic logs from Mobile and QGIS Plugin

deployment/enterprise/docker-compose.maps.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ services:
3030
- ./qgis_nginx.conf:/etc/nginx/conf.d/default.conf
3131
qgis_extractor:
3232
container_name: mergin-qgis-extractor
33-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-extractor-ee:2025.1.0
33+
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/qgis-extractor-ee:2025.3.0
3434
user: 901:999
3535
networks:
3636
- mergin-net
3737
environment:
3838
- OVERVIEWS_DATA_DIR=/data
3939
- MM_WMS_TILE_BUFFER=100
4040
- MM_WMS_AVOID_ARTIFACTS=1
41+
- BROKER_URL=redis://merginmaps-redis:6379/0
42+
- CELERY_RESULT_BACKEND=redis://merginmaps-redis:6379/0
43+
- 'CELERY_TASK_ROUTES={"src.maps.tasks.finish_overview": {"queue": "celery"}}'
4144
volumes:
4245
- ./map_data:/data

deployment/enterprise/docker-compose.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ networks:
55

66
services:
77
server:
8-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
8+
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
99
container_name: merginmaps-server
1010
restart: always
1111
user: 901:999
@@ -22,7 +22,7 @@ services:
2222
networks:
2323
- mergin
2424
web:
25-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-front:2025.5.1
25+
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-front:2025.7.3
2626
container_name: merginmaps-web
2727
restart: always
2828
depends_on:
@@ -52,7 +52,7 @@ services:
5252
- server
5353

5454
celery-beat:
55-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
55+
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
5656
container_name: merginmaps-celery-beat
5757
restart: always
5858
user: 901:999
@@ -66,7 +66,7 @@ services:
6666
- mergin
6767

6868
celery-worker:
69-
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.5.1
69+
image: 433835555346.dkr.ecr.eu-west-1.amazonaws.com/mergin/mergin-ee-back:2025.7.3
7070
container_name: merginmaps-celery-worker
7171
restart: always
7272
user: 901:999
@@ -76,6 +76,8 @@ services:
7676
- ./map_data:/overviews
7777
env_file:
7878
- .prod.env
79+
environment:
80+
- CELERY_ROUTES={ 'qgis-extractor.*':{'queue':'extractor'} }
7981
depends_on:
8082
- db
8183
- redis

server/mergin/sync/models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,9 +1042,12 @@ def clear(self):
10421042
"""Clean up pending upload.
10431043
Uploaded files and table records are removed, and another upload can start.
10441044
"""
1045-
move_to_tmp(self.upload_dir, self.id)
1046-
db.session.delete(self)
1047-
db.session.commit()
1045+
try:
1046+
move_to_tmp(self.upload_dir, self.id)
1047+
db.session.delete(self)
1048+
db.session.commit()
1049+
except Exception:
1050+
logging.exception(f"Failed to clear upload.")
10481051

10491052
def process_chunks(
10501053
self, use_shared_chunk_dir: bool

server/mergin/sync/public_api_v2_controller.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from flask_login import current_user
1414
from marshmallow import ValidationError
1515
from sqlalchemy.exc import IntegrityError
16+
from sqlalchemy.orm.exc import ObjectDeletedError
1617

1718
from mergin.sync.tasks import remove_transaction_chunks
1819

@@ -301,6 +302,7 @@ def create_project_version(id):
301302
upload.clear()
302303
return DataSyncError(failed_files=errors).response(422)
303304

305+
upload_deleted = False
304306
try:
305307
pv = ProjectVersion(
306308
project,
@@ -329,15 +331,20 @@ def create_project_version(id):
329331
remove_transaction_chunks.delay(chunks_ids)
330332

331333
logging.info(
332-
f"Push finished for project: {project.id}, project version: {v_next_version}, upload id: {upload.id}."
334+
f"Push finished for project: {project.id}, project version: {v_next_version}."
333335
)
334336
project_version_created.send(pv)
335337
push_finished.send(pv)
336-
except (psycopg2.Error, FileNotFoundError, IntegrityError) as err:
338+
except (
339+
psycopg2.Error,
340+
FileNotFoundError,
341+
IntegrityError,
342+
ObjectDeletedError,
343+
) as err:
337344
db.session.rollback()
345+
upload_deleted = isinstance(err, ObjectDeletedError)
338346
logging.exception(
339-
f"Failed to finish push for project: {project.id}, project version: {v_next_version}, "
340-
f"upload id: {upload.id}.: {str(err)}"
347+
f"Failed to finish push for project: {project.id}, project version: {v_next_version}: {str(err)}"
341348
)
342349
if (
343350
os.path.exists(version_dir)
@@ -359,8 +366,9 @@ def create_project_version(id):
359366
move_to_tmp(version_dir)
360367
raise
361368
finally:
362-
# remove artifacts
363-
upload.clear()
369+
# remove artifacts only if upload object is still valid
370+
if not upload_deleted:
371+
upload.clear()
364372

365373
result = ProjectSchemaV2().dump(project)
366374
result["files"] = ProjectFileSchema(

server/mergin/tests/test_public_api_v2.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import shutil
2121
from unittest.mock import patch
2222
from sqlalchemy.exc import IntegrityError
23+
from sqlalchemy.orm.exc import ObjectDeletedError
2324
import pytest
2425
from datetime import datetime, timedelta, timezone
2526
import json
@@ -485,6 +486,46 @@ def test_create_version_failures(client):
485486
assert response.status_code == 409
486487

487488

489+
def test_create_version_object_deleted_error(client):
490+
"""Test that ObjectDeletedError during push returns 422 without secondary exception"""
491+
project = Project.query.filter_by(
492+
workspace_id=test_workspace_id, name=test_project
493+
).first()
494+
495+
data = {
496+
"version": "v1",
497+
"changes": {
498+
"added": [],
499+
"removed": [
500+
file_info(test_project_dir, "base.gpkg"),
501+
],
502+
"updated": [],
503+
},
504+
}
505+
506+
# Create a real ObjectDeletedError by using internal SQLAlchemy state
507+
def raise_object_deleted(*args, **kwargs):
508+
# Create a minimal state-like object that ObjectDeletedError can use
509+
class FakeState:
510+
class_ = Upload
511+
512+
def obj(self):
513+
return None
514+
515+
raise ObjectDeletedError(FakeState())
516+
517+
with patch.object(
518+
ProjectVersion,
519+
"__init__",
520+
side_effect=raise_object_deleted,
521+
):
522+
response = client.post(f"v2/projects/{project.id}/versions", json=data)
523+
524+
# Should return 422 UploadError, not 500 from secondary exception
525+
assert response.status_code == 422
526+
assert response.json["code"] == UploadError.code
527+
528+
488529
def test_upload_chunk(client):
489530
"""Test pushing a chunk to a project"""
490531
project = Project.query.filter_by(

server/mergin/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55

66
def get_version():
7-
return "2025.8.2"
7+
return "2026.1.0"

server/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
setup(
88
name="mergin",
9-
version="2025.8.2",
9+
version="2026.1.0",
1010
url="https://github.com/MerginMaps/mergin",
1111
license="AGPL-3.0-only",
1212
author="Lutra Consulting Limited",

0 commit comments

Comments
 (0)