This release is a pre-release and may not be stable for production use.
XMAS-App
The XLeistelle model-driven application schema app is a Python web application to edit and create data according to geo-spatial standards of the XLeistelle, e.g. XPlanung, XTrasse. It is based on NiceGUI for a graphical user interface and xmas-core for the data model and respective functionality.
While it could be advanced to a standalone application, its current focus is integration in QGIS to provide attribute forms etc.
Features
- Render attribute forms for features.
- Create and edit features in combination with the corresponding QGIS Plugin.
- Import GML documents into a database.
- Export GML, JSON-FG or GPKG files from a database.
- Delete plans from a database.
- Display and edit relations of plan objects in a tree view.
Installation
Container Image
Container images are available in the registry. They are built and signed by the pipeline and tagged per release version and per integration branch (dev-amd64); there is no latest tag. See Running via Docker Compose for configuration.
Python >= v3.11
GDAL and its Python bindings are required, so you need to make sure the GDAL system library and Python package versions match. QGIS installations come with GDAL and a Python environment that can readily be used to install the app.
Install with pip, e.g. via OSGeo4W Shell:
pip install xmas-app
Pixi
This project uses Pixi for package management. To install this repo with a self-contained environment, run
git clone https://gitlab.opencode.de/xleitstelle/xmas-app.git
cd xmas-app
pixi install
Running
Preconditions
A Postgres DB with PostGIS extension and adequate permissions for the used role. Missing tables are generated on initialization.
A database created by xplan-tools (xmas-core < 2.0) cannot be upgraded: startup reports SchemaOutdatedError. Start with a fresh database instead, with the PostGIS extension enabled and PGDATABASE pointing at it, and re-import the plans through the plan manager. The app's own tables live in the same schema as the plans, so nothing of them carries over: organisations are created again on first login, plans are attributed to whoever imports them, and attachments have to be uploaded again.
Plans written straight into the database, for example with xmas convert, get no authorization row: the app does not list them, and opening one gives a 404.
Environment Configuration
Copy the example configuration and adjust values as needed:
cp .env.example .env
.env.example documents all available variables with inline comments, including database connection, app settings, and authentication. See that file as the canonical reference.
Important:
- Do not use the example values in production
- All credentials and tokens must be set explicitly
- The application will fail to start if required variables are missing
Running via Docker Compose
docker compose -f compose.yaml up -d
This spins up a complete XMAS-App stack including a PostGIS backend and Keycloak. Configure the stack via .env — see .env.example for the container-mode database settings (PGHOST=postgres, PGPORT=5432).
Optional: testing ROOT_PATH sub-path deployments
To verify running the app under a URL sub-path (so several instances can share one domain), start the reverse-proxy service via its compose profile:
ROOT_PATH=/test docker compose -f compose.yaml --profile proxy up -d
This adds an nginx container (see nginx/default.conf.template) that strips ROOT_PATH before forwarding to xmas-app, mirroring a real front-facing proxy. Browse http://localhost:${PROXY_PORT:-8000}/test/ — not xmas-app's own port directly, since ROOT_PATH only affects URL generation, not routing; see xmas_app/core/settings.py for the setting itself.
Running Locally (host machine)
First activate the Pixi shell:
pixi shell
Then run:
xmas-app
Use the local-mode database settings in .env (PGHOST=127.0.0.1, PGPORT=15432).
Optional: batch import test data
Test data can be downloaded from https://gitlab.opencode.de/xleitstelle/xplanung/testdaten, e.g. a ZIP archive with BP_Plans in v 6.0.
To import all .gml files at once, extract the archive and use the commands below. They write the plans straight into the database, without the authorization rows the app needs, so the app does not show them; for plans to work with in the app, import them through the plan manager.
Linux / macOS / WSL:
for f in <path>/bp/*.gml; do
xmas convert "$f" postgresql://postgres:postgres@localhost:55432/postgres
done
Windows:
for %f in (*.gml) do xmas convert "%f" postgresql://postgres:postgres@127.0.0.1:55432/postgres
Authentication
The application supports two authentication modes, controlled by the AUTH_MODE environment variable.
Static token
Maps a bearer token to an identity record the operator writes. It is used for local development, for the xmas-plugin CI pipeline (which cannot run the interactive OAuth2/PKCE flow headlessly), and for self-hosted installations that do not run an identity provider. See Static tokens in production before using it for the last of these.
Set AUTH_MODE=static in .env.
Requests must include:
Authorization: Bearer <token>
Each token maps to a full identity record via the AUTH_TOKENS environment variable (JSON). The record stands in for the claims a Keycloak access token would otherwise carry:
AUTH_TOKENS={"<generated-token>": {"user_name": "test-user", "user_id": "41a74c7b-dea8-42c2-bd15-9b88012b10d7", "org_id": "25459bec-0f6e-4f3a-a3f4-3b3e6a2a5d11", "email": "test@example.org", "role": "editor"}}
| Field | Required | Meaning |
|---|---|---|
user_name |
no | Human-readable label for whoever edits the env var (not consumed by the app) |
user_id |
yes | Subject identifier; stands in for the Keycloak sub. Any string, see below. Persisted as created_by |
org_id |
no* | UUID of the user's org. A null or absent value marks a grant-scoped external invitee |
email |
no | Display / audit only. Not used as an identity key |
role |
no | One of admin, editor, viewer. Absent or null is treated as no role, which denies writes |
*
org_idis optional syntactically, butnullmeans the user is treated as an external invitee (grant-scoped only) and will be denied org-scoped plan access. For a normal dev user, set a real org UUID. WhenAUTH_MODE=static,AUTH_TOKENSis required, startup fails loudly if it is missing or malformed.
user_id is not required to be a UUID. A Keycloak sub happens to be one, but OIDC
defines the subject as an opaque string, so the columns storing it are text and any
stable, unique-per-deployment string works here (ADR-0005, Appendix A). Records are
parsed into AuthenticatedUser, the same type the Keycloak path produces.
See .env.example for the full format.
Static tokens in production
Static mode is supported for self-hosted installations. It is not suitable for an installation reachable from the public internet, because it lacks what that needs:
- No expiry. A token is valid until it is removed from the configuration.
- No revocation short of editing the configuration and restarting.
- No signature. The token is the secret itself, so anyone who sees it in transit, in a log, in a shell history or in a backup of
.envholds the credential. - No rate limiting. Guessing tokens is slowed by nothing but the network.
- Identity and role are whatever the operator wrote into
AUTH_TOKENS.
How an installation is exposed is the operating organisation's decision, and so is acting on the recommendations below (ADR-0007). An installation that has to be reachable from the internet should use Keycloak.
- Length. With
APP_MODE=prod, the default, startup fails if any token is shorter than 32 characters. Generate one withpython -c "import secrets; print(secrets.token_urlsafe(32))". - One token per person. Recommended, not enforced. With one shared token, every action is attributed to that token's identity, so the record shows the organisation rather than the person.
- Storage. Treat
AUTH_TOKENSlike a password: restrict who can read.env, and keep tokens out of version control, tickets and chat. - One place. Set
AUTH_TOKENSeither in the environment or in.env, not both. The two are merged, so a token removed from one stays valid while it is still in the other. - Rotation. Edit
AUTH_TOKENSand restart. That is the whole mechanism; there is no expiry and no revocation list.
At startup in prod, the application logs that static mode is active and how many tokens are configured, never the tokens themselves.
Keycloak JWT (recommended for production)
Validates RS256-signed access tokens issued by a Keycloak realm via the JWKS endpoint. This is the mode for any installation reachable from the public internet.
Set AUTH_MODE=keycloak in .env. See .env.example for the required Keycloak variables (KEYCLOAK_ISSUER, KEYCLOAK_AUDIENCE, KEYCLOAK_JWKS_URL).
Note:
KEYCLOAK_ISSUERmust match theissclaim in the token exactly — including scheme, host, and port.KEYCLOAK_JWKS_URLis the address of the Keycloak service as seen from inside the Docker network, which may differ from the issuer URL seen by clients.
The local Keycloak instance is started automatically via compose.yaml. A development realm (realm-xmas-dev.json) is imported on first start, including a test user, the xmas-plugin client for the OAuth2/PKCE flow, and the required audience mapper.
For Keycloak setup, Identity Provider federation (e.g. Microsoft Entra ID / SSO), and onboarding documentation, see keycloak/README.md.
Running Tests Locally
Integration tests use testcontainers to start a temporary PostGIS database.
Make sure Docker is running before executing the tests.
The test database is seeded from a git submodule ./testdaten/ (https://gitlab.opencode.de/xleitstelle/xplanung/testdaten).
Initialize the submodule first:
git submodule update --init --recursive
Then run:
pixi run pytest tests
Agentic Coding
See .devcontainer/README.md for Claude Code setup.
Troubleshooting
PyJWKClientConnectionError
The plugin can't reach the app, and the app's container log shows PyJWKClientConnectionError.
Verify that the env variables are being passed correctly and that KEYCLOAK_JWKS_URL has the right value.
Make sure proxy settings aren't blocking Keycloak: add keycloak to the NO_PROXY list if not yet set in the compose file.
License
The code in this repository is licensed under the EUPL-1.2-or-later
© XLeitstelle, 2025
Metadata
Release files for xmas-app 1.0.0.dev3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xmas_app-1.0.0.dev3.tar.gz | 139.1 kB | Without provenance | Details for xmas_app-1.0.0.dev3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| xmas_app-1.0.0.dev3-py3-none-any.whl | Python 3 | none | any | Details for xmas_app-1.0.0.dev3-py3-none-any.whl |
Total release size: 313.8 kB
Release files / xmas_app-1.0.0.dev3.tar.gz
| Download URL | xmas_app-1.0.0.dev3.tar.gz |
|---|---|
| Size | 139.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
00720cfc1b4f903702ee73728ef915705f82d6b98668c8e458b8c5b4a43839eb
|
|
BLAKE2b-256 checksum How to use checksums |
98be5e4e071d6a1b81e539c76e53f90c361b557dbe048d7dff44133e931970ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / xmas_app-1.0.0.dev3-py3-none-any.whl
| Download URL | xmas_app-1.0.0.dev3-py3-none-any.whl |
|---|---|
| Size | 174.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
44ba1c900c6e50a522d845d5079f35f380b7390a9f77ee8d68111fafc402acb1
|
|
BLAKE2b-256 checksum How to use checksums |
a733e1420d3e06118811482f035b0aa9cd3d1c694e34074f9b27fc0b4b256d0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for xmas-app 1.0.0.dev3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.