python-flashapi 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -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
@@ -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
  ---
@@ -5,9 +5,9 @@ flashapi/flask.py,sha256=v-uSb9QSxj20w2iyDgaM4uED82XHLWEO9CRxGT8KCVc,137
5
5
  flashapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  flashapi/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  flashapi/adapters/base.py,sha256=DbXmwMZzZYums4YaJ-OlY8bJxTbGJTxApSfmf5NO-0w,329
8
- flashapi/adapters/django.py,sha256=6rs_mFkkF4qmnJ8DB69nV8mbYuMde1bHCzLBrN5MypI,32633
9
- flashapi/adapters/fastapi.py,sha256=63lcUoK2a6k1RdwRb1kIH2zz-D0tqouJ3MUALUMJ0WI,36820
10
- flashapi/adapters/flask.py,sha256=peVQQ8NTGDLpuplAvUbYTuXGF59eeBbFb_9HTEcxvfE,29121
8
+ flashapi/adapters/django.py,sha256=G5vbeaiK0ENzNoo0rrkbiWs6yQMN1rCnYsgsrjhGegc,33841
9
+ flashapi/adapters/fastapi.py,sha256=sU2j7VIPn6WrmdwOl9_eWQaCgheluGGnq9Q9sQeD3-g,38129
10
+ flashapi/adapters/flask.py,sha256=OeJ1M8U_XyUi7_7h91pif1zphZU71Nnz571FTDHHZQY,30256
11
11
  flashapi/core/__init__.py,sha256=8Ex4oCFI34F4xkA6Q6zSSQ-jE2uw1w-C20yl-Yg3f3c,297
12
12
  flashapi/core/custom_routes.py,sha256=-K0xDx-bW1yJtcJxh5ZdFUI0MzwIKoNKMJCuocdZuWI,8432
13
13
  flashapi/core/pluralize.py,sha256=nL_JwNHwEbz6n3HXdfA1We9_MRNR-cLn4VT-Hz1RW1g,2406
@@ -23,6 +23,7 @@ flashapi/features/auth.py,sha256=FVYb6x6VAIEvAp2xY8LL5I2E2PFdPe5v8dDGmWQfWLc,458
23
23
  flashapi/features/dashboard.py,sha256=eaOJmrJ5lRxDDyo4BhCp5qaNHDn0AUW4IlqhAIL38as,18239
24
24
  flashapi/features/export.py,sha256=M7RryPDgDZn1VlouZNzhvhrZ00k1e0HyyZlAfjYMSiE,4912
25
25
  flashapi/features/filtering.py,sha256=ONPWbVVTHlTRlxUoOcRNkL_a67osr12CJHKKBXHwDPg,3826
26
+ flashapi/features/health.py,sha256=g-vLoIAmS2L8pq7cvG9TX7h60rPEAa6YMlog71AW-u4,2140
26
27
  flashapi/features/pagination.py,sha256=396lvL_c6j3tNNodpPHkYDRkxkDmex2q9HmrUeOpLRA,464
27
28
  flashapi/features/rate_limit.py,sha256=LEI8UqWHEH7TREqaD0lDhhR5jZz8VF5qdu0gBxOVBSc,1313
28
29
  flashapi/features/search.py,sha256=0V0S8Mo2zb_BEeGbNYg8Y4ZF9njLNhwZAkrVrODW3Dc,611
@@ -41,8 +42,8 @@ flashapi/storage/auto.py,sha256=6K6_TSFU2NJSIaFqE3qbGFIY1H8uaL0iLGBEVvUpifs,7611
41
42
  flashapi/storage/base.py,sha256=8C6DL9LAWQ3lIDFKTKD3chXuDNr2LTc5yWoZYQPpKq4,1089
42
43
  flashapi/storage/orm.py,sha256=F_hOxspYkKzH1l_m48jRNdv7TGvp4OM6EwpMixfeP9E,4556
43
44
  flashapi/storage/sqlalchemy.py,sha256=FoSHWDydicRvns8ufO1vnb2o0dGwvASd88sQ3wh1SAI,6651
44
- python_flashapi-0.2.0.dist-info/METADATA,sha256=nRRFMQ3s7x8n0HomOcd-B1bh1pGITxazny9vczB-1tU,10793
45
- python_flashapi-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
46
- python_flashapi-0.2.0.dist-info/licenses/LICENSE,sha256=thh4-7oEXj4Tz_CHQciJHSnRqxCNRRuse5ac-BwBU5c,10772
47
- python_flashapi-0.2.0.dist-info/licenses/NOTICE,sha256=5-7uAManZL-6L6TGoHqSVBioA5MZ_UAzCPi97Ahtvw8,164
48
- python_flashapi-0.2.0.dist-info/RECORD,,
45
+ python_flashapi-0.3.0.dist-info/METADATA,sha256=TygInIQvoXhmIDeRMwPF8hCcn_sEdtXTEYsggjBZgPg,10892
46
+ python_flashapi-0.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
47
+ python_flashapi-0.3.0.dist-info/licenses/LICENSE,sha256=thh4-7oEXj4Tz_CHQciJHSnRqxCNRRuse5ac-BwBU5c,10772
48
+ python_flashapi-0.3.0.dist-info/licenses/NOTICE,sha256=5-7uAManZL-6L6TGoHqSVBioA5MZ_UAzCPi97Ahtvw8,164
49
+ python_flashapi-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any