| Versions | |
| CI | |
| Status | |
| Meta |
A lightweight, modular FastAPI application with Swagger UI, Docker support, Keycloak authentication, and Gunicorn + Uvicorn for production.
- Overview
- Compatibility
- Quick Start
- Configuration
- Environment variables reference
- Authentication
- Plugins
- Running with Docker
- Deployment Notes
- Deployment checklist (v1+)
- API Endpoints
- Project Structure
- Testing & Linting
- Releasing
- License
Giswater API exposes a clean HTTP surface to query and operate a Giswater database. It wraps existing GIS and network operations behind FastAPI endpoints, so desktop tools, mobile apps, and integrations can run the same workflows without directly touching the database.
How it works
- Each route validates input with Pydantic models, builds a standard payload, and calls Postgres procedures in the Giswater schema.
- Modules are toggled via environment variables, so you only expose the domains you need (Basic, OM, CRM, Routing, Water Balance).
- Optional Keycloak authentication protects routes while keeping Swagger/OpenAPI usable.
Why it is useful
- Centralizes complex GIS/business logic behind stable HTTP endpoints.
- Supports automated workflows (field operations, routing) without QGIS client coupling.
- Keeps API surface modular and controllable per deployment.
How to use it
- Base path:
/gw-api/v1(Swagger at/gw-api/v1/docson each tenant host). Platform admin API:/admin(Swagger at/admin/docson the apex host only). - Deploy behind nginx on the host: see
deploy/nginx.conf.example. The compose file binds the app to127.0.0.1:8000. - Tenants are resolved from the leftmost host label of
<tenant>.<BASE_DOMAIN>(e.g.acme.bgeo360.com). - Each tenant owns its own DB pool, Keycloak IDP, file/DB logger, and
API_*toggles viaconfig/tenants/<tenant>.env. - Use REST endpoints directly or via the autogenerated OpenAPI client.
This service is versioned alongside the Giswater database. Use matching ranges to avoid subtle schema/API mismatches.
giswater-api version |
Supported Giswater DB versions |
|---|---|
| 0.1 – 0.7 | 4.0 – 4.7 |
| 0.8 – 1.x | 4.8+ |
When GISWATER_DB_VERSION_CHECK=true, tenant readiness (GET /gw-api/v1/ready) returns 503 if {DB_SCHEMA}.sys_version does not report a giswater version ≥ GISWATER_DB_MIN_VERSION (default 4.8.0).
git clone https://github.com/Giswater/giswater-api.git
cd giswater-apicp .env.example .env
cp config/tenants/example.env config/tenants/test.envdocker compose up --build -dCheck service health:
curl http://127.0.0.1:8000/health📌 Tenant API docs (host-based): http://<tenant>.<BASE_DOMAIN>:8000/gw-api/v1/docs (example: http://test.bgeo360.localhost:8000/gw-api/v1/docs)
Tenant routes (including /gw-api/v1/docs) require a tenant context. In browser, use host-based routing:
BASE_DOMAIN=bgeo360.localhostin.env- open
http://test.bgeo360.localhost:8000/gw-api/v1/docs(for tenant fileconfig/tenants/test.env)
For API clients on localhost/IPs, set DEV_ALLOW_TENANT_HEADER=true and send X-Tenant-ID: <id>.
docker compose downpython3 -m venv venv
source venv/bin/activate # Windows PowerShell: .\venv\Scripts\activate
pip install -e ".[dev]"
uvicorn app.main:app --reloadCopy the env template and customize:
cp .env.example .env.env # global config only
config/tenants/<tenant_id>.env # one file per tenant (filename stem = tenant id = subdomain)
Template files named example.env, sample.env, and template.env are ignored by tenant discovery.
Tenant id rules: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$. Reserved (rejected at load): www, api, admin, static, traefik, localhost.
Full reference (tables + explanations): docs/ENVIRONMENT_VARIABLES.md.
Templates: root .env.example, production-oriented .env.prod.example, per-tenant config/tenants/example.env.
Summary for quick orientation:
BASE_DOMAIN=bgeo360.com
TENANTS_DIR=config/tenants
LOG_DIR=logs
LOG_LEVEL=INFO
LOG_DB_ENABLED=true
LOG_DB_SAMPLE_RATE=1.0
LOG_HTTP_BODY_CAPTURE=true
LOG_DB_MAX_BODY_BYTES=2048
ADMIN_USER=admin
ADMIN_PASSWORD=<set me>
ADMIN_RELOAD_ENABLED=true
ADMIN_WRITE_ENABLED=true
DEV_ALLOW_TENANT_HEADER=false
PLATFORM_KEYCLOAK_ENABLED=false
PLATFORM_KEYCLOAK_URL=
PLATFORM_KEYCLOAK_REALM=
PLATFORM_KEYCLOAK_CLIENT_ID=
PLATFORM_KEYCLOAK_CLIENT_SECRET=
Gunicorn overrides and every variable name are documented in docs/ENVIRONMENT_VARIABLES.md and mirrored in .env.example.
Per tenant (config/tenants/<id>.env) — API_* toggles, DB pool, optional Keycloak. See config/tenants/example.env and docs/ENVIRONMENT_VARIABLES.md.
Each tenant has its own pool. Total connections to your Postgres ≈ N_tenants × DB_POOL_MAX_SIZE (defaults to 10). Plan accordingly.
GET /admin/tenants— list tenants (secrets redacted)GET /admin/tenants/{id}— read onePOST /admin/tenants— create + write.envatomicallyPUT /admin/tenants/{id}— full replace; secrets unset = keep existingDELETE /admin/tenants/{id}— drains pool, archives.envto_archive/POST /admin/tenants/{id}/reload— re-read one.envfrom diskPOST /admin/tenants/reload— rescanTENANTS_DIR
All /admin/* endpoints support either auth path: HTTP Basic with ADMIN_USER/ADMIN_PASSWORD, or a Bearer JWT from the platform Keycloak realm with role platform-admin.
Mutations gated by ADMIN_WRITE_ENABLED=true; full-dir reload also gated by ADMIN_RELOAD_ENABLED=true.
Keycloak integration is per tenant and optional. When the tenant has KEYCLOAK_ENABLED=true:
- Endpoints require a valid
Authorization: Bearer <token>issued by the tenant's realm. - Tokens are decoded against the tenant idp's RS256 public key (no realm round-trip per request).
When disabled, endpoints accept anonymous requests for that tenant.
/admin/* uses a separate platform Keycloak realm (PLATFORM_KEYCLOAK_*) plus HTTP Basic, with platform-admin as the required role.
Swagger's built-in OAuth2 helper is no longer wired in — it can only point at one realm at a time, which is incompatible with multi-tenant. Clients send
Authorization: Bearer ...directly.
Custom plugins can extend the API. Place plugin folders in plugins/:
plugins/
└── my-plugin/
└── ...
Plugins are auto-loaded at startup on the tenant API surface (/gw-api/v1, same feature toggles and OpenAPI as core routers). See example plugin.
Use Compose as the default runtime:
docker compose up --build -d
docker compose ps
docker compose logs -f appDev hot reload:
cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up --buildThe override enables:
- bind mount
.:/app uvicorn --reloadDEV_ALLOW_TENANT_HEADER=true
- Keep
proxy_set_header Host $hostat the reverse proxy (deploy/nginx.conf.example) because tenant resolution depends onHost. - Apex host (
BASE_DOMAIN) serves only/admin/*; tenant hosts (<tenant>.<BASE_DOMAIN>) serve/gw-api/v1/*. - Base compose binds to
127.0.0.1:8000; expose publicly through your proxy/TLS layer. - Production images start Gunicorn +
uvicorn.workers.UvicornWorker(seegunicorn.conf.py,Dockerfile). Override worker count withWEB_CONCURRENCYif needed. - Tune container/Kubernetes probes: allow enough
start_periodfor Postgres pool init;/healthis fast; tenant/gw-api/v1/readyvalidates DB connectivity (and optional DB version check).
| Variable | Default | Meaning |
|---|---|---|
LOG_HTTP_BODY_CAPTURE |
true |
When true, request/response payload text is logged for failed requests (4xx/5xx) with redaction + truncation; binary/multipart payloads are skipped. |
LOG_DB_MAX_BODY_BYTES |
2048 |
Max bytes per stored body (0 uses the same safe internal cap). |
LOG_DB_ENABLED |
true |
Sample API rows into the tenant log table. |
LOG_DB_SAMPLE_RATE |
1.0 |
Fraction of tenant requests logged to DB (1.0 = all; typical for QGIS plugin workloads). |
Detailed reference: docs/ENVIRONMENT_VARIABLES.md. Copy-paste templates: .env.example, .env.prod.example. Operator checklist: docs/DEPLOYMENT_CHECKLIST.md. Per-tenant template: config/tenants/example.env.
- Tenant surface prefix:
/gw-api/v1 - Admin surface prefix:
/admin(apex host only) - Health checks:
/health(global, tenant-independent)/gw-api/v1/health(tenant scope)/admin/health(admin scope)
- OpenAPI docs:
- Tenant:
/gw-api/v1/docson tenant host - Admin:
/admin/docson apex host
- Tenant:
- Module routers are loaded from:
basicom(profile,flow,mincut,waterbalance,mapzones)routingcrmepa(dscenario)system(ready, schema validation, tenant-scoped logs)
Use OpenAPI as source of truth for the full endpoint list in your running environment.
giswater-api/
│── app/
│ │── models/ # Pydantic models
│ │ │── basic/ # Basic module models
│ │ │── crm/ # CRM module models
│ │ │── om/ # OM (mincut, dma, mapzones) models
│ │ │── routing/ # Routing module models
│ │ └── util_models.py # Shared utility models
│ │
│ │── routers/ # API endpoints
│ │ │── basic/ # GIS feature queries
│ │ │── crm/ # Hydrometer CRUD
│ │ │── om/ # OM operations (mincut, profile, flow, dma, mapzones)
│ │ │── routing/ # Optimal path routing
│ │ │── epa/ # EPA scenarios
│ │ │── admin.py # Tenant lifecycle endpoints
│ │ └── system.py # Ready/schemas/logs endpoints
│ │
│ │── utils/ # Utilities and helpers
│ │ │── utils.py # General utilities
│ │ └── routing_utils.py # Valhalla routing helpers
│ │
│ │── config.py # Configuration loader
│ │── database.py # Database connection manager
│ │── dependencies.py # FastAPI dependencies
│ │── host_middleware.py # Host-based tenant resolver
│ │── tenant.py # Tenant registry and lifecycle
│ │── keycloak.py # Keycloak OAuth2/OIDC integration
│ │── auth.py # Admin + tenant auth validation
│ │── main.py # FastAPI app entry point
│ └── static/ # Static files (favicon, etc.)
│
│── config/
│ └── tenants/ # Per-tenant .env files (e.g. test.env, acme.env)
│── deploy/
│ └── nginx.conf.example
│── plugins/ # Plugin directory (see plugins/readme.md)
│── tests/ # Tests
│── .github/workflows/ # CI/CD (ruff, pytest)
│── Dockerfile # Docker build config
│── docker-compose.yml
│── docker-compose.override.yml.example
│── gunicorn.conf.py # Gunicorn + Uvicorn worker defaults for production images
│── pyproject.toml # Project metadata, dependencies, and tooling config
│── docs/
│ ├── DEPLOYMENT_CHECKLIST.md
│ └── ENVIRONMENT_VARIABLES.md # Human-readable env reference (tables + descriptions)
│── scripts/
│ │── release.sh
│ │── release.ps1
│ └── smoke_test.sh
└── README.md
pytestruff check .CI/CD runs both on push via GitHub Actions (.github/workflows/).
- Update
CHANGELOG.mdunder[Unreleased]and move the block to a dated## [X.Y.Z]section; commit. - Follow docs/DEPLOYMENT_CHECKLIST.md for production cutovers.
- Run the release script:
# Bash
./scripts/release.sh 1.0.0
# PowerShell
.\scripts\release.ps1 1.0.0The script will:
- Abort if there are uncommitted changes
- Update the version in
pyproject.toml - Commit, tag
vX.Y.Z, create branchrelease/X.Y - Push everything to origin
This project is free software, licensed under the GNU General Public License (GPL) version 3 or later. Refer to the LICENSE file for details.