Live browser UI to validate European energy market XML against a local XSD registry:
- ENTSO-E ESMP →
XSD/ENTSOE_ESMP/ - Edig@s 5.1 / 6.1 →
XSD/ENTSOG_EDIGAS/5.1/,XSD/ENTSOG_EDIGAS/6.1/ - CGMES, OPDM, RGCE reporting (static extras)
In-memory lxml, vendored Ace UI, uv deps.
Default deploy target: Google Cloud Run (scale-to-zero / free-tier friendly). Schemas are baked into the image; you can still override them with a host mount for local work.
Repo: github.com/Haigutus/xml-validator
Header shows 0.2.<n> (n = git rev-list --count HEAD).
Container builds: --build-arg GIT_COMMIT_COUNT=… → /app/VERSION.
uv sync
uv run python app.py
# → http://0.0.0.0:8030Dependencies: pyproject.toml + uv.lock only.
cp .env.example .env # fill values; gitignored
make up # build + run
make dev # uv run on host| Mode | How | When |
|---|---|---|
| Baked (default) | COPY XSD in image |
Cloud Run, simple local run |
| Host override | -v ./XSD:/app/XSD:ro or ./scripts/run.sh --host-xsd |
Local schema experiments without rebuild |
Mounting ./XSD replaces the image directory at /app/XSD (standard container bind-mount behaviour).
Push to main → Actions builds and deploys via WIF (no keys in repo).
Set repo Actions Variables: GCP_PROJECT_ID, GCP_REGION, SERVICE_NAME, AR_REPO, GCP_SERVICE_ACCOUNT, GCP_WORKLOAD_IDENTITY_PROVIDER.
Local (optional): gcloud auth login, fill .env, make deploy.
GCP once: enable Run + Artifact Registry + IAM Credentials; AR docker repo; deploy SA + WIF pool/provider for this GitHub repo (workloadIdentityUser, AR writer, Run admin).
| Practice | How |
|---|---|
| Scale to zero | --min-instances=0 |
| Startup CPU boost | --cpu-boost |
| Gen1 environment | Faster cold start than gen2 for this workload |
| Small instance | 512 MiB, 1 vCPU, max 2 instances |
| Concurrency | 40 requests per instance |
| Fast probes | /healthz — no XSD index work |
| Lazy XSD index | Built on first validate, not import/health |
| Faster index scan | Root-only iterparse per schema file |
| gunicorn | 1 worker, 4 threads; listens ASAP |
| Request-based CPU | Default throttling (no always-on CPU bill) |
Custom domain: Cloud Run domain mapping + DNS at your registrar (optional DOMAIN=… for local make deploy only).
Schema updates: commit refreshed XSD/ → push to main (CI rebuild) or make deploy locally.
| Folder | Content |
|---|---|
XSD/ENTSOE_ESMP/ |
ENTSO-E ESMP / CIM (replaced on update) |
XSD/ENTSOG_EDIGAS/5.1/ |
Edig@s 5.1 (replaced on update) |
XSD/ENTSOG_EDIGAS/6.1/ |
Edig@s 6.1 (replaced on update) |
XSD/CGMES_*, OPDM_*, … |
Static extras |
Env: XSD_DIR (default ./XSD, container /app/XSD).
./scripts/update_xsds.sh # ENTSO-E + Edig@s 5.1/6.1
./scripts/update_entsoe_xsds.sh
./scripts/update_edigas_xsds.sh 5.1Needs curl/wget, unzip, and 7z or uv (ENTSO-E .7z fallback).
Then:
# local with bake
./scripts/up.sh
# or Cloud Run
git add XSD && git commit -m "Refresh XSDs" && git push
./scripts/deploy-cloudrun.sh./scripts/vendor_ace.sh 1.36.5uv run python xsd.py examples/ACK_positive.xml
XSD_DIR=/path/to/XSD uv run python xsd.py message.xml| In image | Optional at runtime |
|---|---|
| App, uv venv, Ace UI | — |
Baked XSD/ |
Host mount ./XSD → /app/XSD overrides bake |
- Ace/CSS in
assets/ - Deps from
uv sync/ image layers - Schemas from image or mount
Reasonable defaults shipped for Cloud Run:
| Control | Detail |
|---|---|
| Safe XML parse | resolve_entities=False, no_network=True (XXE / external DTD) |
| Size limit | MAX_XML_BYTES (default 10 MiB) on XML body |
| HTTP body cap | Flask MAX_CONTENT_LENGTH slightly above that |
| Security headers | CSP (self), X-Frame-Options: DENY, nosniff, HSTS on HTTPS |
| Non-root image | runs as app user |
| Production server | gunicorn (not Flask dev server) |
| ProxyFix | trusts one hop of X-Forwarded-* (Cloud Run) |
Still public / unauthenticated by design (anyone can paste XML). Optional later: Cloud Armor rate limits, IAP, or auth if abuse appears.