Metadata-Version: 2.4
Name: baltimore-patapsco
Version: 0.11.0
Summary: Shared Django runtime foundations for Baltimore civic applications
Author: Mayor's Office of Performance and Innovation
License-Expression: MIT
License-File: LICENSE
Requires-Dist: asgiref>=3.9,<4.0
Requires-Dist: django>=6.0,<6.1
Requires-Dist: djangorestframework>=3.16,<4.0
Requires-Dist: drf-spectacular>=0.28,<0.30
Requires-Dist: pyyaml>=6.0.3,<7.0
Requires-Dist: sentry-sdk[django]>=2.64.0,<3.0 ; extra == 'observability'
Requires-Dist: dj-database-url>=3.0,<4.0 ; extra == 'runtime'
Requires-Dist: django-environ>=0.12,<0.14 ; extra == 'runtime'
Requires-Dist: whitenoise>=6.9,<7.0 ; extra == 'runtime'
Requires-Python: >=3.13
Provides-Extra: observability
Provides-Extra: runtime
Description-Content-Type: text/markdown

# baltimore-patapsco (Python)

Reusable Django runtime primitives for Baltimore civic applications.

```python
INSTALLED_APPS += ["baltimore.patapsco"]

REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "baltimore.patapsco.api.exception_handler",
}

urlpatterns = [path("", include("baltimore.patapsco.urls"))]
```

Place `RequestContextMiddleware` early in `MIDDLEWARE` — after
`SecurityMiddleware` (and WhiteNoise when present), before session, auth, and
anything that can short-circuit a response — so every later middleware, view,
and log line sees the bound request ID. The reference app shows the canonical
order:

```python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "baltimore.patapsco.observability.RequestContextMiddleware",
    # sessions, common, csrf, auth, messages, clickjacking...
]
```

## Stable health and readiness views

Applications that mount or extend the operational views directly use the public
facade:

```python
from baltimore.patapsco.api import HealthView, ReadinessView
```

`HealthView` is process-only liveness. `ReadinessView` runs the established
database check and returns its public-safe 200 or 503 status document. Both use
no authentication classes and `AllowAny`, and both remain ordinary DRF
`APIView` classes with `as_view()`.

An application may subclass either class, override `get()`, and call
`super().get(request)` before composing app-owned public-safe checks. The parent
response status, request ID, and database result remain the starting contract;
the application owns an explicit serializer and OpenAPI annotation for any
extended payload.

The previous `baltimore.patapsco.api.views` imports resolve to the same class
objects for compatibility. `DiagnosticsView` remains available only from that
module and through the packaged URL configuration; it is intentionally absent
from `baltimore.patapsco.api.__all__` because its staff/public policy is a
separate operational surface. The settings-free package root exports neither
view class.

`api_error_response()` and the DRF exception handler force every 5xx response to
the public `internal_error` code, generic message, and no details. Application
callers cannot opt a server failure out of that safety boundary. An unexpected
exception also rolls back every active database transaction managed by Django's
`ATOMIC_REQUESTS`, so a failed request cannot commit partial writes. Applications
retain ownership of explicitly managed transactions.

Use `build_logging_config()` for the shared JSON/pretty log shape. Modules are
incrementally adoptable; installing the package does not enable auth, email, or
Sentry by itself. Structured log context is copied and recursively redacted,
including credentials nested in mappings and sequences. Credential keys are
matched across case and separator conventions, including `clientAPIKey` and
`X-API-Key`; repeated safe context is preserved and cycles are cut safely.

## Sentry privacy defaults

Install the `observability` extra and call the shared initializer only when a DSN
is configured:

```python
from baltimore.patapsco.observability import configure_sentry

configure_sentry(
    environment="production",
    release="my-app@1.2.3",
    traces_sample_rate=0.1,
)
```

`configure_sentry()` is deny-by-default: it disables automatic PII, request-body
capture, and stack-frame local variables. Its event pipeline also removes request
cookies and query strings, strips query/fragment data from request URLs, and
recursively redacts the shared credential-key fragments, Sentry's credential/session
denylist, and common civic contact and precise-location fields. This protection
also applies to transaction events. Safe operational fields such as request IDs,
routes, and HTTP methods remain available.

Redaction is key-based. Applications must not attach sensitive values under
misleading keys or place resident data in exception messages. Product-specific
fields still require a privacy review before being added to Sentry context.

## Configuration surface

Everything the library reads from the environment or Django settings:

| Name                          | Kind           | Default               | Meaning                                                                                                                                                       |
| ----------------------------- | -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APP_ENV`                     | env var        | `local`               | Environment label in diagnostics and Sentry events; overridden by `PATAPSCO_ENVIRONMENT` when set                                                             |
| `APP_RELEASE`                 | env var        | —                     | Release identifier in diagnostics and Sentry events; overridden by `PATAPSCO_RELEASE` when set                                                                |
| `APP_LOG_FORMAT`              | env var        | `json`                | `json` or `pretty` output from `build_logging_config()`                                                                                                       |
| `APP_LOG_LEVEL`               | env var        | `INFO`                | Root log level from `build_logging_config()`                                                                                                                  |
| `SENTRY_DSN`                  | env var        | —                     | Enables `configure_sentry()`; absent means Sentry stays off                                                                                                   |
| `PATAPSCO_SERVICE_NAME`       | Django setting | project name          | Service label in the diagnostics payload                                                                                                                      |
| `PATAPSCO_ENVIRONMENT`        | Django setting | `APP_ENV` env var     | Overrides the diagnostics `environment` field via Django settings instead of the process environment (a real `override_settings` test seam)                   |
| `PATAPSCO_RELEASE`            | Django setting | `APP_RELEASE` env var | Overrides the diagnostics `release` field via Django settings instead of the process environment (a real `override_settings` test seam)                       |
| `PATAPSCO_DIAGNOSTICS_PUBLIC` | Django setting | `False`               | **Security-relevant:** flips `/api/diagnostics` from `IsAdminUser` to `AllowAny`. Leave `False` unless the deployment deliberately publishes runtime metadata |
