apitally 0.16.1__py3-none-any.whl → 0.16.3__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.
@@ -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 and last_sync_time > 0: # not on first sync
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 and last_sync_time > 0: # not on first sync
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:
apitally/django.py CHANGED
@@ -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
apitally/flask.py CHANGED
@@ -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:
apitally/starlette.py CHANGED
@@ -1,17 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  import time
5
- from typing import Any, Callable, Dict, List, Optional, Union
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
6
6
  from warnings import warn
7
7
 
8
8
  from httpx import HTTPStatusError, Proxy
9
+ from starlette.applications import Starlette
9
10
  from starlette.datastructures import Headers
10
11
  from starlette.requests import Request
11
12
  from starlette.routing import BaseRoute, Match, Router
12
13
  from starlette.schemas import EndpointInfo, SchemaGenerator
13
14
  from starlette.testclient import TestClient
14
- from starlette.types import ASGIApp, Message, Receive, Scope, Send
15
+ from starlette.types import ASGIApp, Lifespan, Message, Receive, Scope, Send
15
16
 
16
17
  from apitally.client.client_asyncio import ApitallyClient
17
18
  from apitally.client.consumers import Consumer as ApitallyConsumer
@@ -40,6 +41,8 @@ class ApitallyMiddleware:
40
41
  proxy: Optional[Union[str, Proxy]] = None,
41
42
  ) -> None:
42
43
  self.app = app
44
+ self.app_version = app_version
45
+ self.openapi_url = openapi_url
43
46
  self.identify_consumer_callback = identify_consumer_callback
44
47
  self.client = ApitallyClient(
45
48
  client_id=client_id,
@@ -47,10 +50,6 @@ class ApitallyMiddleware:
47
50
  request_logging_config=request_logging_config,
48
51
  proxy=proxy,
49
52
  )
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
53
 
55
54
  self.capture_request_body = (
56
55
  self.client.request_logger.config.enabled and self.client.request_logger.config.log_request_body
@@ -59,17 +58,16 @@ class ApitallyMiddleware:
59
58
  self.client.request_logger.config.enabled and self.client.request_logger.config.log_response_body
60
59
  )
61
60
 
62
- def delayed_set_startup_data(self, app_version: Optional[str] = None, openapi_url: Optional[str] = None) -> None:
63
- self._delayed_set_startup_data_task = asyncio.create_task(
64
- self._delayed_set_startup_data(app_version, openapi_url)
61
+ _inject_lifespan_handlers(
62
+ app,
63
+ on_startup=self.on_startup,
64
+ on_shutdown=self.client.handle_shutdown,
65
65
  )
66
66
 
67
- async def _delayed_set_startup_data(
68
- self, app_version: Optional[str] = None, openapi_url: Optional[str] = None
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)
67
+ async def on_startup(self) -> None:
68
+ data = _get_startup_data(self.app, app_version=self.app_version, openapi_url=self.openapi_url)
72
69
  self.client.set_startup_data(data)
70
+ self.client.start_sync_loop()
73
71
 
74
72
  async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
75
73
  if self.client.enabled and scope["type"] == "http" and scope["method"] != "OPTIONS":
@@ -296,8 +294,30 @@ def _get_routes(app: Union[ASGIApp, Router]) -> List[BaseRoute]:
296
294
  return [] # pragma: no cover
297
295
 
298
296
 
299
- def _register_shutdown_handler(app: Union[ASGIApp, Router], shutdown_handler: Callable[[], Any]) -> None:
300
- if isinstance(app, Router):
301
- app.add_event_handler("shutdown", shutdown_handler)
302
- elif hasattr(app, "app"):
303
- _register_shutdown_handler(app.app, shutdown_handler)
297
+ def _inject_lifespan_handlers(
298
+ app: Union[ASGIApp, Router],
299
+ on_startup: Callable[[], Awaitable[Any]],
300
+ on_shutdown: Callable[[], Awaitable[Any]],
301
+ ) -> None:
302
+ """
303
+ Ensures the given startup and shutdown functions are called as part of the app's lifespan context manager.
304
+ """
305
+ router = app
306
+ while not isinstance(router, Router) and hasattr(router, "app"):
307
+ router = router.app
308
+ if not isinstance(router, Router):
309
+ raise TypeError("app must be a Starlette or Router instance")
310
+
311
+ lifespan: Optional[Lifespan] = getattr(router, "lifespan_context", None)
312
+
313
+ @asynccontextmanager
314
+ async def wrapped_lifespan(app: Starlette):
315
+ await on_startup()
316
+ if lifespan is not None:
317
+ async with lifespan(app):
318
+ yield
319
+ else:
320
+ yield
321
+ await on_shutdown()
322
+
323
+ router.lifespan_context = wrapped_lifespan
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apitally
3
- Version: 0.16.1
3
+ Version: 0.16.3
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
@@ -49,9 +49,9 @@ Requires-Dist: inflection>=0.5.1; extra == 'django-rest-framework'
49
49
  Requires-Dist: requests>=2.26.0; extra == 'django-rest-framework'
50
50
  Requires-Dist: uritemplate>=3.0.0; extra == 'django-rest-framework'
51
51
  Provides-Extra: fastapi
52
- Requires-Dist: fastapi>=0.88.0; extra == 'fastapi'
52
+ Requires-Dist: fastapi>=0.94.1; extra == 'fastapi'
53
53
  Requires-Dist: httpx>=0.22.0; extra == 'fastapi'
54
- Requires-Dist: starlette<1.0.0,>=0.22.0; extra == 'fastapi'
54
+ Requires-Dist: starlette<1.0.0,>=0.26.1; extra == 'fastapi'
55
55
  Provides-Extra: flask
56
56
  Requires-Dist: flask>=2.0.0; extra == 'flask'
57
57
  Requires-Dist: requests>=2.26.0; extra == 'flask'
@@ -62,7 +62,7 @@ Provides-Extra: sentry
62
62
  Requires-Dist: sentry-sdk>=2.2.0; extra == 'sentry'
63
63
  Provides-Extra: starlette
64
64
  Requires-Dist: httpx>=0.22.0; extra == 'starlette'
65
- Requires-Dist: starlette<1.0.0,>=0.21.0; extra == 'starlette'
65
+ Requires-Dist: starlette<1.0.0,>=0.26.1; extra == 'starlette'
66
66
  Description-Content-Type: text/markdown
67
67
 
68
68
  <p align="center">
@@ -1,18 +1,18 @@
1
1
  apitally/__init__.py,sha256=ShXQBVjyiSOHxoQJS2BvNG395W4KZfqMxZWBAR0MZrE,22
2
2
  apitally/blacksheep.py,sha256=KvcPFeiwQgWZmRglbm8SLaN6_WRs5kZ3SymB1IuLR-A,9616
3
3
  apitally/common.py,sha256=azDxepViH0QW0MuufTHxeSQyLGzCkocAX_KPziWTx8A,1605
4
- apitally/django.py,sha256=zwe8svC8rfo7TyHfOlkYTeXptxPFoRjvt0bbYvgtJKM,16892
4
+ apitally/django.py,sha256=f_k7yYlvvvhJMR53NcXCfmlLxLX3CeLO9ephF4bzKbo,16892
5
5
  apitally/django_ninja.py,sha256=-CmrwFFRv7thFOUK_OrOSouhHL9bm5sIBNIQlpyE_2c,166
6
6
  apitally/django_rest_framework.py,sha256=-CmrwFFRv7thFOUK_OrOSouhHL9bm5sIBNIQlpyE_2c,166
7
7
  apitally/fastapi.py,sha256=IfKfgsmIY8_AtnuMTW2sW4qnkya61CAE2vBoIpcc9tk,169
8
- apitally/flask.py,sha256=p_u33_FQq2i5AebWB8wYxXX0CPhcX8OJHGWj5dR4sPY,9622
8
+ apitally/flask.py,sha256=OoCEnjtnD51GUGq-adK80ebuiLj-5HXubxffCv5XTCM,9622
9
9
  apitally/litestar.py,sha256=mHoMqBO_gyoopeHljY8e8GTcV29UDf3uhQMxY3GeNpA,13451
10
10
  apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- apitally/starlette.py,sha256=iEcN--2eeUW9d78H42WolWEkss2idvXLjK2OQmvULdM,13218
11
+ apitally/starlette.py,sha256=081vXTNOy6-zZ8ugXknRtQZnFFqG7XKLW4Ho6oaHEUY,13525
12
12
  apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- apitally/client/client_asyncio.py,sha256=9mdi9Hmb6-xn7dNdwP84e4PNAHGg2bYdMEgIfPUAtcQ,7003
13
+ apitally/client/client_asyncio.py,sha256=rTsH5wlLHK3RmyIuEiT6vzjquU-l2OPC34JnC2U6uYw,6658
14
14
  apitally/client/client_base.py,sha256=DvivGeHd3dyOASRvkIo44Zh8RzdBMfH8_rROa2lFbgw,3799
15
- apitally/client/client_threading.py,sha256=7JPu2Uulev7X2RiSLx4HJYfvAP6Z5zB_yuSevMfQC7I,7389
15
+ apitally/client/client_threading.py,sha256=sxMRcxRgk1SxJjSq-qpIcDVmD3Q7Kv4CVT5zEUVt0KM,7257
16
16
  apitally/client/consumers.py,sha256=w_AFQhVgdtJVt7pVySBvSZwQg-2JVqmD2JQtVBoMkus,2626
17
17
  apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
18
18
  apitally/client/request_logging.py,sha256=SMvQd3WDolnb8u9rHVh2_OgXwFjL2jLZt-GpZNQ1XGk,14115
@@ -20,7 +20,7 @@ apitally/client/requests.py,sha256=SDptGOg9XvaEKFj2o3oxJz-JAuZzUrqpHnbOQixf99o,3
20
20
  apitally/client/sentry.py,sha256=qMjHdI0V7c50ruo1WjmjWc8g6oGDv724vSCvcuZ8G9k,1188
21
21
  apitally/client/server_errors.py,sha256=4B2BKDFoIpoWc55UVH6AIdYSgzj6zxCdMNUW77JjhZw,3423
22
22
  apitally/client/validation_errors.py,sha256=6G8WYWFgJs9VH9swvkPXJGuOJgymj5ooWA9OwjUTbuM,1964
23
- apitally-0.16.1.dist-info/METADATA,sha256=eS5sdPc9rWZRyr8WS_ZcvjSxtmiByflQ8n1GI01_0VI,9321
24
- apitally-0.16.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- apitally-0.16.1.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
26
- apitally-0.16.1.dist-info/RECORD,,
23
+ apitally-0.16.3.dist-info/METADATA,sha256=NbeWFQBfMfdu1Ao_0zGhBTMsNO3m1xmWZ6KNhGe2qNQ,9321
24
+ apitally-0.16.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ apitally-0.16.3.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
26
+ apitally-0.16.3.dist-info/RECORD,,