python-flashapi 0.2.0__tar.gz → 0.4.0__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 (102) hide show
  1. python_flashapi-0.4.0/.github/workflows/release-please.yml +48 -0
  2. python_flashapi-0.4.0/.release-please-manifest.json +3 -0
  3. python_flashapi-0.4.0/CHANGELOG.md +57 -0
  4. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/PKG-INFO +21 -5
  5. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/README.md +19 -3
  6. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/custom-logic.md +41 -2
  7. python_flashapi-0.4.0/docs/logo.svg +25 -0
  8. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/pyproject.toml +1 -1
  9. python_flashapi-0.4.0/release-please-config.json +10 -0
  10. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/adapters/django.py +72 -1
  11. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/adapters/fastapi.py +47 -1
  12. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/adapters/flask.py +49 -1
  13. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/custom_routes.py +24 -2
  14. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/schema.py +4 -1
  15. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/visibility.py +36 -0
  16. python_flashapi-0.4.0/src/flashapi/features/cache.py +229 -0
  17. python_flashapi-0.4.0/src/flashapi/features/health.py +69 -0
  18. python_flashapi-0.4.0/src/flashapi/features/idempotency.py +375 -0
  19. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/storage/orm.py +3 -0
  20. python_flashapi-0.4.0/tests/test_features/test_field_selection.py +127 -0
  21. python_flashapi-0.4.0/tests/test_health.py +42 -0
  22. python_flashapi-0.2.0/.github/workflows/publish.yml +0 -29
  23. python_flashapi-0.2.0/CHANGELOG.md +0 -24
  24. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/.github/workflows/ci.yml +0 -0
  25. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/.gitignore +0 -0
  26. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/LICENSE +0 -0
  27. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/NOTICE +0 -0
  28. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/authentication.md +0 -0
  29. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/customization.md +0 -0
  30. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/examples.md +0 -0
  31. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/features.md +0 -0
  32. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/framework-notes.md +0 -0
  33. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/integration.md +0 -0
  34. /python_flashapi-0.2.0/docs/logo.svg → /python_flashapi-0.4.0/docs/logo1.svg +0 -0
  35. /python_flashapi-0.2.0/docs/logo1.svg → /python_flashapi-0.4.0/docs/logo2.svg +0 -0
  36. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/docs/relations.md +0 -0
  37. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/examples/fastapi_example.py +0 -0
  38. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/examples/flask_example.py +0 -0
  39. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/__init__.py +0 -0
  40. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/adapters/__init__.py +0 -0
  41. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/adapters/base.py +0 -0
  42. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/__init__.py +0 -0
  43. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/pluralize.py +0 -0
  44. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/relations.py +0 -0
  45. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/core/response.py +0 -0
  46. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/django.py +0 -0
  47. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/docs/__init__.py +0 -0
  48. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/docs/openapi.py +0 -0
  49. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/fastapi.py +0 -0
  50. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/__init__.py +0 -0
  51. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/audit.py +0 -0
  52. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/auth.py +0 -0
  53. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/dashboard.py +0 -0
  54. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/export.py +0 -0
  55. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/filtering.py +0 -0
  56. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/pagination.py +0 -0
  57. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/rate_limit.py +0 -0
  58. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/search.py +0 -0
  59. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/sorting.py +0 -0
  60. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/webhooks.py +0 -0
  61. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/features/websocket.py +0 -0
  62. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/flask.py +0 -0
  63. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/__init__.py +0 -0
  64. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/base.py +0 -0
  65. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/dataclass.py +0 -0
  66. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/detect.py +0 -0
  67. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/django.py +0 -0
  68. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/pydantic.py +0 -0
  69. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/inspectors/sqlalchemy.py +0 -0
  70. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/py.typed +0 -0
  71. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/storage/__init__.py +0 -0
  72. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/storage/auto.py +0 -0
  73. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/storage/base.py +0 -0
  74. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/src/flashapi/storage/sqlalchemy.py +0 -0
  75. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/__init__.py +0 -0
  76. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_adapters/__init__.py +0 -0
  77. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_adapters/test_fastapi.py +0 -0
  78. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_adapters/test_flask.py +0 -0
  79. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_adapters/test_relations.py +0 -0
  80. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_adapters/test_soft_delete.py +0 -0
  81. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_core/__init__.py +0 -0
  82. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_core/test_pluralize.py +0 -0
  83. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_core/test_response.py +0 -0
  84. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_core/test_schema.py +0 -0
  85. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_core/test_visibility.py +0 -0
  86. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/__init__.py +0 -0
  87. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_audit.py +0 -0
  88. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_auth.py +0 -0
  89. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_dashboard.py +0 -0
  90. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_export.py +0 -0
  91. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_filtering.py +0 -0
  92. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_pagination.py +0 -0
  93. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_rate_limit.py +0 -0
  94. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_search.py +0 -0
  95. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_sorting.py +0 -0
  96. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_features/test_websocket.py +0 -0
  97. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_inspectors/__init__.py +0 -0
  98. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_inspectors/test_dataclass.py +0 -0
  99. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_inspectors/test_detect.py +0 -0
  100. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_inspectors/test_pydantic.py +0 -0
  101. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_storage/__init__.py +0 -0
  102. {python_flashapi-0.2.0 → python_flashapi-0.4.0}/tests/test_storage/test_auto.py +0 -0
@@ -0,0 +1,48 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ release-please:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ release_created: ${{ steps.release.outputs.release_created }}
16
+ tag_name: ${{ steps.release.outputs.tag_name }}
17
+
18
+ steps:
19
+ - uses: googleapis/release-please-action@v4
20
+ id: release
21
+ with:
22
+ release-type: python
23
+ package-name: python-flashapi
24
+
25
+ publish:
26
+ needs: release-please
27
+ if: ${{ needs.release-please.outputs.release_created }}
28
+ runs-on: ubuntu-latest
29
+
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v5
35
+ with:
36
+ python-version: '3.12'
37
+
38
+ - name: Install build tools
39
+ run: pip install build twine
40
+
41
+ - name: Build package
42
+ run: python -m build
43
+
44
+ - name: Publish to PyPI
45
+ run: twine upload dist/*
46
+ env:
47
+ TWINE_USERNAME: __token__
48
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.0"
3
+ }
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ ## [0.4.0](https://github.com/HackermanMe/flashapi/compare/v0.3.0...v0.4.0) (2026-09-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * add field selection (?fields=id,name,price) ([4c07e45](https://github.com/HackermanMe/flashapi/commit/4c07e4536ab442f3a984ec8108154ef47f361098))
9
+ * add idempotency keys, cache layer, and currentUserField auto-injection ([141ae08](https://github.com/HackermanMe/flashapi/commit/141ae084afd4d605540a0cead5c4aac9cd6c41f6))
10
+ * add interactive CRUD dashboard with HTMX and real-time WebSocket ([1b1b4a5](https://github.com/HackermanMe/flashapi/commit/1b1b4a5c9924db5858119a69fb9c2585c687fe6d))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **django:** convert URL path params to OpenAPI format in discover_django_views ([78e5675](https://github.com/HackermanMe/flashapi/commit/78e5675b8fafe0a87a7711f30f9b3db26bfd9dd6))
16
+ * simplify nested if statement for ruff linter ([73fb95b](https://github.com/HackermanMe/flashapi/commit/73fb95bdd7282141081a85d0f44d6d8c4478967e))
17
+ * sync release-please manifest and README version to 0.3.0 ([1538199](https://github.com/HackermanMe/flashapi/commit/1538199c6f9edc3fb4be9a17d9b69c67838a90bb))
18
+
19
+ ## [0.3.0](https://github.com/HackermanMe/flashapi/compare/v0.2.0...v0.3.0) (2026-08-08)
20
+
21
+
22
+ ### Features
23
+
24
+ * add production health check endpoints (/health and /ready) ([cc4e881](https://github.com/HackermanMe/flashapi/commit/cc4e881f09055fa990bb054298ebc8c1f55af7af))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * corriger les gradients et les couleurs dans les fichiers SVG du logo ([50aadaf](https://github.com/HackermanMe/flashapi/commit/50aadaff44919e5666621cc3f645dd34c1364a64))
30
+
31
+
32
+ ### Documentation
33
+
34
+ * add release-please version marker in README ([515aeb8](https://github.com/HackermanMe/flashapi/commit/515aeb83c2f021bebac25d9690c4726003769746))
35
+
36
+ ## 0.1.0 (2026-07-09)
37
+
38
+ Initial release.
39
+
40
+ ### Features
41
+ - Auto-generate REST API + CRUD from model definitions
42
+ - Framework support: FastAPI, Django, Flask
43
+ - Model support: Django ORM, SQLAlchemy, Pydantic, dataclass
44
+ - Pagination, filtering, sorting, full-text search
45
+ - Relation detection: nested routes and `?expand=`
46
+ - OpenAPI 3.1.0 schema generation + Swagger UI
47
+ - French + English pluralization
48
+ - `Model()` wrapper: readonly, exclude, only, plural
49
+ - `@api_doc()` decorator for custom routes in Swagger
50
+ - `engine=` parameter for SQLAlchemy database reuse
51
+ - Custom response formatter support
52
+
53
+ ### Security
54
+ - SQL identifier validation (prevents injection via table/column names)
55
+ - Session rollback on SQLAlchemy errors
56
+ - Input validation for pagination parameters
57
+ - CSRF exempt on Django API views (documented behavior)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-flashapi
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Define your models. FlashAPI does the rest.
5
5
  Project-URL: Homepage, https://github.com/HackermanMe/flashapi
6
6
  Project-URL: Documentation, https://github.com/HackermanMe/flashapi#readme
@@ -64,10 +64,12 @@ Description-Content-Type: text/markdown
64
64
 
65
65
  ---
66
66
 
67
- FlashAPI generates a full REST API with CRUD, pagination, filtering, sorting, full-text search, relations, soft delete, bulk operations, export, audit trail, webhooks, rate limiting, and a live dashboard — from your existing models, in one line.
67
+ FlashAPI generates a full REST API with CRUD, pagination, filtering, sorting, full-text search, relations, soft delete, bulk operations, export, audit trail, webhooks, rate limiting, **idempotency keys**, **cache layer**, **currentUser auto-injection**, and a live dashboard — from your existing models, in one line.
68
68
 
69
69
  Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across all backends (Python, Java Spring, Node.js).
70
70
 
71
+ **New in 0.4.0:** Idempotency keys (prevent double-click/retry duplicates), cache layer with graceful fallback, currentUserField auto-injection, and audit enabled by default.
72
+
71
73
  ---
72
74
 
73
75
  ## Documentation
@@ -88,7 +90,12 @@ Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across
88
90
  ## Installation
89
91
 
90
92
  ```bash
91
- pip install python-flashapi[fastapi] # or python-flashapi[flask] or python-flashapi[all]
93
+ # x-release-please-start-version
94
+ pip install python-flashapi==0.3.0
95
+ # x-release-please-end
96
+
97
+ # With framework extras:
98
+ pip install python-flashapi[fastapi] # or [flask] or [django] or [all]
92
99
  ```
93
100
 
94
101
  ---
@@ -226,8 +233,9 @@ FlashAPI(
226
233
  Model(Config, readonly=True, access="admin"), # GET only, admin
227
234
  Model(Log, only=["list"]), # List only
228
235
  Model(Animal, plural="animaux"), # Custom plural
229
- Model(Invoice, soft_delete=True, audit=True), # Opt-in features
236
+ Model(Invoice, soft_delete=True, audit=True), # Opt-in features (audit=True by default)
230
237
  Model(Eleve, access="staff", scope="tenant", tenant_field="ecole_id"),
238
+ Model(Post, current_user_field="author"), # Auto-inject authenticated user on create
231
239
  ],
232
240
  base_path="/api", # Configurable prefix (default: /api)
233
241
  auth_backend=MyAuth(), # Your AuthBackend implementation
@@ -235,6 +243,14 @@ FlashAPI(
235
243
  rate_limit=100, # 100 requests per window
236
244
  rate_window=60, # 60 seconds window
237
245
  )
246
+
247
+ # Enable idempotency (prevents duplicate operations from double-click/retry)
248
+ # Client sends: Idempotency-Key: <uuid> header
249
+ # Same key + same request → returns stored response (no duplicate created)
250
+
251
+ # Enable cache layer (graceful fallback if Redis down)
252
+ from flashapi.features.cache import CacheLayer, RedisCache, register_cache
253
+ register_cache(CacheLayer(RedisCache(host='localhost')))
238
254
  ```
239
255
 
240
256
  See [Customization docs](docs/customization.md) for all options.
@@ -22,10 +22,12 @@
22
22
 
23
23
  ---
24
24
 
25
- FlashAPI generates a full REST API with CRUD, pagination, filtering, sorting, full-text search, relations, soft delete, bulk operations, export, audit trail, webhooks, rate limiting, and a live dashboard — from your existing models, in one line.
25
+ FlashAPI generates a full REST API with CRUD, pagination, filtering, sorting, full-text search, relations, soft delete, bulk operations, export, audit trail, webhooks, rate limiting, **idempotency keys**, **cache layer**, **currentUser auto-injection**, and a live dashboard — from your existing models, in one line.
26
26
 
27
27
  Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across all backends (Python, Java Spring, Node.js).
28
28
 
29
+ **New in 0.4.0:** Idempotency keys (prevent double-click/retry duplicates), cache layer with graceful fallback, currentUserField auto-injection, and audit enabled by default.
30
+
29
31
  ---
30
32
 
31
33
  ## Documentation
@@ -46,7 +48,12 @@ Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across
46
48
  ## Installation
47
49
 
48
50
  ```bash
49
- pip install python-flashapi[fastapi] # or python-flashapi[flask] or python-flashapi[all]
51
+ # x-release-please-start-version
52
+ pip install python-flashapi==0.3.0
53
+ # x-release-please-end
54
+
55
+ # With framework extras:
56
+ pip install python-flashapi[fastapi] # or [flask] or [django] or [all]
50
57
  ```
51
58
 
52
59
  ---
@@ -184,8 +191,9 @@ FlashAPI(
184
191
  Model(Config, readonly=True, access="admin"), # GET only, admin
185
192
  Model(Log, only=["list"]), # List only
186
193
  Model(Animal, plural="animaux"), # Custom plural
187
- Model(Invoice, soft_delete=True, audit=True), # Opt-in features
194
+ Model(Invoice, soft_delete=True, audit=True), # Opt-in features (audit=True by default)
188
195
  Model(Eleve, access="staff", scope="tenant", tenant_field="ecole_id"),
196
+ Model(Post, current_user_field="author"), # Auto-inject authenticated user on create
189
197
  ],
190
198
  base_path="/api", # Configurable prefix (default: /api)
191
199
  auth_backend=MyAuth(), # Your AuthBackend implementation
@@ -193,6 +201,14 @@ FlashAPI(
193
201
  rate_limit=100, # 100 requests per window
194
202
  rate_window=60, # 60 seconds window
195
203
  )
204
+
205
+ # Enable idempotency (prevents duplicate operations from double-click/retry)
206
+ # Client sends: Idempotency-Key: <uuid> header
207
+ # Same key + same request → returns stored response (no duplicate created)
208
+
209
+ # Enable cache layer (graceful fallback if Redis down)
210
+ from flashapi.features.cache import CacheLayer, RedisCache, register_cache
211
+ register_cache(CacheLayer(RedisCache(host='localhost')))
196
212
  ```
197
213
 
198
214
  See [Customization docs](docs/customization.md) for all options.
@@ -66,6 +66,7 @@ Just add `@api_doc()` on your views. FlashAPI auto-discovers them:
66
66
  # views.py
67
67
  from django.http import JsonResponse
68
68
  from django.views.decorators.csrf import csrf_exempt
69
+ from django.views.decorators.http import require_POST
69
70
  from flashapi import api_doc
70
71
  import json
71
72
 
@@ -73,6 +74,7 @@ import json
73
74
  body={"client_id": "int", "table_id": "int", "plats": "array"},
74
75
  body_required=["client_id", "table_id", "plats"])
75
76
  @csrf_exempt
77
+ @require_POST
76
78
  def checkout(request):
77
79
  body = json.loads(request.body)
78
80
  # your business logic...
@@ -106,12 +108,49 @@ custom_views = [
106
108
  urlpatterns = [
107
109
  path("api/", include(
108
110
  generate_urls(models=[Plat, Commande, Client], extra_views=custom_views)
109
- + custom_views
110
111
  )),
112
+ # Register custom views for actual routing (extra_views only adds them to Swagger)
113
+ path("api/checkout/", views.checkout),
114
+ path("api/stats/chiffre-affaires/", views.chiffre_affaires),
115
+ path("api/reports/bestsellers/", views.bestsellers),
116
+ ]
117
+ ```
118
+
119
+ That's it. `@api_doc` on the view + pass `extra_views` to `generate_urls()` for Swagger documentation, and register the same paths in `urlpatterns` for actual routing.
120
+
121
+ #### Path parameters
122
+
123
+ Django URL parameters (`<int:id>`, `<str:slug>`, etc.) are automatically converted to OpenAPI path parameters in Swagger:
124
+
125
+ ```python
126
+ # views.py
127
+ @api_doc(
128
+ tag="Ingestion",
129
+ summary="Ingest a book from its uploaded PDF",
130
+ methods=["post"],
131
+ params={"book_id": "int"},
132
+ body={"force": "bool"},
133
+ body_required=[],
134
+ response={"status": "string", "total_sentences": "int"},
135
+ )
136
+ @csrf_exempt
137
+ @require_POST
138
+ def ingest_view(request, book_id):
139
+ # book_id comes from the URL path
140
+ ...
141
+
142
+ # urls.py
143
+ extra_views = [
144
+ path("books/<int:book_id>/ingest/", views.ingest_view),
145
+ ]
146
+
147
+ urlpatterns = [
148
+ path("api/", include(generate_urls(models=[Book], extra_views=extra_views))),
149
+ path("api/books/<int:book_id>/ingest/", views.ingest_view),
111
150
  ]
112
151
  ```
113
152
 
114
- That's it. `@api_doc` on the view + pass `extra_views` to `generate_urls()`. FlashAPI reads the decorator metadata and adds your endpoints to Swagger no duplicate declarations.
153
+ In Swagger, this renders as `/books/{book_id}/ingest/` with `book_id` as a required integer path parameter. The `params` dict in `@api_doc` controls the type shown in Swagger (`"int"` integer, `"string"` string, etc.).
115
154
 
116
155
  ### Custom routes (Flask)
117
156
 
@@ -0,0 +1,25 @@
1
+ <svg width="387" height="122" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="bolt" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#FFD700"/>
5
+ <stop offset="100%" stop-color="#FF8C00"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Lightning bolt icon -->
9
+ <!-- FlashAPI text -->
10
+ <!-- Tagline -->
11
+ <g>
12
+ <title>Layer 1</title>
13
+ <text x="113" y="71" font-family="Inter, -apple-system, sans-serif" font-size="42" font-weight="bold" fill="#ff5000" id="svg_2" font-style="italic">Flash</text>
14
+ <text x="222" y="70" font-family="Inter, -apple-system, sans-serif" font-size="42" font-weight="bold" fill="url(#bolt)" id="svg_3" font-style="italic">API</text>
15
+ <text x="113" y="95" font-family="Inter, -apple-system, sans-serif" font-size="13" fill="#666" id="svg_4">Define your models. FlashAPI does the rest.</text>
16
+ <g id="svg_1">
17
+ <circle id="svg_5" cx="52" cy="63.5" r="48" opacity="0.35"/>
18
+ <circle id="svg_6" cx="52" cy="59.5" r="48" fill="#ffc400"/>
19
+ <circle id="svg_7" cx="52" cy="63.5" r="39" opacity="0.35"/>
20
+ <circle id="svg_8" cx="52" cy="59.5" r="39" fill="#ff1200"/>
21
+ <polygon id="svg_9" points="83,8.5 34,59.5 50.01200866699219,56.45100402832031 29.772994995117188,85.5 45,82.5 22,117.5 69,70.5 55,72.5 76,44.5 59,49.5 " opacity="0.35"/>
22
+ <polygon id="svg_10" fill="#ffc400" points="83,4.5 34,55.5 50.01200866699219,52.45100402832031 29.772994995117188,81.5 45,78.5 22,113.5 69,66.5 55,68.5 76,40.5 59,45.5 "/>
23
+ </g>
24
+ </g>
25
+ </svg>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-flashapi"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Define your models. FlashAPI does the rest."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,10 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "python",
5
+ "package-name": "python-flashapi",
6
+ "bump-minor-pre-major": true,
7
+ "extra-files": ["pyproject.toml", "README.md"]
8
+ }
9
+ }
10
+ }
@@ -9,9 +9,10 @@ from flashapi.core.custom_routes import (
9
9
  )
10
10
  from flashapi.core.response import create_error_response, create_item_response, create_list_response
11
11
  from flashapi.core.schema import Model, ModelSchema
12
- from flashapi.core.visibility import export_fields, filter_response, writable_fields
12
+ from flashapi.core.visibility import export_fields, filter_response, select_fields, writable_fields
13
13
  from flashapi.docs.openapi import generate_openapi_schema, get_swagger_html
14
14
  from flashapi.features import apply_filters, apply_search, apply_sorting, paginate
15
+ from flashapi.features.health import get_health_check
15
16
  from flashapi.inspectors import inspect_model
16
17
  from flashapi.storage.orm import DjangoORMStorage
17
18
 
@@ -77,6 +78,7 @@ def generate_urls(
77
78
  schema.scope = wrapper.scope
78
79
  schema.tenant_field = wrapper.tenant_field
79
80
  schema.owner_field = wrapper.owner_field
81
+ schema.current_user_field = wrapper.current_user_field
80
82
 
81
83
  from flashapi.core.schema import validate_soft_delete
82
84
  validate_soft_delete(wrapper.model_class, wrapper.soft_delete)
@@ -113,6 +115,7 @@ def generate_urls(
113
115
  ))
114
116
 
115
117
  urlpatterns.extend(_create_api_root_view(all_schemas, docs))
118
+ urlpatterns.extend(_create_health_views())
116
119
 
117
120
  return urlpatterns
118
121
 
@@ -264,6 +267,7 @@ def _create_django_views(
264
267
  model_scope = schema.scope
265
268
  model_tenant_field = schema.tenant_field
266
269
  model_owner_field = schema.owner_field
270
+ model_current_user_field = schema.current_user_field
267
271
  patterns = []
268
272
 
269
273
  def _check_auth(request, operation):
@@ -333,6 +337,7 @@ def _create_django_views(
333
337
  sort = params.get("sort")
334
338
  search = params.get("search")
335
339
  deleted_param = params.get("deleted", "false").lower() == "true"
340
+ fields_param = params.get("fields") # Field selection: ?fields=id,name,price
336
341
 
337
342
  only_deleted = deleted_param and supports_soft_delete
338
343
  items = storage.list_all(_table, only_deleted=only_deleted)
@@ -348,6 +353,12 @@ def _create_django_views(
348
353
  items = apply_sorting(items, sort, _fields)
349
354
  page_items, total = paginate(items, page, size)
350
355
  page_items = [filter_response(item, _schema) for item in page_items]
356
+
357
+ # Apply field selection if ?fields parameter provided
358
+ if fields_param:
359
+ field_list = [f.strip() for f in fields_param.split(",")]
360
+ page_items = [select_fields(item, field_list, _schema) for item in page_items]
361
+
351
362
  if metrics:
352
363
  metrics.record("READ", entity_name)
353
364
  return JsonResponse(
@@ -369,6 +380,24 @@ def _create_django_views(
369
380
  if scope_filter:
370
381
  data.update(scope_filter)
371
382
 
383
+ # Auto-inject current user if current_user_field specified
384
+ if model_current_user_field and user and auth_backend:
385
+ user_id = auth_backend.get_owner_id(user)
386
+ if user_id is not None:
387
+ # For ForeignKey fields, Django expects field_id
388
+ # For non-FK fields, use field directly
389
+ # Try FK syntax first (most common case)
390
+ try:
391
+ storage._model._meta.get_field(model_current_user_field)
392
+ field = storage._model._meta.get_field(model_current_user_field)
393
+ if field.many_to_one: # ForeignKey
394
+ data[f"{model_current_user_field}_id"] = user_id
395
+ else:
396
+ data[model_current_user_field] = user_id
397
+ except Exception:
398
+ # Fallback: assume FK
399
+ data[f"{model_current_user_field}_id"] = user_id
400
+
372
401
  item = storage.create(_table, data)
373
402
  if metrics:
374
403
  metrics.record("CREATE", entity_name, str(item.get("id", "")))
@@ -659,6 +688,13 @@ def _create_django_views(
659
688
  return JsonResponse(create_error_response("Not found", 404), status=404)
660
689
 
661
690
  item = filter_response(item, _schema)
691
+
692
+ # Apply field selection if ?fields parameter provided
693
+ fields_param = request.GET.get("fields")
694
+ if fields_param:
695
+ field_list = [f.strip() for f in fields_param.split(",")]
696
+ item = select_fields(item, field_list, _schema)
697
+
662
698
  return JsonResponse(create_item_response(item, formatter))
663
699
 
664
700
  if request.method == "PUT" and "update" in _schema.permissions:
@@ -769,3 +805,38 @@ def _create_django_views(
769
805
  patterns.append(path(f"{table}/<str:item_id>/history/", history_view, name=f"{table}_history"))
770
806
 
771
807
  return patterns
808
+
809
+
810
+ def _create_health_views():
811
+ """Create health check views for production monitoring."""
812
+ from django.http import JsonResponse
813
+ from django.urls import path
814
+
815
+ health_check = get_health_check()
816
+
817
+ def liveness_view(request):
818
+ """Liveness probe — is the application running?"""
819
+ return JsonResponse(health_check.liveness())
820
+
821
+ def readiness_view(request):
822
+ """Readiness probe — is the application ready to serve traffic?"""
823
+ data, status_code = health_check.readiness()
824
+ return JsonResponse(data, status=status_code)
825
+
826
+ # Register database check for Django ORM
827
+ def check_database():
828
+ try:
829
+ from django.db import connection
830
+ with connection.cursor() as cursor:
831
+ cursor.execute("SELECT 1")
832
+ return True
833
+ except Exception:
834
+ return False
835
+
836
+ health_check.register_check("database", check_database)
837
+ health_check.mark_ready()
838
+
839
+ return [
840
+ path("health/", liveness_view, name="health_liveness"),
841
+ path("ready/", readiness_view, name="health_readiness"),
842
+ ]
@@ -10,10 +10,11 @@ from pydantic import BaseModel, create_model
10
10
  from flashapi.core.relations import find_expandable_fields, resolve_relations
11
11
  from flashapi.core.response import create_error_response, create_item_response, create_list_response
12
12
  from flashapi.core.schema import FieldType, Model, ModelSchema
13
- from flashapi.core.visibility import export_fields, filter_response, writable_fields
13
+ from flashapi.core.visibility import export_fields, filter_response, select_fields, writable_fields
14
14
  from flashapi.features import apply_filters, apply_search, apply_sorting, paginate
15
15
  from flashapi.features.dashboard import DASHBOARD_HTML, MetricsCollector
16
16
  from flashapi.features.export import CONTENT_TYPES, EXPORTERS
17
+ from flashapi.features.health import get_health_check
17
18
  from flashapi.inspectors import inspect_model
18
19
  from flashapi.storage.auto import AutoStorage
19
20
  from flashapi.storage.sqlalchemy import SQLAlchemyStorage
@@ -143,6 +144,7 @@ class FlashAPI:
143
144
  self._add_dashboard_routes()
144
145
  self._add_websocket_route()
145
146
  self._add_api_root()
147
+ self._add_health_routes()
146
148
 
147
149
  def _prepare_model(self, model_entry) -> None:
148
150
  wrapper = model_entry if isinstance(model_entry, Model) else Model(model_entry)
@@ -248,6 +250,36 @@ class FlashAPI:
248
250
  }
249
251
  return {"resources": resources, "links": links}
250
252
 
253
+ def _add_health_routes(self) -> None:
254
+ """Add health check endpoints for production monitoring."""
255
+ health_check = get_health_check()
256
+
257
+ @self._app.get("/health", tags=["Health"], include_in_schema=False)
258
+ async def liveness():
259
+ """Liveness probe — is the application running?"""
260
+ return health_check.liveness()
261
+
262
+ @self._app.get("/ready", tags=["Health"], include_in_schema=False)
263
+ async def readiness():
264
+ """Readiness probe — is the application ready to serve traffic?"""
265
+ data, status_code = health_check.readiness()
266
+ return JSONResponse(content=data, status_code=status_code)
267
+
268
+ # Register database check if using SQLAlchemy
269
+ if self._session_factory:
270
+ def check_database():
271
+ try:
272
+ session = self._session_factory()
273
+ session.execute("SELECT 1")
274
+ session.close()
275
+ return True
276
+ except Exception:
277
+ return False
278
+ health_check.register_check("database", check_database)
279
+
280
+ # Mark ready after all routes are registered
281
+ health_check.mark_ready()
282
+
251
283
  def _add_rate_limit_middleware(self) -> None:
252
284
  from starlette.middleware.base import BaseHTTPMiddleware
253
285
 
@@ -418,6 +450,13 @@ class FlashAPI:
418
450
 
419
451
  metrics.record("READ", tag)
420
452
  page_items = [filter_response(item, model_schema) for item in page_items]
453
+
454
+ # Apply field selection if ?fields parameter provided
455
+ fields_param = request.query_params.get("fields")
456
+ if fields_param:
457
+ field_list = [f.strip() for f in fields_param.split(",")]
458
+ page_items = [select_fields(item, field_list, model_schema) for item in page_items]
459
+
421
460
  return create_list_response(page_items, total, page, size, formatter)
422
461
 
423
462
  def _add_read_route(self, table, formatter, storage, tag, expandable, model_schema, lookup_field="id") -> None:
@@ -449,6 +488,13 @@ class FlashAPI:
449
488
  item = self._expand_items([item], expand, expandable)[0]
450
489
 
451
490
  item = filter_response(item, model_schema)
491
+
492
+ # Apply field selection if ?fields parameter provided
493
+ fields_param = request.query_params.get("fields")
494
+ if fields_param:
495
+ field_list = [f.strip() for f in fields_param.split(",")]
496
+ item = select_fields(item, field_list, model_schema)
497
+
452
498
  return create_item_response(item, formatter)
453
499
 
454
500
  def _add_history_route(self, table, entity_name, lookup_field="id", model_schema=None) -> None:
@@ -10,9 +10,10 @@ from flashapi.core.custom_routes import (
10
10
  from flashapi.core.relations import find_expandable_fields, resolve_relations
11
11
  from flashapi.core.response import create_error_response, create_item_response, create_list_response
12
12
  from flashapi.core.schema import Model, ModelSchema
13
- from flashapi.core.visibility import export_fields, filter_response, writable_fields
13
+ from flashapi.core.visibility import export_fields, filter_response, select_fields, writable_fields
14
14
  from flashapi.docs.openapi import generate_openapi_schema, get_swagger_html
15
15
  from flashapi.features import apply_filters, apply_search, apply_sorting, paginate
16
+ from flashapi.features.health import get_health_check
16
17
  from flashapi.inspectors import inspect_model
17
18
  from flashapi.storage.auto import AutoStorage
18
19
  from flashapi.storage.sqlalchemy import SQLAlchemyStorage
@@ -138,6 +139,7 @@ def register_models(
138
139
  _add_docs_routes(blueprint, all_schemas, custom_routes or [], flask_app=app)
139
140
 
140
141
  _add_api_root_route(blueprint, all_schemas, base_path, docs)
142
+ _add_health_routes(app, session_factory)
141
143
 
142
144
  app.register_blueprint(blueprint)
143
145
 
@@ -439,6 +441,13 @@ def _create_flask_routes(
439
441
  if _metrics:
440
442
  _metrics.record("READ", entity_name)
441
443
  page_items = [filter_response(item, _schema) for item in page_items]
444
+
445
+ # Apply field selection if ?fields parameter provided
446
+ fields_param = request.args.get("fields")
447
+ if fields_param:
448
+ field_list = [f.strip() for f in fields_param.split(",")]
449
+ page_items = [select_fields(item, field_list, _schema) for item in page_items]
450
+
442
451
  return jsonify(create_list_response(page_items, total, page, size, formatter))
443
452
 
444
453
  if "read" in schema.permissions:
@@ -461,6 +470,13 @@ def _create_flask_routes(
461
470
  item = _expand_items([item], expand, _exp, storage)[0]
462
471
 
463
472
  item = filter_response(item, _schema)
473
+
474
+ # Apply field selection if ?fields parameter provided
475
+ fields_param = request.args.get("fields")
476
+ if fields_param:
477
+ field_list = [f.strip() for f in fields_param.split(",")]
478
+ item = select_fields(item, field_list, _schema)
479
+
464
480
  return jsonify(create_item_response(item, formatter))
465
481
 
466
482
  if "read" in schema.permissions and entity_audit:
@@ -726,3 +742,35 @@ def _create_flask_routes(
726
742
  mimetype=CONTENT_TYPES[fmt],
727
743
  headers={"Content-Disposition": f'attachment; filename="{_table}.{fmt}"'},
728
744
  )
745
+
746
+
747
+ def _add_health_routes(app, session_factory) -> None:
748
+ """Add health check endpoints for production monitoring."""
749
+ from flask import jsonify
750
+ health_check = get_health_check()
751
+
752
+ @app.route("/health")
753
+ def liveness():
754
+ """Liveness probe — is the application running?"""
755
+ return jsonify(health_check.liveness())
756
+
757
+ @app.route("/ready")
758
+ def readiness():
759
+ """Readiness probe — is the application ready to serve traffic?"""
760
+ data, status_code = health_check.readiness()
761
+ return jsonify(data), status_code
762
+
763
+ # Register database check if using SQLAlchemy
764
+ if session_factory:
765
+ def check_database():
766
+ try:
767
+ session = session_factory()
768
+ session.execute("SELECT 1")
769
+ session.close()
770
+ return True
771
+ except Exception:
772
+ return False
773
+ health_check.register_check("database", check_database)
774
+
775
+ # Mark ready after all routes are registered
776
+ health_check.mark_ready()