apitally 0.16.1__tar.gz → 0.16.2__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.
- {apitally-0.16.1 → apitally-0.16.2}/.github/workflows/tests.yaml +0 -7
- {apitally-0.16.1 → apitally-0.16.2}/PKG-INFO +1 -1
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/client_asyncio.py +1 -7
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/client_threading.py +1 -3
- {apitally-0.16.1 → apitally-0.16.2}/apitally/starlette.py +10 -17
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_asyncio.py +3 -2
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_threading.py +3 -1
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_fastapi.py +0 -2
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_litestar.py +0 -1
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_starlette.py +0 -2
- {apitally-0.16.1 → apitally-0.16.2}/.github/workflows/publish.yaml +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/.github/workflows/summary.yaml +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/.gitignore +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/.pre-commit-config.yaml +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/LICENSE +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/Makefile +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/README.md +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/__init__.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/blacksheep.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/__init__.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/client_base.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/consumers.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/logging.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/request_logging.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/requests.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/sentry.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/server_errors.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/client/validation_errors.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/common.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/django.py +1 -1
- {apitally-0.16.1 → apitally-0.16.2}/apitally/django_ninja.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/django_rest_framework.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/fastapi.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/flask.py +1 -1
- {apitally-0.16.1 → apitally-0.16.2}/apitally/litestar.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/apitally/py.typed +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/pyproject.toml +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/renovate.json +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/__init__.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/conftest.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/constants.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/django_ninja_urls.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/django_rest_framework_urls.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_blacksheep.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_consumers.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_request_logging.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_requests.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_server_errors.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_client_validation_errors.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_django_ninja.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_django_rest_framework.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/tests/test_flask.py +0 -0
- {apitally-0.16.1 → apitally-0.16.2}/uv.lock +0 -0
@@ -48,10 +48,7 @@ jobs:
|
|
48
48
|
matrix:
|
49
49
|
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
50
50
|
deps:
|
51
|
-
- starlette
|
52
51
|
- fastapi starlette
|
53
|
-
- fastapi==0.109.0 starlette
|
54
|
-
- fastapi==0.100.1 starlette
|
55
52
|
- fastapi==0.88.0 starlette
|
56
53
|
- flask
|
57
54
|
- flask==2.3.*
|
@@ -61,14 +58,10 @@ jobs:
|
|
61
58
|
- djangorestframework==3.12.* django==3.2.* uritemplate
|
62
59
|
- djangorestframework==3.10.* django==2.2.* uritemplate
|
63
60
|
- django-ninja django
|
64
|
-
- django-ninja==0.22.* django
|
65
61
|
- django-ninja==0.18.0 django
|
66
62
|
- litestar
|
67
|
-
- litestar==2.6.1
|
68
|
-
- litestar==2.3.0
|
69
63
|
- litestar==2.0.1
|
70
64
|
- blacksheep
|
71
|
-
- blacksheep==2.2.0
|
72
65
|
- blacksheep==2.1.0
|
73
66
|
exclude:
|
74
67
|
- python: "3.8"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.16.
|
3
|
+
Version: 0.16.2
|
4
4
|
Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette, Litestar and BlackSheep.
|
5
5
|
Project-URL: Homepage, https://apitally.io
|
6
6
|
Project-URL: Documentation, https://docs.apitally.io
|
@@ -41,7 +41,6 @@ class ApitallyClient(ApitallyClientBase):
|
|
41
41
|
self._stop_sync_loop = False
|
42
42
|
self._sync_loop_task: Optional[asyncio.Task] = None
|
43
43
|
self._sync_data_queue: asyncio.Queue[Dict[str, Any]] = asyncio.Queue()
|
44
|
-
self._set_startup_data_task: Optional[asyncio.Task] = None
|
45
44
|
|
46
45
|
def get_http_client(self) -> httpx.AsyncClient:
|
47
46
|
if httpx.__version__ >= "0.26.0":
|
@@ -67,7 +66,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
67
66
|
try:
|
68
67
|
async with self.get_http_client() as client:
|
69
68
|
tasks = [self.send_sync_data(client), self.send_log_data(client)]
|
70
|
-
if not self._startup_data_sent
|
69
|
+
if not self._startup_data_sent:
|
71
70
|
tasks.append(self.send_startup_data(client))
|
72
71
|
await asyncio.gather(*tasks)
|
73
72
|
last_sync_time = now
|
@@ -92,11 +91,6 @@ class ApitallyClient(ApitallyClientBase):
|
|
92
91
|
def set_startup_data(self, data: Dict[str, Any]) -> None:
|
93
92
|
self._startup_data_sent = False
|
94
93
|
self._startup_data = self.add_uuids_to_data(data)
|
95
|
-
self._set_startup_data_task = asyncio.create_task(self._set_startup_data())
|
96
|
-
|
97
|
-
async def _set_startup_data(self) -> None:
|
98
|
-
async with self.get_http_client() as client:
|
99
|
-
await self.send_startup_data(client)
|
100
94
|
|
101
95
|
async def send_startup_data(self, client: httpx.AsyncClient) -> None:
|
102
96
|
if self._startup_data is not None:
|
@@ -80,7 +80,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
80
80
|
if (now - last_sync_time) >= self.sync_interval:
|
81
81
|
try:
|
82
82
|
with requests.Session() as session:
|
83
|
-
if not self._startup_data_sent
|
83
|
+
if not self._startup_data_sent:
|
84
84
|
self.send_startup_data(session)
|
85
85
|
self.send_sync_data(session)
|
86
86
|
self.send_log_data(session)
|
@@ -105,8 +105,6 @@ class ApitallyClient(ApitallyClientBase):
|
|
105
105
|
def set_startup_data(self, data: Dict[str, Any]) -> None:
|
106
106
|
self._startup_data_sent = False
|
107
107
|
self._startup_data = self.add_uuids_to_data(data)
|
108
|
-
with requests.Session() as session:
|
109
|
-
self.send_startup_data(session)
|
110
108
|
|
111
109
|
def send_startup_data(self, session: requests.Session) -> None:
|
112
110
|
if self._startup_data is not None:
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import asyncio
|
4
3
|
import time
|
5
4
|
from typing import Any, Callable, Dict, List, Optional, Union
|
6
5
|
from warnings import warn
|
@@ -40,6 +39,8 @@ class ApitallyMiddleware:
|
|
40
39
|
proxy: Optional[Union[str, Proxy]] = None,
|
41
40
|
) -> None:
|
42
41
|
self.app = app
|
42
|
+
self.app_version = app_version
|
43
|
+
self.openapi_url = openapi_url
|
43
44
|
self.identify_consumer_callback = identify_consumer_callback
|
44
45
|
self.client = ApitallyClient(
|
45
46
|
client_id=client_id,
|
@@ -47,10 +48,6 @@ class ApitallyMiddleware:
|
|
47
48
|
request_logging_config=request_logging_config,
|
48
49
|
proxy=proxy,
|
49
50
|
)
|
50
|
-
self.client.start_sync_loop()
|
51
|
-
self._delayed_set_startup_data_task: Optional[asyncio.Task] = None
|
52
|
-
self.delayed_set_startup_data(app_version, openapi_url)
|
53
|
-
_register_shutdown_handler(app, self.client.handle_shutdown)
|
54
51
|
|
55
52
|
self.capture_request_body = (
|
56
53
|
self.client.request_logger.config.enabled and self.client.request_logger.config.log_request_body
|
@@ -59,17 +56,13 @@ class ApitallyMiddleware:
|
|
59
56
|
self.client.request_logger.config.enabled and self.client.request_logger.config.log_response_body
|
60
57
|
)
|
61
58
|
|
62
|
-
|
63
|
-
|
64
|
-
self._delayed_set_startup_data(app_version, openapi_url)
|
65
|
-
)
|
59
|
+
_register_event_handler(app, "startup", self.on_startup)
|
60
|
+
_register_event_handler(app, "shutdown", self.client.handle_shutdown)
|
66
61
|
|
67
|
-
|
68
|
-
self, app_version
|
69
|
-
) -> None:
|
70
|
-
await asyncio.sleep(1.0) # Short delay to allow app routes to be registered first
|
71
|
-
data = _get_startup_data(self.app, app_version, openapi_url)
|
62
|
+
def on_startup(self) -> None:
|
63
|
+
data = _get_startup_data(self.app, app_version=self.app_version, openapi_url=self.openapi_url)
|
72
64
|
self.client.set_startup_data(data)
|
65
|
+
self.client.start_sync_loop()
|
73
66
|
|
74
67
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
75
68
|
if self.client.enabled and scope["type"] == "http" and scope["method"] != "OPTIONS":
|
@@ -296,8 +289,8 @@ def _get_routes(app: Union[ASGIApp, Router]) -> List[BaseRoute]:
|
|
296
289
|
return [] # pragma: no cover
|
297
290
|
|
298
291
|
|
299
|
-
def
|
292
|
+
def _register_event_handler(app: Union[ASGIApp, Router], event: str, handler: Callable[[], Any]) -> None:
|
300
293
|
if isinstance(app, Router):
|
301
|
-
app.add_event_handler(
|
294
|
+
app.add_event_handler(event, handler)
|
302
295
|
elif hasattr(app, "app"):
|
303
|
-
|
296
|
+
_register_event_handler(app.app, event, handler)
|
@@ -156,13 +156,14 @@ async def test_send_log_data(client: ApitallyClient, httpx_mock: HTTPXMock):
|
|
156
156
|
assert len(client.request_logger.file_deque) == 0
|
157
157
|
|
158
158
|
|
159
|
-
async def
|
159
|
+
async def test_send_startup_data(client: ApitallyClient, httpx_mock: HTTPXMock):
|
160
160
|
from apitally.client.client_base import HUB_BASE_URL, HUB_VERSION
|
161
161
|
|
162
162
|
httpx_mock.add_response()
|
163
163
|
data = {"paths": [], "client_version": "1.0.0", "starlette_version": "0.28.0", "python_version": "3.11.4"}
|
164
164
|
client.set_startup_data(data)
|
165
|
-
|
165
|
+
async with client.get_http_client() as http_client:
|
166
|
+
await client.send_startup_data(client=http_client)
|
166
167
|
|
167
168
|
request = httpx_mock.get_request(url=f"{HUB_BASE_URL}/{HUB_VERSION}/{CLIENT_ID}/{ENV}/startup")
|
168
169
|
assert request is not None
|
@@ -141,12 +141,14 @@ def test_send_log_data(client: ApitallyClient, requests_mock: Mocker):
|
|
141
141
|
assert len(client.request_logger.file_deque) == 0
|
142
142
|
|
143
143
|
|
144
|
-
def
|
144
|
+
def test_send_startup_data(client: ApitallyClient, requests_mock: Mocker):
|
145
145
|
from apitally.client.client_base import HUB_BASE_URL, HUB_VERSION
|
146
146
|
|
147
147
|
mock = requests_mock.register_uri("POST", f"{HUB_BASE_URL}/{HUB_VERSION}/{CLIENT_ID}/{ENV}/startup")
|
148
148
|
data = {"paths": [], "client_version": "1.0.0", "starlette_version": "0.28.0", "python_version": "3.11.4"}
|
149
149
|
client.set_startup_data(data)
|
150
|
+
with requests.Session() as session:
|
151
|
+
client.send_startup_data(session)
|
150
152
|
|
151
153
|
assert len(mock.request_history) == 1
|
152
154
|
request_data = mock.request_history[0].json()
|
@@ -30,8 +30,6 @@ def app(module_mocker: MockerFixture) -> FastAPI:
|
|
30
30
|
|
31
31
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient._instance", None)
|
32
32
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.start_sync_loop")
|
33
|
-
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.set_startup_data")
|
34
|
-
module_mocker.patch("apitally.starlette.ApitallyMiddleware.delayed_set_startup_data")
|
35
33
|
|
36
34
|
def identify_consumer(request: Request) -> Optional[str]:
|
37
35
|
if consumer := request.query_params.get("consumer"):
|
@@ -37,7 +37,6 @@ async def app(module_mocker: MockerFixture) -> Litestar:
|
|
37
37
|
|
38
38
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient._instance", None)
|
39
39
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.start_sync_loop")
|
40
|
-
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.set_startup_data")
|
41
40
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.handle_shutdown", mocked_handle_shutdown)
|
42
41
|
|
43
42
|
@get("/foo")
|
@@ -28,8 +28,6 @@ if TYPE_CHECKING:
|
|
28
28
|
async def app(request: FixtureRequest, module_mocker: MockerFixture) -> Starlette:
|
29
29
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient._instance", None)
|
30
30
|
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.start_sync_loop")
|
31
|
-
module_mocker.patch("apitally.client.client_asyncio.ApitallyClient.set_startup_data")
|
32
|
-
module_mocker.patch("apitally.starlette.ApitallyMiddleware.delayed_set_startup_data")
|
33
31
|
if request.param == "starlette":
|
34
32
|
return get_starlette_app()
|
35
33
|
elif request.param == "fastapi":
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -75,13 +75,13 @@ class ApitallyMiddleware:
|
|
75
75
|
request_logging_config=self.config.request_logging_config,
|
76
76
|
proxy=self.config.proxy,
|
77
77
|
)
|
78
|
-
self.client.start_sync_loop()
|
79
78
|
self.client.set_startup_data(
|
80
79
|
_get_startup_data(
|
81
80
|
app_version=self.config.app_version,
|
82
81
|
urlconfs=self.config.urlconfs,
|
83
82
|
)
|
84
83
|
)
|
84
|
+
self.client.start_sync_loop()
|
85
85
|
|
86
86
|
self.capture_request_body = (
|
87
87
|
self.client.request_logger.config.enabled and self.client.request_logger.config.log_request_body
|
File without changes
|
File without changes
|
File without changes
|
@@ -51,7 +51,6 @@ class ApitallyMiddleware:
|
|
51
51
|
request_logging_config=request_logging_config,
|
52
52
|
proxy=proxy,
|
53
53
|
)
|
54
|
-
self.client.start_sync_loop()
|
55
54
|
self.delayed_set_startup_data(app_version, openapi_url)
|
56
55
|
|
57
56
|
self.capture_request_body = (
|
@@ -73,6 +72,7 @@ class ApitallyMiddleware:
|
|
73
72
|
def _delayed_set_startup_data(self, app_version: Optional[str] = None, openapi_url: Optional[str] = None) -> None:
|
74
73
|
data = _get_startup_data(self.app, app_version, openapi_url)
|
75
74
|
self.client.set_startup_data(data)
|
75
|
+
self.client.start_sync_loop()
|
76
76
|
|
77
77
|
def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]:
|
78
78
|
if not self.client.enabled:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|