Metadata-Version: 2.4
Name: spaps-server-quickstart
Version: 0.6.6
Summary: Shared FastAPI/Celery scaffolding for Sweet Potato service backends.
Project-URL: Homepage, https://api.sweetpotato.dev
Project-URL: Repository, https://github.com/sweet-potato/spaps
Author-email: buildooor <buildooor@gmail.com>
License: MIT
Keywords: celery,fastapi,quickstart,spaps,sweet-potato
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.12
Requires-Dist: aioboto3<14.0,>=13.3.0
Requires-Dist: alembic<2.0,>=1.13.1
Requires-Dist: asyncpg<0.31,>=0.30.0
Requires-Dist: base58<3.0,>=2.1.1
Requires-Dist: bcrypt<6.0,>=4.0.0
Requires-Dist: bech32<2.0,>=1.2.0
Requires-Dist: botocore<2.0,>=1.35.0
Requires-Dist: celery<6.0,>=5.5.3
Requires-Dist: coincurve<22.0,>=20.0.0
Requires-Dist: cryptography<51.0,>=50.0.0
Requires-Dist: email-validator<3.0,>=2.1.0
Requires-Dist: eth-account<1.0,>=0.13.0
Requires-Dist: fastapi<0.137,>=0.120.0
Requires-Dist: greenlet<4.0,>=3.0.3
Requires-Dist: httpcore<2.0,>=1.0.9
Requires-Dist: httpx<0.29,>=0.28.1
Requires-Dist: orjson<4.0,>=3.10.0
Requires-Dist: packaging<27.0,>=23.0
Requires-Dist: pgvector<0.5,>=0.4.1
Requires-Dist: prometheus-client<1.0,>=0.21.0
Requires-Dist: psycopg[binary]<3.3,>=3.2.10
Requires-Dist: pycryptodome<4.0,>=3.20.0
Requires-Dist: pydantic-settings<3.0,>=2.3.0
Requires-Dist: pydantic<3.0,>=2.8.0
Requires-Dist: pyjwt<3.0,>=2.8.0
Requires-Dist: pyotp<3.0,>=2.9.0
Requires-Dist: python-dotenv<2.0,>=1.0.1
Requires-Dist: python-multipart<0.1,>=0.0.20
Requires-Dist: redis<7.0,>=6.4.0
Requires-Dist: sentry-sdk<3.0,>=2.54.0
Requires-Dist: slowapi<1.0,>=0.1.9
Requires-Dist: solders<1.0,>=0.21.0
Requires-Dist: spaps>=0.1.2
Requires-Dist: sqlalchemy<3.0,>=2.0.30
Requires-Dist: starlette<2.0,>=0.49.1
Requires-Dist: stripe<12.0,>=8.0.0
Requires-Dist: structlog<26.0,>=25.4.0
Requires-Dist: uvicorn[standard]<0.38,>=0.37.0
Requires-Dist: webauthn<3.0,>=2.7.1
Requires-Dist: x402[extensions,fastapi,svm]==2.9.0
Provides-Extra: dev
Requires-Dist: anyio<5.0,>=4.4.0; extra == 'dev'
Requires-Dist: bcrypt<6.0,>=4.0.0; extra == 'dev'
Requires-Dist: coverage<8.0,>=7.4.0; extra == 'dev'
Requires-Dist: docker<8.0,>=7.0.0; extra == 'dev'
Requires-Dist: filelock<4.0,>=3.12.0; extra == 'dev'
Requires-Dist: httpx<0.29,>=0.28.1; extra == 'dev'
Requires-Dist: hypothesis<7.0,>=6.100.0; extra == 'dev'
Requires-Dist: mypy<1.12,>=1.10.0; extra == 'dev'
Requires-Dist: pytest-asyncio<0.24,>=0.23.0; extra == 'dev'
Requires-Dist: pytest-benchmark<6.0,>=4.0.0; extra == 'dev'
Requires-Dist: pytest-cov<6.0,>=5.0.0; extra == 'dev'
Requires-Dist: pytest-testmon<3.0,>=2.1.0; extra == 'dev'
Requires-Dist: pytest-xdist<4.0,>=3.6.1; extra == 'dev'
Requires-Dist: pytest<9.0,>=8.2.0; extra == 'dev'
Requires-Dist: respx<0.23,>=0.22.0; extra == 'dev'
Requires-Dist: ruff<0.5,>=0.4.1; extra == 'dev'
Requires-Dist: testcontainers[postgres]<5.0,>=4.0.0; extra == 'dev'
Requires-Dist: types-pyyaml<7.0,>=6.0.0; extra == 'dev'
Provides-Extra: release
Requires-Dist: build==1.5.1; extra == 'release'
Requires-Dist: twine==6.2.0; extra == 'release'
Provides-Extra: testing
Requires-Dist: docker<8.0,>=7.0.0; extra == 'testing'
Requires-Dist: filelock<4.0,>=3.12.0; extra == 'testing'
Requires-Dist: httpx<0.29,>=0.28.1; extra == 'testing'
Requires-Dist: hypothesis<7.0,>=6.100.0; extra == 'testing'
Requires-Dist: pytest-asyncio<0.24,>=0.23.0; extra == 'testing'
Requires-Dist: pytest-benchmark<6.0,>=4.0.0; extra == 'testing'
Requires-Dist: pytest-testmon<3.0,>=2.1.0; extra == 'testing'
Requires-Dist: pytest-xdist<4.0,>=3.6.1; extra == 'testing'
Requires-Dist: pytest<9.0,>=8.2.0; extra == 'testing'
Requires-Dist: respx<0.23,>=0.22.0; extra == 'testing'
Requires-Dist: testcontainers[postgres]<5.0,>=4.0.0; extra == 'testing'
Description-Content-Type: text/markdown

# spaps-server-quickstart

Reusable FastAPI, SQLAlchemy, Celery, auth, and operational scaffolding for SPAPS-aligned Python services. This is the active backend package in this repository.

Examples in this README use placeholders such as `https://api.example.test` and local-only database URLs. Replace them with values from your own environment.

## Install

```bash
pip install spaps-server-quickstart
```

This package targets Python 3.12+.

## When It Fits

| Need | Package gives you |
| --- | --- |
| A real FastAPI starting point | `create_app`, settings loaders, request logging, and startup checks |
| Shared auth and RBAC | SPAPS auth middleware, auth channels, cookies, and role dependencies |
| Async database plumbing | SQLAlchemy 2 async session helpers and Alembic support |
| Operational scaffolding | Celery setup, deploy templates, local-mode helpers, and service SDK utilities |

## Quick Start

```python
from fastapi import APIRouter

from spaps_server_quickstart import create_app
from spaps_server_quickstart.settings import (
    BaseServiceSettings,
    create_settings_loader,
)


class ExampleSettings(BaseServiceSettings):
    app_name: str = "Example Service"
    spaps_auth_enabled: bool = False


router = APIRouter()


@router.get("/health")
async def health() -> dict[str, bool]:
    return {"ok": True}


app = create_app(
    settings_loader=create_settings_loader(ExampleSettings),
    api_router=router,
)
```

## What the Package Ships

| Area | Highlights |
| --- | --- |
| App lifecycle | `create_app`, startup validation, logging, CORS wiring |
| Auth | `SpapsAuthMiddleware`, auth cookies, auth channel service, local mode |
| Routing and RBAC | Base router builder, authenticated-user dependencies, `require_roles`, `has_required_roles` |
| Database | Async sessionmaker factory, migration runner, Alembic helpers |
| Tasks | Celery app factory plus notification and ping task helpers |
| Service SDK helpers | Admin token cache, server-side SPAPS clients, trusted-service middleware, feature evaluation |
| Templates | Domain examples, tests, deployment scaffolding, and local operations helpers |
| Reference contract | The `golden_path` app blueprint, seeded paid-access policy, and downstream runbook for the core auth -> billing -> entitlement -> access loop |

## Golden App Pack

If you want one SPAPS-native reference contract instead of a blank scaffold, start with the `golden_path` blueprint.

It is the canonical pack for:

- browser auth and one-call session bootstrap via `GET /api/auth/session-context`
- Stripe checkout or subscription flows
- entitlement projection into `paid_access`
- policy-gated access through the seeded `access-paid-features` rule
- an operator runbook under `docs/downstream_apps/golden-path-reference/`

## Local Development Mode

For the SPAPS app in this repo, `SPAPS_LOCAL_MODE` is the canonical local
auth/persona switch. It controls the route dependencies in
`middleware/spaps_deps.py`: missing API keys fall back to the local test
application, missing JWTs fall back to the selected test persona, explicit API
keys still validate normally, and real JWTs still win when present.

| `ENV` | `SPAPS_LOCAL_MODE` | SPAPS app behavior |
| --- | --- | --- |
| `prod` / `production` | unset or `false` | local mode off; real API key/JWT required |
| `prod` / `production` | `true` | startup fails; bypass is not allowed |
| `dev` / `development` / `test` | unset | local mode on by default |
| `dev` / `development` / `test` | `true` | local mode on |
| `dev` / `development` / `test` | `false` | local mode off |
| `staging` / `ci` | unset | local mode off unless explicitly enabled |
| unknown env | any | production-strict; local bypass is disabled |

Check the resolved state with `/health/local-mode`, `/health/ready`, or the
`X-SPAPS-Mode` response header. `X-SPAPS-Environment` carries the normalized
environment used for the safety decision.

`DEVELOPMENT_ENVIRONMENT=local` is the base quickstart/template knob for
services that explicitly wire `LocalAuthMiddleware`; it is not the SPAPS app
runtime switch.

```python
from spaps_server_quickstart.local_mode import (
    LocalAuthMiddleware,
    get_default_registry,
)
from spaps_server_quickstart.settings import BaseServiceSettings

settings = BaseServiceSettings(development_environment="local")

if settings.is_local_development:
    app.add_middleware(LocalAuthMiddleware, registry=get_default_registry())
```

Default personas:

| Persona | Token | Roles |
| --- | --- | --- |
| Admin | `local-admin` | `admin`, `user` |
| User | `local-user` | `user` |

## Configuration

Common settings exposed through `BaseServiceSettings`:

| Setting | Purpose |
| --- | --- |
| `spaps_local_mode` | Canonical SPAPS app local auth/persona switch |
| `development_environment` | Base-template `LocalAuthMiddleware` switch when set to `local`; not the SPAPS app mode switch |
| `spaps_auth_enabled` | Turns SPAPS auth middleware on or off |
| `spaps_auth_exempt_paths` | Comma-separated paths that skip SPAPS auth |
| `cors_allow_origins` | Shared CORS configuration |
| `database_url` | Async SQLAlchemy database URL |
| `redis_url` | Redis URL used by Celery defaults |
| `secure_messages_enabled` | Enables secure-messaging gateway support |

For per-process local origins that should not live in environment settings,
pass them to the app factory:

```python
app = create_app(
    settings_loader=create_settings_loader(ExampleSettings),
    api_router=router,
    extra_cors_origins=["http://localhost:5173"],
)
```

When settings origins are empty, `extra_cors_origins` enables CORS only for
those explicit origins. It does not add the wildcard origin unless CORS is
enabled manually without any configured origins.

Example environment:

```bash
SPAPS_LOCAL_MODE=true
# Only set DEVELOPMENT_ENVIRONMENT=local for downstream services that wire LocalAuthMiddleware.
SPAPS_API_URL=https://api.example.test
SPAPS_API_KEY=spaps_sec_example
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/service
SPAPS_AUTH_EXEMPT_PATHS=/health,/docs
CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173
```

## Validation

From the repository root, the `Makefile` is the canonical workflow:

```bash
make install
make pytest
make lint
make typecheck
make pytest-cov
make test
```

Useful additional commands:

```bash
make pytest-full
make pytest PYTEST_ARGS='tests/properties'
make migration-lint
make migration-downgrade-check
make migrate
make migrate-rehearsal
make perf-bench
make perf-k6
make new-migration MSG="add_example_table"
make local-up
```

The `tests/properties/` suite uses deterministic Hypothesis checks for pure
security helpers: wallet canonicalization, nonce parsing, publishable route
scope matching, rate-limit tier resolution, one-time auth challenges, and
webhook HMAC signing.

`make migration-lint` checks Alembic migrations changed from `BASE_REF` for
rollback and production-lock hazards. `make migrate-rehearsal` reuses the
existing production-dump restore path, applies migrations, and writes a timing
report under `.skillbox/reports/`. The restore path renders the cached dump
through `scripts/dev/restore_prod_dump_stream.py` so local-only placeholder rows
can satisfy FK validation for known historical dump orphans without disabling
constraints.

`make perf-bench` runs the opt-in pytest-benchmark hot-path suite and compares
the result with committed budgets under `tests/benchmarks/baselines/`.
`make perf-k6` runs the k6 scenario against `SPAPS_E2E_TARGET` after a local
stack is already running. These targets are report-only; normal PR validation
does not execute performance measurements.

## Troubleshooting

### `make pytest` reports no affected tests

That is expected when testmon sees no impacted tests. Use `make pytest-full` when you need a full run.

### Local auth bypass is not active

For the SPAPS app, check `/health/local-mode`, `/health/ready`, `X-SPAPS-Mode`,
and the `SPAPS_LOCAL_MODE` value. For generated services using the base
quickstart middleware, confirm `DEVELOPMENT_ENVIRONMENT=local` is set before
startup and that `LocalAuthMiddleware` is wired into the app.

### Unauthenticated routes still require auth

Check `spaps_auth_exempt_paths` and make sure the configured paths match the router paths exactly.

### Startup fails on environment validation

Review the required settings and any safety checks triggered during startup.

### I need a new service quickly

Start from the included templates, then keep the repo-root validation path intact while adapting domain code and settings.

## Limitations

- This package is a scaffold, not a finished application.
- Downstream services still need their own routers, schemas, repositories, and deployment settings.
- Some operational templates intentionally need environment-specific edits before production use.

## FAQ

### Is this the active backend package in this repo?

Yes. This package replaces the old Node/Express stack as the active backend surface.

### Does it include local auth bypass support?

Yes. The SPAPS app uses `SPAPS_LOCAL_MODE`; generated services can still use
`DEVELOPMENT_ENVIRONMENT=local` with the local-mode helpers.

### Can I use it without Celery?

Yes. Celery helpers are included, but you can compose only the pieces your service needs.

### Does it include RBAC helpers?

Yes. Use `require_roles`, `has_required_roles`, and the authenticated-user dependencies.

### What is the recommended validation path here?

From the repository root: `make pytest`, `make lint`, `make typecheck`, `make pytest-cov`, and `make test`.

## Metadata

- `package_name`: `spaps-server-quickstart`
- `latest_version`: `0.6.4`
- `minimum_runtime`: `Python >=3.12`
- `api_base_url`: `https://api.sweetpotato.dev`

## About Contributions

> *About Contributions:* Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via `gh` and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.

## License

MIT
