Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 38 additions & 26 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,72 @@ updates:
commit-message:
prefix: "ci(github)"
schedule:
interval: "weekly"
day: "monday"
interval: "monthly"
time: "08:00"
open-pull-requests-limit: 1
cooldown:
default-days: 7
groups:
actions:
patterns:
- "*"
- package-ecosystem: "uv"
target-branch: "main"
# Use the default branch so this configuration also applies to security updates.
directory: "/"
labels:
- "dependencies"
- "type: misc"
commit-message:
prefix: "build(deps)"
schedule:
interval: "daily"
interval: "monthly"
time: "08:00"
# Keep in sync with group patterns below; this list gates version updates.
# These limits and cooldown affect version updates only; security fixes stay immediate.
open-pull-requests-limit: 2
cooldown:
default-days: 7
# Avoid raising minimum requirements when the existing range already permits an update.
versioning-strategy: "increase-if-necessary"
# SemVer filtering here affects version updates only; major security fixes remain eligible.
# Match every package so vulnerable transitive dependencies remain eligible.
allow:
- dependency-name: "PyJWT"
- dependency-name: "passlib"
- dependency-name: "bcrypt"
- dependency-name: "ruff"
- dependency-name: "ty"
- dependency-name: "prek"
- dependency-name: "types-*"
- dependency-name: "sqlalchemy-stubs"
- dependency-name: "posthog"
- dependency-name: "sentry-sdk"
- dependency-name: "prometheus-fastapi-instrumentator"
- dependency-name: "*"
update-types:
- "version-update:semver-minor"
- "version-update:semver-patch"
groups:
security:
applies-to: "security-updates"
patterns:
- "PyJWT"
- "passlib"
- "bcrypt"
- "*"
quality:
applies-to: "version-updates"
patterns:
- "ruff"
- "ty"
- "prek"
- "types-*"
- "sqlalchemy-stubs"
observability:
python:
applies-to: "version-updates"
patterns:
- "posthog"
- "sentry-sdk"
- "prometheus-fastapi-instrumentator"
- package-ecosystem: "docker"
- "*"
# Compose manifests live at the root; Dockerfiles live under src/.
# Update uv in the Dockerfiles together with every workflow's UV_VERSION manually.
- package-ecosystem: "docker-compose"
directory: "/"
schedule:
interval: "daily"
interval: "monthly"
time: "08:00"
open-pull-requests-limit: 1
cooldown:
default-days: 7
allow:
- dependency-name: "ghcr.io/astral-sh/uv"
- dependency-name: "localstack/localstack"
update-types:
- "version-update:semver-minor"
- "version-update:semver-patch"
groups:
localstack:
patterns:
- "localstack/localstack"
5 changes: 2 additions & 3 deletions client/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ test = [
"types-requests>=2.33.0.20260712",
]
docs = [
"sphinx>=3.0.0,!=3.5.0",
"sphinx>=5.3.0",
"sphinxemoji>=0.1.8",
"sphinx-copybutton>=0.3.1",
"docutils<0.18",
# cf. https://github.com/readthedocs/readthedocs.org/issues/9038
"Jinja2<3.1",
"Jinja2>=3.1.6,<4.0.0",
"furo>=2022.3.4",
]

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ server = [
"pydantic>=2.0.0,<3.0.0",
"pydantic-settings>=2.15.0,<3.0.0",
"requests>=2.32.0,<3.0.0",
"PyJWT>=2.12.0,<3.0.0",
"PyJWT>=2.15.0,<3.0.0",
"bcrypt==5.0.0",
"uvicorn>=0.11.1,<1.0.0",
"asyncpg>=0.25.0,<1.0.0",
Expand Down
11 changes: 9 additions & 2 deletions src/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import socket
from typing import Union

from pydantic import field_validator, model_validator
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

__all__ = ["settings"]
Expand Down Expand Up @@ -41,11 +41,18 @@ def sqlachmey_uri(cls, v: str) -> str:
return v

# Security
JWT_SECRET: str = os.environ.get("JWT_SECRET") or secrets.token_urlsafe(32)
JWT_SECRET: str = Field(default_factory=lambda: secrets.token_urlsafe(32))
JWT_EXPIRE_MINUTES: int = 60
JWT_UNLIMITED: int = 60 * 24 * 365
JWT_ALGORITHM: str = "HS256"

@field_validator("JWT_SECRET")
@classmethod
def jwt_secret_can_be_blank(cls, v: str) -> str:
# Compose passes an empty value when no secret is configured. Normalize it
# after settings loading so it cannot replace the generated default.
return v or secrets.token_urlsafe(32)

# DB conversion
MAX_BOXES_PER_DETECTION: int = 5
DECIMALS_PER_COORD: int = 3
Expand Down
24 changes: 24 additions & 0 deletions src/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
import logging
import secrets

import pytest
from pydantic import ValidationError

from app.core.config import Settings


@pytest.mark.parametrize("secret", [None, ""])
def test_settings_generate_jwt_secret_when_unset_or_empty(monkeypatch, secret):
if secret is None:
monkeypatch.delenv("JWT_SECRET", raising=False)
else:
monkeypatch.setenv("JWT_SECRET", secret)

first = Settings().JWT_SECRET
second = Settings().JWT_SECRET
assert len(first) >= 32
assert len(second) >= 32
assert first != second


def test_settings_preserve_configured_jwt_secret(monkeypatch):
secret = secrets.token_urlsafe(32)
monkeypatch.setenv("JWT_SECRET", secret)
first = Settings().JWT_SECRET
second = Settings().JWT_SECRET
assert first == secret
assert second == secret


@pytest.mark.parametrize(
"name",
["SEQUENCE_RELAXATION_SECONDS", "SEQUENCE_MIN_INTERVAL_SECONDS", "SEQUENCE_CONTINUITY_SECONDS"],
Expand Down
30 changes: 15 additions & 15 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading