Skip to content
41 changes: 28 additions & 13 deletions dbsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import re
import pathlib
import logging
import typing

import psycopg2
import psycopg2.extensions
Expand Down Expand Up @@ -495,8 +496,10 @@ def _get_project_version(work_path) -> str:
return mp.version()


def _get_project_id(mp: MerginProject):
def _get_project_id(mp: typing.Union[MerginProject, str]):
"""Returns the project ID"""
if isinstance(mp, str):
mp = _get_mergin_project(mp)
try:
project_id = uuid.UUID(mp.project_id())
except (
Expand All @@ -523,7 +526,7 @@ def _set_db_project_comment(
"version": version,
}
if project_id:
comment["project_id"] = project_id
comment["project_id"] = str(project_id)
if error:
comment["error"] = error
cur = conn.cursor()
Expand All @@ -535,7 +538,7 @@ def _set_db_project_comment(
conn.commit()


def _get_db_project_comment(conn, schema):
def _get_db_project_comment(conn, schema) -> typing.Optional[typing.Dict]:
"""Get Mergin Maps project name and its current version in db schema"""
cur = conn.cursor()
schema = _add_quotes_to_schema_name(schema)
Expand Down Expand Up @@ -821,12 +824,12 @@ def pull(conn_cfg, mc):

os.remove(gpkg_basefile_old)
conn = psycopg2.connect(conn_cfg.conn_info)
version = _get_project_version(work_dir)
_set_db_project_comment(
conn,
conn_cfg.base,
conn_cfg.mergin_project,
version,
version=_get_project_version(work_dir),
project_id=_get_project_id(work_dir),
)


Expand Down Expand Up @@ -1043,7 +1046,13 @@ def push(conn_cfg, mc):
_geodiff_apply_changeset(
conn_cfg.driver, conn_cfg.conn_info, conn_cfg.base, tmp_changeset_file, ignored_tables, include_tables
)
_set_db_project_comment(conn, conn_cfg.base, conn_cfg.mergin_project, version)
_set_db_project_comment(
conn,
conn_cfg.base,
conn_cfg.mergin_project,
version,
project_id=_get_project_id(work_dir),
)


def init(
Expand Down Expand Up @@ -1124,18 +1133,23 @@ def init(
f"Downloading version {db_proj_info['version']} of Mergin Maps project {conn_cfg.mergin_project} "
f"to {work_dir}"
)
project_info = mc.project_info(conn_cfg.mergin_project)
db_project_id = db_proj_info.get("project_id", None)
if db_project_id != project_info["id"]:
raise DbSyncError(
"Mergin Maps project ID doesn't match Mergin Maps project ID stored in the database. "
"Did you change configuration from one Mergin Maps project to another? "
f"You either need to remove schema `{conn_cfg.base}` from Database or use `--force-init` option. "
f"{FORCE_INIT_MESSAGE}"
)
mc.download_project(conn_cfg.mergin_project, work_dir, db_proj_info["version"])
else:
# Get project ID from DB if available
try:
local_version = _get_project_version(work_dir)
logging.debug(f"Working directory {work_dir} already exists, with project version {local_version}")
# Compare local and database project version
db_project_id_str = getattr(
db_proj_info,
"project_id",
None,
)
db_project_id_str = db_proj_info.get("project_id", None)
db_project_id = uuid.UUID(db_project_id_str) if db_project_id_str else None
mp = _get_mergin_project(work_dir)
local_project_id = _get_project_id(mp)
Expand Down Expand Up @@ -1298,6 +1312,7 @@ def init(
conn_cfg.base,
conn_cfg.mergin_project,
local_version,
project_id=_get_project_id(work_dir),
)
else:
if not modified_schema_exists:
Expand Down Expand Up @@ -1417,12 +1432,12 @@ def init(
mc.push_project(work_dir)

# mark project version into db schema
version = _get_project_version(work_dir)
_set_db_project_comment(
conn,
conn_cfg.base,
conn_cfg.mergin_project,
version,
version=_get_project_version(work_dir),
project_id=_get_project_id(work_dir),
)


Expand Down
31 changes: 29 additions & 2 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
API_USER = os.environ.get("TEST_API_USERNAME")
USER_PWD = os.environ.get("TEST_API_PASSWORD")
WORKSPACE = os.environ.get("TEST_API_WORKSPACE")
TMP_DIR = tempfile.gettempdir()
TMP_DIR = os.path.join(tempfile.gettempdir(), "dbsync_test")
TEST_DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"test_data",
Expand Down Expand Up @@ -178,7 +178,7 @@ def init_sync_from_geopackage(
dbsync_init(mc)


@pytest.fixture(scope="function")
@pytest.fixture(scope="session")
def mc():
assert SERVER_URL and API_USER and USER_PWD
# assert SERVER_URL and SERVER_URL.rstrip('/') != 'https://app.merginmaps.com/' and API_USER and USER_PWD
Expand Down Expand Up @@ -310,3 +310,30 @@ def init_sync_from_db(mc: MerginClient, project_name: str, path_sql_file: str, i
)

dbsync_init(mc)


def _clean_workspace(mc: MerginClient, workspace: str) -> None:
"""Immediately delete all projects in the test workspace."""

projects = mc.projects_list(only_namespace=workspace)

for project in projects:
mc.delete_project_now(f"{workspace}/{project['name']}")


def _remove_dir(dir_path: str) -> None:
"""Remove directory if it exists."""

if os.path.exists(dir_path):
shutil.rmtree(dir_path)


@pytest.fixture(autouse=True, scope="session")
def clean_dir_and_workspace(mc: MerginClient):
"""Remove and recreate temporary directory for test files. Remove it after test session."""
_clean_workspace(mc, WORKSPACE)
_remove_dir(TMP_DIR)
os.makedirs(TMP_DIR)
yield
_remove_dir(TMP_DIR)
_clean_workspace(mc, WORKSPACE)
64 changes: 63 additions & 1 deletion test/test_init_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
MerginClient,
)

from dbsync import dbsync_pull, dbsync_push, config, DbSyncError
from dbsync import dbsync_pull, dbsync_push, config, DbSyncError, dbsync_init

from .conftest import (
GEODIFF_EXE,
Expand Down Expand Up @@ -148,3 +148,65 @@ def test_missing_table(mc: MerginClient):
init_sync_from_db(mc, project_name, path_test_data("create_another_schema.sql"))

assert "The 'modified' schema does not exist" in str(err.value)


def test_mm_project_change(mc: MerginClient, db_connection):
"""Test that after init and local changes the changes are correctly pushed to database"""
project_name = "test_project_change"
project_full_name = complete_project_name(project_name)
project_dir = name_project_dir(project_name)
db_schema_main = "test_init_from_db_main"
db_schema_base = "test_init_from_db_base"

path_synced_gpkg = project_dir + "/" + filename_sync_gpkg()

init_sync_from_db(mc, project_name, path_test_data("create_base.sql"))

cur = db_connection.cursor()

# check that there are 3 features prior to changes
cur.execute(f'SELECT COUNT(*) from {db_schema_main}."simple"')
assert cur.fetchone()[0] == 3

mc.download_project(project_full_name, project_dir)

# make changes in GPKG to create new version of the project
shutil.copy(path_test_data("inserted_point_from_db.gpkg"), path_synced_gpkg)

# push project
mc.push_project(project_dir)

# run sync
dbsync_pull(mc)
dbsync_push(mc)

# check that new feature was added
cur.execute(f'SELECT COUNT(*) from {db_schema_main}."simple"')
assert cur.fetchone()[0] == 4

project_name = "test_project_change_2"
project_full_name = complete_project_name(project_name)
project_dir = name_project_dir(project_name)
mc.create_project_and_push(project_full_name, project_dir)

# change config to new project
config.update(
{
"CONNECTIONS": [
{
"driver": "postgres",
"conn_info": DB_CONNINFO,
"modified": db_schema_main,
"base": db_schema_base,
"mergin_project": project_full_name,
"sync_file": filename_sync_gpkg(),
}
]
}
)

# run init
with pytest.raises(
DbSyncError, match="Mergin Maps project ID doesn't match Mergin Maps project ID stored in the database"
):
dbsync_init(mc)
Loading