healthy-api 0.10.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.
@@ -0,0 +1,27 @@
1
+ __version__ = "0.10.0"
2
+
3
+ import os
4
+
5
+ from typing import Type, TYPE_CHECKING, Union
6
+
7
+ if TYPE_CHECKING:
8
+ from .adapters.fastapi import FastapiAdapter
9
+ from .adapters.flask import FlaskAdapter
10
+
11
+ T_FlaskAdapter = Type[FlaskAdapter]
12
+ T_FastApiAdapter = Type[FastapiAdapter]
13
+
14
+
15
+ def load_adapter() -> Union["T_FlaskAdapter", "T_FastApiAdapter"]:
16
+ framework = os.getenv("HAPI_WEB_FRAMEWORK")
17
+
18
+ if framework == "flask":
19
+ from .adapters.flask import FlaskAdapter
20
+
21
+ return FlaskAdapter
22
+ elif framework == "fastapi":
23
+ from .adapters.fastapi import FastapiAdapter
24
+
25
+ return FastapiAdapter
26
+ else:
27
+ raise ValueError("You must specify a web framework (flask/fastapi)")
File without changes
@@ -0,0 +1,103 @@
1
+ import sys
2
+ from abc import ABC, abstractmethod
3
+ from datetime import datetime
4
+ from typing import Callable, Dict, List, Optional, TYPE_CHECKING, Union
5
+
6
+ if TYPE_CHECKING:
7
+ from .fastapi import FastApiApplication
8
+ from .flask import FlaskApplication
9
+
10
+ from ..git import git_stats, GitReturn
11
+ from ..version import read_version_file
12
+
13
+ if sys.version_info >= (3, 8):
14
+ from typing import Literal
15
+ else:
16
+ from typing_extensions import Literal
17
+
18
+ SupportedApplication = Union["FlaskApplication", "FastApiApplication"]
19
+ FuncList = List[Callable]
20
+
21
+ ResponseJson = Dict[
22
+ Union[Literal["uptime"], Literal["app"], Literal["status"], Literal["git"], str],
23
+ Union[str, Literal["OK"], Literal["DOWN"], GitReturn],
24
+ ]
25
+
26
+
27
+ class BaseAdapter(ABC):
28
+ DEFAULT_ENABLE = True
29
+ DEFAULT_ENABLE_GIT = True
30
+ DEFAULT_ENABLE_VERSION = True
31
+ DEFAULT_ENDPOINT = "/_health"
32
+
33
+ def __init__(
34
+ self,
35
+ app: Optional[SupportedApplication] = None,
36
+ extra_checks: Optional[FuncList] = None,
37
+ ) -> None:
38
+ if app is not None:
39
+ check_fns = extra_checks if extra_checks is not None else []
40
+ self.init_app(app, check_fns)
41
+
42
+ def init_app(
43
+ self,
44
+ app: Optional[SupportedApplication],
45
+ extra_checks: Optional[FuncList] = None,
46
+ ) -> None:
47
+ if app is None:
48
+ raise ValueError("None is not a valid application")
49
+
50
+ self.app = app
51
+ self.extra_checks = extra_checks if extra_checks is not None else []
52
+ self.start_time = datetime.now()
53
+ self.config = self.load_config()
54
+ self.logger = self.get_logger()
55
+
56
+ if self.config["HAPI_ENABLE"]:
57
+ self.load_router()
58
+
59
+ def health(self):
60
+ data: ResponseJson = {
61
+ "uptime": str(datetime.now() - self.start_time),
62
+ "app": self.app_name(),
63
+ }
64
+
65
+ raw_results: List[bool] = []
66
+ if self.extra_checks:
67
+ results = {}
68
+ for func in self.extra_checks:
69
+ try:
70
+ res: bool = func()
71
+ raw_results.append(res)
72
+ except Exception as e:
73
+ self.logger.error(f"Error in healthcheck: {str(e)}")
74
+ res = False
75
+
76
+ results[func.__doc__] = "OK" if res else "DOWN"
77
+ data.update(results)
78
+
79
+ data["status"] = "OK" if all(raw_results) else "DOWN"
80
+
81
+ if self.config["HAPI_ENABLE_GIT"]:
82
+ data.update({"git": git_stats()})
83
+
84
+ if self.config["HAPI_ENABLE_VERSION"]:
85
+ data.update({"version": read_version_file()})
86
+
87
+ return self.json_response(data)
88
+
89
+ @abstractmethod
90
+ def get_logger(self) -> Callable:
91
+ raise NotImplementedError
92
+
93
+ @abstractmethod
94
+ def json_response(self, data: dict):
95
+ raise NotImplementedError
96
+
97
+ @abstractmethod
98
+ def load_config(self) -> dict:
99
+ raise NotImplementedError
100
+
101
+ @abstractmethod
102
+ def load_router(self) -> None:
103
+ raise NotImplementedError
@@ -0,0 +1,41 @@
1
+ import logging
2
+ import os
3
+ from fastapi import FastAPI
4
+ from typing import Type, cast
5
+
6
+ from .base_adapter import BaseAdapter
7
+
8
+ FastApiApplication = Type[FastAPI]
9
+
10
+
11
+ class FastapiAdapter(BaseAdapter):
12
+ def app_name(self) -> str:
13
+ app = cast(FastAPI, self.app)
14
+ return app.title
15
+
16
+ def get_logger(self):
17
+ return logging.getLogger()
18
+
19
+ def json_response(self, data: dict) -> dict:
20
+ # FastAPI will handle the json headers
21
+ return data
22
+
23
+ def load_config(self) -> dict:
24
+ return {
25
+ "HAPI_ENABLE": bool(int(os.environ.get("HAPI_ENABLE", self.DEFAULT_ENABLE))),
26
+ "HAPI_ENABLE_GIT": bool(
27
+ int(os.environ.get("HAPI_ENABLE_GIT", self.DEFAULT_ENABLE_GIT))
28
+ ),
29
+ "HAPI_ENABLE_VERSION": bool(
30
+ int(os.environ.get("HAPI_ENABLE_VERSION", self.DEFAULT_ENABLE_VERSION))
31
+ ),
32
+ "HAPI_ENDPOINT": os.environ.get("HAPI_ENDPOINT", self.DEFAULT_ENDPOINT),
33
+ }
34
+
35
+ def load_router(self) -> None:
36
+ app = cast(FastAPI, self.app)
37
+ endpoint = cast(str, self.config["HAPI_ENDPOINT"])
38
+
39
+ @app.get(path=endpoint)
40
+ def _health():
41
+ return self.health()
@@ -0,0 +1,39 @@
1
+ import flask
2
+ from typing import Any, Dict, Type, cast
3
+
4
+ from .base_adapter import BaseAdapter
5
+
6
+ FlaskApplication = Type[flask.Flask]
7
+
8
+
9
+ class FlaskAdapter(BaseAdapter):
10
+ def app_name(self) -> str:
11
+ app = cast(flask.Flask, self.app)
12
+ return app.name
13
+
14
+ def get_logger(self) -> Any:
15
+ app = cast(flask.Flask, self.app)
16
+ return app.logger
17
+
18
+ def json_response(self, data: dict) -> flask.Response:
19
+ return flask.jsonify(data)
20
+
21
+ def load_config(self) -> Dict[str, Any]:
22
+ app = cast(flask.Flask, self.app)
23
+ config = {
24
+ "HAPI_ENABLE": bool(int(app.config.get("HAPI_ENABLE", self.DEFAULT_ENABLE))),
25
+ "HAPI_ENABLE_GIT": bool(
26
+ int(app.config.get("HAPI_ENABLE_GIT", self.DEFAULT_ENABLE_GIT))
27
+ ),
28
+ "HAPI_ENABLE_VERSION": bool(
29
+ int(app.config.get("HAPI_ENABLE_VERSION", self.DEFAULT_ENABLE_VERSION))
30
+ ),
31
+ "HAPI_ENDPOINT": app.config.get("HAPI_ENDPOINT", self.DEFAULT_ENDPOINT),
32
+ }
33
+ app.config.update(config)
34
+ return config
35
+
36
+ def load_router(self) -> None:
37
+ app = cast(flask.Flask, self.app)
38
+ endpoint = cast(str, self.config["HAPI_ENDPOINT"])
39
+ app.add_url_rule(rule=endpoint, view_func=self.health)
healthy_api/git.py ADDED
@@ -0,0 +1,41 @@
1
+ import re
2
+ import sys
3
+ import subprocess
4
+ from typing import Union
5
+
6
+
7
+ if sys.version_info >= (3, 8):
8
+ from typing import Literal, TypedDict
9
+ else:
10
+ from typing_extensions import Literal, TypedDict
11
+
12
+
13
+ class GitReturn(TypedDict):
14
+ commit: Union[str, Literal["Unknown"]]
15
+ author: Union[str, Literal["Unknown"]]
16
+ date: Union[str, Literal["Unknown"]]
17
+
18
+
19
+ GIT_CMD = "git log | head -4"
20
+ RE_COMMIT = re.compile(r"commit[\W]+(\w+)", re.MULTILINE)
21
+ RE_AUTHOR = re.compile(r"Author\:[\W]+(.*)", re.MULTILINE)
22
+ RE_DATE = re.compile(r"Date\:[\W]+(.*)", re.MULTILINE)
23
+
24
+
25
+ def git_stats() -> GitReturn:
26
+ try:
27
+ ret = subprocess.check_output([GIT_CMD], shell=True).decode("utf-8")
28
+ commit = re.search(RE_COMMIT, ret)
29
+ author = re.search(RE_AUTHOR, ret)
30
+ date = re.search(RE_DATE, ret)
31
+ return {
32
+ "commit": commit.group(1) if commit else "Unknown",
33
+ "author": author.group(1) if author else "Unknown",
34
+ "date": date.group(1) if date else "Unknown",
35
+ }
36
+ except subprocess.CalledProcessError:
37
+ return {
38
+ "commit": "Unknown",
39
+ "author": "Unknown",
40
+ "date": "Unknown",
41
+ }
healthy_api/py.typed ADDED
File without changes
healthy_api/version.py ADDED
@@ -0,0 +1,9 @@
1
+ import os
2
+
3
+
4
+ def read_version_file() -> str:
5
+ path = os.path.join(os.getcwd(), ".version")
6
+ if os.path.exists(path):
7
+ with open(path, "r") as f:
8
+ return f.read().strip()
9
+ return ""
@@ -0,0 +1,13 @@
1
+ =======
2
+ Credits
3
+ =======
4
+
5
+ Development Lead
6
+ ----------------
7
+
8
+ * Herman Singh <kartstig@gmail.com>
9
+
10
+ Contributors
11
+ ------------
12
+
13
+ None yet. Why not be the first?
@@ -0,0 +1,11 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2017, Herman Paul Singh
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.1
2
+ Name: healthy-api
3
+ Version: 0.10.0
4
+ Summary: Healthy-API adds a healthcheck endpoint for all web frameworks.
5
+ Author-email: Herman Singh <kartstig@gmail.com>
6
+ Maintainer-email: Herman Singh <kartstig@gmail.com>
7
+ License: MIT License
8
+ Project-URL: Donate, https://github.com/sponsors/Kartstig/dashboard
9
+ Project-URL: Documentation, https://readthedocs.org/projects/healthy_api
10
+ Project-URL: Source Code, https://github.com/Kartstig/healthy_api
11
+ Project-URL: Issue Tracker, https://github.com/Kartstig/healthy_api/issues
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python :: 3.5
21
+ Classifier: Programming Language :: Python :: 3.6
22
+ Classifier: Programming Language :: Python :: 3.7
23
+ Classifier: Programming Language :: Python :: 3.8
24
+ Classifier: Programming Language :: Python :: 3.9
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Programming Language :: Python :: 3.11
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >3.7
30
+ Description-Content-Type: text/x-rst
31
+ License-File: LICENSE
32
+ License-File: AUTHORS.rst
33
+ Provides-Extra: all
34
+ Requires-Dist: Flask >=0.12 ; extra == 'all'
35
+ Requires-Dist: fastapi ; extra == 'all'
36
+ Provides-Extra: fastapi
37
+ Requires-Dist: fastapi ; extra == 'fastapi'
38
+ Provides-Extra: flask
39
+ Requires-Dist: Flask >=0.12 ; extra == 'flask'
40
+
41
+ ===============================
42
+ Healthy-API
43
+ ===============================
44
+
45
+ Healthchecks for Any Framework (FastAPI/Flask)
46
+
47
+ .. _FastAPI: https://github.com/tiangolo/fastapi/
48
+
49
+ .. _Flask: https://github.com/pallets/flask/
50
+
51
+ .. image:: https://img.shields.io/pypi/v/Healthy-API.svg
52
+ :target: https://pypi.python.org/pypi/Healthy-API
53
+ :alt: PyPI
54
+
55
+ .. image:: https://github.com/Kartstig/healthy_api/actions/workflows/pytest.yml/badge.svg?branch=master
56
+ :target: https://github.com/Kartstig/healthy_api/actions/workflows/pytest.yml
57
+ :alt: GitHub Actions
58
+
59
+ .. image:: https://readthedocs.org/projects/healthy-api/badge/?version=latest
60
+ :target: https://healthy-api.readthedocs.io/en/latest/?badge=latest
61
+ :alt: Documentation Status
62
+
63
+ .. image:: https://codecov.io/gh/Kartstig/healthy_api/graph/badge.svg?token=mTG6WudJwK
64
+ :target: https://codecov.io/gh/Kartstig/healthy_api
65
+ :alt: Codecov
66
+
67
+ .. image:: https://img.shields.io/pypi/dm/Healthy-API
68
+ :alt: PyPI - Downloads
69
+
70
+ Healthy-API is designed to work with both Flask and FastAPI. Healthy-API is really simple
71
+ to set up. Healthy-API will provide an enpoint at `/_health` where you will get a JSON response
72
+ of the system's uptime, current git revision, version, and function you want.
73
+
74
+ You can also add in extra checks by passing in a list of checks to the
75
+ constructor.
76
+
77
+ Installing
78
+ ----------
79
+
80
+ Install and update using `pip`\:
81
+
82
+ .. code-block:: text
83
+
84
+ pip install -U "healthy-api[flask]"
85
+
86
+ or
87
+
88
+ .. code-block:: text
89
+
90
+ pip install -U "healthy-api[fastapi]"
91
+
92
+ FastAPI Configuration
93
+ ---------------------
94
+
95
+ .. code-block:: python
96
+
97
+ from fastapi import FastAPI
98
+ from healthy_api.adapters.fastapi import FlaskAdapter as HealthyApi
99
+
100
+ app = FastAPI(__name__)
101
+
102
+ def db_check():
103
+ """Database"""
104
+ try:
105
+ with get_session_ctx() as session:
106
+ (res,) = session.execute(text("SELECT 1")).fetchone()
107
+ return bool(res == 1)
108
+ except Exception as e:
109
+ logger.error(f"Unable to connect to database: {e}")
110
+ return False
111
+
112
+ HealthyApi(app, extra_checks=[db_check])
113
+
114
+
115
+ Flask Configuration
116
+ -------------------
117
+
118
+ .. code-block:: python
119
+
120
+ from Flask import Flask
121
+ from healthy_api.adapters.flask import Flask as HealthyApi
122
+
123
+ app = Flask(__name__)
124
+
125
+ HealthyApi(app)
126
+
127
+ Or if you can use the `init_app` function:
128
+
129
+ .. code-block:: python
130
+
131
+ from Flask import Flask
132
+ from healthy_api.adapters.flask import Flask as HealthyApi
133
+
134
+ app = Flask(__name__)
135
+
136
+ healthy_api = HealthyApi()
137
+ healthy_api.init_app(app)
138
+
139
+ * Free software: MIT license
140
+ * Documentation: https://healthy_api.readthedocs.io.
141
+
142
+
143
+ Features
144
+ --------
145
+
146
+ * Current Git Commit
147
+ * Current Version
148
+ * Accepts custom functions
149
+
150
+
151
+ Configuration
152
+ -------------
153
+
154
+ +---------------------+---------------------------------+------+------------+
155
+ | Config Key | Description | Type | Default |
156
+ +=====================+=================================+======+============+
157
+ | HAPI_ENABLE | Enable/Disable Healthy-API | bool | True |
158
+ +---------------------+---------------------------------+------+------------+
159
+ | HAPI_ENABLE_GIT | Enable/Disable Git Stats | bool | True |
160
+ +---------------------+---------------------------------+------+------------+
161
+ | HAPI_ENABLE_VERSION | Enable/Disable Version Stats | bool | True |
162
+ +---------------------+---------------------------------+------+------------+
163
+ | HAPI_ENDPOINT | Custom Route | str | /_health |
164
+ +---------------------+---------------------------------+------+------------+
165
+
166
+
167
+ Sponsorship
168
+ -----------
169
+
170
+ Put your logo here! `Become a sponsor`_ and support this project!
171
+
172
+ .. _Become a sponsor: https://github.com/sponsors/Kartstig
173
+
174
+
175
+
176
+ Credits
177
+ -------
178
+
179
+ This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
180
+
181
+ .. _Cookiecutter: https://github.com/audreyr/cookiecutter
182
+ .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
183
+
@@ -0,0 +1,14 @@
1
+ healthy_api/__init__.py,sha256=sPV_cS_2BLlXJeeZYNR0gl9xF06Fb-pHXzjhkC26ipM,708
2
+ healthy_api/git.py,sha256=RHH0dmAqwleV9ywhY_CGfSaFdRsgtAaXZjCdBAJlK_4,1181
3
+ healthy_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ healthy_api/version.py,sha256=p5GWoqYY8cTWmKSDQXYf6XO29-72hqZt8_VwXjsVSpk,207
5
+ healthy_api/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ healthy_api/adapters/base_adapter.py,sha256=iiVjuvQsRKEl3DsUV1pAEvlSYChVj99K_qkpbtW5dfw,2988
7
+ healthy_api/adapters/fastapi.py,sha256=Q5fOZPqvQiJdkwTQTYe4cjjsL8WiwwoA-R7E3NGFkco,1204
8
+ healthy_api/adapters/flask.py,sha256=g0gunAadltbHEUrSNqLbVlX6Wkmhr5hgaDrYhaE2Npg,1284
9
+ healthy_api-0.10.0.dist-info/AUTHORS.rst,sha256=G4CE063JlrHhq5P8Whw2skts5wdl1YJHJRuaSbD-zLs,156
10
+ healthy_api-0.10.0.dist-info/LICENSE,sha256=jZBIByrw8sUNYiSuqo4YApodjtgQIh7BjzmTcc5KCMU,1077
11
+ healthy_api-0.10.0.dist-info/METADATA,sha256=b2LWpTEhdHq9T19D-jxUST4VwpxdxLTaycWPtDRd168,5757
12
+ healthy_api-0.10.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
13
+ healthy_api-0.10.0.dist-info/top_level.txt,sha256=AcjTG-juyolwOC4A49yzM1Brfqw2szpPGC_UChYjoVc,16
14
+ healthy_api-0.10.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ healthy_api
2
+ lib