Compare commits

...

8 commits

Author SHA1 Message Date
8d34c450e3 Cleanup version computation, add gisaf.__version__
All checks were successful
/ test (push) Successful in 31s
/ build (push) Successful in 2m22s
2025-03-16 19:52:28 +01:00
a3a2efe471 Use dynamic versioning
All checks were successful
/ test (push) Successful in 31s
/ build (push) Successful in 32s
2025-03-16 19:02:10 +01:00
d54bb178dd Update README.md
All checks were successful
/ test (push) Successful in 32s
2024-12-30 17:02:17 +00:00
93b9903e0d Update README.md
All checks were successful
/ test (push) Successful in 30s
2024-12-27 23:47:03 +00:00
e53ed92a56 Update doc
All checks were successful
/ test (push) Successful in 30s
/ build (push) Successful in 7s
2024-12-27 19:24:26 +01:00
c4dba88ed6 Update doc
All checks were successful
/ test (push) Successful in 31s
/ build (push) Successful in 7s
2024-12-27 19:11:15 +01:00
6035f93868 Deployment: move doc 2024-12-27 17:53:52 +01:00
cf98b3b160 Deployment: fix typo 2024-12-27 17:53:03 +01:00
10 changed files with 238 additions and 160 deletions

View file

@ -1,5 +1,4 @@
.venv .venv
.git
dist dist
.pytest_cache .pytest_cache
.mypy_cache .mypy_cache

View file

@ -30,6 +30,8 @@ jobs:
echo '${{ toJSON(env) }}' echo '${{ toJSON(env) }}'
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install app with 'uv pip install' - name: Install app with 'uv pip install'
run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps . run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
@ -48,40 +50,35 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get the version from git - name: Install the latest version of uv
id: version uses: astral-sh/setup-uv@v4
run: echo "version=$(git describe --dirty --tags)" >> $GITHUB_OUTPUT with:
version: "0.6.6"
- name: Check if the container should be built - name: Install
id: builder run: uv sync
env:
RUN: ${{ toJSON(inputs.build || !contains(steps.version.outputs.version, '-g')) }}
run: |
echo "run=$RUN" >> $GITHUB_OUTPUT
echo "Run build: $RUN"
- name: Info - version and test if the git version is clean (then python package and image container should be built)
env:
VERSION: ${{ steps.version.outputs.version }}
RUN: ${{ steps.builder.outputs.run }}
FORCE: ${{ toJSON(inputs.build) }}
run: |
echo "Version $VERSION, force (manual input): $FORCE, run the build: $RUN"
- name: Set the version in pyproject.toml (workaround for uv not supporting dynamic version) - name: Get version
if: fromJSON(steps.builder.outputs.run) run: echo "VERSION=$(.venv/bin/dunamai from any --style semver)" >> $GITHUB_ENV
env:
VERSION: ${{ steps.version.outputs.version }} - name: Version
run: sed "s/0.0.0/${VERSION}/" -i pyproject.toml run: echo $VERSION
- name: Get distance from tag
run: echo "DISTANCE=$(.venv/bin/dunamai from any --format '{distance}')" >> $GITHUB_ENV
- name: Distance
run: echo $DISTANCE
- name: Workaround for bug of podman-login - name: Workaround for bug of podman-login
if: fromJSON(steps.builder.outputs.run) if: env.DISTANCE == '0'
run: | run: |
mkdir -p $HOME/.docker mkdir -p $HOME/.docker
echo "{ \"auths\": {} }" > $HOME/.docker/config.json echo "{ \"auths\": {} }" > $HOME/.docker/config.json
- name: Log in to the container registry (with another workaround) - name: Log in to the container registry (with another workaround)
if: fromJSON(steps.builder.outputs.run) if: env.DISTANCE == '0'
uses: actions/podman-login@v1 uses: actions/podman-login@v1
with: with:
registry: ${{ vars.REGISTRY }} registry: ${{ vars.REGISTRY }}
@ -90,37 +87,31 @@ jobs:
auth_file_path: /tmp/auth.json auth_file_path: /tmp/auth.json
- name: Build the container image - name: Build the container image
if: fromJSON(steps.builder.outputs.run) if: env.DISTANCE == '0'
uses: actions/buildah-build@v1 uses: actions/buildah-build@v1
with: with:
image: gisaf-backend image: gisaf-backend
oci: true oci: true
labels: gisaf-backend labels: gisaf-backend
tags: latest ${{ steps.version.outputs.version }} tags: "latest ${{ env.VERSION }}"
containerfiles: | containerfiles: |
./Containerfile ./Containerfile
build-args: |
APP_VERSION=${{ steps.version.outputs.version }}
- name: Push the image to the registry - name: Push the image to the registry
if: fromJSON(steps.builder.outputs.run) if: env.DISTANCE == '0'
uses: actions/push-to-registry@v2 uses: actions/push-to-registry@v2
with: with:
registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}" registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}"
image: gisaf-backend image: gisaf-backend
tags: latest ${{ steps.version.outputs.version }} tags: "latest ${{ env.VERSION }}"
- name: Install uv - name: Build wheel
uses: astral-sh/setup-uv@v4 if: env.DISTANCE == '0'
with:
version: "0.5.9"
- name: Build python package
if: fromJSON(steps.builder.outputs.run)
run: uv build --wheel run: uv build --wheel
- name: Publish Python package (home) - name: Publish Python package (home)
if: fromJSON(steps.builder.outputs.run) if: env.DISTANCE == '0'
env: env:
LOCAL_PYPI_TOKEN: ${{ secrets.LOCAL_PYPI_TOKEN }} LOCAL_PYPI_TOKEN: ${{ secrets.LOCAL_PYPI_TOKEN }}
run: uv publish --publish-url https://code.philo.ydns.eu/api/packages/philorg/pypi --token $LOCAL_PYPI_TOKEN run: uv publish --publish-url https://code.philo.ydns.eu/api/packages/philorg/pypi --token $LOCAL_PYPI_TOKEN
continue-on-error: true

View file

@ -1,19 +1,17 @@
# Build: podman build -t code.philo.ydns.eu/philorg/gisaf-backend -f Containerfile # Build: podman build -t code.philo.ydns.eu/philorg/gisaf-backend -f Containerfile
FROM code.philo.ydns.eu/philorg/gisaf-backend-deps FROM docker.io/library/python:latest
ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV GISAF__BACKEND__PORT 8898 ENV GISAF__BACKEND__PORT 8898
ENV GISAF__BACKEND__BIND__ADDR 0.0.0.0 ENV GISAF__BACKEND__BIND__ADDR 0.0.0.0
ARG APP_VERSION=0.0.0
COPY . /src COPY . /app
RUN uv pip install \ # Sync the project into a new environment, using the frozen lockfile
--python=$UV_PROJECT_ENVIRONMENT \ WORKDIR /app
--no-deps \
/src RUN uv pip install --system .
RUN echo $APP_VERSION > /app/version.txt
CMD uvicorn gisaf.application:app --port ${GISAF__BACKEND__PORT} --host ${GISAF__BACKEND__BIND__ADDR} CMD uvicorn gisaf.application:app --port ${GISAF__BACKEND__PORT} --host ${GISAF__BACKEND__BIND__ADDR}

View file

@ -1,23 +1,40 @@
# Gisaf # Gisaf
*Gisaf* is a web based GIS (Geographical Information System) initially developed *Gisaf* is a web based GIS (Geographical Information System) initially developed
for the CSR Goematics unit in Auroville, India. for the CSR Geomatics unit in Auroville, India.
Its main audiences are local administrations which need a platform
to develop and maintain a geographical, topological and environmental data,
allow teams of surveyors to upload data and update the GIS in near real time,
and collect data from a wide range of sources.
Although it just works out of the box, it is primarily intended to be customized
and extended with the data, uses, needs.
## Features ## Features
- Layers defined with Python plugins or ANSI-standard categories - Layers defined with Python plugins or ANSI-standard categories
- Open source industry standard interfaces (PostGIS database, OGCAPI)
- Support of different geographical projections - Support of different geographical projections
- Integrated administration interface - Integrated administration interface
- Export of data from standard formats (Geopackage, Shapefile) - Export of data from standard formats (Geopackage, Shapefile)
- Import and update of data managed in a well defined workflow with "baskets" - Import and update of data managed in a well defined workflow with "baskets"
- Acquisition of different kinds of surveyors' equipments, accuracies
- Robust mechanisms of identification of the data sources, survey times
- Geographical feature statuses (eg. Existing, Deprecated, Future)
- Role-based access to data - Role-based access to data
- Support temporal and other data for different features - Support temporal and other data for different features
- Easy integration with IoT sensors, eg. using the MQTT protocol
- Detailed information for all features - Detailed information for all features
- Tagging of features - Tagging of features
- Customizable, user triggered actions - Customizable, user triggered actions
- Background map - Customizable background maps
- Customizable dashboards
- Customizable base maps (domain specific set of layers) - Customizable base maps (domain specific set of layers)
- Python and Jupyter integration: easy to interact with, add features, components,
explore data and conduct scientific research
- Plugin architecture: add functions with Python packages - Plugin architecture: add functions with Python packages
- Free and open source
## Software stack ## Software stack
@ -31,3 +48,12 @@ for the CSR Goematics unit in Auroville, India.
## Installation / deployment ## Installation / deployment
See the documentation in the deployment directory. See the documentation in the deployment directory.
## Configuration
Documentation is being rewritten. Please contact us.
## Demo
A very basic demo without any data, basically to give a feel of the user interface,
is available [here](https://gisaf.philo.ydns.eu).

105
deployment/README.md Normal file
View file

@ -0,0 +1,105 @@
# Gisaf deployment / installation
This documentation covers the specifics of Gisaf deployment, but
it does no cover application configuration details,
the set up of a public facing web server handling dns domain / https security.
*Gisaf* is shipped as containers for easy and effective installation,
and packages to deploy them for different environments.
There are 4 containers:
- Frontend (Angular build: static files, served by an nginx process,
eventually forwarding requests to the backend)
- Backend (Python server)
- Postgis database server
- Redis server
Commands below assume that they are run from their respective directories
(`deployment/systemd`, `deployment/kubernetes`, ...).
## Systemd
With the help of *podman*, systemd can handle starting/restarting services, etc,
even with a non-privileged user.
The 4 containers are in the same *pod*, as it is meant primarily for a lightweight
production environment, but this can be easily adapted.
The `deployment/systemd` directory contains files used for running *Gisaf* with Systemd.
Podman is used to facilitate the creation of the relevant services.
### Deployment
```sh
cp -r * $HOME/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start gisaf-pod.service
```
Environment variables for the Gisaf configuration can be added
in the `gisaf-backend.container` file.
Note that starting on system boot requires the user to be enabled accordingly with:
```sh
sudo loginctl enable-linger <username>
```
## Plain Kubernetes (no Helm)
The `kubernetes` directory contains files for deployment on Kubernetes.
The standard installation uses a namespace named `gisaf`.
The Kubernetes configuration is split in 2 files: `gisaf.yaml` and `config.yaml`
Deployment:
```sh
kubectl create namespace gisaf
kubectl apply -f config.yaml
kubectl apply -f gisaf.yaml
```
Update after modification on the server (frontend and backend):
```sh
kubectl --namespace gisaf rollout restart deployment gisaf-server-deployment
```
## Helm
The `helm` chart is in the directory named `helm`.
### Deploy on Kubernetes
Deploying with Helm on Kubernetes makes it straightforward to
run on cloud services.
```sh
kubectl create namespace gisaf
helm install gisaf helm
```
#### Update
```sh
helm upgrade gisaf helm
```
### Publish the Helm chart
First, build the Helm package:
```sh
helm package helm
```
Then upload it:
```sh
helm --user phil:<password> -X POST \
--upload-file gisaf-0.1.0.tgz \
https://code.philo.ydns.eu/api/packages/philorg/helm/api/charts
```

View file

@ -1,57 +0,0 @@
# Kubernetes deployment
This directory contains files for deployment on Kubernetes.
The standard installation uses a namespace named `gisaf`.
Commands below assume that they are run from this directory.
## Plain Kubernetes
2 files: `gisaf.yaml` and `config.yaml`
Deploy on Kubernetes (without Helm)
```sh
kubectl create namespace gisaf
kubectl apply -f config.yaml
```
Update after modification:
```sh
kubectl --namespace gisaf rollout restart deployment gisaf-server-deployment
```
## Helm
The `helm` chart is in the directory named `helm`.
### Deploy on Kubernetes
```sh
kubectl create namespace gisaf
helm install gisaf helm
```
#### Update
```sh
helm upgrade gisaf helm
```
### Publish the Helm chart
First, build the Helm package:
```sh
helm package heml
```
Then upload it:
```sh
helm --user phil:<password> -X POST \
--upload-file gisaf-0.1.0.tgz \
https://code.philo.ydns.eu/api/packages/philorg/helm/api/charts
```

View file

@ -1,7 +1,7 @@
[project] [project]
name = "gisaf-backend" name = "gisaf-backend"
version = "0.0.0"
description = "Gisaf backend" description = "Gisaf backend"
dynamic = ["version"]
authors = [{ name = "phil", email = "phil.dev@philome.mooo.com" }] authors = [{ name = "phil", email = "phil.dev@philome.mooo.com" }]
dependencies = [ dependencies = [
"aiopath>=0.7.1", "aiopath>=0.7.1",
@ -40,20 +40,9 @@ readme = "README.md"
[project.scripts] [project.scripts]
gisaf = "gisaf.cli:cli" gisaf = "gisaf.cli:cli"
[project.optional-dependencies] [dependency-groups]
contextily = ["contextily>=1.4.0"] dev = [
mqtt = ["aiomqtt>=1.2.1"] "dunamai>=1.23.0",
all = ["gisaf-backend[contextily]", "gisaf-backend[mqtt]"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/gisaf"]
[tool.uv]
dev-dependencies = [
"ipdb>=0.13.13", "ipdb>=0.13.13",
"pandas-stubs>=2.1.4.231218", "pandas-stubs>=2.1.4.231218",
"pretty-errors>=1.2.25", "pretty-errors>=1.2.25",
@ -64,3 +53,27 @@ dev-dependencies = [
"types-passlib>=1.7.7.20240311", "types-passlib>=1.7.7.20240311",
"pytest>=8.3.4", "pytest>=8.3.4",
] ]
[project.optional-dependencies]
contextily = ["contextily>=1.4.0"]
mqtt = ["aiomqtt>=1.2.1"]
all = ["gisaf-backend[contextily]", "gisaf-backend[mqtt]"]
[build-system]
requires = ["hatchling", "uv-dynamic-versioning"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "uv-dynamic-versioning"
[tool.hatch.build.targets.wheel]
packages = ["src/gisaf"]
[tool.uv-dynamic-versioning]
style = "semver"
[tool.uv]
package = true
[tool.black]
line-length = 98

View file

@ -0,0 +1,11 @@
import importlib.metadata
try:
from dunamai import Version, Style
__version__ = Version.from_git().serialize(style=Style.SemVer, dirty=True)
except ImportError:
# __name__ could be used if the package name is the same
# as the directory - not the case here
# __version__ = importlib.metadata.version(__name__)
__version__ = importlib.metadata.version("gisaf-backend")

View file

@ -14,25 +14,7 @@ from pydantic_settings import (
YamlConfigSettingsSource, YamlConfigSettingsSource,
) )
from gisaf import __version__
def get_version():
version = importlib_version("gisaf-backend")
if version == "0.0.0":
from subprocess import run
try:
v_git_describe = run(
["git", "describe", "--always", "--tags", "--dirty"],
capture_output=True,
text=True,
cwd=Path(__file__).parent.parent,
)
v_git_describe.check_returncode()
version = v_git_describe.stdout.strip()
except Exception:
logger.warn("Version not set in Python package and cannot run git describe")
version = "?"
return version
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -48,12 +30,8 @@ config_files = [
class DashboardHome(BaseModel): class DashboardHome(BaseModel):
title: str = "Gisaf - home/dashboards" title: str = "Gisaf - home/dashboards"
content_file: Path = ( content_file: Path = Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html"
Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html" footer_file: Path = Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_footer.html"
)
footer_file: Path = (
Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_footer.html"
)
class GisafConfig(BaseModel): class GisafConfig(BaseModel):
@ -141,7 +119,9 @@ class DB(BaseModel):
echo: bool = False echo: bool = False
def get_sqla_url(self): def get_sqla_url(self):
return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}" return (
f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
)
def get_pg_url(self): def get_pg_url(self):
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}" return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
@ -223,9 +203,7 @@ class OGCAPI(BaseModel):
class TileServer(BaseModel): class TileServer(BaseModel):
baseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_files_dir" baseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_files_dir"
useRequestUrl: bool = False useRequestUrl: bool = False
spriteBaseDir: Path = ( spriteBaseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_sprites_dir"
Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_sprites_dir"
)
spriteUrl: str = "/tiles/sprite/sprite" spriteUrl: str = "/tiles/sprite/sprite"
spriteBaseUrl: str = "https://gisaf.example.org" spriteBaseUrl: str = "https://gisaf.example.org"
openMapTilesKey: str | None = None openMapTilesKey: str | None = None
@ -332,9 +310,7 @@ class Config(BaseSettings):
dotenv_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]: ) -> Tuple[PydanticBaseSettingsSource, ...]:
configs = [ configs = [YamlConfigSettingsSource(settings_cls, yaml_file=cf) for cf in config_files]
YamlConfigSettingsSource(settings_cls, yaml_file=cf) for cf in config_files
]
return ( return (
env_settings, env_settings,
init_settings, init_settings,
@ -370,7 +346,7 @@ class Config(BaseSettings):
plot: Plot = Plot() plot: Plot = Plot()
plugins: dict[str, dict[str, Any]] = {} plugins: dict[str, dict[str, Any]] = {}
survey: Survey = Survey() survey: Survey = Survey()
version: str = get_version() version: str = __version__
weather_station: dict[str, dict[str, Any]] = {} weather_station: dict[str, dict[str, Any]] = {}
widgets: Widgets = Widgets() widgets: Widgets = Widgets()

26
uv.lock generated
View file

@ -1,4 +1,5 @@
version = 1 version = 1
revision = 1
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]] [[package]]
@ -276,7 +277,7 @@ name = "click"
version = "8.1.7" version = "8.1.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "platform_system == 'Windows'" }, { name = "colorama", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
wheels = [ wheels = [
@ -425,6 +426,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 },
] ]
[[package]]
name = "dunamai"
version = "1.23.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/4e/a5c8c337a1d9ac0384298ade02d322741fb5998041a5ea74d1cd2a4a1d47/dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4", size = 44681 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/4c/963169386309fec4f96fd61210ac0a0666887d0fb0a50205395674d20b71/dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041", size = 26342 },
]
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.19.0" version = "0.19.0"
@ -538,7 +551,6 @@ wheels = [
[[package]] [[package]]
name = "gisaf-backend" name = "gisaf-backend"
version = "0.0.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiopath" }, { name = "aiopath" },
@ -587,6 +599,7 @@ mqtt = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "asyncpg-stubs" }, { name = "asyncpg-stubs" },
{ name = "dunamai" },
{ name = "ipdb" }, { name = "ipdb" },
{ name = "pandas-stubs" }, { name = "pandas-stubs" },
{ name = "pretty-errors" }, { name = "pretty-errors" },
@ -599,17 +612,17 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "aiomqtt", marker = "extra == 'all'", specifier = ">=1.2.1" },
{ name = "aiomqtt", marker = "extra == 'mqtt'", specifier = ">=1.2.1" }, { name = "aiomqtt", marker = "extra == 'mqtt'", specifier = ">=1.2.1" },
{ name = "aiopath", specifier = ">=0.7.1" }, { name = "aiopath", specifier = ">=0.7.1" },
{ name = "aiosqlite", specifier = ">=0.19.0" }, { name = "aiosqlite", specifier = ">=0.19.0" },
{ name = "apscheduler", specifier = ">=3.10.4" }, { name = "apscheduler", specifier = ">=3.10.4" },
{ name = "asyncpg", specifier = ">=0.28.0" }, { name = "asyncpg", specifier = ">=0.28.0" },
{ name = "contextily", marker = "extra == 'all'", specifier = ">=1.4.0" },
{ name = "contextily", marker = "extra == 'contextily'", specifier = ">=1.4.0" }, { name = "contextily", marker = "extra == 'contextily'", specifier = ">=1.4.0" },
{ name = "fastapi", specifier = ">=0.111" }, { name = "fastapi", specifier = ">=0.111" },
{ name = "geoalchemy2", specifier = ">=0.14.2" }, { name = "geoalchemy2", specifier = ">=0.14.2" },
{ name = "geopandas", specifier = ">=1.0.1" }, { name = "geopandas", specifier = ">=1.0.1" },
{ name = "gisaf-backend", extras = ["contextily"], marker = "extra == 'all'" },
{ name = "gisaf-backend", extras = ["mqtt"], marker = "extra == 'all'" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "itsdangerous", specifier = ">=2.1.2" }, { name = "itsdangerous", specifier = ">=2.1.2" },
{ name = "matplotlib", specifier = ">=3.8.3" }, { name = "matplotlib", specifier = ">=3.8.3" },
@ -633,10 +646,12 @@ requires-dist = [
{ name = "uvicorn", specifier = ">=0.23.2" }, { name = "uvicorn", specifier = ">=0.23.2" },
{ name = "websockets", specifier = ">=12.0" }, { name = "websockets", specifier = ">=12.0" },
] ]
provides-extras = ["all", "contextily", "mqtt"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "asyncpg-stubs", specifier = ">=0.29.1" }, { name = "asyncpg-stubs", specifier = ">=0.29.1" },
{ name = "dunamai", specifier = ">=1.23.0" },
{ name = "ipdb", specifier = ">=0.13.13" }, { name = "ipdb", specifier = ">=0.13.13" },
{ name = "pandas-stubs", specifier = ">=2.1.4.231218" }, { name = "pandas-stubs", specifier = ">=2.1.4.231218" },
{ name = "pretty-errors", specifier = ">=1.2.25" }, { name = "pretty-errors", specifier = ">=1.2.25" },
@ -1207,6 +1222,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 },
{ url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 },
{ url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 },
{ url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
] ]
[[package]] [[package]]
@ -1786,7 +1802,7 @@ name = "tzlocal"
version = "5.2" version = "5.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "tzdata", marker = "platform_system == 'Windows'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 } sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 }
wheels = [ wheels = [