Skip to content

feat: LightAPI v2 — annotation-driven REST framework with async support#22

Merged
iklobato merged 4 commits intomasterfrom
2-async-support
Mar 5, 2026
Merged

feat: LightAPI v2 — annotation-driven REST framework with async support#22
iklobato merged 4 commits intomasterfrom
2-async-support

Conversation

@iklobato
Copy link
Copy Markdown
Owner

@iklobato iklobato commented Mar 5, 2026

Summary

This PR delivers LightAPI v2 — a ground-up redesign of the framework — and adds opt-in asynchronous I/O support on top.

v2 Core Refactor (feat: implement LightAPI v2 full refactor)

  • One-class architecture: A single RestEndpoint subclass acts as the SQLAlchemy ORM model, the Pydantic v2 schema, and the HTTP handler simultaneously — no Base mixin, no separate model files
  • RestEndpointMeta metaclass: Processes type annotations at class definition time to create SQLAlchemy Table objects and two Pydantic v2 schemas (create / read)
  • Auto-injected columns: Every endpoint gets id, created_at, updated_at, version (optimistic locking) with no declaration needed
  • Field(**kwargs): Re-exports pydantic.Field with extra kwargs (unique, index, foreign_key, decimal_places, exclude) processed by the metaclass
  • HttpMethod mixins: HttpMethod.GET, .POST, .PUT, .PATCH, .DELETE restrict allowed verbs at class definition time
  • Meta inner class: Per-endpoint config for authentication, filtering, pagination, serializer, cache, reflect, table
  • Authentication: JWTAuthentication + IsAuthenticated / IsAdminUser / AllowAny permission classes with per-method permission dicts
  • Filtering: FieldFilter, SearchFilter, OrderingFilter backends configured via Filtering(backends=..., fields=..., search=..., ordering=...)
  • Pagination: page_number and cursor styles via Pagination(style=..., page_size=...)
  • Serializer: Read/write field projection via Serializer(read=[...], write=[...]) or Serializer(fields=[...])
  • Cache: Redis-backed response caching via Cache(ttl=..., vary_on=[...])
  • Table reflection: Meta.reflect = True + Meta.table = "name" to map an endpoint to an existing database table
  • YAML config: LightApi.from_config("lightapi.yaml") for zero-code bootstrapping
  • 135 tests across CRUD, auth, filtering, pagination, serializer, schema, middleware, reflection, YAML config, and more

Async Support (docs: overhaul all documentation and examples for v2)

  • Opt-in async I/O: Swap create_engine for create_async_engine — LightAPI detects AsyncEngine automatically and routes all built-in CRUD operations through async sessions
  • lightapi/session.py: New get_sync_session and get_async_session context managers with automatic commit/rollback
  • Async CRUD helpers: _list_async, _retrieve_async, _create_async, _update_async, _destroy_async on every RestEndpoint
  • async def queryset: Detected and awaited automatically; sync queryset() still works on async engines
  • async def method overrides: async def post, get, etc. are detected via asyncio.iscoroutinefunction and awaited
  • Background tasks: self.background(fn, *args) schedules Starlette BackgroundTasks fire-and-forget after the response
  • Async middleware: async def process() in Middleware subclasses is awaited; sync and async middleware coexist
  • Async reflection: Meta.reflect = True works with AsyncEngine via conn.run_sync(metadata.reflect)
  • Startup validation: Missing async extras (sqlalchemy[asyncio], asyncpg/aiosqlite) raise ConfigurationError with clear install instructions
  • Type coercion in filters: _coerce_filter_value converts string query params to correct Python types (bool/int/float) for strict databases like PostgreSQL
  • model_rebuild(): Fixes Pydantic forward-reference resolution for Optional types in generated schemas
  • pyproject.toml: New [project.optional-dependencies.async] group (sqlalchemy[asyncio], asyncpg, aiosqlite, greenlet); pytest-asyncio added to dev deps; asyncio_mode = "auto" configured
  • 7 new async test files: test_async_crud, test_async_queryset, test_async_middleware, test_async_reflection, test_async_session, test_background_tasks, test_mixed_sync_async

Documentation & Examples

  • All docs rewritten for v2: 20 pages across getting-started/, tutorial/, advanced/, and api-reference/ — removed all v1 (aiohttp, lightapi.models, lightapi.database, RESTEndpoint, Configuration) references
  • New docs/advanced/async.md: Comprehensive async reference page
  • 65 v1 example files removed: Replaced with 4 focused v2 examples (v2_quickstart.py, v2_full_demo.py, smoke_async.py, postgres_full.py)
  • Nav cleanup: Removed broken docs/examples/ snippet-include section from docs/.pages

Test plan

  • uv run pytest — all 135 tests pass
  • uv run python examples/v2_quickstart.py — SQLite CRUD works
  • uv run python examples/smoke_async.py — async smoke test passes end-to-end
  • uv run python examples/postgres_full.py — PostgreSQL async demo runs (requires Docker PostgreSQL)

iklobato added 2 commits March 5, 2026 02:15
… framework

Unifies SQLAlchemy ORM model, Pydantic v2 schema, and REST endpoint into a
single annotated class (RestEndpoint). All features are v2-native:

Core engine
- RestEndpointMeta metaclass with SQLAlchemy 2.0 imperative mapping
- PEP-563 string annotation resolution via typing.get_type_hints()
- ORM-style insert (session.add/flush/refresh) to correctly populate
  created_at, updated_at, version in responses
- Dynamic Optional patch schema for PATCH partial updates
- Re-apply serializer field projection after model_dump() to prevent
  null bleed-through for non-serialized Optional fields
- Read schema uses Optional[T] for all user fields (output, not input)
- Replace deprecated datetime.utcnow() with timezone-aware equivalent
- Optimistic locking: PUT/PATCH require version, mismatch → 409
- Global SQLAlchemy registry singleton (_registry.py)

New modules
- lightapi/fields.py  — custom Field() wrapper strips LightAPI kwargs
- lightapi/methods.py — HttpMethod marker mixins (GET/POST/PUT/PATCH/DELETE)
- lightapi/schema.py  — SchemaFactory, _row_to_dict, _apply_fields
- lightapi/_registry.py — global registry/metadata/engine singleton

Refactored modules
- lightapi/rest.py    — RestEndpointMeta + RestEndpoint CRUD methods
- lightapi/lightapi.py — Starlette/Uvicorn-native LightApi, YAML from_config
- lightapi/auth.py    — AllowAny, IsAuthenticated, IsAdminUser permissions
- lightapi/filters.py — FieldFilter, SearchFilter, OrderingFilter
- lightapi/pagination.py — PageNumberPaginator, CursorPaginator
- lightapi/cache.py   — Redis utility functions
- lightapi/config.py  — Authentication, Filtering, Pagination, Serializer, Cache

Tests (98 passing)
- tests/test_crud.py, test_auth.py, test_filtering.py, test_http_methods.py
- tests/test_middleware.py, test_queryset.py, test_reflection.py
- tests/test_schema.py, test_serializer.py, test_pipeline.py, test_yaml_config.py
- conftest.py uses StaticPool for in-memory SQLite stability

Documentation
- README.md fully rewritten for v2 API
- docs/index.md, quickstart.md, first-steps.md, installation.md updated
- All remaining v1 docs pages marked with deprecation notice

Example
- examples/v2_full_demo.py — full-feature PostgreSQL demo covering CRUD,
  optimistic locking, filtering, pagination, serializer, JWT auth + permissions,
  HttpMethod mixins, custom queryset, cursor pagination, middleware
Remove 65+ deprecated v1 example files (aiohttp-based numbered scripts,
YAML configs, duplicate files) and replace with 4 focused v2 examples:
v2_quickstart.py, v2_full_demo.py, smoke_async.py, postgres_full.py.

Rewrite every stale documentation page to reflect the v2 API:
- getting-started: introduction (remove aiohttp), configuration (LightApi
  constructor, YAML schema), first-steps, quickstart
- tutorial: basic-api, endpoints, database, requests, responses — all
  updated from v1 patterns (request.data, lightapi.database, Base) to
  v2 (RestEndpoint metaclass, Starlette Request, async sessions)
- advanced: authentication, filtering, pagination, caching, validation —
  all rewritten for Meta-based config API
- api-reference: core, auth, database, models, filters, pagination, cache,
  validation, exceptions, swagger/openapi — all rewritten for v2
- docs/.pages nav: remove broken examples/ section and technical-reference/

Also fix minor v1 references in installation.md and troubleshooting.md.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 2026

Important

Review skipped

Too many files!

This PR contains 161 files, which is 11 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6737c8e-da36-4f8a-a3d3-50b9f1b52b17

📥 Commits

Reviewing files that changed from the base of the PR and between 13c71ac and 712b908.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (161)
  • .cursorrules
  • .github/workflows/python-publish.yml
  • .github/workflows/test-dev.yml
  • LIGHTAPI_VALIDATION_REPORT.md
  • README.md
  • YAML_CONFIGURATION_GUIDE.md
  • YAML_SYSTEM_SUMMARY.md
  • docs/.pages
  • docs/advanced/async.md
  • docs/advanced/authentication.md
  • docs/advanced/caching.md
  • docs/advanced/filtering.md
  • docs/advanced/middleware.md
  • docs/advanced/pagination.md
  • docs/advanced/validation.md
  • docs/api-reference/auth.md
  • docs/api-reference/cache.md
  • docs/api-reference/core.md
  • docs/api-reference/database.md
  • docs/api-reference/exceptions.md
  • docs/api-reference/filters.md
  • docs/api-reference/index.md
  • docs/api-reference/models.md
  • docs/api-reference/pagination.md
  • docs/api-reference/rest.md
  • docs/api-reference/swagger.md
  • docs/api-reference/validation.md
  • docs/examples/auth.md
  • docs/examples/basic-crud.md
  • docs/examples/caching.md
  • docs/examples/filtering-pagination.md
  • docs/examples/middleware.md
  • docs/getting-started/configuration.md
  • docs/getting-started/first-steps.md
  • docs/getting-started/installation.md
  • docs/getting-started/introduction.md
  • docs/getting-started/quickstart.md
  • docs/index.md
  • docs/technical-reference/core-api.md
  • docs/technical-reference/endpoints.md
  • docs/technical-reference/middleware.md
  • docs/technical-reference/models.md
  • docs/troubleshooting.md
  • docs/tutorial/basic-api.md
  • docs/tutorial/database.md
  • docs/tutorial/endpoints.md
  • docs/tutorial/requests.md
  • docs/tutorial/responses.md
  • examples/01_database_transactions.py
  • examples/01_error_handling_basic.py
  • examples/01_example.py
  • examples/01_general_usage.py
  • examples/01_response_customization.py
  • examples/01_rest_crud_basic.py
  • examples/02_authentication_jwt.py
  • examples/03_advanced_validation.py
  • examples/03_validation_custom_fields.py
  • examples/04_advanced_filtering_pagination.py
  • examples/04_filtering_pagination.py
  • examples/04_search_functionality.py
  • examples/05_advanced_caching_redis.py
  • examples/05_caching_redis_custom.py
  • examples/06_async_performance.py
  • examples/07_middleware_cors_auth.py
  • examples/07_middleware_custom.py
  • examples/08_swagger_openapi_docs.py
  • examples/09_yaml_advanced_permissions.py
  • examples/09_yaml_basic_example.py
  • examples/09_yaml_comprehensive_example.py
  • examples/09_yaml_configuration.py
  • examples/09_yaml_database_types.py
  • examples/09_yaml_environment_variables.py
  • examples/09_yaml_minimal_readonly.py
  • examples/10_batch_operations.py
  • examples/10_blog_post.py
  • examples/10_comprehensive_ideal_usage.py
  • examples/10_mega_example.py
  • examples/10_nested_resources.py
  • examples/10_relationships_sqlalchemy.py
  • examples/10_user_goal_example.py
  • examples/README.md
  • examples/YAML_EXAMPLES_INDEX.md
  • examples/__init__.py
  • examples/advanced_caching_redis_05.py
  • examples/advanced_permissions_config.yaml
  • examples/basic_config.yaml
  • examples/batch_operations_10.py
  • examples/comprehensive_config.yaml
  • examples/config_advanced.yaml
  • examples/config_basic.yaml
  • examples/config_minimal.yaml
  • examples/config_mysql.yaml
  • examples/config_postgresql.yaml
  • examples/config_readonly.yaml
  • examples/db_multi_database_config.yaml
  • examples/db_mysql_config.yaml
  • examples/db_postgresql_config.yaml
  • examples/db_sqlite_config.yaml
  • examples/development_config.yaml
  • examples/env_development_config.yaml
  • examples/env_multi_database_config.yaml
  • examples/env_production_config.yaml
  • examples/env_staging_config.yaml
  • examples/mega_example_10.py
  • examples/minimal_blog_config.yaml
  • examples/minimal_config.yaml
  • examples/mysql_config.yaml
  • examples/postgres_full.py
  • examples/postgresql_config.yaml
  • examples/production_config.yaml
  • examples/readonly_analytics_config.yaml
  • examples/readonly_config.yaml
  • examples/search_functionality_04.py
  • examples/smoke_async.py
  • examples/sqlite_config.yaml
  • examples/staging_config.yaml
  • examples/test_all_examples.py
  • examples/v2_full_demo.py
  • examples/v2_quickstart.py
  • examples/validation_custom_fields_03.py
  • examples/yaml_comprehensive_example_09.py
  • lightapi/__init__.py
  • lightapi/_registry.py
  • lightapi/auth.py
  • lightapi/cache.py
  • lightapi/config.py
  • lightapi/core.py
  • lightapi/database.py
  • lightapi/exceptions.py
  • lightapi/fields.py
  • lightapi/filters.py
  • lightapi/handlers.py
  • lightapi/lightapi.py
  • lightapi/methods.py
  • lightapi/pagination.py
  • lightapi/rest.py
  • lightapi/schema.py
  • lightapi/session.py
  • lightapi/swagger.py
  • mkdocs.yml
  • pyproject.toml
  • pytest.ini
  • tests/conftest.py
  • tests/test_async_crud.py
  • tests/test_async_middleware.py
  • tests/test_async_queryset.py
  • tests/test_async_reflection.py
  • tests/test_async_session.py
  • tests/test_auth.py
  • tests/test_background_tasks.py
  • tests/test_crud.py
  • tests/test_filtering.py
  • tests/test_http_methods.py
  • tests/test_middleware.py
  • tests/test_mixed_sync_async.py
  • tests/test_pipeline.py
  • tests/test_queryset.py
  • tests/test_reflection.py
  • tests/test_schema.py
  • tests/test_serializer.py
  • tests/test_yaml_config.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2-async-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

iklobato added 2 commits March 5, 2026 03:40
v2 uses PEP 604 union syntax (X | Y) and built-in generics (list[str],
dict[str, type]) that require Python >= 3.10. Drop 3.8 and 3.9 from
the test matrix to match requires-python = ">=3.10" in pyproject.toml.

Also fix the install command from `.[test,dev]` to `.[dev,async]` so
the async test suite (pytest-asyncio, aiosqlite, asyncpg) installs
correctly in CI.
F821 undefined names in legacy v1 code:
- cache.py: add missing Optional/Dict imports
- filters.py: replace Query/List annotations with Any
- pagination.py: replace Query/List annotations with Any/list[Any]

F401 unused imports:
- auth.py: remove unused Any from typing import
- core.py: remove unused hashlib import
- core.py: remove unused swagger route imports (imported but never called)
- database.py: remove duplicate/unused os import
- handlers.py: remove inspect import (only used for the now-removed mapper line)
- swagger.py: remove unused List and sql_inspect imports

F811 duplicate definition:
- filters.py: remove duplicate no-op ParameterFilter (shadowed by the real one)

F841 unused local variable:
- handlers.py: remove unused `mapper = inspect(self.model)` assignment

F402 shadowed import in loop:
- lightapi.py: remove inline `import importlib` inside for loop (already imported at top)
@iklobato iklobato merged commit 5b1e3c9 into master Mar 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant