python-flashapi 0.2.0__tar.gz → 0.3.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 (98) hide show
  1. python_flashapi-0.3.0/.github/workflows/release-please.yml +48 -0
  2. python_flashapi-0.3.0/.release-please-manifest.json +3 -0
  3. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/CHANGELOG.md +17 -0
  4. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/PKG-INFO +8 -3
  5. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/README.md +6 -1
  6. python_flashapi-0.3.0/docs/logo.svg +25 -0
  7. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/pyproject.toml +1 -1
  8. python_flashapi-0.3.0/release-please-config.json +10 -0
  9. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/adapters/django.py +37 -0
  10. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/adapters/fastapi.py +32 -0
  11. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/adapters/flask.py +34 -0
  12. python_flashapi-0.3.0/src/flashapi/features/health.py +69 -0
  13. python_flashapi-0.3.0/tests/test_health.py +42 -0
  14. python_flashapi-0.2.0/.github/workflows/publish.yml +0 -29
  15. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/.github/workflows/ci.yml +0 -0
  16. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/.gitignore +0 -0
  17. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/LICENSE +0 -0
  18. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/NOTICE +0 -0
  19. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/authentication.md +0 -0
  20. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/custom-logic.md +0 -0
  21. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/customization.md +0 -0
  22. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/examples.md +0 -0
  23. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/features.md +0 -0
  24. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/framework-notes.md +0 -0
  25. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/integration.md +0 -0
  26. /python_flashapi-0.2.0/docs/logo.svg → /python_flashapi-0.3.0/docs/logo1.svg +0 -0
  27. /python_flashapi-0.2.0/docs/logo1.svg → /python_flashapi-0.3.0/docs/logo2.svg +0 -0
  28. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/docs/relations.md +0 -0
  29. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/examples/fastapi_example.py +0 -0
  30. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/examples/flask_example.py +0 -0
  31. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/__init__.py +0 -0
  32. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/adapters/__init__.py +0 -0
  33. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/adapters/base.py +0 -0
  34. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/__init__.py +0 -0
  35. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/custom_routes.py +0 -0
  36. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/pluralize.py +0 -0
  37. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/relations.py +0 -0
  38. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/response.py +0 -0
  39. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/schema.py +0 -0
  40. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/core/visibility.py +0 -0
  41. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/django.py +0 -0
  42. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/docs/__init__.py +0 -0
  43. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/docs/openapi.py +0 -0
  44. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/fastapi.py +0 -0
  45. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/__init__.py +0 -0
  46. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/audit.py +0 -0
  47. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/auth.py +0 -0
  48. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/dashboard.py +0 -0
  49. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/export.py +0 -0
  50. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/filtering.py +0 -0
  51. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/pagination.py +0 -0
  52. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/rate_limit.py +0 -0
  53. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/search.py +0 -0
  54. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/sorting.py +0 -0
  55. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/webhooks.py +0 -0
  56. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/features/websocket.py +0 -0
  57. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/flask.py +0 -0
  58. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/__init__.py +0 -0
  59. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/base.py +0 -0
  60. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/dataclass.py +0 -0
  61. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/detect.py +0 -0
  62. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/django.py +0 -0
  63. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/pydantic.py +0 -0
  64. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/inspectors/sqlalchemy.py +0 -0
  65. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/py.typed +0 -0
  66. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/storage/__init__.py +0 -0
  67. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/storage/auto.py +0 -0
  68. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/storage/base.py +0 -0
  69. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/storage/orm.py +0 -0
  70. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/src/flashapi/storage/sqlalchemy.py +0 -0
  71. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/__init__.py +0 -0
  72. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_adapters/__init__.py +0 -0
  73. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_adapters/test_fastapi.py +0 -0
  74. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_adapters/test_flask.py +0 -0
  75. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_adapters/test_relations.py +0 -0
  76. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_adapters/test_soft_delete.py +0 -0
  77. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_core/__init__.py +0 -0
  78. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_core/test_pluralize.py +0 -0
  79. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_core/test_response.py +0 -0
  80. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_core/test_schema.py +0 -0
  81. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_core/test_visibility.py +0 -0
  82. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/__init__.py +0 -0
  83. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_audit.py +0 -0
  84. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_auth.py +0 -0
  85. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_dashboard.py +0 -0
  86. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_export.py +0 -0
  87. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_filtering.py +0 -0
  88. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_pagination.py +0 -0
  89. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_rate_limit.py +0 -0
  90. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_search.py +0 -0
  91. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_sorting.py +0 -0
  92. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_features/test_websocket.py +0 -0
  93. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_inspectors/__init__.py +0 -0
  94. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_inspectors/test_dataclass.py +0 -0
  95. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_inspectors/test_detect.py +0 -0
  96. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_inspectors/test_pydantic.py +0 -0
  97. {python_flashapi-0.2.0 → python_flashapi-0.3.0}/tests/test_storage/__init__.py +0 -0
  98. {python_flashapi-0.2.0 → python_flashapi-0.3.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.3.0"
3
+ }
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/HackermanMe/flashapi/compare/v0.2.0...v0.3.0) (2026-08-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * add production health check endpoints (/health and /ready) ([cc4e881](https://github.com/HackermanMe/flashapi/commit/cc4e881f09055fa990bb054298ebc8c1f55af7af))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * corriger les gradients et les couleurs dans les fichiers SVG du logo ([50aadaf](https://github.com/HackermanMe/flashapi/commit/50aadaff44919e5666621cc3f645dd34c1364a64))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * add release-please version marker in README ([515aeb8](https://github.com/HackermanMe/flashapi/commit/515aeb83c2f021bebac25d9690c4726003769746))
19
+
3
20
  ## 0.1.0 (2026-07-09)
4
21
 
5
22
  Initial release.
@@ -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.3.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
@@ -88,7 +88,12 @@ Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across
88
88
  ## Installation
89
89
 
90
90
  ```bash
91
- pip install python-flashapi[fastapi] # or python-flashapi[flask] or python-flashapi[all]
91
+ # x-release-please-start-version
92
+ pip install python-flashapi==0.2.0
93
+ # x-release-please-end
94
+
95
+ # With framework extras:
96
+ pip install python-flashapi[fastapi] # or [flask] or [django] or [all]
92
97
  ```
93
98
 
94
99
  ---
@@ -46,7 +46,12 @@ Part of the **FlashAPI Ecosystem** — ensuring SDK client compatibility across
46
46
  ## Installation
47
47
 
48
48
  ```bash
49
- pip install python-flashapi[fastapi] # or python-flashapi[flask] or python-flashapi[all]
49
+ # x-release-please-start-version
50
+ pip install python-flashapi==0.2.0
51
+ # x-release-please-end
52
+
53
+ # With framework extras:
54
+ pip install python-flashapi[fastapi] # or [flask] or [django] or [all]
50
55
  ```
51
56
 
52
57
  ---
@@ -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.3.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"]
8
+ }
9
+ }
10
+ }
@@ -12,6 +12,7 @@ from flashapi.core.schema import Model, ModelSchema
12
12
  from flashapi.core.visibility import export_fields, filter_response, 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
 
@@ -113,6 +114,7 @@ def generate_urls(
113
114
  ))
114
115
 
115
116
  urlpatterns.extend(_create_api_root_view(all_schemas, docs))
117
+ urlpatterns.extend(_create_health_views())
116
118
 
117
119
  return urlpatterns
118
120
 
@@ -769,3 +771,38 @@ def _create_django_views(
769
771
  patterns.append(path(f"{table}/<str:item_id>/history/", history_view, name=f"{table}_history"))
770
772
 
771
773
  return patterns
774
+
775
+
776
+ def _create_health_views():
777
+ """Create health check views for production monitoring."""
778
+ from django.http import JsonResponse
779
+ from django.urls import path
780
+
781
+ health_check = get_health_check()
782
+
783
+ def liveness_view(request):
784
+ """Liveness probe — is the application running?"""
785
+ return JsonResponse(health_check.liveness())
786
+
787
+ def readiness_view(request):
788
+ """Readiness probe — is the application ready to serve traffic?"""
789
+ data, status_code = health_check.readiness()
790
+ return JsonResponse(data, status=status_code)
791
+
792
+ # Register database check for Django ORM
793
+ def check_database():
794
+ try:
795
+ from django.db import connection
796
+ with connection.cursor() as cursor:
797
+ cursor.execute("SELECT 1")
798
+ return True
799
+ except Exception:
800
+ return False
801
+
802
+ health_check.register_check("database", check_database)
803
+ health_check.mark_ready()
804
+
805
+ return [
806
+ path("health/", liveness_view, name="health_liveness"),
807
+ path("ready/", readiness_view, name="health_readiness"),
808
+ ]
@@ -14,6 +14,7 @@ from flashapi.core.visibility import export_fields, filter_response, writable_fi
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
 
@@ -13,6 +13,7 @@ from flashapi.core.schema import Model, ModelSchema
13
13
  from flashapi.core.visibility import export_fields, filter_response, 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
 
@@ -726,3 +728,35 @@ def _create_flask_routes(
726
728
  mimetype=CONTENT_TYPES[fmt],
727
729
  headers={"Content-Disposition": f'attachment; filename="{_table}.{fmt}"'},
728
730
  )
731
+
732
+
733
+ def _add_health_routes(app, session_factory) -> None:
734
+ """Add health check endpoints for production monitoring."""
735
+ from flask import jsonify
736
+ health_check = get_health_check()
737
+
738
+ @app.route("/health")
739
+ def liveness():
740
+ """Liveness probe — is the application running?"""
741
+ return jsonify(health_check.liveness())
742
+
743
+ @app.route("/ready")
744
+ def readiness():
745
+ """Readiness probe — is the application ready to serve traffic?"""
746
+ data, status_code = health_check.readiness()
747
+ return jsonify(data), status_code
748
+
749
+ # Register database check if using SQLAlchemy
750
+ if session_factory:
751
+ def check_database():
752
+ try:
753
+ session = session_factory()
754
+ session.execute("SELECT 1")
755
+ session.close()
756
+ return True
757
+ except Exception:
758
+ return False
759
+ health_check.register_check("database", check_database)
760
+
761
+ # Mark ready after all routes are registered
762
+ health_check.mark_ready()
@@ -0,0 +1,69 @@
1
+ """
2
+ Health check endpoints for production monitoring.
3
+
4
+ Provides /health (liveness) and /ready (readiness) endpoints for Kubernetes,
5
+ load balancers, and monitoring systems.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from typing import Any
12
+
13
+
14
+ class HealthCheck:
15
+ """Health check manager with liveness and readiness probes."""
16
+
17
+ def __init__(self) -> None:
18
+ self._start_time = time.time()
19
+ self._ready = False
20
+ self._checks: dict[str, callable] = {}
21
+
22
+ def mark_ready(self) -> None:
23
+ """Mark the application as ready to receive traffic."""
24
+ self._ready = True
25
+
26
+ def register_check(self, name: str, check_fn: callable) -> None:
27
+ """Register a custom readiness check (e.g., database connection)."""
28
+ self._checks[name] = check_fn
29
+
30
+ def liveness(self) -> dict[str, Any]:
31
+ """
32
+ Liveness probe — is the application running?
33
+ Returns 200 if alive, should return 5xx if deadlocked/crashed.
34
+ """
35
+ uptime = int(time.time() - self._start_time)
36
+ return {"status": "ok", "uptime": uptime}
37
+
38
+ def readiness(self) -> tuple[dict[str, Any], int]:
39
+ """
40
+ Readiness probe — is the application ready to serve traffic?
41
+ Returns 200 if ready, 503 if not yet ready or dependencies failing.
42
+ """
43
+ if not self._ready:
44
+ return {"status": "not_ready", "reason": "Application still initializing"}, 503
45
+
46
+ failed_checks = []
47
+ for name, check_fn in self._checks.items():
48
+ try:
49
+ if not check_fn():
50
+ failed_checks.append(name)
51
+ except Exception as e:
52
+ failed_checks.append(f"{name}: {e}")
53
+
54
+ if failed_checks:
55
+ return {
56
+ "status": "not_ready",
57
+ "failed_checks": failed_checks,
58
+ }, 503
59
+
60
+ uptime = int(time.time() - self._start_time)
61
+ return {"status": "ready", "uptime": uptime, "checks_passed": len(self._checks)}, 200
62
+
63
+
64
+ _health = HealthCheck()
65
+
66
+
67
+ def get_health_check() -> HealthCheck:
68
+ """Get the global health check instance."""
69
+ return _health
@@ -0,0 +1,42 @@
1
+ """Tests for health check endpoints."""
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+ from pydantic import BaseModel
6
+
7
+ from flashapi.adapters.fastapi import FlashAPI
8
+
9
+
10
+ class TestItem(BaseModel):
11
+ name: str
12
+
13
+
14
+ @pytest.fixture
15
+ def app(tmp_path):
16
+ """Create a test FastAPI app."""
17
+ db_path = str(tmp_path / "test.db")
18
+ flash = FlashAPI([TestItem], database=db_path)
19
+ return flash.app
20
+
21
+
22
+ def test_liveness_probe(app):
23
+ """Test GET /health returns liveness status."""
24
+ client = TestClient(app)
25
+ response = client.get("/health")
26
+ assert response.status_code == 200
27
+ data = response.json()
28
+ assert data["status"] == "ok"
29
+ assert "uptime" in data
30
+ assert isinstance(data["uptime"], int)
31
+
32
+
33
+ def test_readiness_probe(app):
34
+ """Test GET /ready returns readiness status."""
35
+ client = TestClient(app)
36
+ response = client.get("/ready")
37
+ assert response.status_code == 200
38
+ data = response.json()
39
+ assert data["status"] == "ready"
40
+ assert "uptime" in data
41
+ assert "checks_passed" in data
42
+ assert data["checks_passed"] >= 0 # At least 0 checks (in-memory DB has no check)
@@ -1,29 +0,0 @@
1
- name: Publish to PyPI
2
-
3
- on:
4
- push:
5
- tags: ['v*']
6
-
7
- jobs:
8
- publish:
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - uses: actions/checkout@v4
13
-
14
- - name: Set up Python
15
- uses: actions/setup-python@v5
16
- with:
17
- python-version: '3.12'
18
-
19
- - name: Install build tools
20
- run: pip install build twine
21
-
22
- - name: Build package
23
- run: python -m build
24
-
25
- - name: Publish to PyPI
26
- run: twine upload dist/*
27
- env:
28
- TWINE_USERNAME: __token__
29
- TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
File without changes
File without changes