# LightAPI v2 — Agent Context
# Branch: 1-v2-full-refactor
# Last updated: 2026-03-05
# SPEC: specs/1-v2-full-refactor/spec.md
# PLAN: specs/1-v2-full-refactor/plan.md

## Active Feature
Full refactor of LightAPI to v2. RestEndpoint is simultaneously the SQLAlchemy model and
Pydantic schema. No models.py, schemas.py, Column(), or @validates in user code.

## Tech Stack
- Python 3.10+
- SQLAlchemy 2.0 — imperative mapping (registry.map_imperatively), Select-based queries
- Pydantic v2 — Field(), create_model(), model_validate(), model_dump()
- Starlette — Request, JSONResponse, Response, Router, Route
- Uvicorn — ASGI server
- PyJWT: JWT auth
- Redis: caching via RedisCache/cache_manager
- PyYAML — YAML config loading
- pytest + httpx + SQLite — testing only

## Module Map
| File | Role |
|------|------|
| lightapi/__init__.py   | Public API re-exports (`__all__`) |
| lightapi/lightapi.py   | LightApi: register(), build_app(), run(), from_config(), from_dict() |
| lightapi/rest.py       | RestEndpointMeta, RestEndpoint auto-CRUD (sync and async share one core) |
| lightapi/endpoint_handler.py | EndpointHandler (one per route), EndpointGuard, MiddlewareChain, verb tables |
| lightapi/repository.py | Repository: row persistence, timestamps, optimistic locking |
| lightapi/schema.py     | SchemaFactory (pydantic schemas, row_to_dict, patch_schema), FieldProjection |
| lightapi/table_mapping.py | TableSource: DeclaredTable (columns from fields), ReflectedTable |
| lightapi/session_manager.py | SessionManager (engine, metadata, registry, run_blocking) |
| lightapi/session.py    | get_sync_session, get_async_session (public) |
| lightapi/config.py     | Config (JWT settings), Authentication, Filtering, Pagination, Cache, Serializer |
| lightapi/constants.py  | Enums: HttpVerb, ExecutionMode, TableReflection, JwtAlgorithm, ApiError, ResponseKey |
| lightapi/methods.py    | HttpMethod.GET/POST/PUT/PATCH/DELETE marker mixins (public) |
| lightapi/fields.py     | lightapi.Field() wrapper (strips foreign_key/unique/index/exclude) |
| lightapi/authentication/ | BaseAuthentication, JWTAuthentication, BasicAuthentication, permissions |
| lightapi/auth.py       | Compatibility re-exports of lightapi.authentication |
| lightapi/_login.py     | LoginRequest, LoginEndpoint (/auth/login, /auth/token) |
| lightapi/rate_limiter.py | RateLimiter, RateLimitWindow |
| lightapi/cache.py      | RedisCacheBackend, EndpointCache (per route), legacy BaseCache/RedisCache |
| lightapi/filters.py    | BaseFilter, FieldFilter, SearchFilter, OrderingFilter |
| lightapi/pagination.py | PaginationStyle, Cursor, NoPagination, PageNumberPaginator, CursorPaginator |
| lightapi/yaml_config.py | Pydantic models of lightapi.yaml and how they build the app (LightAPIConfig) |
| lightapi/_dict_config_loader.py | DictConfig behind the deprecated LightApi.from_dict |
| lightapi/core.py       | Middleware, CORSMiddleware, AuthenticationMiddleware, Response (compat) |
| lightapi/health.py     | /healthz |
| lightapi/server.py     | `lightapi serve` command |
| lightapi/exceptions.py | ConfigurationError, SerializationError |

## Key Invariants (enforce in all generated code)
1. RestEndpoint IS the SQLAlchemy model AND the Pydantic schema.
2. Fields = annotated class attrs with Field(). Column() never appears in user code.
3. queryset is the only name for static and dynamic query definitions.
4. All validation via Pydantic Field(). @validates must not exist.
5. Every DB row passes through SchemaFactory.row_to_dict → FieldProjection.apply → model_validate → model_dump.
6. IsAdminUser checks payload["is_admin"] == True.
7. version (int, default 1) is auto-injected; PUT/PATCH require it; 409 on mismatch.
8. GET list always returns 200 with {"results": []} on empty; never 404.
9. DB errors propagate to Starlette's default 500 handler.

## Auto-Injected Columns (MUST NOT be redeclared)
id, created_at, updated_at, version

## Type Map (annotation → SQLAlchemy column)
str → String, Optional[str] → String nullable
int → Integer, Optional[int] → Integer nullable
float → Float, Optional[float] → Float nullable
bool → Boolean, Optional[bool] → Boolean nullable
datetime → DateTime, Optional[datetime] → DateTime nullable
Decimal → Numeric(scale=N)
UUID → Uuid

## Field() Custom Kwargs (stripped before Pydantic sees them)
foreign_key, unique, index, exclude, decimal_places
Stored in json_schema_extra; read by RestEndpointMeta.

## Error Responses
404: {"detail": "not found"}
409: {"detail": "version conflict"}
422: {"detail": [{loc, msg, type}, ...]}  ← Pydantic v2 format
403: {"detail": "not allowed"}
405: {"detail": "method not allowed"} + Allow header

## Test Conventions (from constitution)
- test_<what>_<condition>_<expected> naming
- Arrange / Act / Assert
- sqlite:///:memory: for all DB fixtures
- Mock only at I/O boundary (DB session, Redis client)
- No external services

## Constitution Gates (must pass before merge)
1. ruff check
2. ruff format --check
3. mypy --strict
4. pytest -x (≥90% coverage on lightapi/)
