Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions mergin/client_push.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,14 +268,31 @@ def push_project_finalize(job):
resp = job.mc.post("/v1/project/push/finish/%s" % job.transaction_id)
job.server_resp = json.load(resp)
except ClientError as err:
# Log additional metadata on server error 502 or 504
if hasattr(err, "http_error") and err.http_error in (502, 504):
# Log additional metadata on server error 502 or 504 (extended logging only)
http_code = getattr(err, "http_error", None)
if http_code in (502, 504):
job.mp.log.error(
f"Push failed with HTTP error {err.http_error}. "
f"Push failed with HTTP error {http_code}. "
f"Upload details: {len(job.upload_queue_items)} file chunks, total size {job.total_size} bytes."
)
job.mp.log.error("Files:")
for f in job.changes.get("added", []) + job.changes.get("updated", []):
path = f.get("path", "<unknown>")
size = f.get("size", "?")
if "diff" in f:
diff_info = f.get("diff", {})
diff_size = diff_info.get("size", "?")
# best-effort: number of geodiff changes (if available)
changes_cnt = _geodiff_changes_count(job.mp, diff_info.get("path"))
if changes_cnt is None:
job.mp.log.error(f" - {path}, size={size}, diff_size={diff_size}, changes=n/a")
else:
job.mp.log.error(f" - {path}, size={size}, diff_size={diff_size}, changes={changes_cnt}")
else:
job.mp.log.error(f" - {path}, size={size}")

# server returns various error messages with filename or something generic
# it would be better if it returned list of failed files (and reasons) whenever possible
# it would be better if it returned list of failed files (and reasons) whenever possible
job.mp.log.error("--- push finish failed! " + str(err))

# if push finish fails, the transaction is not killed, so we
Expand Down Expand Up @@ -303,6 +320,25 @@ def push_project_finalize(job):
job.mp.log.info("--- push finished - new project version " + job.server_resp["version"])


def _geodiff_changes_count(mp: MerginProject, diff_rel_path: str):
"""
Best-effort: return number of changes in the .gpkg diff (int) or None.
Never raises – diagnostics/logging must not fail.
"""

try:
diff_abs = mp.fpath_meta(diff_rel_path)
except FileNotFoundError:
return None

try:
from pygeodiff import GeoDiff, GeoDiffLibError

return GeoDiff().changes_count(diff_abs)
except GeoDiffLibError:
return None


def push_project_cancel(job):
"""
To be called (from main thread) to cancel a job that has uploads in progress.
Expand Down
99 changes: 99 additions & 0 deletions mergin/test/test_push_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from types import SimpleNamespace
from pathlib import Path
import logging
import tempfile
import pytest

from pygeodiff import GeoDiff
from mergin.client_push import push_project_finalize, UploadJob
from mergin.common import ClientError


@pytest.mark.parametrize("status_code", [502, 504])
def test_push_finalize_logs_on_5xx_real_diff(caplog, status_code, tmp_path):
# --- data ---
data_dir = Path(__file__).resolve().parent / "test_data"
base = data_dir / "base.gpkg"
modified = data_dir / "inserted_1_A.gpkg"
assert base.exists()
assert modified.exists()

diff_path = tmp_path / "base_to_inserted_1_A.diff"
GeoDiff().create_changeset(str(base), str(modified), str(diff_path))
diff_size = diff_path.stat().st_size
file_size = modified.stat().st_size

# --- logger ---
logger = logging.getLogger("mergin_test")
logger.setLevel(logging.DEBUG)
logger.propagate = True
caplog.set_level(logging.ERROR, logger="mergin_test")

# --- fake MP/MC len tam, kde treba ---
class MP:
def __init__(self, log):
self.log = log

def update_metadata(self, *a, **k):
pass

def apply_push_changes(self, *a, **k):
pass

def fpath_meta(self, rel):
return str(diff_path) if rel == diff_path.name else rel

tx = "tx-1"

class MC:
def post(self, url, *args, **kwargs):
if url.endswith(f"/v1/project/push/finish/{tx}"):
err = ClientError("Gateway error")
setattr(err, "http_error", status_code)
raise err
if url.endswith(f"/v1/project/push/cancel/{tx}"):
return SimpleNamespace(msg="cancelled")
return SimpleNamespace(msg="ok")

tmp_dir = tempfile.TemporaryDirectory(prefix="python-api-client-test-")

# --- real UploadJob objekt ---
job = UploadJob(
project_path="u/p",
changes={
"added": [],
"updated": [
{
"path": modified.name,
"size": file_size,
"diff": {"path": diff_path.name, "size": diff_size},
"chunks": [1],
}
],
"removed": [],
},
transaction_id=tx,
mp=MP(logger),
mc=MC(),
tmp_dir=tmp_dir,
)

# nastav to, čo finalize() očakáva
job.total_size = 1234
job.transferred_size = 1234
job.upload_queue_items = [1] # len pre log „Upload details“
job.executor = SimpleNamespace(shutdown=lambda wait=True: None)
job.futures = [SimpleNamespace(done=lambda: True, exception=lambda: None, running=lambda: False)]
job.server_resp = {"version": "n/a"} # aj tak sa 5xx vyhodí skôr

with pytest.raises(ClientError):
push_project_finalize(job)

text = caplog.text
assert f"Push failed with HTTP error {status_code}" in text
assert "Upload details:" in text
assert "Files:" in text
assert modified.name in text
assert f"size={file_size}" in text
assert f"diff_size={diff_size}" in text
assert ("changes=" in text) or ("changes=n/a" in text)
6 changes: 3 additions & 3 deletions scripts/update_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


def replace_in_file(filepath, regex, sub):
with open(filepath, 'r') as f:
with open(filepath, "r") as f:
content = f.read()

content_new = re.sub(regex, sub, content, flags=re.M)
Expand All @@ -15,14 +15,14 @@ def replace_in_file(filepath, regex, sub):

dir_path = os.path.dirname(os.path.realpath(__file__))
parser = argparse.ArgumentParser()
parser.add_argument('--version', help='version to replace')
parser.add_argument("--version", help="version to replace")
args = parser.parse_args()
ver = args.version
print("using version " + ver)

about_file = os.path.join(dir_path, os.pardir, "mergin", "version.py")
print("patching " + about_file)
replace_in_file(about_file, "__version__\s=\s\".*", "__version__ = \"" + ver + "\"")
replace_in_file(about_file, '__version__\s=\s".*', '__version__ = "' + ver + '"')

setup_file = os.path.join(dir_path, os.pardir, "setup.py")
print("patching " + setup_file)
Expand Down
44 changes: 20 additions & 24 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,31 @@
from setuptools import setup, find_packages

setup(
name='mergin-client',
version='0.10.5',
url='https://github.com/MerginMaps/python-api-client',
license='MIT',
author='Lutra Consulting Ltd.',
author_email='info@merginmaps.com',
description='Mergin Maps utils and client',
long_description='Mergin Maps utils and client',

name="mergin-client",
version="0.10.5",
url="https://github.com/MerginMaps/python-api-client",
license="MIT",
author="Lutra Consulting Ltd.",
author_email="info@merginmaps.com",
description="Mergin Maps utils and client",
long_description="Mergin Maps utils and client",
packages=find_packages(),

platforms='any',
platforms="any",
install_requires=[
'python-dateutil==2.8.2',
'pygeodiff==2.0.4',
'pytz==2022.1',
'click==8.1.3',
"python-dateutil==2.8.2",
"pygeodiff==2.0.4",
"pytz==2022.1",
"click==8.1.3",
],

entry_points={
'console_scripts': ['mergin=mergin.cli:cli'],
"console_scripts": ["mergin=mergin.cli:cli"],
},

classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3'
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
],
package_data={'mergin': ['cert.pem']}
package_data={"mergin": ["cert.pem"]},
)