lightapi 0.1.20__tar.gz → 0.1.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightapi-0.1.22/.dockerignore +29 -0
- lightapi-0.1.22/.github/workflows/docker-publish.yml +92 -0
- lightapi-0.1.22/Dockerfile +65 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/PKG-INFO +76 -7
- {lightapi-0.1.20 → lightapi-0.1.22}/README.md +75 -6
- lightapi-0.1.22/docker/entrypoint.py +79 -0
- lightapi-0.1.22/docker/lightapi.example.yaml +32 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/async.md +2 -2
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/authentication.md +43 -6
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/middleware.md +1 -1
- lightapi-0.1.22/docs/advanced/rate-limiting.md +84 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/auth.md +25 -3
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/core.md +32 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/index.md +1 -0
- lightapi-0.1.22/docs/deployment/docker.md +250 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/advanced-permissions.md +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/auth.md +28 -3
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/basic-crud.md +3 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/custom-application.md +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/yaml-configuration.md +13 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/configuration.md +26 -4
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/first-steps.md +10 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/quickstart.md +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/index.md +1 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/core-api.md +18 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/troubleshooting.md +3 -2
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/basic-api.md +21 -15
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/database.md +7 -3
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/endpoints.md +8 -7
- lightapi-0.1.22/examples/01_minimal.py +53 -0
- lightapi-0.1.22/examples/02_crud.py +78 -0
- lightapi-0.1.22/examples/03_auth_jwt.py +82 -0
- lightapi-0.1.22/examples/04_auth_basic.py +73 -0
- lightapi-0.1.22/examples/05_permissions.py +103 -0
- lightapi-0.1.22/examples/06_pagination_page.py +64 -0
- lightapi-0.1.22/examples/07_pagination_cursor.py +57 -0
- lightapi-0.1.22/examples/08_filtering.py +67 -0
- lightapi-0.1.22/examples/09_caching.py +73 -0
- lightapi-0.1.22/examples/10_async.py +63 -0
- lightapi-0.1.22/examples/11_mixed_sync_async.py +69 -0
- lightapi-0.1.22/examples/12_queryset.py +70 -0
- lightapi-0.1.22/examples/13_middleware.py +86 -0
- lightapi-0.1.22/examples/14_background.py +69 -0
- lightapi-0.1.22/examples/15_yaml_config.py +73 -0
- lightapi-0.1.22/examples/16_rate_limit.py +70 -0
- lightapi-0.1.22/examples/17_relationships.py +58 -0
- lightapi-0.1.22/examples/18_full_api.py +171 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/examples/v2_full_demo.py +8 -3
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/__init__.py +15 -1
- lightapi-0.1.22/lightapi/_dict_config_loader.py +156 -0
- lightapi-0.1.22/lightapi/_login.py +157 -0
- lightapi-0.1.22/lightapi/auth.py +25 -0
- lightapi-0.1.22/lightapi/auth_checker.py +68 -0
- lightapi-0.1.22/lightapi/auth_service.py +106 -0
- lightapi-0.1.22/lightapi/auth_strategy.py +66 -0
- lightapi-0.1.22/lightapi/authentication/__init__.py +24 -0
- lightapi-0.1.22/lightapi/authentication/base.py +101 -0
- lightapi-0.1.22/lightapi/authentication/basic.py +77 -0
- lightapi-0.1.22/lightapi/authentication/jwt.py +105 -0
- lightapi-0.1.22/lightapi/body_reader.py +15 -0
- lightapi-0.1.22/lightapi/cache.py +294 -0
- lightapi-0.1.22/lightapi/cache_handler.py +126 -0
- lightapi-0.1.22/lightapi/cache_helper.py +54 -0
- lightapi-0.1.22/lightapi/cache_service.py +143 -0
- lightapi-0.1.22/lightapi/config.py +170 -0
- lightapi-0.1.22/lightapi/constants.py +99 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/core.py +0 -76
- lightapi-0.1.22/lightapi/crud_async.py +274 -0
- lightapi-0.1.22/lightapi/crud_sync.py +281 -0
- lightapi-0.1.22/lightapi/handler_factory.py +167 -0
- lightapi-0.1.22/lightapi/http_dispatcher.py +145 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/lightapi.py +409 -69
- lightapi-0.1.22/lightapi/middleware_pipeline.py +84 -0
- lightapi-0.1.22/lightapi/middleware_runner.py +36 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/pagination.py +81 -91
- lightapi-0.1.22/lightapi/queryset.py +65 -0
- lightapi-0.1.22/lightapi/rate_limiter.py +174 -0
- lightapi-0.1.22/lightapi/response_wrapper.py +12 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/rest.py +86 -191
- lightapi-0.1.22/lightapi/route_builder.py +92 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/schema.py +46 -2
- lightapi-0.1.22/lightapi/session_manager.py +227 -0
- lightapi-0.1.22/lightapi/table_mapping.py +235 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/yaml_loader.py +160 -12
- {lightapi-0.1.20 → lightapi-0.1.22}/pyproject.toml +19 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/conftest.py +8 -2
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_crud.py +2 -2
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_middleware.py +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_queryset.py +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_reflection.py +7 -7
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_auth.py +7 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_background_tasks.py +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_cache_v2.py +9 -9
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_crud.py +1 -1
- lightapi-0.1.22/tests/test_example_01_minimal.py +203 -0
- lightapi-0.1.22/tests/test_example_03_auth_jwt.py +154 -0
- lightapi-0.1.22/tests/test_example_06_pagination_page.py +60 -0
- lightapi-0.1.22/tests/test_example_08_filtering.py +99 -0
- lightapi-0.1.22/tests/test_examples_e2e.py +625 -0
- lightapi-0.1.22/tests/test_login_auth.py +1001 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_mixed_sync_async.py +1 -1
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_queryset.py +6 -5
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_reflection.py +10 -13
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_rest.py +21 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_yaml_config.py +168 -2
- {lightapi-0.1.20 → lightapi-0.1.22}/uv.lock +1 -1
- lightapi-0.1.20/docs/deployment/docker.md +0 -50
- lightapi-0.1.20/lightapi/_registry.py +0 -37
- lightapi-0.1.20/lightapi/auth.py +0 -147
- lightapi-0.1.20/lightapi/cache.py +0 -173
- lightapi-0.1.20/lightapi/config.py +0 -137
- {lightapi-0.1.20 → lightapi-0.1.22}/.cursorrules +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/pages-publish.yml +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/python-publish.yml +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/test-dev.yml +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/.gitignore +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/LICENSE +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/api_config.yaml +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/caching.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/filtering.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/pagination.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/validation.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/cache.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/database.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/exceptions.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/filters.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/models.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/pagination.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/rest.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/swagger.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/validation.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/production.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/security.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/basic-rest.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/caching.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/environment-variables.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/filtering-pagination.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/middleware.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/readonly-apis.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/validation.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/installation.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/introduction.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/cache.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/endpoints.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/handlers.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/middleware.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/models.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/.pages +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/requests.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/responses.md +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/examples/postgres_full.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/examples/smoke_async.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/examples/v2_quickstart.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/exceptions.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/fields.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/filters.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/methods.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/session.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/swagger.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/mkdocs.yml +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/overrides/404.html +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/pytest.ini +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/requirements.txt +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/run_server.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_session.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_filtering.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_http_methods.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_lightapi_init.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_middleware.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_pipeline.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_schema.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_serializer.py +0 -0
- {lightapi-0.1.20 → lightapi-0.1.22}/update_version.py +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.github
|
|
3
|
+
.venv
|
|
4
|
+
.pytest_cache
|
|
5
|
+
.mypy_cache
|
|
6
|
+
.ruff_cache
|
|
7
|
+
.coverage
|
|
8
|
+
.cursor
|
|
9
|
+
.cursorrules
|
|
10
|
+
.specify
|
|
11
|
+
__pycache__
|
|
12
|
+
**/__pycache__
|
|
13
|
+
*.pyc
|
|
14
|
+
*.pyo
|
|
15
|
+
*.pyd
|
|
16
|
+
*.db
|
|
17
|
+
*.log
|
|
18
|
+
dist
|
|
19
|
+
build
|
|
20
|
+
site
|
|
21
|
+
docs
|
|
22
|
+
examples
|
|
23
|
+
tests
|
|
24
|
+
overrides
|
|
25
|
+
mkdocs.yml
|
|
26
|
+
README.md
|
|
27
|
+
LICENSE
|
|
28
|
+
*.md
|
|
29
|
+
specs
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
name: Publish Docker image
|
|
2
|
+
|
|
3
|
+
# Builds the ready-to-use LightAPI image and pushes it to Docker Hub.
|
|
4
|
+
#
|
|
5
|
+
# Triggers:
|
|
6
|
+
# - on push of a `v*` tag (release) → tags the image with that version + `latest`
|
|
7
|
+
# - on push to `master` → tags the image with `master`
|
|
8
|
+
# - manual workflow_dispatch → tags the image with `manual-<run-number>`
|
|
9
|
+
#
|
|
10
|
+
# Required repository secrets:
|
|
11
|
+
# - DOCKERHUB_USERNAME Docker Hub username (e.g. `iklobato`)
|
|
12
|
+
# - DOCKERHUB_TOKEN Docker Hub access token (read+write+delete scope)
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags: ["v*.*.*"]
|
|
17
|
+
branches: ["master"]
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
publish:
|
|
25
|
+
name: Build & push iklobato/lightapi
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout
|
|
30
|
+
uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- name: Read package version
|
|
33
|
+
id: version
|
|
34
|
+
shell: bash
|
|
35
|
+
run: |
|
|
36
|
+
# Extract version from pyproject.toml (e.g. 0.1.21)
|
|
37
|
+
VERSION=$(python -c "
|
|
38
|
+
import tomllib, pathlib
|
|
39
|
+
data = tomllib.loads(pathlib.Path('pyproject.toml').read_text())
|
|
40
|
+
print(data['project']['version'])
|
|
41
|
+
")
|
|
42
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
43
|
+
echo "Detected lightapi version: $VERSION"
|
|
44
|
+
|
|
45
|
+
- name: Set up QEMU
|
|
46
|
+
uses: docker/setup-qemu-action@v3
|
|
47
|
+
|
|
48
|
+
- name: Set up Docker Buildx
|
|
49
|
+
uses: docker/setup-buildx-action@v3
|
|
50
|
+
|
|
51
|
+
- name: Log in to Docker Hub
|
|
52
|
+
uses: docker/login-action@v3
|
|
53
|
+
with:
|
|
54
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
55
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
56
|
+
|
|
57
|
+
- name: Compute image tags
|
|
58
|
+
id: meta
|
|
59
|
+
uses: docker/metadata-action@v5
|
|
60
|
+
with:
|
|
61
|
+
images: iklobato/lightapi
|
|
62
|
+
tags: |
|
|
63
|
+
# On a v*.*.* tag → :0.1.21, :0.1, :latest
|
|
64
|
+
type=semver,pattern={{version}}
|
|
65
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
66
|
+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
67
|
+
# On a push to master → :master (rolling head)
|
|
68
|
+
type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }}
|
|
69
|
+
# Manual run → :manual-<run-number>
|
|
70
|
+
type=raw,value=manual-${{ github.run_number }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
|
71
|
+
|
|
72
|
+
- name: Build and push
|
|
73
|
+
uses: docker/build-push-action@v6
|
|
74
|
+
with:
|
|
75
|
+
context: .
|
|
76
|
+
platforms: linux/amd64,linux/arm64
|
|
77
|
+
push: true
|
|
78
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
79
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
80
|
+
build-args: |
|
|
81
|
+
LIGHTAPI_VERSION=${{ steps.version.outputs.version }}
|
|
82
|
+
cache-from: type=gha
|
|
83
|
+
cache-to: type=gha,mode=max
|
|
84
|
+
|
|
85
|
+
- name: Image digest summary
|
|
86
|
+
run: |
|
|
87
|
+
echo "### Published image" >> "$GITHUB_STEP_SUMMARY"
|
|
88
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
89
|
+
echo "Tags:" >> "$GITHUB_STEP_SUMMARY"
|
|
90
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
91
|
+
echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
|
|
92
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
|
|
3
|
+
# Ready-to-run LightAPI image.
|
|
4
|
+
# Users mount their config at /app/lightapi.yaml and run the container —
|
|
5
|
+
# no Python code or build step required on their side.
|
|
6
|
+
#
|
|
7
|
+
# Build: docker build -t lightapi:local .
|
|
8
|
+
# Run: docker run --rm -p 8000:8000 \
|
|
9
|
+
# -v "$(pwd)/lightapi.yaml:/app/lightapi.yaml:ro" \
|
|
10
|
+
# -e DATABASE_URL=sqlite:////app/data.db \
|
|
11
|
+
# lightapi:local
|
|
12
|
+
|
|
13
|
+
FROM python:3.12-slim AS base
|
|
14
|
+
|
|
15
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
16
|
+
PYTHONUNBUFFERED=1 \
|
|
17
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
18
|
+
PIP_NO_CACHE_DIR=1 \
|
|
19
|
+
LIGHTAPI_CONFIG=/app/lightapi.yaml \
|
|
20
|
+
LIGHTAPI_HOST=0.0.0.0 \
|
|
21
|
+
LIGHTAPI_PORT=8000 \
|
|
22
|
+
LIGHTAPI_LOG_LEVEL=info
|
|
23
|
+
|
|
24
|
+
WORKDIR /app
|
|
25
|
+
|
|
26
|
+
# Build dependencies for psycopg2 / asyncpg native code. Keep build-essential
|
|
27
|
+
# in the layer so we can compile, then remove it.
|
|
28
|
+
RUN apt-get update \
|
|
29
|
+
&& apt-get install -y --no-install-recommends \
|
|
30
|
+
build-essential \
|
|
31
|
+
libpq-dev \
|
|
32
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
33
|
+
|
|
34
|
+
# Install LightAPI + async + PostgreSQL drivers. We use the published wheel
|
|
35
|
+
# rather than copying the source so the image stays useful even when this
|
|
36
|
+
# Dockerfile is built outside the repo.
|
|
37
|
+
ARG LIGHTAPI_VERSION
|
|
38
|
+
RUN if [ -n "$LIGHTAPI_VERSION" ]; then \
|
|
39
|
+
pip install "lightapi[async]==$LIGHTAPI_VERSION" psycopg2-binary ; \
|
|
40
|
+
else \
|
|
41
|
+
pip install "lightapi[async]" psycopg2-binary ; \
|
|
42
|
+
fi \
|
|
43
|
+
&& apt-get purge -y --auto-remove build-essential \
|
|
44
|
+
&& rm -rf /root/.cache
|
|
45
|
+
|
|
46
|
+
# Copy the launcher. Everything user-supplied lives outside /app/entrypoint.py.
|
|
47
|
+
COPY docker/entrypoint.py /app/entrypoint.py
|
|
48
|
+
|
|
49
|
+
# Non-root runtime user
|
|
50
|
+
RUN groupadd --system --gid 1001 lightapi \
|
|
51
|
+
&& useradd --system --uid 1001 --gid lightapi --home-dir /app --no-create-home lightapi \
|
|
52
|
+
&& chown -R lightapi:lightapi /app
|
|
53
|
+
USER lightapi
|
|
54
|
+
|
|
55
|
+
EXPOSE 8000
|
|
56
|
+
|
|
57
|
+
# Lightweight health check — touches the root path expecting any HTTP response.
|
|
58
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
59
|
+
CMD python -c "import urllib.request, sys; \
|
|
60
|
+
import os; \
|
|
61
|
+
url='http://127.0.0.1:'+os.environ.get('LIGHTAPI_PORT','8000')+'/'; \
|
|
62
|
+
sys.exit(0 if urllib.request.urlopen(url, timeout=2).status else 1)" \
|
|
63
|
+
|| exit 1
|
|
64
|
+
|
|
65
|
+
ENTRYPOINT ["python", "/app/entrypoint.py"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lightapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.22
|
|
4
4
|
Summary: A lightweight framework for building API endpoints using Python's native libraries.
|
|
5
5
|
Project-URL: Repository, https://github.com/henriqueblobato/LightApi
|
|
6
6
|
Project-URL: Issues, https://github.com/henriqueblobato/LightApi/issues
|
|
@@ -131,6 +131,17 @@ uv add "lightapi[async]"
|
|
|
131
131
|
|
|
132
132
|
**Optional Redis caching**: `redis` is included as a core dependency but Redis caching only activates when `Meta.cache = Cache(ttl=N)` is set on an endpoint. A `RuntimeWarning` is emitted at startup if Redis is unreachable.
|
|
133
133
|
|
|
134
|
+
**Docker (no install required)**: run the API straight from the published image — just mount your config:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
docker run --rm -p 8000:8000 \
|
|
138
|
+
-v "$(pwd)/lightapi.yaml:/app/lightapi.yaml:ro" \
|
|
139
|
+
-e DATABASE_URL=sqlite:////app/data.db \
|
|
140
|
+
iklobato/lightapi:latest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
See [Docker deployment](docs/deployment/docker.md) for the full guide.
|
|
144
|
+
|
|
134
145
|
---
|
|
135
146
|
|
|
136
147
|
## Quick Start
|
|
@@ -337,7 +348,7 @@ Use `Meta.authentication` with a backend and an optional permission class:
|
|
|
337
348
|
```python
|
|
338
349
|
import os
|
|
339
350
|
from lightapi import RestEndpoint, Authentication, Field
|
|
340
|
-
from lightapi import JWTAuthentication, IsAuthenticated, IsAdminUser
|
|
351
|
+
from lightapi.authentication import JWTAuthentication, IsAuthenticated, IsAdminUser
|
|
341
352
|
|
|
342
353
|
os.environ["LIGHTAPI_JWT_SECRET"] = "your-secret-key"
|
|
343
354
|
|
|
@@ -360,6 +371,60 @@ class AdminOnlyEndpoint(RestEndpoint):
|
|
|
360
371
|
2. Permission class `.has_permission(request)` — checks `request.state.user`
|
|
361
372
|
3. Returns `401` if authentication fails, `403` if permission denied
|
|
362
373
|
|
|
374
|
+
**Custom authentication:** Subclass `JWTAuthentication` or `BasicAuthentication` and override `validate_credentials()`:
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from lightapi.authentication import JWTAuthentication
|
|
378
|
+
|
|
379
|
+
class MyAuthBackend(JWTAuthentication):
|
|
380
|
+
async def validate_credentials(self, username: str, password: str) -> dict | None:
|
|
381
|
+
# Custom validation logic - query your database, check LDAP, etc.
|
|
382
|
+
user = await self.get_user_from_db(username)
|
|
383
|
+
if user and await user.verify_password(password):
|
|
384
|
+
return {"sub": str(user.id), "is_admin": user.is_admin}
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
class ProtectedEndpoint(RestEndpoint):
|
|
388
|
+
secret: str = Field(min_length=1)
|
|
389
|
+
class Meta:
|
|
390
|
+
authentication = Authentication(backend=MyAuthBackend)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Login and token endpoints:** When using `JWTAuthentication` or `BasicAuthentication`, pass `login_validator` to obtain automatic `/auth/login` and `/auth/token` endpoints (backward compatible):
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
def my_validator(username: str, password: str):
|
|
397
|
+
# Return user payload dict or None
|
|
398
|
+
user = db.query(User).filter_by(username=username).first()
|
|
399
|
+
if user and user.check_password(password):
|
|
400
|
+
return {"sub": str(user.id), "is_admin": user.is_admin}
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
app = LightApi(engine=engine, login_validator=my_validator)
|
|
404
|
+
app.register({"/secrets": ProtectedEndpoint})
|
|
405
|
+
# POST /auth/login and POST /auth/token now accept {"username":"...","password":"..."}
|
|
406
|
+
# JWT mode: 200 {"token":"...","user":{...}}; Basic-only: 200 {"user":{...}}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Rate limiting:** Add per-endpoint rate limiting via `Authentication` config or global rate limiter:
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
from lightapi import RestEndpoint, Authentication
|
|
413
|
+
from lightapi.authentication import JWTAuthentication
|
|
414
|
+
|
|
415
|
+
class LimitedEndpoint(RestEndpoint):
|
|
416
|
+
data: str = Field(min_length=1)
|
|
417
|
+
class Meta:
|
|
418
|
+
# Per-endpoint: 5 requests per minute
|
|
419
|
+
authentication = Authentication(
|
|
420
|
+
backend=JWTAuthentication,
|
|
421
|
+
rate_limiter={"requests": 5, "window": 60}
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Or global rate limiter (applied to all endpoints)
|
|
425
|
+
app = LightApi(engine=engine, rate_limiter={"requests": 100, "window": 60})
|
|
426
|
+
```
|
|
427
|
+
|
|
363
428
|
**Built-in permission classes:**
|
|
364
429
|
|
|
365
430
|
| Class | Condition |
|
|
@@ -670,7 +735,7 @@ class OrderEndpoint(RestEndpoint):
|
|
|
670
735
|
|
|
671
736
|
### Async Method Overrides
|
|
672
737
|
|
|
673
|
-
Override individual HTTP verbs with `async def
|
|
738
|
+
Override individual HTTP verbs with `async def`. Mode is auto-detected — no explicit `mode="async"` needed:
|
|
674
739
|
|
|
675
740
|
```python
|
|
676
741
|
class ProductEndpoint(RestEndpoint):
|
|
@@ -688,6 +753,8 @@ class ProductEndpoint(RestEndpoint):
|
|
|
688
753
|
return await self._list_async(request)
|
|
689
754
|
```
|
|
690
755
|
|
|
756
|
+
**Auto-detect mode:** LightAPI automatically detects whether an endpoint method is sync or async by checking if it's a coroutine function. Simply define `async def get()` and the framework will use async execution.
|
|
757
|
+
|
|
691
758
|
**Built-in async CRUD helpers** available on every `RestEndpoint`:
|
|
692
759
|
|
|
693
760
|
| Method | Description |
|
|
@@ -865,12 +932,14 @@ Override these methods to customise behaviour. Both `def` (sync) and `async def`
|
|
|
865
932
|
| `update` | `(data, pk, partial)` | `UPDATE WHERE id=pk AND version=N RETURNING` |
|
|
866
933
|
| `destroy` | `(request, pk)` | `DELETE WHERE id=pk` |
|
|
867
934
|
| `queryset` | `(request)` | Returns base `select(cls._model_class)` |
|
|
868
|
-
| `get` | `(request)` | Override GET (collection or detail) |
|
|
869
|
-
| `post` | `(request)` | Override POST |
|
|
870
|
-
| `put` | `(request)` | Override PUT |
|
|
871
|
-
| `patch` | `(request)` | Override PATCH |
|
|
935
|
+
| `get` | `(request)` | Override GET (collection or detail) — can return `dict` |
|
|
936
|
+
| `post` | `(request)` | Override POST — can return `dict` |
|
|
937
|
+
| `put` | `(request)` | Override PUT — can return `dict` |
|
|
938
|
+
| `patch` | `(request)` | Override PATCH — can return `dict` |
|
|
872
939
|
| `delete` | `(request)` | Override DELETE |
|
|
873
940
|
|
|
941
|
+
**Return dict or Response:** Endpoint override methods can return either a `dict` (auto-wrapped to `JSONResponse`) or a Starlette `Response` object:
|
|
942
|
+
|
|
874
943
|
**Async CRUD helpers** (available when using an async engine):
|
|
875
944
|
|
|
876
945
|
| Helper | Description |
|
|
@@ -75,6 +75,17 @@ uv add "lightapi[async]"
|
|
|
75
75
|
|
|
76
76
|
**Optional Redis caching**: `redis` is included as a core dependency but Redis caching only activates when `Meta.cache = Cache(ttl=N)` is set on an endpoint. A `RuntimeWarning` is emitted at startup if Redis is unreachable.
|
|
77
77
|
|
|
78
|
+
**Docker (no install required)**: run the API straight from the published image — just mount your config:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
docker run --rm -p 8000:8000 \
|
|
82
|
+
-v "$(pwd)/lightapi.yaml:/app/lightapi.yaml:ro" \
|
|
83
|
+
-e DATABASE_URL=sqlite:////app/data.db \
|
|
84
|
+
iklobato/lightapi:latest
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
See [Docker deployment](docs/deployment/docker.md) for the full guide.
|
|
88
|
+
|
|
78
89
|
---
|
|
79
90
|
|
|
80
91
|
## Quick Start
|
|
@@ -281,7 +292,7 @@ Use `Meta.authentication` with a backend and an optional permission class:
|
|
|
281
292
|
```python
|
|
282
293
|
import os
|
|
283
294
|
from lightapi import RestEndpoint, Authentication, Field
|
|
284
|
-
from lightapi import JWTAuthentication, IsAuthenticated, IsAdminUser
|
|
295
|
+
from lightapi.authentication import JWTAuthentication, IsAuthenticated, IsAdminUser
|
|
285
296
|
|
|
286
297
|
os.environ["LIGHTAPI_JWT_SECRET"] = "your-secret-key"
|
|
287
298
|
|
|
@@ -304,6 +315,60 @@ class AdminOnlyEndpoint(RestEndpoint):
|
|
|
304
315
|
2. Permission class `.has_permission(request)` — checks `request.state.user`
|
|
305
316
|
3. Returns `401` if authentication fails, `403` if permission denied
|
|
306
317
|
|
|
318
|
+
**Custom authentication:** Subclass `JWTAuthentication` or `BasicAuthentication` and override `validate_credentials()`:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from lightapi.authentication import JWTAuthentication
|
|
322
|
+
|
|
323
|
+
class MyAuthBackend(JWTAuthentication):
|
|
324
|
+
async def validate_credentials(self, username: str, password: str) -> dict | None:
|
|
325
|
+
# Custom validation logic - query your database, check LDAP, etc.
|
|
326
|
+
user = await self.get_user_from_db(username)
|
|
327
|
+
if user and await user.verify_password(password):
|
|
328
|
+
return {"sub": str(user.id), "is_admin": user.is_admin}
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
class ProtectedEndpoint(RestEndpoint):
|
|
332
|
+
secret: str = Field(min_length=1)
|
|
333
|
+
class Meta:
|
|
334
|
+
authentication = Authentication(backend=MyAuthBackend)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Login and token endpoints:** When using `JWTAuthentication` or `BasicAuthentication`, pass `login_validator` to obtain automatic `/auth/login` and `/auth/token` endpoints (backward compatible):
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
def my_validator(username: str, password: str):
|
|
341
|
+
# Return user payload dict or None
|
|
342
|
+
user = db.query(User).filter_by(username=username).first()
|
|
343
|
+
if user and user.check_password(password):
|
|
344
|
+
return {"sub": str(user.id), "is_admin": user.is_admin}
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
app = LightApi(engine=engine, login_validator=my_validator)
|
|
348
|
+
app.register({"/secrets": ProtectedEndpoint})
|
|
349
|
+
# POST /auth/login and POST /auth/token now accept {"username":"...","password":"..."}
|
|
350
|
+
# JWT mode: 200 {"token":"...","user":{...}}; Basic-only: 200 {"user":{...}}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Rate limiting:** Add per-endpoint rate limiting via `Authentication` config or global rate limiter:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from lightapi import RestEndpoint, Authentication
|
|
357
|
+
from lightapi.authentication import JWTAuthentication
|
|
358
|
+
|
|
359
|
+
class LimitedEndpoint(RestEndpoint):
|
|
360
|
+
data: str = Field(min_length=1)
|
|
361
|
+
class Meta:
|
|
362
|
+
# Per-endpoint: 5 requests per minute
|
|
363
|
+
authentication = Authentication(
|
|
364
|
+
backend=JWTAuthentication,
|
|
365
|
+
rate_limiter={"requests": 5, "window": 60}
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Or global rate limiter (applied to all endpoints)
|
|
369
|
+
app = LightApi(engine=engine, rate_limiter={"requests": 100, "window": 60})
|
|
370
|
+
```
|
|
371
|
+
|
|
307
372
|
**Built-in permission classes:**
|
|
308
373
|
|
|
309
374
|
| Class | Condition |
|
|
@@ -614,7 +679,7 @@ class OrderEndpoint(RestEndpoint):
|
|
|
614
679
|
|
|
615
680
|
### Async Method Overrides
|
|
616
681
|
|
|
617
|
-
Override individual HTTP verbs with `async def
|
|
682
|
+
Override individual HTTP verbs with `async def`. Mode is auto-detected — no explicit `mode="async"` needed:
|
|
618
683
|
|
|
619
684
|
```python
|
|
620
685
|
class ProductEndpoint(RestEndpoint):
|
|
@@ -632,6 +697,8 @@ class ProductEndpoint(RestEndpoint):
|
|
|
632
697
|
return await self._list_async(request)
|
|
633
698
|
```
|
|
634
699
|
|
|
700
|
+
**Auto-detect mode:** LightAPI automatically detects whether an endpoint method is sync or async by checking if it's a coroutine function. Simply define `async def get()` and the framework will use async execution.
|
|
701
|
+
|
|
635
702
|
**Built-in async CRUD helpers** available on every `RestEndpoint`:
|
|
636
703
|
|
|
637
704
|
| Method | Description |
|
|
@@ -809,12 +876,14 @@ Override these methods to customise behaviour. Both `def` (sync) and `async def`
|
|
|
809
876
|
| `update` | `(data, pk, partial)` | `UPDATE WHERE id=pk AND version=N RETURNING` |
|
|
810
877
|
| `destroy` | `(request, pk)` | `DELETE WHERE id=pk` |
|
|
811
878
|
| `queryset` | `(request)` | Returns base `select(cls._model_class)` |
|
|
812
|
-
| `get` | `(request)` | Override GET (collection or detail) |
|
|
813
|
-
| `post` | `(request)` | Override POST |
|
|
814
|
-
| `put` | `(request)` | Override PUT |
|
|
815
|
-
| `patch` | `(request)` | Override PATCH |
|
|
879
|
+
| `get` | `(request)` | Override GET (collection or detail) — can return `dict` |
|
|
880
|
+
| `post` | `(request)` | Override POST — can return `dict` |
|
|
881
|
+
| `put` | `(request)` | Override PUT — can return `dict` |
|
|
882
|
+
| `patch` | `(request)` | Override PATCH — can return `dict` |
|
|
816
883
|
| `delete` | `(request)` | Override DELETE |
|
|
817
884
|
|
|
885
|
+
**Return dict or Response:** Endpoint override methods can return either a `dict` (auto-wrapped to `JSONResponse`) or a Starlette `Response` object:
|
|
886
|
+
|
|
818
887
|
**Async CRUD helpers** (available when using an async engine):
|
|
819
888
|
|
|
820
889
|
| Helper | Description |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Container entrypoint: load lightapi.yaml and start uvicorn.
|
|
2
|
+
|
|
3
|
+
Resolved configuration values, in order of precedence:
|
|
4
|
+
|
|
5
|
+
1. Environment variables:
|
|
6
|
+
- LIGHTAPI_CONFIG Path to the YAML config (default /app/lightapi.yaml).
|
|
7
|
+
- LIGHTAPI_HOST Host for uvicorn to bind (default 0.0.0.0).
|
|
8
|
+
- LIGHTAPI_PORT Port for uvicorn (default 8000).
|
|
9
|
+
- LIGHTAPI_LOG_LEVEL Uvicorn log level (default info).
|
|
10
|
+
- DATABASE_URL Substituted into ${DATABASE_URL} placeholders in the YAML.
|
|
11
|
+
- LIGHTAPI_JWT_SECRET Required when the config uses JWT authentication.
|
|
12
|
+
2. Defaults baked into this script.
|
|
13
|
+
|
|
14
|
+
The YAML schema is documented at
|
|
15
|
+
https://iklobato.github.io/lightapi/getting-started/configuration/
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import uvicorn
|
|
26
|
+
|
|
27
|
+
from lightapi import LightApi
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("lightapi.entrypoint")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve_config_path() -> Path:
|
|
33
|
+
path = Path(os.environ.get("LIGHTAPI_CONFIG", "/app/lightapi.yaml"))
|
|
34
|
+
if path.exists():
|
|
35
|
+
return path
|
|
36
|
+
|
|
37
|
+
sys.stderr.write(
|
|
38
|
+
f"LightAPI config file not found at {path}.\n"
|
|
39
|
+
"Mount your config into the container, for example:\n"
|
|
40
|
+
" docker run -v ./lightapi.yaml:/app/lightapi.yaml lightapi\n"
|
|
41
|
+
"Or set LIGHTAPI_CONFIG to a different path.\n"
|
|
42
|
+
)
|
|
43
|
+
sys.exit(2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main() -> None:
|
|
47
|
+
logging.basicConfig(
|
|
48
|
+
level=os.environ.get("LIGHTAPI_LOG_LEVEL", "info").upper(),
|
|
49
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
config_path = _resolve_config_path()
|
|
53
|
+
logger.info("Loading LightAPI config from %s", config_path)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
app = LightApi.from_config(str(config_path))
|
|
57
|
+
except Exception:
|
|
58
|
+
logger.exception("Failed to load LightAPI config")
|
|
59
|
+
sys.exit(3)
|
|
60
|
+
|
|
61
|
+
starlette_app = app.build_app()
|
|
62
|
+
|
|
63
|
+
host = os.environ.get("LIGHTAPI_HOST", "0.0.0.0")
|
|
64
|
+
port = int(os.environ.get("LIGHTAPI_PORT", "8000"))
|
|
65
|
+
log_level = os.environ.get("LIGHTAPI_LOG_LEVEL", "info").lower()
|
|
66
|
+
|
|
67
|
+
logger.info("Starting uvicorn on %s:%s", host, port)
|
|
68
|
+
uvicorn.run(
|
|
69
|
+
starlette_app,
|
|
70
|
+
host=host,
|
|
71
|
+
port=port,
|
|
72
|
+
log_level=log_level,
|
|
73
|
+
proxy_headers=True,
|
|
74
|
+
forwarded_allow_ips="*",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Minimal LightAPI config — mount this into the container at /app/lightapi.yaml.
|
|
2
|
+
#
|
|
3
|
+
# docker run --rm -p 8000:8000 \
|
|
4
|
+
# -v "$(pwd)/lightapi.example.yaml:/app/lightapi.yaml:ro" \
|
|
5
|
+
# -e DATABASE_URL=sqlite:////app/data.db \
|
|
6
|
+
# iklobato/lightapi:latest
|
|
7
|
+
#
|
|
8
|
+
# See https://iklobato.github.io/lightapi/getting-started/configuration/ for
|
|
9
|
+
# the full YAML schema (auth, filtering, pagination, per-method permissions,
|
|
10
|
+
# YAML reflection, defaults, middleware, etc.).
|
|
11
|
+
|
|
12
|
+
database:
|
|
13
|
+
url: "${DATABASE_URL}" # ${VAR} env-var substitution
|
|
14
|
+
|
|
15
|
+
endpoints:
|
|
16
|
+
- route: /books
|
|
17
|
+
fields:
|
|
18
|
+
title: { type: str, min_length: 1, max_length: 200 }
|
|
19
|
+
author: { type: str, min_length: 1 }
|
|
20
|
+
year: { type: int, optional: true }
|
|
21
|
+
meta:
|
|
22
|
+
methods: [GET, POST, PUT, PATCH, DELETE]
|
|
23
|
+
pagination:
|
|
24
|
+
style: page_number
|
|
25
|
+
page_size: 25
|
|
26
|
+
|
|
27
|
+
- route: /authors
|
|
28
|
+
fields:
|
|
29
|
+
name: { type: str, min_length: 1 }
|
|
30
|
+
bio: { type: str, optional: true }
|
|
31
|
+
meta:
|
|
32
|
+
methods: [GET, POST]
|
|
@@ -56,7 +56,7 @@ Define `async def queryset` to scope the base query with async I/O or request-le
|
|
|
56
56
|
from sqlalchemy import select
|
|
57
57
|
from starlette.requests import Request
|
|
58
58
|
from lightapi import RestEndpoint, Field
|
|
59
|
-
from lightapi.
|
|
59
|
+
from lightapi.authentication import IsAuthenticated
|
|
60
60
|
from lightapi.config import Authentication
|
|
61
61
|
|
|
62
62
|
class OrderEndpoint(RestEndpoint):
|
|
@@ -314,7 +314,7 @@ from httpx import ASGITransport, AsyncClient
|
|
|
314
314
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
315
315
|
from pydantic import Field
|
|
316
316
|
from lightapi import LightApi, RestEndpoint
|
|
317
|
-
from lightapi.
|
|
317
|
+
from lightapi.authentication import AllowAny
|
|
318
318
|
from lightapi.config import Authentication
|
|
319
319
|
|
|
320
320
|
@pytest_asyncio.fixture
|
|
@@ -39,7 +39,8 @@ Any request that does not carry a valid `Authorization: Bearer <token>` header n
|
|
|
39
39
|
## Authentication class
|
|
40
40
|
|
|
41
41
|
```python
|
|
42
|
-
from lightapi import Authentication
|
|
42
|
+
from lightapi import Authentication
|
|
43
|
+
from lightapi.authentication import JWTAuthentication, IsAuthenticated
|
|
43
44
|
|
|
44
45
|
Authentication(
|
|
45
46
|
backend=JWTAuthentication, # Authentication backend class
|
|
@@ -70,9 +71,44 @@ Authorization: Bearer <jwt-token>
|
|
|
70
71
|
|
|
71
72
|
The token payload is stored in `request.state.user` after successful authentication.
|
|
72
73
|
|
|
73
|
-
###
|
|
74
|
+
### Auto-registered login endpoint
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
When any endpoint declares `Authentication(backend=JWTAuthentication)` (or
|
|
77
|
+
`BasicAuthentication`), LightAPI automatically registers `POST /auth/login`
|
|
78
|
+
and `POST /auth/token` (the same handler under both paths). The base path
|
|
79
|
+
defaults to `/auth` and can be changed via `LightApi(auth_path="/api/auth")`.
|
|
80
|
+
|
|
81
|
+
Pass a `login_validator(username, password) -> dict | None` to `LightApi(...)`
|
|
82
|
+
to validate credentials:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
def login_validator(username: str, password: str):
|
|
86
|
+
if username == "admin" and password == "secret":
|
|
87
|
+
return {"sub": "1", "username": "admin", "is_admin": True}
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
app = LightApi(engine=engine, login_validator=login_validator)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The dict returned by the validator is the JWT payload. On JWT-protected apps,
|
|
94
|
+
`POST /auth/login` returns `{"token": "<jwt>", "user": {...}}`; the client
|
|
95
|
+
then sends `Authorization: Bearer <jwt>` on subsequent requests.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# 1. Log in to obtain a token
|
|
99
|
+
curl -X POST http://localhost:8000/auth/login \
|
|
100
|
+
-H 'Content-Type: application/json' \
|
|
101
|
+
-d '{"username":"admin","password":"secret"}'
|
|
102
|
+
# → 200 {"token": "eyJhbGc...", "user": {"sub": "1", ...}}
|
|
103
|
+
|
|
104
|
+
# 2. Call a protected endpoint
|
|
105
|
+
curl -H 'Authorization: Bearer eyJhbGc...' http://localhost:8000/posts
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Generating tokens manually
|
|
109
|
+
|
|
110
|
+
If you do not want to use `login_validator` and the auto-registered endpoint,
|
|
111
|
+
issue tokens directly:
|
|
76
112
|
|
|
77
113
|
```python
|
|
78
114
|
import jwt, os, datetime
|
|
@@ -104,7 +140,8 @@ Allows access only if `JWTAuthentication.authenticate()` returns `True` (valid t
|
|
|
104
140
|
Allows access only if the token payload contains `"is_admin": true`.
|
|
105
141
|
|
|
106
142
|
```python
|
|
107
|
-
from lightapi import Authentication
|
|
143
|
+
from lightapi import Authentication
|
|
144
|
+
from lightapi.authentication import JWTAuthentication, IsAdminUser
|
|
108
145
|
|
|
109
146
|
class AdminEndpoint(RestEndpoint):
|
|
110
147
|
name: str
|
|
@@ -148,7 +185,7 @@ class ArticleEndpoint(RestEndpoint):
|
|
|
148
185
|
Subclass `BaseAuthentication` to implement your own logic:
|
|
149
186
|
|
|
150
187
|
```python
|
|
151
|
-
from lightapi.
|
|
188
|
+
from lightapi.authentication import BaseAuthentication
|
|
152
189
|
|
|
153
190
|
class ApiKeyAuthentication(BaseAuthentication):
|
|
154
191
|
def authenticate(self, request) -> bool:
|
|
@@ -171,7 +208,7 @@ class SecureEndpoint(RestEndpoint):
|
|
|
171
208
|
Subclass `BasePermission` to implement your own access control:
|
|
172
209
|
|
|
173
210
|
```python
|
|
174
|
-
from lightapi.
|
|
211
|
+
from lightapi.authentication import BasePermission
|
|
175
212
|
|
|
176
213
|
class IsOwner(BasePermission):
|
|
177
214
|
def has_permission(self, request) -> bool:
|
|
@@ -217,7 +217,7 @@ import pytest_asyncio
|
|
|
217
217
|
from httpx import ASGITransport, AsyncClient
|
|
218
218
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
219
219
|
from lightapi import LightApi, RestEndpoint
|
|
220
|
-
from lightapi.
|
|
220
|
+
from lightapi.authentication import AllowAny
|
|
221
221
|
from lightapi.config import Authentication
|
|
222
222
|
from lightapi.core import Middleware
|
|
223
223
|
from pydantic import Field
|