xmas-app 0.10.0__tar.gz → 0.11.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xmas_app-0.10.0 → xmas_app-0.11.1}/PKG-INFO +1 -1
- {xmas_app-0.10.0 → xmas_app-0.11.1}/pyproject.toml +2 -1
- xmas_app-0.11.1/xmas_app/deps/version_guard.py +26 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/main.py +47 -36
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/settings.py +1 -1
- {xmas_app-0.10.0 → xmas_app-0.11.1}/LICENSE +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/README.md +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/db.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/db_uow.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/form.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/models/crud.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/pygeoapi/config.yaml +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/pygeoapi/openapi.yaml +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/pygeoapi/provider.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/schema.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/services/crud.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/split_service.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/util/__init__.py +0 -0
- {xmas_app-0.10.0 → xmas_app-0.11.1}/xmas_app/util/codelist.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xmas-app"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.1"
|
|
4
4
|
description = "The XLeitstelle model-driven application schema app."
|
|
5
5
|
authors = [{ name = "Tobias Kraft", email = "tobias.kraft@gv.hamburg.de" }]
|
|
6
6
|
classifiers = [
|
|
@@ -58,6 +58,7 @@ args = [
|
|
|
58
58
|
]
|
|
59
59
|
cmd = """
|
|
60
60
|
pixi workspace version {{version}}
|
|
61
|
+
&& git pull
|
|
61
62
|
&& pixi lock
|
|
62
63
|
&& git add pyproject.toml pixi.lock
|
|
63
64
|
&& git commit -m "bump to $(pixi workspace version get)"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from fastapi import Header, HTTPException
|
|
2
|
+
from semver import format_version, parse
|
|
3
|
+
|
|
4
|
+
from xmas_app.settings import settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def enforce_plugin_version(
|
|
8
|
+
user_agent: str = Header(...),
|
|
9
|
+
):
|
|
10
|
+
if not user_agent.startswith(settings.qgis_plugin_name):
|
|
11
|
+
return
|
|
12
|
+
try:
|
|
13
|
+
_, plugin_version = user_agent.split("/")
|
|
14
|
+
client_v = parse(plugin_version)
|
|
15
|
+
except Exception:
|
|
16
|
+
raise HTTPException(
|
|
17
|
+
status_code=400,
|
|
18
|
+
detail=f"Invalid {settings.qgis_plugin_name} user-agent header: '{user_agent}'",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if client_v < settings.qgis_plugin_min_version:
|
|
22
|
+
# 426 Upgrade Required is appropriate
|
|
23
|
+
raise HTTPException(
|
|
24
|
+
status_code=426,
|
|
25
|
+
detail=f"Plugin version {settings.qgis_plugin_min_version}+ required, got {format_version(**client_v)}",
|
|
26
|
+
)
|
|
@@ -7,39 +7,18 @@ import re
|
|
|
7
7
|
from contextlib import asynccontextmanager
|
|
8
8
|
from functools import partial
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from tempfile import NamedTemporaryFile
|
|
10
|
+
from tempfile import NamedTemporaryFile, gettempdir
|
|
11
11
|
from typing import Any, Literal
|
|
12
12
|
from uuid import uuid4
|
|
13
13
|
|
|
14
14
|
import pydantic
|
|
15
15
|
import pydantic_core
|
|
16
|
+
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
17
|
+
from nicegui import app, run, ui
|
|
18
|
+
from nicegui.events import ClickEventArguments, ValueChangeEventArguments
|
|
16
19
|
from nicegui.observables import ObservableSet
|
|
17
20
|
from pydantic import ValidationError
|
|
18
21
|
from starlette.applications import Starlette
|
|
19
|
-
from xplan_tools.util import get_geometry_type_from_wkt
|
|
20
|
-
|
|
21
|
-
from xmas_app.schema import ErrorDetail, ErrorResponse, SplitPayload, SplitSuccess
|
|
22
|
-
from xmas_app.split_service import PlanSplitService, SplitValidationError
|
|
23
|
-
|
|
24
|
-
log_dir = Path(__file__).parent / "logs"
|
|
25
|
-
log_dir.mkdir(exist_ok=True)
|
|
26
|
-
log_file = log_dir / "xmas_app.log"
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger("xmas_app")
|
|
29
|
-
|
|
30
|
-
if not logger.handlers:
|
|
31
|
-
logger.setLevel(logging.DEBUG)
|
|
32
|
-
fh = logging.FileHandler(log_file, mode="w", encoding="utf-8")
|
|
33
|
-
formatter = logging.Formatter(
|
|
34
|
-
"%(asctime)s - %(levelname)s - %(name)s - %(message)s"
|
|
35
|
-
)
|
|
36
|
-
fh.setFormatter(formatter)
|
|
37
|
-
logger.addHandler(fh)
|
|
38
|
-
logger.debug(f"Writing logs to {log_file}")
|
|
39
|
-
|
|
40
|
-
from fastapi import APIRouter, HTTPException, Request, status
|
|
41
|
-
from nicegui import app, run, ui
|
|
42
|
-
from nicegui.events import ClickEventArguments, ValueChangeEventArguments
|
|
43
22
|
from xplan_tools.interface import repo_factory
|
|
44
23
|
from xplan_tools.interface.db import DBRepository
|
|
45
24
|
from xplan_tools.interface.gml import GMLRepository
|
|
@@ -47,13 +26,40 @@ from xplan_tools.model import model_factory
|
|
|
47
26
|
from xplan_tools.util import (
|
|
48
27
|
cast_geom_to_multi,
|
|
49
28
|
cast_geom_to_single,
|
|
29
|
+
get_geometry_type_from_wkt,
|
|
50
30
|
)
|
|
51
31
|
|
|
52
32
|
from xmas_app.db import get_db_feature_ids, get_nodes
|
|
33
|
+
from xmas_app.deps.version_guard import enforce_plugin_version
|
|
53
34
|
from xmas_app.form import ModelForm
|
|
54
35
|
from xmas_app.models.crud import InsertPayload, UpdatePayload
|
|
36
|
+
from xmas_app.schema import ErrorDetail, ErrorResponse, SplitPayload, SplitSuccess
|
|
55
37
|
from xmas_app.services import crud
|
|
56
38
|
from xmas_app.settings import get_appschema, settings
|
|
39
|
+
from xmas_app.split_service import PlanSplitService, SplitValidationError
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_log_dir() -> Path:
|
|
43
|
+
if settings.app_mode == "prod":
|
|
44
|
+
return Path(gettempdir()) / "xmas_log"
|
|
45
|
+
else:
|
|
46
|
+
# dev: local repo logs
|
|
47
|
+
return Path(__file__).parent
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
log_dir = _resolve_log_dir()
|
|
51
|
+
log_dir.mkdir(exist_ok=True)
|
|
52
|
+
log_file = log_dir / "xmas_app.log"
|
|
53
|
+
|
|
54
|
+
logger = logging.getLogger("xmas_app")
|
|
55
|
+
|
|
56
|
+
logger.handlers.clear()
|
|
57
|
+
logger.setLevel(logging.DEBUG if settings.debug else logging.INFO)
|
|
58
|
+
fh = logging.FileHandler(log_file, mode="w", encoding="utf-8")
|
|
59
|
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s")
|
|
60
|
+
fh.setFormatter(formatter)
|
|
61
|
+
logger.addHandler(fh)
|
|
62
|
+
logger.debug(f"Writing logs to {log_file}")
|
|
57
63
|
|
|
58
64
|
ui.element.default_props("dense")
|
|
59
65
|
|
|
@@ -114,10 +120,8 @@ async def get_model_for_select(
|
|
|
114
120
|
)
|
|
115
121
|
|
|
116
122
|
|
|
117
|
-
@ui.page("/")
|
|
118
|
-
def index(
|
|
119
|
-
request: Request,
|
|
120
|
-
):
|
|
123
|
+
@ui.page("/", dependencies=[Depends(enforce_plugin_version)])
|
|
124
|
+
def index():
|
|
121
125
|
"""
|
|
122
126
|
Render the main index page of the XPlan-GUI application.
|
|
123
127
|
Args:
|
|
@@ -138,7 +142,11 @@ def index(
|
|
|
138
142
|
)
|
|
139
143
|
|
|
140
144
|
|
|
141
|
-
@ui.page(
|
|
145
|
+
@ui.page(
|
|
146
|
+
"/plan-tree/{id}",
|
|
147
|
+
reconnect_timeout=5,
|
|
148
|
+
dependencies=[Depends(enforce_plugin_version)],
|
|
149
|
+
)
|
|
142
150
|
async def plan_tree(
|
|
143
151
|
request: Request,
|
|
144
152
|
id: str,
|
|
@@ -385,7 +393,7 @@ async def plan_tree(
|
|
|
385
393
|
tree_filter.bind_value_to(tree, "filter")
|
|
386
394
|
|
|
387
395
|
|
|
388
|
-
@ui.page("/plans")
|
|
396
|
+
@ui.page("/plans", dependencies=[Depends(enforce_plugin_version)])
|
|
389
397
|
async def plans(
|
|
390
398
|
request: Request,
|
|
391
399
|
appschema: str = settings.appschema,
|
|
@@ -708,7 +716,7 @@ async def plans(
|
|
|
708
716
|
new_plan_select.set_value(new_plan_select.options[0])
|
|
709
717
|
|
|
710
718
|
|
|
711
|
-
@ui.page("/feature/{id}")
|
|
719
|
+
@ui.page("/feature/{id}", dependencies=[Depends(enforce_plugin_version)])
|
|
712
720
|
async def feature(
|
|
713
721
|
request: Request,
|
|
714
722
|
id: str,
|
|
@@ -939,7 +947,7 @@ async def feature(
|
|
|
939
947
|
await add_form(feature_type, feature)
|
|
940
948
|
|
|
941
949
|
|
|
942
|
-
@ui.page("/feature/{id}/associations")
|
|
950
|
+
@ui.page("/feature/{id}/associations", dependencies=[Depends(enforce_plugin_version)])
|
|
943
951
|
def get_associations(
|
|
944
952
|
request: Request,
|
|
945
953
|
id: str,
|
|
@@ -1121,20 +1129,22 @@ def validate_featuretype(
|
|
|
1121
1129
|
return "OK"
|
|
1122
1130
|
|
|
1123
1131
|
|
|
1124
|
-
@app.post(
|
|
1132
|
+
@app.post(
|
|
1133
|
+
"/insert-features", status_code=201, dependencies=[Depends(enforce_plugin_version)]
|
|
1134
|
+
)
|
|
1125
1135
|
async def insert_features(payload: InsertPayload):
|
|
1126
1136
|
"""Insert a number of features."""
|
|
1127
1137
|
await crud.create(payload)
|
|
1128
1138
|
|
|
1129
1139
|
|
|
1130
|
-
@app.post("/update-features")
|
|
1140
|
+
@app.post("/update-features", dependencies=[Depends(enforce_plugin_version)])
|
|
1131
1141
|
async def update_features(payload: UpdatePayload):
|
|
1132
1142
|
"""Update a number of features."""
|
|
1133
1143
|
await crud.update(payload)
|
|
1134
1144
|
|
|
1135
1145
|
|
|
1136
1146
|
@app.post(
|
|
1137
|
-
"/
|
|
1147
|
+
"/split-tool",
|
|
1138
1148
|
response_model=SplitSuccess,
|
|
1139
1149
|
status_code=status.HTTP_201_CREATED, # semantically 'created'
|
|
1140
1150
|
responses={
|
|
@@ -1142,6 +1152,7 @@ async def update_features(payload: UpdatePayload):
|
|
|
1142
1152
|
422: {"model": ErrorResponse, "description": "Validation Error"},
|
|
1143
1153
|
500: {"model": ErrorResponse, "description": "Server Error"},
|
|
1144
1154
|
},
|
|
1155
|
+
dependencies=[Depends(enforce_plugin_version)],
|
|
1145
1156
|
)
|
|
1146
1157
|
async def receive_split_plans(payload: SplitPayload) -> SplitSuccess:
|
|
1147
1158
|
logger.info("Split plans endpoint reached.")
|
|
@@ -55,7 +55,7 @@ class Settings(BaseSettings):
|
|
|
55
55
|
"https://registry.gdi-de.org/codelist/de.xleitstelle.xplanung"
|
|
56
56
|
)
|
|
57
57
|
qgis_plugin_name: str = "XMAS-Plugin"
|
|
58
|
-
qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=
|
|
58
|
+
qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=10)
|
|
59
59
|
|
|
60
60
|
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
|
61
61
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|