xmas-app 0.10.0__tar.gz → 0.11.0__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.0
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.0"
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 = [
@@ -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
+ )
@@ -3,6 +3,7 @@ import importlib
3
3
  import inspect
4
4
  import io
5
5
  import logging
6
+ import os
6
7
  import re
7
8
  from contextlib import asynccontextmanager
8
9
  from functools import partial
@@ -18,10 +19,23 @@ from pydantic import ValidationError
18
19
  from starlette.applications import Starlette
19
20
  from xplan_tools.util import get_geometry_type_from_wkt
20
21
 
22
+ from xmas_app.deps.version_guard import enforce_plugin_version
21
23
  from xmas_app.schema import ErrorDetail, ErrorResponse, SplitPayload, SplitSuccess
22
24
  from xmas_app.split_service import PlanSplitService, SplitValidationError
23
25
 
24
- log_dir = Path(__file__).parent / "logs"
26
+
27
+ def _resolve_log_dir() -> Path:
28
+ # If launched from QGIS plugin
29
+ if os.getenv("XMAS_APP_FROM_PLUGIN") == "1":
30
+ plugin_dir = os.getenv("XMAS_APP_PLUGIN_DIR")
31
+ if plugin_dir:
32
+ return Path(plugin_dir) / "webapp_logs"
33
+
34
+ # default: local repo logs (dev)
35
+ return Path(__file__).parent / "logs"
36
+
37
+
38
+ log_dir = _resolve_log_dir()
25
39
  log_dir.mkdir(exist_ok=True)
26
40
  log_file = log_dir / "xmas_app.log"
27
41
 
@@ -37,7 +51,7 @@ if not logger.handlers:
37
51
  logger.addHandler(fh)
38
52
  logger.debug(f"Writing logs to {log_file}")
39
53
 
40
- from fastapi import APIRouter, HTTPException, Request, status
54
+ from fastapi import APIRouter, Depends, HTTPException, Request, status
41
55
  from nicegui import app, run, ui
42
56
  from nicegui.events import ClickEventArguments, ValueChangeEventArguments
43
57
  from xplan_tools.interface import repo_factory
@@ -114,10 +128,8 @@ async def get_model_for_select(
114
128
  )
115
129
 
116
130
 
117
- @ui.page("/")
118
- def index(
119
- request: Request,
120
- ):
131
+ @ui.page("/", dependencies=[Depends(enforce_plugin_version)])
132
+ def index():
121
133
  """
122
134
  Render the main index page of the XPlan-GUI application.
123
135
  Args:
@@ -138,7 +150,11 @@ def index(
138
150
  )
139
151
 
140
152
 
141
- @ui.page("/plan-tree/{id}", reconnect_timeout=5)
153
+ @ui.page(
154
+ "/plan-tree/{id}",
155
+ reconnect_timeout=5,
156
+ dependencies=[Depends(enforce_plugin_version)],
157
+ )
142
158
  async def plan_tree(
143
159
  request: Request,
144
160
  id: str,
@@ -385,7 +401,7 @@ async def plan_tree(
385
401
  tree_filter.bind_value_to(tree, "filter")
386
402
 
387
403
 
388
- @ui.page("/plans")
404
+ @ui.page("/plans", dependencies=[Depends(enforce_plugin_version)])
389
405
  async def plans(
390
406
  request: Request,
391
407
  appschema: str = settings.appschema,
@@ -708,7 +724,7 @@ async def plans(
708
724
  new_plan_select.set_value(new_plan_select.options[0])
709
725
 
710
726
 
711
- @ui.page("/feature/{id}")
727
+ @ui.page("/feature/{id}", dependencies=[Depends(enforce_plugin_version)])
712
728
  async def feature(
713
729
  request: Request,
714
730
  id: str,
@@ -939,7 +955,7 @@ async def feature(
939
955
  await add_form(feature_type, feature)
940
956
 
941
957
 
942
- @ui.page("/feature/{id}/associations")
958
+ @ui.page("/feature/{id}/associations", dependencies=[Depends(enforce_plugin_version)])
943
959
  def get_associations(
944
960
  request: Request,
945
961
  id: str,
@@ -1121,20 +1137,22 @@ def validate_featuretype(
1121
1137
  return "OK"
1122
1138
 
1123
1139
 
1124
- @app.post("/insert-features", status_code=201)
1140
+ @app.post(
1141
+ "/insert-features", status_code=201, dependencies=[Depends(enforce_plugin_version)]
1142
+ )
1125
1143
  async def insert_features(payload: InsertPayload):
1126
1144
  """Insert a number of features."""
1127
1145
  await crud.create(payload)
1128
1146
 
1129
1147
 
1130
- @app.post("/update-features")
1148
+ @app.post("/update-features", dependencies=[Depends(enforce_plugin_version)])
1131
1149
  async def update_features(payload: UpdatePayload):
1132
1150
  """Update a number of features."""
1133
1151
  await crud.update(payload)
1134
1152
 
1135
1153
 
1136
1154
  @app.post(
1137
- "/split_tool",
1155
+ "/split-tool",
1138
1156
  response_model=SplitSuccess,
1139
1157
  status_code=status.HTTP_201_CREATED, # semantically 'created'
1140
1158
  responses={
@@ -1142,6 +1160,7 @@ async def update_features(payload: UpdatePayload):
1142
1160
  422: {"model": ErrorResponse, "description": "Validation Error"},
1143
1161
  500: {"model": ErrorResponse, "description": "Server Error"},
1144
1162
  },
1163
+ dependencies=[Depends(enforce_plugin_version)],
1145
1164
  )
1146
1165
  async def receive_split_plans(payload: SplitPayload) -> SplitSuccess:
1147
1166
  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