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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmas-app
3
- Version: 0.10.0
3
+ Version: 0.11.1
4
4
  Summary: The XLeitstelle model-driven application schema app.
5
5
  License: EUPL-1.2-or-later
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xmas-app"
3
- version = "0.10.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("/plan-tree/{id}", reconnect_timeout=5)
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("/insert-features", status_code=201)
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
- "/split_tool",
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=9)
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