feat: integrate security audits into pre-commit and CI/CD with backend pip-audit migration #74

Merged
darkhelm merged 5 commits from feat/security-audit-precommit-cicd into main 2026-07-13 22:20:10 -04:00
15 changed files with 519 additions and 69 deletions

View File

@@ -697,10 +697,74 @@ jobs:
- *failure_diagnostics_step
frontend-audit:
name: Frontend Dependency Audit
runs-on: ubuntu-act
timeout-minutes: 15
needs: build_cicd
steps:
- *identify_runner_step
- *configure_registry_host_step
- *ensure_cicd_image_step
- name: Run frontend dependency audit
env:
HEAD_SHA: ${{ needs.build_cicd.outputs.head_sha }}
run: |
set -o pipefail
LOG_FILE="$(mktemp)"
docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" bash -c "
cd /workspace &&
uv --directory backend run poe audit-frontend
" 2>&1 | tee "${LOG_FILE}"
TEST_STATUS=${PIPESTATUS[0]}
if [ "${TEST_STATUS}" -ne 0 ]; then
echo "❌ Frontend dependency audit failed (exit=${TEST_STATUS})"
echo "--- Last 200 lines of frontend audit output ---"
tail -n 200 "${LOG_FILE}" || true
exit "${TEST_STATUS}"
fi
- *failure_diagnostics_step
backend-audit:
name: Backend Dependency Audit
runs-on: ubuntu-act
timeout-minutes: 15
needs: build_cicd
steps:
- *identify_runner_step
- *configure_registry_host_step
- *ensure_cicd_image_step
- name: Run backend dependency audit
env:
HEAD_SHA: ${{ needs.build_cicd.outputs.head_sha }}
run: |
set -o pipefail
LOG_FILE="$(mktemp)"
docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" bash -c "
cd /workspace &&
uv --directory backend run poe audit-backend
" 2>&1 | tee "${LOG_FILE}"
TEST_STATUS=${PIPESTATUS[0]}
if [ "${TEST_STATUS}" -ne 0 ]; then
echo "❌ Backend dependency audit failed (exit=${TEST_STATUS})"
echo "--- Last 200 lines of backend audit output ---"
tail -n 200 "${LOG_FILE}" || true
exit "${TEST_STATUS}"
fi
- *failure_diagnostics_step
cicd-tests-complete:
name: CICD Tests Complete
runs-on: ubuntu-act
needs: [backend-tests, precommit-tests, frontend-tests, xdoctest]
needs: [backend-tests, precommit-tests, frontend-tests, xdoctest, frontend-audit, backend-audit]
if: always()
steps:
- name: Confirm all source lanes passed
@@ -710,13 +774,17 @@ jobs:
precommit_status="${{ needs['precommit-tests'].result }}"
frontend_status="${{ needs['frontend-tests'].result }}"
xdoctest_status="${{ needs.xdoctest.result }}"
frontend_audit_status="${{ needs['frontend-audit'].result }}"
backend_audit_status="${{ needs['backend-audit'].result }}"
echo "backend-tests=${backend_status}"
echo "precommit-tests=${precommit_status}"
echo "frontend-tests=${frontend_status}"
echo "xdoctest=${xdoctest_status}"
echo "frontend-audit=${frontend_audit_status}"
echo "backend-audit=${backend_audit_status}"
if [ "${backend_status}" != "success" ] || [ "${precommit_status}" != "success" ] || [ "${frontend_status}" != "success" ] || [ "${xdoctest_status}" != "success" ]; then
if [ "${backend_status}" != "success" ] || [ "${precommit_status}" != "success" ] || [ "${frontend_status}" != "success" ] || [ "${xdoctest_status}" != "success" ] || [ "${frontend_audit_status}" != "success" ] || [ "${backend_audit_status}" != "success" ]; then
echo "❌ One or more CICD source lanes failed"
exit 1
fi

View File

@@ -103,6 +103,7 @@ jobs:
env:
# Prefer dedicated Renovate token, then fall back to existing CI tokens.
RENOVATE_TOKEN_SECRET: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
ACTIONS_TRIGGER_TOKEN: ${{ secrets.ACTIONS_TRIGGER_TOKEN }}
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
RENOVATE_DRY_RUN: ${{ inputs.dry_run }}
@@ -120,6 +121,7 @@ jobs:
RENOVATE_BRANCH_CONCURRENT_LIMIT: "5"
RENOVATE_NODE_ARGS: --max-old-space-size=768
RENOVATE_OSV_VULNERABILITY_ALERTS: "false"
RENOVATE_FETCH_CHANGELOGS: "off"
LOG_LEVEL: debug
run: |
set -euo pipefail
@@ -288,7 +290,18 @@ jobs:
SELECTED_TOKEN_SOURCE="${SELECTED_TOKEN_RESULT%%:*}"
SELECTED_TOKEN="${SELECTED_TOKEN_RESULT#*:}"
export RENOVATE_TOKEN="${SELECTED_TOKEN}"
export RENOVATE_HOST_RULES="[{\"matchHost\":\"${ENDPOINT_AUTHORITY}\",\"token\":\"${SELECTED_TOKEN}\",\"authType\":\"Token-Only\"}]"
RENOVATE_HOST_RULES_PAYLOAD="[{\"matchHost\":\"${ENDPOINT_AUTHORITY}\",\"token\":\"${SELECTED_TOKEN}\",\"authType\":\"Token-Only\"},{\"matchHost\":\"registry.npmjs.org\",\"timeout\":90000,\"concurrentRequestLimit\":2},{\"matchHost\":\"pypi.org\",\"timeout\":90000,\"concurrentRequestLimit\":2},{\"matchHost\":\"files.pythonhosted.org\",\"timeout\":90000,\"concurrentRequestLimit\":2},{\"matchHost\":\"mcr.microsoft.com\",\"timeout\":90000,\"concurrentRequestLimit\":2}]"
# Authenticate GitHub datasource calls to avoid anonymous GraphQL rate limiting.
if [ -n "${RENOVATE_GITHUB_COM_TOKEN:-}" ]; then
RENOVATE_HOST_RULES_PAYLOAD="${RENOVATE_HOST_RULES_PAYLOAD%]}"
RENOVATE_HOST_RULES_PAYLOAD="${RENOVATE_HOST_RULES_PAYLOAD},{\"matchHost\":\"api.github.com\",\"hostType\":\"github\",\"token\":\"${RENOVATE_GITHUB_COM_TOKEN}\"}]"
echo "✓ Using RENOVATE_GITHUB_COM_TOKEN for GitHub datasource requests"
else
echo "⚠ RENOVATE_GITHUB_COM_TOKEN is not set; GitHub datasource may hit anonymous rate limits"
fi
export RENOVATE_HOST_RULES="${RENOVATE_HOST_RULES_PAYLOAD}"
# Rewrite external clone URLs to local-network git transport.
export RENOVATE_GIT_REWRITE_FROM="https://dogar.darkhelm.org/"
export RENOVATE_GIT_REWRITE_TO="http://${RENOVATE_GIT_USERNAME}:${SELECTED_TOKEN}@kankali.darkhelm.lan:3001/"
@@ -327,10 +340,21 @@ jobs:
unset RENOVATE_DRY_RUN
fi
# Manual workflow dispatch should execute immediately, regardless of repository schedule windows.
if [ "${GITHUB_EVENT_NAME:-}" = "workflow_dispatch" ]; then
export RENOVATE_IGNORE_SCHEDULE="true"
echo "✓ Manual run detected: ignoring Renovate schedule constraints"
else
unset RENOVATE_IGNORE_SCHEDULE
fi
run_renovate_with_endpoint() {
endpoint="$1"
enabled_managers="$2"
batch_label="$3"
export RENOVATE_ENDPOINT="${endpoint}"
echo "Running Renovate with endpoint: ${RENOVATE_ENDPOINT}"
echo "Running Renovate batch '${batch_label}' with endpoint: ${RENOVATE_ENDPOINT}"
echo "Enabled managers for '${batch_label}': ${enabled_managers}"
# shellcheck disable=SC2086
docker run --rm ${DOCKER_ADD_HOST_ARG} \
@@ -355,6 +379,8 @@ jobs:
-e RENOVATE_BRANCH_CONCURRENT_LIMIT \
-e RENOVATE_NODE_ARGS \
-e RENOVATE_OSV_VULNERABILITY_ALERTS \
-e RENOVATE_FETCH_CHANGELOGS \
-e RENOVATE_IGNORE_SCHEDULE \
--entrypoint /bin/sh \
"${RENOVATE_IMAGE}" \
-lc 'set -e
@@ -365,20 +391,32 @@ jobs:
git config --global --add "url.${RENOVATE_GIT_REWRITE_TO}.insteadOf" "https://x-access-token:${RENOVATE_TOKEN}@dogar.darkhelm.org/"
git config --global --add "url.${RENOVATE_GIT_REWRITE_TO}.insteadOf" "${RENOVATE_GIT_REWRITE_FROM}"
echo "Configured git dogar->kankali rewrite rules"
renovate --platform "${RENOVATE_PLATFORM}" --endpoint "${RENOVATE_ENDPOINT}" --git-url "${RENOVATE_GIT_URL}" --git-author "${RENOVATE_GIT_AUTHOR}" --onboarding="${RENOVATE_ONBOARDING}" --pr-concurrent-limit "${RENOVATE_PR_CONCURRENT_LIMIT}" --branch-concurrent-limit "${RENOVATE_BRANCH_CONCURRENT_LIMIT}" --osv-vulnerability-alerts="${RENOVATE_OSV_VULNERABILITY_ALERTS}" DarkHelm.org/plex-playlist'
renovate --platform "${RENOVATE_PLATFORM}" --endpoint "${RENOVATE_ENDPOINT}" --git-url "${RENOVATE_GIT_URL}" --git-author "${RENOVATE_GIT_AUTHOR}" --onboarding="${RENOVATE_ONBOARDING}" --pr-concurrent-limit "${RENOVATE_PR_CONCURRENT_LIMIT}" --branch-concurrent-limit "${RENOVATE_BRANCH_CONCURRENT_LIMIT}" --osv-vulnerability-alerts="${RENOVATE_OSV_VULNERABILITY_ALERTS}" --enabled-managers "${enabled_managers}" DarkHelm.org/plex-playlist'
}
if run_renovate_with_endpoint "${BASE_ENDPOINT}" 2>&1 | tee /tmp/renovate.log; then
:
else
primary_status=$?
if [ "${primary_status}" -eq 137 ] || grep -qE '(^|[[:space:]])Killed($|[[:space:]])|exitcode '\''137'\''' /tmp/renovate.log; then
echo "❌ Renovate terminated by OOM/kill signal on primary endpoint; skipping endpoint retry"
exit "${primary_status}"
run_batch_with_fallback() {
enabled_managers="$1"
batch_label="$2"
if run_renovate_with_endpoint "${BASE_ENDPOINT}" "${enabled_managers}" "${batch_label}" 2>&1 | tee -a /tmp/renovate.log; then
return 0
fi
echo "⚠ Renovate run failed with primary endpoint. Retrying with API endpoint form..."
run_renovate_with_endpoint "${API_ENDPOINT}" 2>&1 | tee /tmp/renovate.log
fi
primary_status=$?
if [ "${primary_status}" -eq 137 ]; then
echo "❌ Renovate batch '${batch_label}' terminated by OOM/kill signal on primary endpoint; skipping endpoint retry"
return "${primary_status}"
fi
echo "⚠ Renovate batch '${batch_label}' failed with primary endpoint. Retrying with API endpoint form..."
run_renovate_with_endpoint "${API_ENDPOINT}" "${enabled_managers}" "${batch_label}" 2>&1 | tee -a /tmp/renovate.log
}
: > /tmp/renovate.log
# Run low-memory batches sequentially to keep peak usage below constrained runner limits.
run_batch_with_fallback "docker-compose,dockerfile,github-actions,pep621,custom.regex" "core"
run_batch_with_fallback "npm" "npm"
echo "✓ Renovate execution completed"

View File

@@ -70,6 +70,14 @@ repos:
types: [python]
pass_filenames: false
- id: bandit
name: bandit
entry: bash -c 'cd backend && uv run bandit -q -r src/backend'
language: system
files: ^backend/src/backend/.*\.py$
types: [python]
pass_filenames: false
# Custom hook to enforce no types in docstrings
- repo: local
hooks:

View File

@@ -1,4 +1,4 @@
ARG PLAYWRIGHT_BASE_IMAGE=mcr.microsoft/playwright:v1.56.1-jammy
ARG PLAYWRIGHT_BASE_IMAGE=mcr.microsoft.com/playwright:v1.56.1-jammy
FROM ${PLAYWRIGHT_BASE_IMAGE}
WORKDIR /workspace/frontend

View File

@@ -0,0 +1,4 @@
[project]
id = plex-playlist
url = /codebases/plex-playlist/findings
name = plex-playlist

View File

@@ -6,21 +6,23 @@ requires = ["hatchling"]
dev = [
"ruff==0.14.1",
"pyright==1.1.410",
"pydoclint==0.8.3",
"pytest==8.4.2",
"pytest-asyncio==1.2.0",
"pydoclint==0.9.1",
"pytest==9.1.1",
"pytest-asyncio==1.4.0",
"pytest-cov==7.0.0",
"typeguard==4.4.4",
"httpx==0.28.1", # For testing async HTTP calls
"pytest-mock==3.15.1",
# File format and linting tools
"pre-commit==4.3.0", # For running pre-commit hooks in CI
"pre-commit==4.6.0", # For running pre-commit hooks in CI
"pyyaml==6.0.3",
"yamllint==1.37.1",
"toml-sort==0.24.3",
"language-formatters-pre-commit-hooks==2.15.0", # For pretty-format-toml
"xdoctest==1.3.0", # For doctest support
"poethepoet==0.41.0" # Task runner for unified development workflows
"language-formatters-pre-commit-hooks==2.16.0", # For pretty-format-toml
"xdoctest==1.3.0", # For doctest support # Task runner for unified development workflows
"poethepoet==0.41.0",
"bandit>=1.9.4",
"pip-audit>=2.9.0"
]
[project]
@@ -33,10 +35,10 @@ classifiers = [
"Programming Language :: Python :: 3.14"
]
dependencies = [
"fastapi==0.120.2",
"fastapi==0.139.0",
"sqlalchemy==2.0.44",
"psycopg[binary]==3.2.12",
"uvicorn==0.38.0"
"uvicorn==0.51.0"
]
description = "Backend service for Plex playlist management"
keywords = ["plex", "playlist", "media", "management"]
@@ -83,6 +85,8 @@ path = "src/backend/__init__.py"
[tool.poe]
[tool.poe.tasks]
audit-backend = {shell = "uv run pip-audit", help = "Scan backend dependencies for known vulnerabilities"}
audit-frontend = {shell = "cd ../frontend && corepack yarn npm audit", help = "Scan frontend dependencies for known vulnerabilities"}
build-cicd = {shell = "./scripts/build-cicd-local.sh", help = "Build both CI/CD images"}
# === Docker CI/CD Image Tasks ===
build-cicd-base = {shell = "./scripts/build-cicd-local.sh --base-only", help = "Build CI/CD base image only"}
@@ -105,7 +109,7 @@ clean = [
]
deps-check = [
{shell = "cd backend && uv pip check"},
{shell = "cd frontend && yarn audit"}
{shell = "cd frontend && corepack yarn npm audit"}
]
# === Dependency Management ===
deps-install = [

View File

@@ -11,6 +11,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.main import app, compatibility_status, get_api_session
pytestmark = pytest.mark.integration
client = TestClient(app)

360
backend/uv.lock generated
View File

@@ -33,6 +33,48 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z"}
]
[[package]]
dependencies = [
{name = "colorama", marker = "sys_platform == 'win32'"},
{name = "pyyaml"},
{name = "rich"},
{name = "stevedore"}
]
name = "bandit"
sdist = {url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z"}
source = {registry = "https://pypi.org/simple"}
version = "1.9.4"
wheels = [
{url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z"}
]
[[package]]
name = "boolean-py"
sdist = {url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z"}
source = {registry = "https://pypi.org/simple"}
version = "5.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z"}
]
[[package]]
dependencies = [
{name = "msgpack"},
{name = "requests"}
]
name = "cachecontrol"
sdist = {url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.14.4"
wheels = [
{url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z"}
]
[package.optional-dependencies]
filecache = [
{name = "filelock"}
]
[[package]]
name = "certifi"
sdist = {url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z"}
@@ -153,6 +195,30 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z"}
]
[[package]]
dependencies = [
{name = "license-expression"},
{name = "packageurl-python"},
{name = "py-serializable"},
{name = "sortedcontainers"}
]
name = "cyclonedx-python-lib"
sdist = {url = "https://files.pythonhosted.org/packages/75/c9/5d0ccdd19bc7d8ab803b90695c1706aa2ea8529685d18e682dc2524d2630/cyclonedx_python_lib-11.11.0.tar.gz", hash = "sha256:4b3194db72b613717f2912447e67ab618c75ff7dcac6c4af3c0e9e1ac617c102", size = 1442983, upload-time = "2026-06-17T11:57:49.055Z"}
source = {registry = "https://pypi.org/simple"}
version = "11.11.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/22/f3/56ccb2884aaa3db5622368e5191a3384b15f35392aa93df8b2f508c660d2/cyclonedx_python_lib-11.11.0-py3-none-any.whl", hash = "sha256:3049fc83e06a059b5c5907a527625a8ed5073caab10607ed4c9e5503b590fd44", size = 528689, upload-time = "2026-06-17T11:57:47.358Z"}
]
[[package]]
name = "defusedxml"
sdist = {url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.7.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z"}
]
[[package]]
name = "distlib"
sdist = {url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z"}
@@ -164,11 +230,11 @@ wheels = [
[[package]]
name = "docstring-parser-fork"
sdist = {url = "https://files.pythonhosted.org/packages/66/bf/27f9cab2f0cd1d17a4420572088bbc19f36d726fbcf165edf226a8926dbc/docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c", size = 34551, upload-time = "2025-09-07T17:27:38.272Z"}
sdist = {url = "https://files.pythonhosted.org/packages/04/ce/48b17c6c24d0af112f73721a90fac245450e548b82b53169260720f60d07/docstring_parser_fork-0.0.16.tar.gz", hash = "sha256:9ecf861ceab8b87d1297a71001c15a1956ad02e87434ee6473643a5988094f7c", size = 36360, upload-time = "2026-07-03T07:27:46.668Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.0.14"
version = "0.0.16"
wheels = [
{url = "https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af", size = 43063, upload-time = "2025-09-07T17:27:37.012Z"}
{url = "https://files.pythonhosted.org/packages/c1/69/427c34e61827818942b48ececd7c892b8f58ba4ce4cfc89ba9fd8dbe8a8d/docstring_parser_fork-0.0.16-py3-none-any.whl", hash = "sha256:c024c97af582d6acf85a14f0b332b3a2ab08626648d251c25e47191f33064386", size = 25253, upload-time = "2026-07-03T07:27:45.591Z"}
]
[[package]]
@@ -176,14 +242,15 @@ dependencies = [
{name = "annotated-doc"},
{name = "pydantic"},
{name = "starlette"},
{name = "typing-extensions"}
{name = "typing-extensions"},
{name = "typing-inspection"}
]
name = "fastapi"
sdist = {url = "https://files.pythonhosted.org/packages/a0/fb/79e556bc8f9d360e5cc2fa7364a7ad6bda6f1736938b43a2791fa8baee7b/fastapi-0.120.2.tar.gz", hash = "sha256:4c5ab43e2a90335bbd8326d1b659eac0f3dbcc015e2af573c4f5de406232c4ac", size = 338684, upload-time = "2025-10-29T13:47:35.802Z"}
sdist = {url = "https://files.pythonhosted.org/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.120.2"
version = "0.139.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/81/cc/1c33d05f62c9349bb80dfe789cc9a7409bdfb337a63fa347fd651d25294a/fastapi-0.120.2-py3-none-any.whl", hash = "sha256:bedcf2c14240e43d56cb9a339b32bcf15104fe6b5897c0222603cb7ec416c8eb", size = 108383, upload-time = "2025-10-29T13:47:32.978Z"}
{url = "https://files.pythonhosted.org/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z"}
]
[[package]]
@@ -312,15 +379,77 @@ dependencies = [
{name = "packaging"},
{name = "requests"},
{name = "ruamel-yaml"},
{name = "setuptools"},
{name = "toml-sort"}
]
name = "language-formatters-pre-commit-hooks"
sdist = {url = "https://files.pythonhosted.org/packages/f9/2e/4a162be0994d54f4b7db2ab2620d96ecbfb85bfacf3aa22a9f3737f52bdb/language_formatters_pre_commit_hooks-2.15.0.tar.gz", hash = "sha256:fc724bf8a643c1abefd81f3f3fe0a71489eedcfe3aaae28114f36bbb03a80b7c", size = 27160, upload-time = "2025-06-25T23:02:21.146Z"}
sdist = {url = "https://files.pythonhosted.org/packages/f4/44/76ee99d0bf8cfe0cd421ce0fbf44c14b823bfa7eac66dacd393f371edc2f/language_formatters_pre_commit_hooks-2.16.0.tar.gz", hash = "sha256:e21db1fd2c7418397e25234ca94fb1e9311b0f297f53fac8c906d8c3af018abb", size = 30858, upload-time = "2026-01-11T11:44:39.97Z"}
source = {registry = "https://pypi.org/simple"}
version = "2.15.0"
version = "2.16.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/d5/b8/390c065913ad7dcc73c38460a9fe322ef16c677f6a01b0244418f59c6d1c/language_formatters_pre_commit_hooks-2.15.0-py2.py3-none-any.whl", hash = "sha256:7cce28ae8e048c2b8b22c5ad48f368c7993486e9d91788cf7216621658ebb187", size = 28154, upload-time = "2025-06-25T23:02:20.031Z"}
{url = "https://files.pythonhosted.org/packages/ff/fb/e845de2f41b7c2b99498ed867f0a9946c6919e6f6e6f7e5fe1110b28bb58/language_formatters_pre_commit_hooks-2.16.0-py2.py3-none-any.whl", hash = "sha256:907971fd574a9bb70182698f33aaad902e8638868389d9aa7b9a11feb975b0b0", size = 28149, upload-time = "2026-01-11T11:44:38.634Z"}
]
[[package]]
dependencies = [
{name = "boolean-py"}
]
name = "license-expression"
sdist = {url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z"}
source = {registry = "https://pypi.org/simple"}
version = "30.4.4"
wheels = [
{url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z"}
]
[[package]]
dependencies = [
{name = "mdurl"}
]
name = "markdown-it-py"
sdist = {url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z"}
source = {registry = "https://pypi.org/simple"}
version = "4.2.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z"}
]
[[package]]
name = "mdurl"
sdist = {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.1.2"
wheels = [
{url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z"}
]
[[package]]
name = "msgpack"
sdist = {url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z"}
source = {registry = "https://pypi.org/simple"}
version = "1.2.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z"},
{url = "https://files.pythonhosted.org/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z"},
{url = "https://files.pythonhosted.org/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z"},
{url = "https://files.pythonhosted.org/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z"},
{url = "https://files.pythonhosted.org/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z"},
{url = "https://files.pythonhosted.org/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z"},
{url = "https://files.pythonhosted.org/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z"},
{url = "https://files.pythonhosted.org/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z"},
{url = "https://files.pythonhosted.org/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z"},
{url = "https://files.pythonhosted.org/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z"},
{url = "https://files.pythonhosted.org/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z"},
{url = "https://files.pythonhosted.org/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z"},
{url = "https://files.pythonhosted.org/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z"},
{url = "https://files.pythonhosted.org/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z"},
{url = "https://files.pythonhosted.org/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z"},
{url = "https://files.pythonhosted.org/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z"},
{url = "https://files.pythonhosted.org/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z"},
{url = "https://files.pythonhosted.org/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z"},
{url = "https://files.pythonhosted.org/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z"},
{url = "https://files.pythonhosted.org/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z"},
{url = "https://files.pythonhosted.org/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z"},
{url = "https://files.pythonhosted.org/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z"}
]
[[package]]
@@ -332,6 +461,15 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z"}
]
[[package]]
name = "packageurl-python"
sdist = {url = "https://files.pythonhosted.org/packages/f5/d6/3b5a4e3cfaef7a53869a26ceb034d1ff5e5c27c814ce77260a96d50ab7bb/packageurl_python-0.17.6.tar.gz", hash = "sha256:1252ce3a102372ca6f86eb968e16f9014c4ba511c5c37d95a7f023e2ca6e5c25", size = 50618, upload-time = "2025-11-24T15:20:17.998Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.17.6"
wheels = [
{url = "https://files.pythonhosted.org/packages/b1/2f/c7277b7615a93f51b5fbc1eacfc1b75e8103370e786fd8ce2abf6e5c04ab/packageurl_python-0.17.6-py3-none-any.whl", hash = "sha256:31a85c2717bc41dd818f3c62908685ff9eebcb68588213745b14a6ee9e7df7c9", size = 36776, upload-time = "2025-11-24T15:20:16.962Z"}
]
[[package]]
name = "packaging"
sdist = {url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z"}
@@ -359,6 +497,61 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z"}
]
[[package]]
name = "pip"
sdist = {url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z"}
source = {registry = "https://pypi.org/simple"}
version = "26.1.2"
wheels = [
{url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z"}
]
[[package]]
dependencies = [
{name = "pip"}
]
name = "pip-api"
sdist = {url = "https://files.pythonhosted.org/packages/b9/f1/ee85f8c7e82bccf90a3c7aad22863cc6e20057860a1361083cd2adacb92e/pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625", size = 123017, upload-time = "2024-07-09T20:32:30.641Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.0.34"
wheels = [
{url = "https://files.pythonhosted.org/packages/91/f7/ebf5003e1065fd00b4cbef53bf0a65c3d3e1b599b676d5383ccb7a8b88ba/pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb", size = 120369, upload-time = "2024-07-09T20:32:29.099Z"}
]
[[package]]
dependencies = [
{name = "cachecontrol", extra = ["filecache"]},
{name = "cyclonedx-python-lib"},
{name = "packaging"},
{name = "pip-api"},
{name = "pip-requirements-parser"},
{name = "platformdirs"},
{name = "requests"},
{name = "rich"},
{name = "tomli"},
{name = "tomli-w"}
]
name = "pip-audit"
sdist = {url = "https://files.pythonhosted.org/packages/66/a4/f21d5f0a0edabcbce31560b73c7c5a6f72ae87af4236fd1069c8f59a353d/pip_audit-2.10.1.tar.gz", hash = "sha256:1eb4565d19ebe5d48996f4b770b4d2b32887e12cb12cfa637f1a064011b55ffc", size = 54275, upload-time = "2026-06-10T22:17:01.744Z"}
source = {registry = "https://pypi.org/simple"}
version = "2.10.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/a3/a7/b0c504148114047bd1bc9d97447453c6850ca176bb2f3c0038835994e8b7/pip_audit-2.10.1-py3-none-any.whl", hash = "sha256:99ef3f600a317c1945f1e89e227ef26e1c2d618429b8bd3fa6f4f7c440c4611a", size = 62023, upload-time = "2026-06-10T22:17:00.309Z"}
]
[[package]]
dependencies = [
{name = "packaging"},
{name = "pyparsing"}
]
name = "pip-requirements-parser"
sdist = {url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359, upload-time = "2022-12-21T15:25:22.732Z"}
source = {registry = "https://pypi.org/simple"}
version = "32.0.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648, upload-time = "2022-12-21T15:25:21.046Z"}
]
[[package]]
name = "platformdirs"
sdist = {url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z"}
@@ -381,8 +574,10 @@ version = "0.1.0"
[package.dev-dependencies]
dev = [
{name = "bandit"},
{name = "httpx"},
{name = "language-formatters-pre-commit-hooks"},
{name = "pip-audit"},
{name = "poethepoet"},
{name = "pre-commit"},
{name = "pydoclint"},
@@ -401,22 +596,24 @@ dev = [
[package.metadata]
requires-dist = [
{name = "fastapi", specifier = "==0.120.2"},
{name = "fastapi", specifier = "==0.139.0"},
{name = "psycopg", extras = ["binary"], specifier = "==3.2.12"},
{name = "sqlalchemy", specifier = "==2.0.44"},
{name = "uvicorn", specifier = "==0.38.0"}
{name = "uvicorn", specifier = "==0.51.0"}
]
[package.metadata.requires-dev]
dev = [
{name = "bandit", specifier = ">=1.9.4"},
{name = "httpx", specifier = "==0.28.1"},
{name = "language-formatters-pre-commit-hooks", specifier = "==2.15.0"},
{name = "language-formatters-pre-commit-hooks", specifier = "==2.16.0"},
{name = "pip-audit", specifier = ">=2.9.0"},
{name = "poethepoet", specifier = "==0.41.0"},
{name = "pre-commit", specifier = "==4.3.0"},
{name = "pydoclint", specifier = "==0.8.3"},
{name = "pre-commit", specifier = "==4.6.0"},
{name = "pydoclint", specifier = "==0.9.1"},
{name = "pyright", specifier = "==1.1.410"},
{name = "pytest", specifier = "==8.4.2"},
{name = "pytest-asyncio", specifier = "==1.2.0"},
{name = "pytest", specifier = "==9.1.1"},
{name = "pytest-asyncio", specifier = "==1.4.0"},
{name = "pytest-cov", specifier = "==7.0.0"},
{name = "pytest-mock", specifier = "==3.15.1"},
{name = "pyyaml", specifier = "==6.0.3"},
@@ -458,11 +655,11 @@ dependencies = [
{name = "virtualenv"}
]
name = "pre-commit"
sdist = {url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z"}
sdist = {url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z"}
source = {registry = "https://pypi.org/simple"}
version = "4.3.0"
version = "4.6.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z"}
{url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z"}
]
[[package]]
@@ -498,6 +695,18 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/53/cf/10c3e95827a3ca8af332dfc471befec86e15a14dc83cee893c49a4910dad/psycopg_binary-3.2.12-cp314-cp314-win_amd64.whl", hash = "sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39", size = 3005787, upload-time = "2025-10-26T00:36:06.783Z"}
]
[[package]]
dependencies = [
{name = "defusedxml"}
]
name = "py-serializable"
sdist = {url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z"}
source = {registry = "https://pypi.org/simple"}
version = "2.1.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z"}
]
[[package]]
dependencies = [
{name = "annotated-types"},
@@ -549,11 +758,11 @@ dependencies = [
{name = "docstring-parser-fork"}
]
name = "pydoclint"
sdist = {url = "https://files.pythonhosted.org/packages/70/58/bb3c7edd2bd06b07d9bbb494e52e2d2d6d53405b166aac531b1a27cef972/pydoclint-0.8.3.tar.gz", hash = "sha256:0c69c0ed92c6f6b5aec2a14371bbd9e0a77980da8e2fa0f708092aa06b3b20b3", size = 186619, upload-time = "2025-11-26T06:53:43.361Z"}
sdist = {url = "https://files.pythonhosted.org/packages/4c/46/e5043a43a127627c4eb901f511787096fe2b50d27bdf5e36dcc21e502dec/pydoclint-0.9.1.tar.gz", hash = "sha256:eb699ecf95124b8d0b881d55cd53cccaa567172005d1223ec91a0259b51a5650", size = 206434, upload-time = "2026-07-03T08:17:08.879Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.8.3"
version = "0.9.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl", hash = "sha256:5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164", size = 79075, upload-time = "2025-11-26T06:53:42.129Z"}
{url = "https://files.pythonhosted.org/packages/bc/35/e2fa913ba2d692ccefbf2e21337fe3fd44200efd9a6ee1ba65766eff7d14/pydoclint-0.9.1-py3-none-any.whl", hash = "sha256:685b4a1c3c852045e4523b61d9c3f789672dfab3a454fe51a9e346c9e21dfcdb", size = 84702, upload-time = "2026-07-03T08:17:07.827Z"}
]
[[package]]
@@ -565,6 +774,15 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z"}
]
[[package]]
name = "pyparsing"
sdist = {url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z"}
source = {registry = "https://pypi.org/simple"}
version = "3.3.2"
wheels = [
{url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z"}
]
[[package]]
dependencies = [
{name = "nodeenv"},
@@ -587,11 +805,11 @@ dependencies = [
{name = "pygments"}
]
name = "pytest"
sdist = {url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z"}
sdist = {url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z"}
source = {registry = "https://pypi.org/simple"}
version = "8.4.2"
version = "9.1.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z"}
{url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z"}
]
[[package]]
@@ -599,11 +817,11 @@ dependencies = [
{name = "pytest"}
]
name = "pytest-asyncio"
sdist = {url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z"}
sdist = {url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z"}
source = {registry = "https://pypi.org/simple"}
version = "1.2.0"
version = "1.4.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z"}
{url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z"}
]
[[package]]
@@ -673,6 +891,19 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z"}
]
[[package]]
dependencies = [
{name = "markdown-it-py"},
{name = "pygments"}
]
name = "rich"
sdist = {url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z"}
source = {registry = "https://pypi.org/simple"}
version = "15.0.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z"}
]
[[package]]
name = "ruamel-yaml"
sdist = {url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z"}
@@ -708,15 +939,6 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z"}
]
[[package]]
name = "setuptools"
sdist = {url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z"}
source = {registry = "https://pypi.org/simple"}
version = "80.9.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z"}
]
[[package]]
name = "sniffio"
sdist = {url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z"}
@@ -726,6 +948,15 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z"}
]
[[package]]
name = "sortedcontainers"
sdist = {url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z"}
source = {registry = "https://pypi.org/simple"}
version = "2.4.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z"}
]
[[package]]
dependencies = [
{name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'"},
@@ -751,6 +982,15 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z"}
]
[[package]]
name = "stevedore"
sdist = {url = "https://files.pythonhosted.org/packages/d7/dd/04d56c2a5232358df41f3d0f0e31833d378b6c8ed7803a6b1b7867b0eba6/stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c", size = 514850, upload-time = "2026-07-02T11:38:08.509Z"}
source = {registry = "https://pypi.org/simple"}
version = "5.9.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z"}
]
[[package]]
dependencies = [
{name = "tomlkit"}
@@ -763,6 +1003,42 @@ wheels = [
{url = "https://files.pythonhosted.org/packages/0e/1c/f5a9a4f918ff0e1578fdc3f607a7ec704c5da1b13ddc290fd0aefa97ff2e/toml_sort-0.24.3-py3-none-any.whl", hash = "sha256:d70642f54827b4c17a6cde788dcb45d598c1eb9833544a2fcec5eb7b0b744ed4", size = 16538, upload-time = "2025-09-10T02:43:53.67Z"}
]
[[package]]
name = "tomli"
sdist = {url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z"}
source = {registry = "https://pypi.org/simple"}
version = "2.4.1"
wheels = [
{url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z"},
{url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z"},
{url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z"},
{url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z"},
{url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z"},
{url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z"},
{url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z"},
{url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z"},
{url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z"},
{url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z"},
{url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z"},
{url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z"},
{url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z"},
{url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z"},
{url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z"},
{url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z"},
{url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z"},
{url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z"},
{url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z"}
]
[[package]]
name = "tomli-w"
sdist = {url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z"}
source = {registry = "https://pypi.org/simple"}
version = "1.2.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z"}
]
[[package]]
name = "tomlkit"
sdist = {url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z"}
@@ -829,11 +1105,11 @@ dependencies = [
{name = "h11"}
]
name = "uvicorn"
sdist = {url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z"}
sdist = {url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z"}
source = {registry = "https://pypi.org/simple"}
version = "0.38.0"
version = "0.51.0"
wheels = [
{url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z"}
{url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z"}
]
[[package]]

View File

@@ -77,6 +77,9 @@ poe build-if-dockerfile-changed # Only build if Dockerfiles changed
## 🛠 Utility Tasks
```bash
poe audit-backend # Run backend pip-audit dependency audit
poe audit-frontend # Run frontend Yarn npm audit
poe deps-check # Run dependency checks across backend and frontend
poe deps-install # Install all dependencies (backend + frontend)
poe deps-update # Update all dependencies
poe clean # Clean build artifacts and caches

View File

@@ -4,12 +4,16 @@ import vue from 'eslint-plugin-vue';
import vueParser from 'vue-eslint-parser';
import jsdoc from 'eslint-plugin-jsdoc';
import tsdoc from 'eslint-plugin-tsdoc';
import security from 'eslint-plugin-security';
const securityRules = security.configs?.recommended?.rules ?? {};
export default [
// Ignore patterns (replaces .eslintignore)
{
ignores: [
'node_modules/',
'.yarn/',
'dist/',
'dist-ssr/',
'*.local',
@@ -29,9 +33,13 @@ export default [
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: {
security,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
...securityRules,
},
},
@@ -47,10 +55,12 @@ export default [
'@typescript-eslint': typescript,
jsdoc,
tsdoc,
security,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
...securityRules,
// TSDoc rules for TypeScript files
'tsdoc/syntax': 'error',
'jsdoc/require-description': 'error',
@@ -81,11 +91,13 @@ export default [
'@typescript-eslint': typescript,
jsdoc,
tsdoc,
security,
},
rules: {
...vue.configs['vue3-essential'].rules,
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
...securityRules,
// TSDoc rules for Vue files
'tsdoc/syntax': 'error',
'jsdoc/require-description': 'error',

View File

@@ -35,6 +35,7 @@
"@vue/tsconfig": "^0.5.0",
"eslint": "^9.33.0",
"eslint-plugin-jsdoc": "^50.0.0",
"eslint-plugin-security": "^4.0.1",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-vue": "^9.28.0",
"jsdom": "^23.0.0",

View File

@@ -1905,6 +1905,15 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-security@npm:^4.0.1":
version: 4.0.1
resolution: "eslint-plugin-security@npm:4.0.1"
dependencies:
safe-regex: "npm:^2.1.1"
checksum: 10c0/5fdb3be0a1e3707198169f64635e1d904e0e31f53f008bdf99738210bdd9df62ba098015e165401d0a9a01eb958fe0fc69af3980008664af3b88a9b71cde18ed
languageName: node
linkType: hard
"eslint-plugin-tsdoc@npm:^0.3.0":
version: 0.3.0
resolution: "eslint-plugin-tsdoc@npm:0.3.0"
@@ -3277,6 +3286,7 @@ __metadata:
"@vue/tsconfig": "npm:^0.5.0"
eslint: "npm:^9.33.0"
eslint-plugin-jsdoc: "npm:^50.0.0"
eslint-plugin-security: "npm:^4.0.1"
eslint-plugin-tsdoc: "npm:^0.3.0"
eslint-plugin-vue: "npm:^9.28.0"
jsdom: "npm:^23.0.0"
@@ -3382,6 +3392,15 @@ __metadata:
languageName: node
linkType: hard
"regexp-tree@npm:~0.1.1":
version: 0.1.27
resolution: "regexp-tree@npm:0.1.27"
bin:
regexp-tree: bin/regexp-tree
checksum: 10c0/f636f44b4a0d93d7d6926585ecd81f63e4ce2ac895bc417b2ead0874cd36b337dcc3d0fedc63f69bf5aaeaa4340f36ca7e750c9687cceaf8087374e5284e843c
languageName: node
linkType: hard
"require-from-string@npm:^2.0.2":
version: 2.0.2
resolution: "require-from-string@npm:2.0.2"
@@ -3547,6 +3566,15 @@ __metadata:
languageName: node
linkType: hard
"safe-regex@npm:^2.1.1":
version: 2.1.1
resolution: "safe-regex@npm:2.1.1"
dependencies:
regexp-tree: "npm:~0.1.1"
checksum: 10c0/53eb5d3ecf4b3c0954dff465eb179af4d2f5f77f74ba7b57489adbc4fa44454c3d391f37379cd28722d9ac6fa5b70be3f4645d4bd25df395fd99b934f6ec9265
languageName: node
linkType: hard
"safer-buffer@npm:>= 2.1.2 < 3.0.0":
version: 2.1.2
resolution: "safer-buffer@npm:2.1.2"

View File

@@ -96,6 +96,12 @@
"labels": ["major-update"],
"prPriority": 5,
"reviewersFromCodeOwners": true
},
{
"description": "Skip automatic updates for project Python runtime constraint",
"matchManagers": ["pep621"],
"matchPackageNames": ["python"],
"enabled": false
}
],
"customManagers": [

0
scripts/quick-renovate-check.sh Normal file → Executable file
View File

View File

@@ -34,5 +34,5 @@ docker run --rm \
-e PLAYWRIGHT_JUNIT_OUTPUT_FILE=/tmp/playwright-artifacts/playwright-results.xml \
-v "${ROOT_DIR}/frontend:/workspace/frontend" \
-v "${E2E_ARTIFACT_DIR}:/tmp/playwright-artifacts" \
mcr.microsoft/playwright:v1.56.1-jammy \
mcr.microsoft.com/playwright:v1.56.1-jammy \
bash -lc "cd /workspace/frontend && corepack enable && yarn install --immutable && yarn test:e2e --reporter=list --timeout=90000"