Compare commits

..

No commits in common. "master" and "0.3.6.5" have entirely different histories.

20 changed files with 267 additions and 434 deletions

View file

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

View file

@ -0,0 +1,103 @@
on:
push:
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
build:
description: "Build container"
required: true
default: false
type: boolean
jobs:
test:
runs-on: container
container:
image: tiptop:5000/treetrail-backend-ci
volumes:
- "uv_cache:/root/.cache/uv"
- "ca-cert:/etc/containers/certs.d"
services:
treetrail-database:
image: tiptop:5000/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: 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 if the 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: 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: ${{ 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: ${{ steps.version.outputs.version }}

View file

@ -1,70 +0,0 @@
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

View file

@ -1,21 +0,0 @@
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,13 +1,22 @@
FROM docker.io/library/python:latest # Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-base -f Containerfile.base
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ FROM tiptop:5000/treetrail-backend-deps
COPY . /app ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages
ARG APP_VERSION=0.0.0
# Sync the project into a new environment, using the frozen lockfile COPY . /src
WORKDIR /app
RUN uv pip install --system . #RUN --mount=type=cache,target=/root/.cache \
# 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,6 +1,6 @@
# Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-ci -f Containerfile.ci # Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-ci -f Containerfile.ci
FROM code.philo.ydns.eu/philorg/python-ci FROM tiptop:5000/python-ci
COPY ./pyproject.toml ./README.md ./uv.lock /_lock/ COPY ./pyproject.toml ./README.md ./uv.lock /_lock/

View file

@ -1,4 +0,0 @@
FROM docker.io/postgis/postgis:17-3.5-alpine
ENV POSTGRES_USER treetrail
ENV POSTGRES_PASSWORD treetrail

View file

@ -1,6 +1,6 @@
# Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-deps -f Containerfile.deps # Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-deps -f Containerfile.deps
FROM code.philo.ydns.eu/philorg/trixie_python FROM tiptop:5000/trixie_python
COPY ./pyproject.toml ./README.md ./uv.lock /_lock/ COPY ./pyproject.toml ./README.md ./uv.lock /_lock/

View file

@ -1,5 +1,5 @@
# Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-full -f Containerfile.full_copy # Build: podman build -t code.philo.ydns.eu/philorg/treetrail-backend-full -f Containerfile.full_copy
FROM code.philo.ydns.eu/philorg/trixie_python FROM localhost/trixie_python
ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages ENV PYTHONPATH $UV_PROJECT_ENVIRONMENT/lib/python3.12/site-packages
ENV PATH=/app/bin:$PATH ENV PATH=/app/bin:$PATH

View file

@ -1,6 +1,6 @@
# Build: podman build -t code.philo.ydns.eu/philorg/trixie_python -f Containerfile.trixie_python # Build: podman build -t code.philo.ydns.eu/philorg/trixie_python -f Containerfile.trixie_python
FROM docker.io/library/debian:trixie-slim FROM debian:trixie-slim
RUN <<EOT RUN <<EOT
apt-get update -qy apt-get update -qy

View file

@ -1,6 +1,3 @@
*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

@ -4,8 +4,7 @@
vars: vars:
force_rm: false force_rm: false
cache: false cache: false
repository: code.philo.ydns.eu repository: tiptop:5000
organisation: philorg
tasks: tasks:
#- name: Read conf #- name: Read conf
@ -13,56 +12,13 @@
# file: prod.yaml # file: prod.yaml
# name: conf # name: conf
- name: Build the database image
tags: db
containers.podman.podman_image:
name: treetrail-database
state: build
path: "{{ playbook_dir }}"
build:
format: oci
force_rm: "{{ force_rm }}"
cache: "{{ cache }}"
file: Containerfile.database
push: true
push_args:
dest: "{{ repository }}/{{ organisation }}"
- name: Using the variables - name: Using the variables
ansible.builtin.debug: ansible.builtin.debug:
var: force_rm var: force_rm
- name: Get the version from git
tags:
- ci
- deps
- backend
command: git describe --dirty --tags
register: version
args:
chdir: "{{ playbook_dir }}"
- name: Build the base CI image
tags: ci
containers.podman.podman_image:
name: treetrail-backend-ci
tag: "{{ version.stdout }}"
state: build
path: "{{ playbook_dir }}"
build:
format: oci
force_rm: "{{ force_rm }}"
cache: "{{ cache }}"
file: Containerfile.ci
push: true
push_args:
dest: "{{ repository }}/{{ organisation }}"
- name: Build the base image, only with python dependencies - name: Build the base image, only with python dependencies
tags: deps
containers.podman.podman_image: containers.podman.podman_image:
name: treetrail-backend-deps name: treetrail-backend-deps
tag: "{{ version.stdout }}"
state: build state: build
path: "{{ playbook_dir }}" path: "{{ playbook_dir }}"
build: build:
@ -72,13 +28,17 @@
file: Containerfile.deps file: Containerfile.deps
push: true push: true
push_args: push_args:
dest: "{{ repository }}/{{ organisation }}" dest: "{{ repository }}/treetrail-backend-deps"
- name: Get the version from git
command: git describe --dirty --tags
register: version
args:
chdir: "{{ playbook_dir }}"
- name: Build the backend container image - name: Build the backend container image
tags: backend
containers.podman.podman_image: containers.podman.podman_image:
name: treetrail-backend name: "treetrail-backend:{{ version.stdout }}"
tag: "{{ version.stdout }}"
state: build state: build
force: true force: true
path: "{{ playbook_dir }}" path: "{{ playbook_dir }}"
@ -90,4 +50,4 @@
extra_args: "--build-arg APP_VERSION={{ version.stdout }}" extra_args: "--build-arg APP_VERSION={{ version.stdout }}"
push: true push: true
push_args: push_args:
dest: "{{ repository }}/{{ organisation }}" dest: "{{ repository }}/treetrail-backend:{{ version.stdout }}"

View file

@ -1,80 +1,77 @@
[project] [project]
name = "treetrail-backend" name = "treetrail-backend"
dynamic = ["version"] version = "0.0.0"
#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 = [{ name = "Philippe May", email = "phil.treetrail@philome.mooo.com" }] authors = [
{ 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>=19.0.1", "pyarrow",
"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", "uv-dynamic-versioning"] requires = ["hatchling"]
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.black] #[tool.pdm.version]
line-length = 98 #source = "scm"
#write_to = "treetrail/_version.py"
#write_template = "__version__ = '{}'"
#

View file

@ -1,8 +1,3 @@
import importlib.metadata from treetrail.utils import get_version
try: __version__ = get_version()
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

@ -64,10 +64,16 @@ def get_cache_dir() -> Path:
return Path(conf.storage.root_cache_path) return Path(conf.storage.root_cache_path)
class DB(BaseSettings): class MyBaseSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_db_") model_config = SettingsConfigDict(
env_prefix="treetrail_",
env_nested_delimiter="_",
)
class DB(MyBaseSettings):
# uri: str # uri: str
host: str = "localhost" host: str = "treetrail-database"
port: int = 5432 port: int = 5432
user: str = "treetrail" user: str = "treetrail"
db: str = "treetrail" db: str = "treetrail"
@ -85,19 +91,16 @@ class DB(BaseSettings):
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}"
class App(BaseSettings): class App(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_app_")
title: str = "Tree Trail" title: str = "Tree Trail"
class Storage(BaseSettings): class Storage(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_storage_")
root_attachment_path: str = "/var/lib/treetrail/attachments" root_attachment_path: str = "/var/lib/treetrail/attachments"
root_cache_path: str = "/var/lib/treetrail/cache" root_cache_path: str = "/var/lib/treetrail/cache"
class Tiles(BaseSettings): class Tiles(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_tiles_")
baseDir: str = "/var/lib/treetrail/mbtiles_files" baseDir: str = "/var/lib/treetrail/mbtiles_files"
useRequestUrl: bool = True useRequestUrl: bool = True
spriteBaseDir: str = "/var/lib/treetrail/mbtiles_sprites" spriteBaseDir: str = "/var/lib/treetrail/mbtiles_sprites"
@ -106,8 +109,7 @@ class Tiles(BaseSettings):
osmBaseDir: str = "/var/lib/treetrail/osm" osmBaseDir: str = "/var/lib/treetrail/osm"
class Map(BaseSettings): class Map(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_map_")
zoom: float = 14.0 zoom: float = 14.0
pitch: float = 0.0 pitch: float = 0.0
lat: float = 45.8822 lat: float = 45.8822
@ -116,14 +118,12 @@ class Map(BaseSettings):
background: str = "OpenFreeMap" background: str = "OpenFreeMap"
class Geo(BaseSettings): class Geo(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_geo_")
simplify_geom_factor: int = 10000000 simplify_geom_factor: int = 10000000
simplify_preserve_topology: bool = False simplify_preserve_topology: bool = False
class Security(BaseSettings): class Security(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_security_")
""" """
JWT security configuration JWT security configuration
""" """
@ -133,14 +133,12 @@ class Security(BaseSettings):
access_token_expire_minutes: float = 30 access_token_expire_minutes: float = 30
class ExternalMapStyle(BaseSettings): class ExternalMapStyle(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_external_map_style_")
name: str name: str
url: str url: str
class Config(BaseSettings): class Config(MyBaseSettings):
model_config = SettingsConfigDict(env_prefix="treetrail_")
@classmethod @classmethod
def settings_customise_sources( def settings_customise_sources(
@ -163,7 +161,7 @@ class Config(BaseSettings):
tiles: Tiles = Tiles() tiles: Tiles = Tiles()
security: Security = Security() security: Security = Security()
geo: Geo = Geo() geo: Geo = Geo()
version: str = "-" version: str = '-'
db: DB = DB() db: DB = DB()
base_href: str = "/treetrail" base_href: str = "/treetrail"

View file

@ -14,7 +14,7 @@ from treetrail.config import conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CREATE_DB_TIMEOUT = 10 CREATE_DB_TIMEOUT = 30
engine = create_async_engine( engine = create_async_engine(
conf.db.get_sqla_url(), conf.db.get_sqla_url(),
@ -33,7 +33,7 @@ async def create_db(drop=False):
await conn.run_sync(SQLModel.metadata.drop_all) await conn.run_sync(SQLModel.metadata.drop_all)
await conn.run_sync(SQLModel.metadata.create_all) await conn.run_sync(SQLModel.metadata.create_all)
logger.debug(f"Connect to database with config: {conf.db}") logger.debug(f'Connect to database with config: {conf.db}')
while attempts > 0: while attempts > 0:
try: try:
await try_once() await try_once()
@ -68,7 +68,6 @@ async def populate_init_db():
"""Populate the database for a fresh install""" """Populate the database for a fresh install"""
from sqlalchemy import text from sqlalchemy import text
from treetrail.security import create_user, add_role, add_user_role from treetrail.security import create_user, add_role, add_user_role
logger.info("Populating initial database") logger.info("Populating initial database")
user = await create_user(username="admin", password="admin") user = await create_user(username="admin", password="admin")
@ -77,7 +76,7 @@ async def populate_init_db():
async with db_session() as session: async with db_session() as session:
for initial in initials: for initial in initials:
await session.execute(text(initial)) await session.execute(text(initial))
logger.debug(f"Added map style {initial}") logger.debug(f'Added map style {initial}')
await session.commit() await session.commit()

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) if not x.startswith("_") and x != "metadata"]: for field in [x for x in dir(obj)
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,30 +50,24 @@ 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, *,
def geopandas_query( # simplify_tolerance: float|None=None,
session, crs=None, cast=True,
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:
@ -84,11 +78,34 @@ def geopandas_query(
# 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'

0
tests/__init__.py Normal file
View file

View file

@ -1,122 +0,0 @@
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,5 +1,4 @@
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'",
@ -343,7 +342,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 = "sys_platform == 'win32'" }, { name = "colorama", marker = "platform_system == 'Windows'" },
] ]
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 = [
@ -397,18 +396,6 @@ 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"
@ -1122,7 +1109,6 @@ 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]]
@ -1145,37 +1131,27 @@ wheels = [
[[package]] [[package]]
name = "pyarrow" name = "pyarrow"
version = "19.0.1" version = "17.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7f/09/a9046344212690f0632b9c709f9bf18506522feb333c894d0de81d62341a/pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e", size = 1129437 } dependencies = [
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/ff/77/e62aebd343238863f2c9f080ad2ef6ace25c919c6ab383436b5b81cbeef7/pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466", size = 25283133 }, { 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/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/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/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/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/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/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/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/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/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/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/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/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/16/33/2a67c0f783251106aeeee516f4806161e7b481f7d744d0d643d2f30230a5/pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960", size = 25250108 }, { 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/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]]
@ -1613,6 +1589,7 @@ wheels = [
[[package]] [[package]]
name = "treetrail-backend" name = "treetrail-backend"
version = "0.0.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },
@ -1641,7 +1618,6 @@ 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" },
@ -1669,7 +1645,7 @@ requires-dist = [
{ name = "passlib", extras = ["bcrypt"] }, { name = "passlib", extras = ["bcrypt"] },
{ name = "pillow" }, { name = "pillow" },
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "pyarrow", specifier = ">=19.0.1" }, { name = "pyarrow" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-jose", extras = ["cryptography"] }, { name = "python-jose", extras = ["cryptography"] },
{ name = "python-multipart" }, { name = "python-multipart" },
@ -1682,10 +1658,8 @@ 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" },