Compare commits

...

6 commits

Author SHA1 Message Date
7d0d6011d7 CI: switch to Woodpecker 2025-06-27 18:06:27 +02:00
ec4cc9a58a CI: fix container version
Some checks failed
/ test (push) Has been cancelled
/ build (push) Has been cancelled
2025-03-16 14:21:50 +01:00
7105edecd1 CI: build container from plain Python image
All checks were successful
/ test (push) Successful in 25s
/ build (push) Successful in 27s
2025-03-16 14:14:39 +01:00
ea7e3087cd Use dynamic versioning
Some checks failed
/ test (push) Successful in 26s
/ build (push) Failing after 8s
2025-03-15 17:43:07 +01:00
c7deb34bae Add annotations to kube 2025-03-15 16:30:07 +01:00
3745cfe3f0 Add kube yaml
All checks were successful
/ test (push) Successful in 25s
2024-12-08 06:12:18 +01:00
13 changed files with 352 additions and 284 deletions

View file

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

View file

@ -1,121 +0,0 @@
on:
push:
tags:
- "**"
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
jobs:
test:
runs-on: container
container:
image: code.philo.ydns.eu/philorg/treetrail-backend-ci
volumes:
- "uv_cache:/root/.cache/uv"
- "ca-cert:/etc/containers/certs.d"
services:
treetrail-database:
image: code.philo.ydns.eu/philorg/treetrail-database
steps:
- name: Echo env
if: ${{ inputs.verbose }}
run: |
echo '${{ toJSON(env) }}'
- uses: actions/checkout@v4
- name: Install app with 'uv pip install'
run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
- name: Run tests (API call)
run: TREETRAIL_DB_HOST=treetrail-database pytest -s tests/basic.py
build:
runs-on: container
needs: test
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get the version from git
id: version
run: echo "version=$(git describe --dirty --tags)" >> $GITHUB_OUTPUT
- name: Check if the container should be built
id: builder
env:
RUN: ${{ toJSON(inputs.build || !contains(steps.version.outputs.version, '-')) }}
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)
if: fromJSON(steps.builder.outputs.run)
env:
VERSION: ${{ steps.version.outputs.version }}
run: sed "s/0.0.0/${VERSION}/" -i pyproject.toml
- name: Workaround for bug of podman-login
if: fromJSON(steps.builder.outputs.run)
run: |
mkdir -p $HOME/.docker
echo "{ \"auths\": {} }" > $HOME/.docker/config.json
- name: Log in to the container registry (with another workaround)
if: fromJSON(steps.builder.outputs.run)
uses: actions/podman-login@v1
with:
registry: ${{ vars.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
auth_file_path: /tmp/auth.json
- name: Build the container image
if: fromJSON(steps.builder.outputs.run)
uses: actions/buildah-build@v1
with:
image: treetrail-backend
oci: true
labels: treetrail-backend
tags: latest ${{ steps.version.outputs.version }}
containerfiles: |
./Containerfile
build-args: |
APP_VERSION=${{ steps.version.outputs.version }}
- name: Push the image to the registry
if: fromJSON(steps.builder.outputs.run)
uses: actions/push-to-registry@v2
with:
registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}"
image: treetrail-backend
tags: latest ${{ steps.version.outputs.version }}
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "0.5.5"
- name: Build python package
if: fromJSON(steps.builder.outputs.run)
run: uv build --wheel
- name: Publish Python package (home)
if: fromJSON(steps.builder.outputs.run)
env:
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

View file

@ -1,34 +0,0 @@
on:
push:
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
jobs:
test:
runs-on: container
container:
image: code.philo.ydns.eu/philorg/treetrail-backend-ci
volumes:
- "uv_cache:/root/.cache/uv"
- "ca-cert:/etc/containers/certs.d"
services:
treetrail-database:
image: code.philo.ydns.eu/philorg/treetrail-database
steps:
- name: Echo env
if: ${{ inputs.verbose }}
run: |
echo '${{ toJSON(env) }}'
- uses: actions/checkout@v4
- name: Install app with 'uv pip install'
run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
- name: Run tests (API call)
run: TREETRAIL_DB_HOST=treetrail-database pytest -s tests/basic.py

70
.woodpecker/build.yaml Normal file
View file

@ -0,0 +1,70 @@
when:
- event: manual
- event: tag
depends_on:
- test
steps:
python_sync:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv sync
python_build:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv build --wheel
- uv cache prune --ci
python_publish:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
environment:
OWNER: philorg
REGISTRY_URL: https://code.philo.ydns.eu
REGISTRY_TOKEN:
from_secret: registry_token
commands:
- uv publish --publish-url $REGISTRY_URL/api/packages/$OWNER/pypi --token $REGISTRY_TOKEN dist/*.whl
failure: ignore
container_build_publish:
image: quay.io/podman/stable:latest
# Caution: This image is built daily. It might fill up your image store quickly.
#pull: true
volumes:
- containers:/var/lib/containers
- uv-cache:/uv-cache
# Fill in the trusted checkbox in Woodpecker's settings as well
privileged: true
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
registry: code.philo.ydns.eu
org: philorg
container_name: treetrail-backend
registry_token:
from_secret: registry_token
commands:
# Login at the registry
- podman login -u __token__ --password $registry_token $registry
# Build the container image
- podman build --volume=/var/lib/containers:/var/lib/containers --tag $registry/$org/$container_name:latest --tag $registry/$org/$container_name:$CI_COMMIT_TAG .
# Push the image
- podman push $registry/$org/$container_name:latest
- podman push $registry/$org/$container_name:$CI_COMMIT_TAG

21
.woodpecker/test.yaml Normal file
View file

@ -0,0 +1,21 @@
when:
- event: push
branch: main
- event: manual
- event: tag
steps:
sync:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv sync
test:
image: code.philo.ydns.eu/philorg/uv
commands:
- .venv/bin/pytest -s tests/basic.py

View file

@ -1,22 +1,13 @@
# Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend -f Containerfile FROM docker.io/library/python:latest
FROM code.philo.ydns.eu/philorg/treetrail-backend-deps COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages COPY . /app
ARG APP_VERSION=0.0.0
COPY . /src # Sync the project into a new environment, using the frozen lockfile
WORKDIR /app
#RUN --mount=type=cache,target=/root/.cache \ RUN uv pip install --system .
# cd /src && \
# uv sync --locked --no-dev --no-editable
RUN uv pip install \
--python=$UV_PROJECT_ENVIRONMENT \
--no-deps \
/src
RUN echo $APP_VERSION > /app/version.txt
CMD [ \ CMD [ \
"uvicorn", "treetrail.application:app", \ "uvicorn", "treetrail.application:app", \

View file

@ -1,3 +1,6 @@
*Tree Trail* is a fun and pedagogic tool to discover the trails and trees around. *Tree Trail* is a fun and pedagogic tool to discover the trails and trees around.
This is the server (back-end), written in Python. This is the server (back-end), written in Python.
[![status-badge](https://code.philo.ydns.eu/woodpecker/api/badges/20/status.svg)](https://code.philo.ydns.eu/woodpecker/repos/20)

View file

@ -1,77 +1,80 @@
[project] [project]
name = "treetrail-backend" name = "treetrail-backend"
version = "0.0.0" dynamic = ["version"]
#dynamic = ["version"]
description = "A fun and pedagogic tool to discover the trails and trees around" description = "A fun and pedagogic tool to discover the trails and trees around"
authors = [ authors = [{ name = "Philippe May", email = "phil.treetrail@philome.mooo.com" }]
{ name = "Philippe May", email = "phil.treetrail@philome.mooo.com" }
]
dependencies = [ dependencies = [
"aiofiles", "aiofiles",
"aiohttp-client-cache", "aiohttp-client-cache",
"aiosqlite", "aiosqlite",
"asyncpg", "asyncpg",
"fastapi", "fastapi",
"geoalchemy2", "geoalchemy2",
"geopandas", "geopandas",
"httptools>=0.6.1", "httptools>=0.6.1",
"orjson", "orjson",
"pandas", "pandas",
"passlib[bcrypt]", "passlib[bcrypt]",
"pillow", "pillow",
"psycopg2-binary", "psycopg2-binary",
"pyarrow", "pyarrow>=19.0.1",
"pydantic-settings", "pydantic-settings",
"python-jose[cryptography]", "python-jose[cryptography]",
"python-multipart", "python-multipart",
"requests", "requests",
"sqlalchemy[asyncio]", "sqlalchemy[asyncio]",
"sqlmodel", "sqlmodel",
"uvicorn[standard]", "uvicorn[standard]",
"uvloop", "uvloop",
] ]
requires-python = ">=3.11" requires-python = ">=3.11"
readme = "README.md" readme = "README.md"
license = {text = "MIT"} license = { text = "MIT" }
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Framework :: FastAPI", "Framework :: FastAPI",
"Environment :: Web Environment", "Environment :: Web Environment",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License (GPL)", "License :: OSI Approved :: GNU General Public License (GPL)",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Operating System :: MacOS :: MacOS X", "Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX", "Operating System :: POSIX",
"Programming Language :: Python", "Programming Language :: Python",
] ]
#[project.scripts] #[project.scripts]
#treetrail-backend = "treetrail_backend:main" #treetrail-backend = "treetrail_backend:main"
[dependency-groups]
dev = ["dunamai>=1.23.0", "ipdb>=0.13.13"]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling", "uv-dynamic-versioning"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.hatch.version]
source = "uv-dynamic-versioning"
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src/treetrail"] packages = ["src/treetrail"]
[tool.uv-dynamic-versioning]
style = "semver"
[tool.uv] [tool.uv]
package = true package = true
dev-dependencies = [ dev-dependencies = [
"httpx", "httpx",
"ipdb", "ipdb",
"pandas-stubs", "pandas-stubs",
"pytest", "pytest",
"types-Pillow", "types-Pillow",
"types-PyYAML", "types-PyYAML",
"types-aiofiles", "types-aiofiles",
"types-passlib", "types-passlib",
"types-python-jose", "types-python-jose",
"types-requests", "types-requests",
] ]
#[tool.pdm.version] [tool.black]
#source = "scm" line-length = 98
#write_to = "treetrail/_version.py"
#write_template = "__version__ = '{}'"
#

View file

@ -1,3 +1,8 @@
from treetrail.utils import get_version import importlib.metadata
__version__ = get_version() try:
from dunamai import Version, Style
__version__ = Version.from_git().serialize(style=Style.SemVer, dirty=True)
except (ImportError, RuntimeError):
__version__ = importlib.metadata.version(__name__)

View file

@ -7,19 +7,19 @@ import pandas as pd
from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.engine.row import Row from sqlalchemy.engine.row import Row
from sqlalchemy.sql.selectable import Select from sqlalchemy.sql.selectable import Select
import geopandas as gpd # type: ignore import geopandas as gpd # type: ignore
from treetrail.config import conf from treetrail.config import conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AlchemyEncoder(json.JSONEncoder): class AlchemyEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta): if isinstance(obj.__class__, DeclarativeMeta):
# an SQLAlchemy class # an SQLAlchemy class
fields = {} fields = {}
for field in [x for x in dir(obj) for field in [x for x in dir(obj) if not x.startswith("_") and x != "metadata"]:
if not x.startswith('_') and x != 'metadata']:
data = obj.__getattribute__(field) data = obj.__getattribute__(field)
try: try:
# this will fail on non-encodable values, like other classes # this will fail on non-encodable values, like other classes
@ -50,24 +50,30 @@ def get_attachment_root(type: str):
def get_attachment_tree_root(): def get_attachment_tree_root():
return get_attachment_root('tree') return get_attachment_root("tree")
def get_attachment_trail_root(): def get_attachment_trail_root():
return get_attachment_root('trail') return get_attachment_root("trail")
def get_attachment_poi_root(): def get_attachment_poi_root():
return get_attachment_root('poi') return get_attachment_root("poi")
def pandas_query(session, query): def pandas_query(session, query):
return pd.read_sql_query(query, session.connection()) return pd.read_sql_query(query, session.connection())
def geopandas_query(session, query: Select, model, *,
# simplify_tolerance: float|None=None, def geopandas_query(
crs=None, cast=True, session,
): query: Select,
model,
*,
# simplify_tolerance: float|None=None,
crs=None,
cast=True,
):
## XXX: I could not get the add_columns work without creating a subquery, ## XXX: I could not get the add_columns work without creating a subquery,
## so moving the simplification to geopandas - see in _get_df ## so moving the simplification to geopandas - see in _get_df
# if simplify_tolerance is not None: # if simplify_tolerance is not None:
@ -78,34 +84,11 @@ def geopandas_query(session, query: Select, model, *,
# query = query.add_columns(new_column) # query = query.add_columns(new_column)
return gpd.GeoDataFrame.from_postgis(query, session.connection(), crs=crs) return gpd.GeoDataFrame.from_postgis(query, session.connection(), crs=crs)
def mkdir(dir: Path | str) -> Path: def mkdir(dir: Path | str) -> Path:
path = Path(dir) path = Path(dir)
if not path.is_dir(): if not path.is_dir():
logger.info(f'Create directory {path}') logger.info(f"Create directory {path}")
path.mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
return path return path
def get_version() -> str:
version_file_src = Path(__file__).parent / 'version.txt'
version_file_in_container = Path("/app") / 'version.txt'
if version_file_src.exists():
with open(version_file_src) as version:
return version.read().strip()
if version_file_in_container.exists():
with open(version_file_in_container) as version:
return version.read().strip()
else:
logger.debug('No version file, using git')
try:
from subprocess import run
git_version_cmd = run(['git', 'describe', '--broken', '--tags', '--always', '--dirty'],
capture_output=True)
if git_version_cmd.returncode == 0:
return git_version_cmd.stdout.strip().decode()
else:
logger.debug('git returns with the error below, version set as 0.0.0')
logger.debug(git_version_cmd.stderr.decode())
return '0.0.0'
except FileNotFoundError as err:
logger.debug('git not found: version set as 0.0.0')
return '0.0.0'

View file

122
treetrail-kube.yaml Normal file
View file

@ -0,0 +1,122 @@
apiVersion: v1
kind: Service
metadata:
name: treetrail
annotations:
io.kubernetes.cri-o.SandboxID/gisaf-backend: treetrail-cri-o
io.kubernetes.cri-o.SandboxID/gisaf-database: treetrail-cri-o
io.kubernetes.cri-o.SandboxID/gisaf-frontend: treetrail-cri-o
io.kubernetes.cri-o.SandboxID/gisaf-redis: treetrail-cri-o
io.podman.annotations.infra.name: treetrail-infra
labels:
app: treetrail
spec:
ports:
- name: "80"
nodePort: 31080
port: 80
targetPort: 80
- name: "4532"
nodePort: 31432
port: 4532
targetPort: 4532
selector:
app: treetrail
type: NodePort
---
apiVersion: v1
kind: Pod
metadata:
labels:
app: treetrail
name: treetrail
spec:
containers:
- image: code.philo.ydns.eu/philorg/treetrail-frontend:latest
args:
- nginx
- -g
- daemon off;
name: treetrail-frontend
ports:
- containerPort: 80
hostPort: 8080
- image: code.philo.ydns.eu/philorg/treetrail-backend:latest
name: treetrail-backend
- image: code.philo.ydns.eu/philorg/treetrail-database:latest
args:
- postgres
name: treetrail-database
ports:
- containerPort: 4532
hostPort: 15432
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: treetrail-pgdata
volumes:
- name: treetrail-pgdata
persistentVolumeClaim:
claimName: treetrail-pgdata-pvc
restartPolicy: Always
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: treetrail-pgdata-pv
labels:
type: local
app: postgres
spec:
storageClassName: manual
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
hostPath:
path: /data/postgresql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: treetrail-pgdata-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: treetrail
namespace: default
#annotations:
# kubernetes.io/ingress.class: traefik
# #traefik.ingress.kubernetes.io/router.middlewares: default-strip-prefix@kubernetescrd
spec:
rules:
- http:
paths:
- path: /treetrail
pathType: Prefix
backend:
service:
name: treetrail-frontend
port:
number: 80
- path: /treetrail/v1
pathType: Prefix
backend:
service:
name: treetrail-backend
port:
number: 8081
- path: /treetrail/plantekey
pathType: Prefix
backend:
service:
name: treetrail-backend
port:
number: 8081

70
uv.lock generated
View file

@ -1,4 +1,5 @@
version = 1 version = 1
revision = 1
requires-python = ">=3.11" requires-python = ">=3.11"
resolution-markers = [ resolution-markers = [
"python_full_version < '3.12'", "python_full_version < '3.12'",
@ -342,7 +343,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 = [
@ -396,6 +397,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"
@ -1109,6 +1122,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]]
@ -1131,27 +1145,37 @@ wheels = [
[[package]] [[package]]
name = "pyarrow" name = "pyarrow"
version = "17.0.0" version = "19.0.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/7f/09/a9046344212690f0632b9c709f9bf18506522feb333c894d0de81d62341a/pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e", size = 1129437 }
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/27/4e/ea6d43f324169f8aec0e57569443a38bab4b398d09769ca64f7b4d467de3/pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28", size = 1112479 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/46/ce89f87c2936f5bb9d879473b9663ce7a4b1f4359acc2f0eb39865eaa1af/pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977", size = 29028748 }, { url = "https://files.pythonhosted.org/packages/a0/55/f1a8d838ec07fe3ca53edbe76f782df7b9aafd4417080eebf0b42aab0c52/pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90", size = 30713987 },
{ url = "https://files.pythonhosted.org/packages/8d/8e/ce2e9b2146de422f6638333c01903140e9ada244a2a477918a368306c64c/pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3", size = 27190965 }, { url = "https://files.pythonhosted.org/packages/13/12/428861540bb54c98a140ae858a11f71d041ef9e501e6b7eb965ca7909505/pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00", size = 32135613 },
{ url = "https://files.pythonhosted.org/packages/3b/c8/5675719570eb1acd809481c6d64e2136ffb340bc387f4ca62dce79516cea/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15", size = 39269081 }, { url = "https://files.pythonhosted.org/packages/2f/8a/23d7cc5ae2066c6c736bce1db8ea7bc9ac3ef97ac7e1c1667706c764d2d9/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae", size = 41149147 },
{ url = "https://files.pythonhosted.org/packages/5e/78/3931194f16ab681ebb87ad252e7b8d2c8b23dad49706cadc865dff4a1dd3/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597", size = 39864921 }, { url = "https://files.pythonhosted.org/packages/a2/7a/845d151bb81a892dfb368bf11db584cf8b216963ccce40a5cf50a2492a18/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5", size = 42178045 },
{ url = "https://files.pythonhosted.org/packages/d8/81/69b6606093363f55a2a574c018901c40952d4e902e670656d18213c71ad7/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420", size = 38740798 }, { url = "https://files.pythonhosted.org/packages/a7/31/e7282d79a70816132cf6cae7e378adfccce9ae10352d21c2fecf9d9756dd/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3", size = 40532998 },
{ url = "https://files.pythonhosted.org/packages/4c/21/9ca93b84b92ef927814cb7ba37f0774a484c849d58f0b692b16af8eebcfb/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4", size = 39871877 }, { url = "https://files.pythonhosted.org/packages/b8/82/20f3c290d6e705e2ee9c1fa1d5a0869365ee477e1788073d8b548da8b64c/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6", size = 42084055 },
{ url = "https://files.pythonhosted.org/packages/30/d1/63a7c248432c71c7d3ee803e706590a0b81ce1a8d2b2ae49677774b813bb/pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03", size = 25151089 }, { url = "https://files.pythonhosted.org/packages/ff/77/e62aebd343238863f2c9f080ad2ef6ace25c919c6ab383436b5b81cbeef7/pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466", size = 25283133 },
{ url = "https://files.pythonhosted.org/packages/d4/62/ce6ac1275a432b4a27c55fe96c58147f111d8ba1ad800a112d31859fae2f/pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22", size = 29019418 }, { url = "https://files.pythonhosted.org/packages/78/b4/94e828704b050e723f67d67c3535cf7076c7432cd4cf046e4bb3b96a9c9d/pyarrow-19.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b", size = 30670749 },
{ url = "https://files.pythonhosted.org/packages/8e/0a/dbd0c134e7a0c30bea439675cc120012337202e5fac7163ba839aa3691d2/pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053", size = 27152197 }, { url = "https://files.pythonhosted.org/packages/7e/3b/4692965e04bb1df55e2c314c4296f1eb12b4f3052d4cf43d29e076aedf66/pyarrow-19.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294", size = 32128007 },
{ url = "https://files.pythonhosted.org/packages/cb/05/3f4a16498349db79090767620d6dc23c1ec0c658a668d61d76b87706c65d/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a", size = 39263026 }, { url = "https://files.pythonhosted.org/packages/22/f7/2239af706252c6582a5635c35caa17cb4d401cd74a87821ef702e3888957/pyarrow-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14", size = 41144566 },
{ url = "https://files.pythonhosted.org/packages/c2/0c/ea2107236740be8fa0e0d4a293a095c9f43546a2465bb7df34eee9126b09/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc", size = 39880798 }, { url = "https://files.pythonhosted.org/packages/fb/e3/c9661b2b2849cfefddd9fd65b64e093594b231b472de08ff658f76c732b2/pyarrow-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34", size = 42202991 },
{ url = "https://files.pythonhosted.org/packages/f6/b0/b9164a8bc495083c10c281cc65064553ec87b7537d6f742a89d5953a2a3e/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a", size = 38715172 }, { url = "https://files.pythonhosted.org/packages/fe/4f/a2c0ed309167ef436674782dfee4a124570ba64299c551e38d3fdaf0a17b/pyarrow-19.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6", size = 40507986 },
{ url = "https://files.pythonhosted.org/packages/f1/c4/9625418a1413005e486c006e56675334929fad864347c5ae7c1b2e7fe639/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b", size = 39874508 }, { url = "https://files.pythonhosted.org/packages/27/2e/29bb28a7102a6f71026a9d70d1d61df926887e36ec797f2e6acfd2dd3867/pyarrow-19.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832", size = 42087026 },
{ url = "https://files.pythonhosted.org/packages/ae/49/baafe2a964f663413be3bd1cf5c45ed98c5e42e804e2328e18f4570027c1/pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7", size = 25099235 }, { url = "https://files.pythonhosted.org/packages/16/33/2a67c0f783251106aeeee516f4806161e7b481f7d744d0d643d2f30230a5/pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960", size = 25250108 },
{ url = "https://files.pythonhosted.org/packages/2b/8d/275c58d4b00781bd36579501a259eacc5c6dfb369be4ddeb672ceb551d2d/pyarrow-19.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c", size = 30653552 },
{ url = "https://files.pythonhosted.org/packages/a0/9e/e6aca5cc4ef0c7aec5f8db93feb0bde08dbad8c56b9014216205d271101b/pyarrow-19.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae", size = 32103413 },
{ url = "https://files.pythonhosted.org/packages/6a/fa/a7033f66e5d4f1308c7eb0dfcd2ccd70f881724eb6fd1776657fdf65458f/pyarrow-19.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4", size = 41134869 },
{ url = "https://files.pythonhosted.org/packages/2d/92/34d2569be8e7abdc9d145c98dc410db0071ac579b92ebc30da35f500d630/pyarrow-19.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2", size = 42192626 },
{ url = "https://files.pythonhosted.org/packages/0a/1f/80c617b1084fc833804dc3309aa9d8daacd46f9ec8d736df733f15aebe2c/pyarrow-19.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6", size = 40496708 },
{ url = "https://files.pythonhosted.org/packages/e6/90/83698fcecf939a611c8d9a78e38e7fed7792dcc4317e29e72cf8135526fb/pyarrow-19.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136", size = 42075728 },
{ url = "https://files.pythonhosted.org/packages/40/49/2325f5c9e7a1c125c01ba0c509d400b152c972a47958768e4e35e04d13d8/pyarrow-19.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef", size = 25242568 },
{ url = "https://files.pythonhosted.org/packages/3f/72/135088d995a759d4d916ec4824cb19e066585b4909ebad4ab196177aa825/pyarrow-19.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0", size = 30702371 },
{ url = "https://files.pythonhosted.org/packages/2e/01/00beeebd33d6bac701f20816a29d2018eba463616bbc07397fdf99ac4ce3/pyarrow-19.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9", size = 32116046 },
{ url = "https://files.pythonhosted.org/packages/1f/c9/23b1ea718dfe967cbd986d16cf2a31fe59d015874258baae16d7ea0ccabc/pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3", size = 41091183 },
{ url = "https://files.pythonhosted.org/packages/3a/d4/b4a3aa781a2c715520aa8ab4fe2e7fa49d33a1d4e71c8fc6ab7b5de7a3f8/pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6", size = 42171896 },
{ url = "https://files.pythonhosted.org/packages/23/1b/716d4cd5a3cbc387c6e6745d2704c4b46654ba2668260d25c402626c5ddb/pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a", size = 40464851 },
{ url = "https://files.pythonhosted.org/packages/ed/bd/54907846383dcc7ee28772d7e646f6c34276a17da740002a5cefe90f04f7/pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8", size = 42085744 },
] ]
[[package]] [[package]]
@ -1589,7 +1613,6 @@ wheels = [
[[package]] [[package]]
name = "treetrail-backend" name = "treetrail-backend"
version = "0.0.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },
@ -1618,6 +1641,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "dunamai" },
{ name = "httpx" }, { name = "httpx" },
{ name = "ipdb" }, { name = "ipdb" },
{ name = "pandas-stubs" }, { name = "pandas-stubs" },
@ -1645,7 +1669,7 @@ requires-dist = [
{ name = "passlib", extras = ["bcrypt"] }, { name = "passlib", extras = ["bcrypt"] },
{ name = "pillow" }, { name = "pillow" },
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "pyarrow" }, { name = "pyarrow", specifier = ">=19.0.1" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-jose", extras = ["cryptography"] }, { name = "python-jose", extras = ["cryptography"] },
{ name = "python-multipart" }, { name = "python-multipart" },
@ -1658,8 +1682,10 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "dunamai", specifier = ">=1.23.0" },
{ name = "httpx" }, { name = "httpx" },
{ name = "ipdb" }, { name = "ipdb" },
{ name = "ipdb", specifier = ">=0.13.13" },
{ name = "pandas-stubs" }, { name = "pandas-stubs" },
{ name = "pytest" }, { name = "pytest" },
{ name = "types-aiofiles" }, { name = "types-aiofiles" },