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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- run: uv sync
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run python scripts/license_header.py --check

test:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
AG Tech API Template
Copyright 2026 AG Technology Group LLC

This product includes software developed by AG Technology Group LLC.
3 changes: 3 additions & 0 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import asyncio
from logging.config import fileConfig

Expand Down
3 changes: 3 additions & 0 deletions alembic/versions/af85030565fd_initial_schema.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""initial schema

Revision ID: af85030565fd
Expand Down
3 changes: 3 additions & 0 deletions alembic/versions/b3c7a1d9e2f4_add_user_role_column.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""add user role column

Revision ID: b3c7a1d9e2f4
Expand Down
3 changes: 3 additions & 0 deletions alembic/versions/c4e8f2a1b5d7_add_user_name_column.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""add user name column

Revision ID: c4e8f2a1b5d7
Expand Down
3 changes: 3 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

3 changes: 3 additions & 0 deletions app/analytics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""Analytics event abstraction with a pluggable backend."""

from __future__ import annotations
Expand Down
3 changes: 3 additions & 0 deletions app/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from app.auth.roles import UserRole, require_role
from app.auth.users import auth_backend, current_active_user, fastapi_users

Expand Down
3 changes: 3 additions & 0 deletions app/auth/backend.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from fastapi_users.authentication import AuthenticationBackend, CookieTransport, JWTStrategy

from app.config import settings
Expand Down
3 changes: 3 additions & 0 deletions app/auth/refresh.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import uuid
from datetime import UTC, datetime, timedelta

Expand Down
3 changes: 3 additions & 0 deletions app/auth/roles.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from enum import StrEnum

from fastapi import Depends, HTTPException, status
Expand Down
3 changes: 3 additions & 0 deletions app/auth/security_logging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from enum import StrEnum

import structlog
Expand Down
3 changes: 3 additions & 0 deletions app/auth/users.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from collections.abc import AsyncGenerator
from uuid import UUID

Expand Down
3 changes: 3 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from pydantic import model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

Expand Down
3 changes: 3 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from collections.abc import AsyncGenerator

from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
Expand Down
3 changes: 3 additions & 0 deletions app/features.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""Environment-variable-backed feature flags."""

from __future__ import annotations
Expand Down
3 changes: 3 additions & 0 deletions app/logging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""Structured logging configuration using structlog."""

import logging
Expand Down
3 changes: 3 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import time
import uuid

Expand Down
3 changes: 3 additions & 0 deletions app/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from app.models.note import Note
from app.models.refresh_token import RefreshToken
from app.models.user import User
Expand Down
3 changes: 3 additions & 0 deletions app/models/note.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import uuid
from datetime import datetime

Expand Down
3 changes: 3 additions & 0 deletions app/models/refresh_token.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import uuid
from datetime import datetime

Expand Down
3 changes: 3 additions & 0 deletions app/models/user.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from fastapi_users.db import SQLAlchemyBaseUserTableUUID
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
Expand Down
3 changes: 3 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from app.routers.admin import router as admin_router
from app.routers.notes import router as notes_router

Expand Down
3 changes: 3 additions & 0 deletions app/routers/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, status
Expand Down
3 changes: 3 additions & 0 deletions app/routers/auth_refresh.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import UUID

from fastapi import APIRouter, Cookie, Depends, Response
Expand Down
3 changes: 3 additions & 0 deletions app/routers/notes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, status
Expand Down
3 changes: 3 additions & 0 deletions app/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from app.schemas.note import NoteCreate, NoteRead, NoteUpdate
from app.schemas.user import UserCreate, UserRead, UserUpdate

Expand Down
3 changes: 3 additions & 0 deletions app/schemas/note.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from datetime import datetime
from uuid import UUID

Expand Down
3 changes: 3 additions & 0 deletions app/schemas/user.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import UUID

from fastapi_users import schemas
Expand Down
3 changes: 3 additions & 0 deletions app/telemetry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""OpenTelemetry auto-instrumentation for FastAPI."""

from __future__ import annotations
Expand Down
113 changes: 113 additions & 0 deletions scripts/license_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""Stamp or verify SPDX license headers on Python sources.

Usage:
python scripts/license_header.py --check verify every tracked .py file (CI)
python scripts/license_header.py --fix add the header where missing

Default mode is --check. Re-running is safe: a file that already carries an
SPDX identifier in its first lines is left untouched (idempotent), so the CI
check and a manual --fix can never disagree.
"""

from __future__ import annotations

import re
import subprocess
import sys
from pathlib import Path

HEADER = (
"# SPDX-FileCopyrightText: 2026 AG Technology Group LLC\n"
"# SPDX-License-Identifier: Apache-2.0\n"
)

GENERATED_BANNER = re.compile(r"@generated|do not edit|automatically generated", re.IGNORECASE)
ENCODING = re.compile(r"coding[:=]\s*[-\w.]+")


def tracked_py_files() -> list[str]:
result = subprocess.run(
["git", "ls-files", "*.py"],
capture_output=True,
text=True,
check=True,
)
return [line for line in result.stdout.splitlines() if line]


def classify(text: str) -> str:
"""Return "stamped", "skip", or "needs"."""
lines = text.splitlines()
if GENERATED_BANNER.search("\n".join(lines[:15])):
return "skip"
if "SPDX-License-Identifier" in "\n".join(lines[:10]):
return "stamped"
return "needs"


def apply_header(text: str) -> str:
"""Insert the header below a shebang/encoding line, above everything else."""
lines = text.splitlines(keepends=True)
prefix: list[str] = []
if lines and lines[0].startswith("#!"):
prefix.append(lines.pop(0))
if lines and lines[0].lstrip().startswith("#") and ENCODING.search(lines[0]):
prefix.append(lines.pop(0))
while lines and lines[0].strip() == "":
lines.pop(0)
body = "".join(lines)
separator = "\n" if body else ""
return "".join(prefix) + HEADER + separator + body


def main() -> int:
args = sys.argv[1:]
fix = "--fix" in args
explicit = [a for a in args if not a.startswith("--")]
files = explicit or tracked_py_files()

stamped: list[str] = []
already: list[str] = []
skipped: list[str] = []
missing: list[str] = []

for name in files:
path = Path(name)
try:
text = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
continue
status = classify(text)
if status == "skip":
skipped.append(name)
elif status == "stamped":
already.append(name)
elif fix:
path.write_text(apply_header(text), encoding="utf-8")
stamped.append(name)
else:
missing.append(name)

if fix:
print(
f"license-header: stamped {len(stamped)}, already {len(already)}, skipped {len(skipped)}"
)
for name in stamped:
print(f" + {name}")
elif missing:
print(f"license-header: {len(missing)} file(s) missing an SPDX header:", file=sys.stderr)
for name in missing:
print(f" - {name}", file=sys.stderr)
print('Run "uv run python scripts/license_header.py --fix" to add them.', file=sys.stderr)
return 1
else:
print(f"license-header: OK — {len(already)} stamped, {len(skipped)} skipped")
return 0


if __name__ == "__main__":
raise SystemExit(main())
3 changes: 3 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

3 changes: 3 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from collections.abc import AsyncGenerator
from uuid import uuid4

Expand Down
3 changes: 3 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

"""App-level / infrastructure route behaviour: security.txt and global rate limiting."""

from httpx import AsyncClient
Expand Down
3 changes: 3 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

import importlib
import os
from datetime import UTC, datetime, timedelta
Expand Down
3 changes: 3 additions & 0 deletions tests/test_notes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import uuid4

from httpx import AsyncClient
Expand Down
3 changes: 3 additions & 0 deletions tests/test_roles.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2026 AG Technology Group LLC
# SPDX-License-Identifier: Apache-2.0

from uuid import uuid4

from httpx import AsyncClient
Expand Down