Files
AI-Horde/pyproject.toml
tazlin 5564c077bd feat(kudos): S3 archive client with a byte budget and verified restore
- horde/kudos_archive.py writes zstd-compressed archive objects with manifests to an S3-compatible bucket under a normalized deployment prefix, keeps statistic dumps within a byte budget by evicting only that tier, and verifies a read-back against the manifest
- KudosArchive.from_environment returns None unless the KUDOS_ARCHIVE_* endpoint, bucket and both token variables are set
- zstandard is a runtime dependency; kudos_archive and kudos_retention are under strict mypy
- The managed Garage test store and the CI test stack create a kudos-archive bucket and export the KUDOS_ARCHIVE_* variables
- tests/unit/test_kudos_archive.py covers the codec, manifests, budget eviction and restore verification
2026-11-03 12:29:16 -05:00

237 lines
7.1 KiB
TOML

# SPDX-FileCopyrightText: 2022 Konstantinos Thoukydidis <mail@dbzer0.com>
# SPDX-FileCopyrightText: 2023 Tazlin
#
# SPDX-License-Identifier: AGPL-3.0-or-later
[project]
name = "AI-Horde"
version = "5.1.15"
description = "A Crowdsourced Generative AI cluster for everyone."
authors = [
{name = "db0", email = "mail@dbzer0.com"},
{name = "Tazlin", email = "tazlin@haidra.net"},
]
readme = "README.md"
requires-python = ">=3.12,<3.14"
dependencies = [
"Werkzeug~=2.2.2",
"Flask~=2.2.2",
"flask-restx",
"flask-limiter~=3.0",
"Flask-Caching",
"waitress~=2.1.2",
"requests>=2.27",
"markdown>=3.7.0,<4.0.0",
"flask-dance[sqla]",
"blinker",
"python-dotenv",
"loguru",
"bleach~=5.0.1",
"alt-profanity-check",
"safetext",
"python-dateutil~=2.8.2",
"redis~=4.3.5",
"pillow>=10.0.1",
"flask-sqlalchemy~=3.1.1",
"oauthlib~=3.2.2",
"sqlalchemy~=2.0.35",
"psycopg2-binary",
"boto3>=1.33.7",
# Compresses kudos archive objects (horde.kudos_archive.ZstdCodec).
"zstandard>=0.23",
"regex",
"unidecode",
"patreon @ git+https://github.com/Patreon/patreon-python.git",
"emoji",
"semver>=3.0.2",
# Used by the tiny kudos pricing model and resolved deterministically by uv.lock.
"numpy",
# The single source of truth for image sampler capabilities: which solver knobs each sampler
# accepts and over what range, how many model evaluations a step of it costs, and which
# sampler/scheduler/baseline combinations cannot produce an image. Request validation, pricing and
# the published constraints endpoint all read it, so the vocabulary cannot drift between this API,
# the workers, and the clients that share this package.
# Also contains the text chat completions request limits and chat capabilities foundations used
# to enforce compliance with different standards (OAI vs horde adaptations).
"horde_sdk~=0.33.3",
# The image model reference loader (REPLICA fetch plus the pending/beta overlay) and the
# baseline vocabulary both come from here, so it is pinned explicitly rather than being
# inherited from horde_sdk.
"horde_model_reference>=8.7.0,<9.0.0",
"markdownify",
"stripe>=15,<16",
"environs",
"setuptools<82",
# logfire is hard-pinned deliberately: the OTel/Logfire bridge API is not yet
# stable across minors and our telemetry wiring (horde/telemetry.py) is tested
# against this exact version. Bump only with a telemetry smoke re-run.
# fastapi and httpx: the chat front's server spans and its calls to the horde (horde.oai.__main__).
"logfire[flask,sqlalchemy,redis,requests,fastapi,httpx]==4.32.1",
"chardet<6",
"pycurl>=7.45.7,<8",
# The OpenAI-compatible chat front (horde/oai) is an ASGI app, so one process holds thousands of waiting requests and streams.
"fastapi",
# Serves the horde/oai ASGI app.
"uvicorn",
# The horde/oai front calls the Horde API asynchronously through it.
"httpx",
# Server-sent events for the horde/oai streaming responses.
"sse-starlette",
# Reads the horde/oai front's HORDE_OAI_* settings into a validated model.
"pydantic-settings",
# httpcore tries to import sniffio on every request to detect the async library. Current anyio no longer
# depends on sniffio, and a failed import is never cached, so without it every Horde call from the front
# walked the whole import path and cost more CPU than the request itself.
"sniffio",
# A chat job's reply to a strict json_schema response format is validated against the client's schema on submit.
"jsonschema>=4.26,<5",
]
license = {file = "LICENSE"}
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"License :: OSI Approved :: GNU Affero General Public License v3",
]
[project.urls]
"Homepage" = "https://aihorde.net"
"Bug Tracker" = "https://github.com/Haidra-Org/AI-Horde"
[dependency-groups]
dev = [
"pytest==9.0.2",
"ruff==0.15.12",
"tox~=4.14.1",
"pytest-asyncio",
"pytest-cov",
"sqlparse",
"fakeredis[lua]",
"freezegun",
"testcontainers[postgres,redis]",
"mypy",
"pip-audit",
"reuse",
"pre-commit",
"locust>=2.43.4",
"types-python-dateutil>=2.9.0.20260716",
# Mocks the Horde API under the horde/oai front tests.
"respx",
# Official OpenAI client; the chat front's conformance tests drive it against the front.
"openai>=1.109,<2",
# Issues a throwaway CA and server certificate for the loopback HTTPS outbound tests.
"trustme",
]
# Optional runtime group used by the Dockerfile's AI_HORDE_DEPENDENCY_GROUPS
# build arg. Keep profiler wheels out of the default image; publish/use the
# telemetry image variant when PYROSCOPE_ENABLED=true.
telemetry-profiling = [
"pyroscope-io",
"pyroscope-otel",
]
docs = [
"zensical==0.0.74",
]
[tool.ruff]
line-length = 140
lint.select = [
#"A",
"I",
"E",
"W",
"F",
"UP",
"YTT",
#"B",
#"C4",
"PIE",
#"RET",
#"SIM",
"COM",
# "D",
#"ANN"
]
lint.ignore = [
"COM812", # The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule
# Ignore D rules for non-google docstring standard
"D203",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",]
[tool.ruff.lint.per-file-ignores]
"horde/sandbox.py" = ["F401"]
"server.py" = ["E402"]
[tool.black]
line-length = 140
include = '\.pyi?$'
[tool.pytest.ini_options]
testpaths = ["tests/unit"]
markers = [
"unit: Unit tests (in-process; Postgres + fakeredis via fixtures)",
"integration: Integration tests (in-process Flask client; real services via fixtures)",
"object_storage: Integration tests that require S3-compatible object storage",
]
[tool.coverage.run]
branch = true
source = ["horde"]
omit = [
"horde/templates/*",
"horde/data/*",
"horde/sandbox.py",
]
[tool.coverage.report]
show_missing = true
skip_covered = false
precision = 1
[tool.coverage.xml]
output = "coverage.xml"
[tool.mypy]
# Permissive baseline so existing untyped modules don't block adoption.
# Files explicitly listed under [[tool.mypy.overrides]] with strict = true
# form the "typed allowlist".
plugins = ["sqlalchemy.ext.mypy.plugin"]
python_version = "3.12"
ignore_missing_imports = true
follow_imports = "silent"
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
# --- Strict allowlist ---
# Rule of thumb: a module joins this list when its public surface is fully
# typed AND mypy passes strict on it.
[[tool.mypy.overrides]]
module = [
"horde.telemetry",
"horde.metrics",
"horde.request_scheduling",
"horde.classes.base.kudos",
"horde.database.kudos_counters",
"horde.database.kudos_db",
"horde.database.kudos_ledger",
"horde.database.kudos_legacy_projection",
"horde.database.kudos_reconciliation",
"horde.database.kudos_reservations",
"horde.database.kudos_retention",
"horde.kudos_archive",
"horde.sampler_constraints",
"horde.sampler_work_policy",
]
strict = true