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.
Files changed (180) hide show
  1. lightapi-0.1.22/.dockerignore +29 -0
  2. lightapi-0.1.22/.github/workflows/docker-publish.yml +92 -0
  3. lightapi-0.1.22/Dockerfile +65 -0
  4. {lightapi-0.1.20 → lightapi-0.1.22}/PKG-INFO +76 -7
  5. {lightapi-0.1.20 → lightapi-0.1.22}/README.md +75 -6
  6. lightapi-0.1.22/docker/entrypoint.py +79 -0
  7. lightapi-0.1.22/docker/lightapi.example.yaml +32 -0
  8. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/async.md +2 -2
  9. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/authentication.md +43 -6
  10. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/middleware.md +1 -1
  11. lightapi-0.1.22/docs/advanced/rate-limiting.md +84 -0
  12. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/auth.md +25 -3
  13. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/core.md +32 -1
  14. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/index.md +1 -0
  15. lightapi-0.1.22/docs/deployment/docker.md +250 -0
  16. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/advanced-permissions.md +1 -1
  17. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/auth.md +28 -3
  18. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/basic-crud.md +3 -0
  19. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/custom-application.md +1 -1
  20. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/yaml-configuration.md +13 -1
  21. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/configuration.md +26 -4
  22. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/first-steps.md +10 -1
  23. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/quickstart.md +1 -1
  24. {lightapi-0.1.20 → lightapi-0.1.22}/docs/index.md +1 -0
  25. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/core-api.md +18 -1
  26. {lightapi-0.1.20 → lightapi-0.1.22}/docs/troubleshooting.md +3 -2
  27. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/basic-api.md +21 -15
  28. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/database.md +7 -3
  29. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/endpoints.md +8 -7
  30. lightapi-0.1.22/examples/01_minimal.py +53 -0
  31. lightapi-0.1.22/examples/02_crud.py +78 -0
  32. lightapi-0.1.22/examples/03_auth_jwt.py +82 -0
  33. lightapi-0.1.22/examples/04_auth_basic.py +73 -0
  34. lightapi-0.1.22/examples/05_permissions.py +103 -0
  35. lightapi-0.1.22/examples/06_pagination_page.py +64 -0
  36. lightapi-0.1.22/examples/07_pagination_cursor.py +57 -0
  37. lightapi-0.1.22/examples/08_filtering.py +67 -0
  38. lightapi-0.1.22/examples/09_caching.py +73 -0
  39. lightapi-0.1.22/examples/10_async.py +63 -0
  40. lightapi-0.1.22/examples/11_mixed_sync_async.py +69 -0
  41. lightapi-0.1.22/examples/12_queryset.py +70 -0
  42. lightapi-0.1.22/examples/13_middleware.py +86 -0
  43. lightapi-0.1.22/examples/14_background.py +69 -0
  44. lightapi-0.1.22/examples/15_yaml_config.py +73 -0
  45. lightapi-0.1.22/examples/16_rate_limit.py +70 -0
  46. lightapi-0.1.22/examples/17_relationships.py +58 -0
  47. lightapi-0.1.22/examples/18_full_api.py +171 -0
  48. {lightapi-0.1.20 → lightapi-0.1.22}/examples/v2_full_demo.py +8 -3
  49. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/__init__.py +15 -1
  50. lightapi-0.1.22/lightapi/_dict_config_loader.py +156 -0
  51. lightapi-0.1.22/lightapi/_login.py +157 -0
  52. lightapi-0.1.22/lightapi/auth.py +25 -0
  53. lightapi-0.1.22/lightapi/auth_checker.py +68 -0
  54. lightapi-0.1.22/lightapi/auth_service.py +106 -0
  55. lightapi-0.1.22/lightapi/auth_strategy.py +66 -0
  56. lightapi-0.1.22/lightapi/authentication/__init__.py +24 -0
  57. lightapi-0.1.22/lightapi/authentication/base.py +101 -0
  58. lightapi-0.1.22/lightapi/authentication/basic.py +77 -0
  59. lightapi-0.1.22/lightapi/authentication/jwt.py +105 -0
  60. lightapi-0.1.22/lightapi/body_reader.py +15 -0
  61. lightapi-0.1.22/lightapi/cache.py +294 -0
  62. lightapi-0.1.22/lightapi/cache_handler.py +126 -0
  63. lightapi-0.1.22/lightapi/cache_helper.py +54 -0
  64. lightapi-0.1.22/lightapi/cache_service.py +143 -0
  65. lightapi-0.1.22/lightapi/config.py +170 -0
  66. lightapi-0.1.22/lightapi/constants.py +99 -0
  67. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/core.py +0 -76
  68. lightapi-0.1.22/lightapi/crud_async.py +274 -0
  69. lightapi-0.1.22/lightapi/crud_sync.py +281 -0
  70. lightapi-0.1.22/lightapi/handler_factory.py +167 -0
  71. lightapi-0.1.22/lightapi/http_dispatcher.py +145 -0
  72. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/lightapi.py +409 -69
  73. lightapi-0.1.22/lightapi/middleware_pipeline.py +84 -0
  74. lightapi-0.1.22/lightapi/middleware_runner.py +36 -0
  75. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/pagination.py +81 -91
  76. lightapi-0.1.22/lightapi/queryset.py +65 -0
  77. lightapi-0.1.22/lightapi/rate_limiter.py +174 -0
  78. lightapi-0.1.22/lightapi/response_wrapper.py +12 -0
  79. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/rest.py +86 -191
  80. lightapi-0.1.22/lightapi/route_builder.py +92 -0
  81. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/schema.py +46 -2
  82. lightapi-0.1.22/lightapi/session_manager.py +227 -0
  83. lightapi-0.1.22/lightapi/table_mapping.py +235 -0
  84. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/yaml_loader.py +160 -12
  85. {lightapi-0.1.20 → lightapi-0.1.22}/pyproject.toml +19 -1
  86. {lightapi-0.1.20 → lightapi-0.1.22}/tests/conftest.py +8 -2
  87. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_crud.py +2 -2
  88. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_middleware.py +1 -1
  89. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_queryset.py +1 -1
  90. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_reflection.py +7 -7
  91. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_auth.py +7 -1
  92. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_background_tasks.py +1 -1
  93. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_cache_v2.py +9 -9
  94. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_crud.py +1 -1
  95. lightapi-0.1.22/tests/test_example_01_minimal.py +203 -0
  96. lightapi-0.1.22/tests/test_example_03_auth_jwt.py +154 -0
  97. lightapi-0.1.22/tests/test_example_06_pagination_page.py +60 -0
  98. lightapi-0.1.22/tests/test_example_08_filtering.py +99 -0
  99. lightapi-0.1.22/tests/test_examples_e2e.py +625 -0
  100. lightapi-0.1.22/tests/test_login_auth.py +1001 -0
  101. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_mixed_sync_async.py +1 -1
  102. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_queryset.py +6 -5
  103. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_reflection.py +10 -13
  104. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_rest.py +21 -0
  105. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_yaml_config.py +168 -2
  106. {lightapi-0.1.20 → lightapi-0.1.22}/uv.lock +1 -1
  107. lightapi-0.1.20/docs/deployment/docker.md +0 -50
  108. lightapi-0.1.20/lightapi/_registry.py +0 -37
  109. lightapi-0.1.20/lightapi/auth.py +0 -147
  110. lightapi-0.1.20/lightapi/cache.py +0 -173
  111. lightapi-0.1.20/lightapi/config.py +0 -137
  112. {lightapi-0.1.20 → lightapi-0.1.22}/.cursorrules +0 -0
  113. {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/pages-publish.yml +0 -0
  114. {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/python-publish.yml +0 -0
  115. {lightapi-0.1.20 → lightapi-0.1.22}/.github/workflows/test-dev.yml +0 -0
  116. {lightapi-0.1.20 → lightapi-0.1.22}/.gitignore +0 -0
  117. {lightapi-0.1.20 → lightapi-0.1.22}/LICENSE +0 -0
  118. {lightapi-0.1.20 → lightapi-0.1.22}/api_config.yaml +0 -0
  119. {lightapi-0.1.20 → lightapi-0.1.22}/docs/.pages +0 -0
  120. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/.pages +0 -0
  121. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/caching.md +0 -0
  122. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/filtering.md +0 -0
  123. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/pagination.md +0 -0
  124. {lightapi-0.1.20 → lightapi-0.1.22}/docs/advanced/validation.md +0 -0
  125. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/.pages +0 -0
  126. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/cache.md +0 -0
  127. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/database.md +0 -0
  128. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/exceptions.md +0 -0
  129. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/filters.md +0 -0
  130. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/models.md +0 -0
  131. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/pagination.md +0 -0
  132. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/rest.md +0 -0
  133. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/swagger.md +0 -0
  134. {lightapi-0.1.20 → lightapi-0.1.22}/docs/api-reference/validation.md +0 -0
  135. {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/.pages +0 -0
  136. {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/production.md +0 -0
  137. {lightapi-0.1.20 → lightapi-0.1.22}/docs/deployment/security.md +0 -0
  138. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/.pages +0 -0
  139. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/basic-rest.md +0 -0
  140. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/caching.md +0 -0
  141. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/environment-variables.md +0 -0
  142. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/filtering-pagination.md +0 -0
  143. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/middleware.md +0 -0
  144. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/readonly-apis.md +0 -0
  145. {lightapi-0.1.20 → lightapi-0.1.22}/docs/examples/validation.md +0 -0
  146. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/.pages +0 -0
  147. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/installation.md +0 -0
  148. {lightapi-0.1.20 → lightapi-0.1.22}/docs/getting-started/introduction.md +0 -0
  149. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/.pages +0 -0
  150. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/cache.md +0 -0
  151. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/endpoints.md +0 -0
  152. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/handlers.md +0 -0
  153. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/middleware.md +0 -0
  154. {lightapi-0.1.20 → lightapi-0.1.22}/docs/technical-reference/models.md +0 -0
  155. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/.pages +0 -0
  156. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/requests.md +0 -0
  157. {lightapi-0.1.20 → lightapi-0.1.22}/docs/tutorial/responses.md +0 -0
  158. {lightapi-0.1.20 → lightapi-0.1.22}/examples/postgres_full.py +0 -0
  159. {lightapi-0.1.20 → lightapi-0.1.22}/examples/smoke_async.py +0 -0
  160. {lightapi-0.1.20 → lightapi-0.1.22}/examples/v2_quickstart.py +0 -0
  161. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/exceptions.py +0 -0
  162. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/fields.py +0 -0
  163. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/filters.py +0 -0
  164. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/methods.py +0 -0
  165. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/session.py +0 -0
  166. {lightapi-0.1.20 → lightapi-0.1.22}/lightapi/swagger.py +0 -0
  167. {lightapi-0.1.20 → lightapi-0.1.22}/mkdocs.yml +0 -0
  168. {lightapi-0.1.20 → lightapi-0.1.22}/overrides/404.html +0 -0
  169. {lightapi-0.1.20 → lightapi-0.1.22}/pytest.ini +0 -0
  170. {lightapi-0.1.20 → lightapi-0.1.22}/requirements.txt +0 -0
  171. {lightapi-0.1.20 → lightapi-0.1.22}/run_server.py +0 -0
  172. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_async_session.py +0 -0
  173. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_filtering.py +0 -0
  174. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_http_methods.py +0 -0
  175. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_lightapi_init.py +0 -0
  176. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_middleware.py +0 -0
  177. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_pipeline.py +0 -0
  178. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_schema.py +0 -0
  179. {lightapi-0.1.20 → lightapi-0.1.22}/tests/test_serializer.py +0 -0
  180. {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.20
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.auth import IsAuthenticated
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.auth import AllowAny
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, JWTAuthentication, IsAuthenticated
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
- ### Generating tokens
74
+ ### Auto-registered login endpoint
74
75
 
75
- LightAPI does not include a login endpoint you generate tokens in your own code:
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, JWTAuthentication, IsAdminUser
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.auth import BaseAuthentication
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.auth import BasePermission
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.auth import AllowAny
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