apitally 0.12.0__py3-none-any.whl → 0.13.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.
- apitally/__init__.py +1 -1
- apitally/client/base.py +6 -1
- apitally/litestar.py +21 -4
- apitally/starlette.py +30 -14
- {apitally-0.12.0.dist-info → apitally-0.13.0.dist-info}/METADATA +38 -24
- {apitally-0.12.0.dist-info → apitally-0.13.0.dist-info}/RECORD +12 -12
- {apitally-0.12.0.dist-info → apitally-0.13.0.dist-info}/WHEEL +1 -1
- {apitally-0.12.0.dist-info → apitally-0.13.0.dist-info/licenses}/LICENSE +0 -0
apitally/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.
|
1
|
+
__version__ = "0.0.0"
|
apitally/client/base.py
CHANGED
@@ -4,6 +4,7 @@ import asyncio
|
|
4
4
|
import contextlib
|
5
5
|
import os
|
6
6
|
import re
|
7
|
+
import sys
|
7
8
|
import threading
|
8
9
|
import time
|
9
10
|
import traceback
|
@@ -328,7 +329,11 @@ class ServerErrorCounter:
|
|
328
329
|
cutoff = MAX_EXCEPTION_TRACEBACK_LENGTH - len(prefix)
|
329
330
|
lines = []
|
330
331
|
length = 0
|
331
|
-
|
332
|
+
if sys.version_info >= (3, 10):
|
333
|
+
traceback_lines = traceback.format_exception(exception)
|
334
|
+
else:
|
335
|
+
traceback_lines = traceback.format_exception(type(exception), exception, exception.__traceback__)
|
336
|
+
for line in traceback_lines[::-1]:
|
332
337
|
if length + len(line) > cutoff:
|
333
338
|
lines.append(prefix)
|
334
339
|
break
|
apitally/litestar.py
CHANGED
@@ -72,16 +72,31 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
72
72
|
response_time = 0.0
|
73
73
|
response_headers = Headers()
|
74
74
|
response_body = b""
|
75
|
+
response_size = 0
|
76
|
+
response_chunked = False
|
75
77
|
start_time = time.perf_counter()
|
76
78
|
|
77
79
|
async def send_wrapper(message: Message) -> None:
|
78
|
-
nonlocal
|
80
|
+
nonlocal \
|
81
|
+
response_time, \
|
82
|
+
response_status, \
|
83
|
+
response_headers, \
|
84
|
+
response_body, \
|
85
|
+
response_size, \
|
86
|
+
response_chunked
|
79
87
|
if message["type"] == "http.response.start":
|
80
88
|
response_time = time.perf_counter() - start_time
|
81
89
|
response_status = message["status"]
|
82
90
|
response_headers = Headers(message["headers"])
|
83
|
-
|
84
|
-
|
91
|
+
response_chunked = (
|
92
|
+
response_headers.get("Transfer-Encoding") == "chunked"
|
93
|
+
or "Content-Length" not in response_headers
|
94
|
+
)
|
95
|
+
elif message["type"] == "http.response.body":
|
96
|
+
if response_chunked:
|
97
|
+
response_size += len(message.get("body", b""))
|
98
|
+
if response_status == 400:
|
99
|
+
response_body += message.get("body", b"")
|
85
100
|
await send(message)
|
86
101
|
|
87
102
|
await app(scope, receive, send_wrapper)
|
@@ -91,6 +106,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
91
106
|
response_time=response_time,
|
92
107
|
response_headers=response_headers,
|
93
108
|
response_body=response_body,
|
109
|
+
response_size=response_size,
|
94
110
|
)
|
95
111
|
else:
|
96
112
|
await app(scope, receive, send) # pragma: no cover
|
@@ -104,6 +120,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
104
120
|
response_time: float,
|
105
121
|
response_headers: Headers,
|
106
122
|
response_body: bytes,
|
123
|
+
response_size: int = 0,
|
107
124
|
) -> None:
|
108
125
|
if response_status < 100 or not request.route_handler.paths:
|
109
126
|
return # pragma: no cover
|
@@ -120,7 +137,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
120
137
|
status_code=response_status,
|
121
138
|
response_time=response_time,
|
122
139
|
request_size=request.headers.get("Content-Length"),
|
123
|
-
response_size=response_headers.get("Content-Length"),
|
140
|
+
response_size=response_size or response_headers.get("Content-Length"),
|
124
141
|
)
|
125
142
|
if response_status == 400 and response_body and len(response_body) < 4096:
|
126
143
|
with contextlib.suppress(json.JSONDecodeError):
|
apitally/starlette.py
CHANGED
@@ -59,40 +59,53 @@ class ApitallyMiddleware:
|
|
59
59
|
if scope["type"] == "http" and scope["method"] != "OPTIONS":
|
60
60
|
request = Request(scope)
|
61
61
|
response_status = 0
|
62
|
-
response_time =
|
62
|
+
response_time: Optional[float] = None
|
63
63
|
response_headers = Headers()
|
64
64
|
response_body = b""
|
65
|
+
response_size = 0
|
66
|
+
response_chunked = False
|
67
|
+
exception: Optional[BaseException] = None
|
65
68
|
start_time = time.perf_counter()
|
66
69
|
|
67
70
|
async def send_wrapper(message: Message) -> None:
|
68
|
-
nonlocal
|
71
|
+
nonlocal \
|
72
|
+
response_time, \
|
73
|
+
response_status, \
|
74
|
+
response_headers, \
|
75
|
+
response_body, \
|
76
|
+
response_size, \
|
77
|
+
response_chunked
|
69
78
|
if message["type"] == "http.response.start":
|
70
79
|
response_time = time.perf_counter() - start_time
|
71
80
|
response_status = message["status"]
|
72
81
|
response_headers = Headers(scope=message)
|
73
|
-
|
74
|
-
|
82
|
+
response_chunked = (
|
83
|
+
response_headers.get("Transfer-Encoding") == "chunked"
|
84
|
+
or "Content-Length" not in response_headers
|
85
|
+
)
|
86
|
+
elif message["type"] == "http.response.body":
|
87
|
+
if response_chunked:
|
88
|
+
response_size += len(message.get("body", b""))
|
89
|
+
if response_status == 422:
|
90
|
+
response_body += message.get("body", b"")
|
75
91
|
await send(message)
|
76
92
|
|
77
93
|
try:
|
78
94
|
await self.app(scope, receive, send_wrapper)
|
79
95
|
except BaseException as e:
|
80
|
-
|
81
|
-
request=request,
|
82
|
-
response_status=500,
|
83
|
-
response_time=time.perf_counter() - start_time,
|
84
|
-
response_headers=response_headers,
|
85
|
-
response_body=response_body,
|
86
|
-
exception=e,
|
87
|
-
)
|
96
|
+
exception = e
|
88
97
|
raise e from None
|
89
|
-
|
98
|
+
finally:
|
99
|
+
if response_time is None:
|
100
|
+
response_time = time.perf_counter() - start_time
|
90
101
|
self.add_request(
|
91
102
|
request=request,
|
92
103
|
response_status=response_status,
|
93
104
|
response_time=response_time,
|
94
105
|
response_headers=response_headers,
|
95
106
|
response_body=response_body,
|
107
|
+
response_size=response_size,
|
108
|
+
exception=exception,
|
96
109
|
)
|
97
110
|
else:
|
98
111
|
await self.app(scope, receive, send) # pragma: no cover
|
@@ -104,6 +117,7 @@ class ApitallyMiddleware:
|
|
104
117
|
response_time: float,
|
105
118
|
response_headers: Headers,
|
106
119
|
response_body: bytes,
|
120
|
+
response_size: int = 0,
|
107
121
|
exception: Optional[BaseException] = None,
|
108
122
|
) -> None:
|
109
123
|
path_template, is_handled_path = self.get_path_template(request)
|
@@ -111,6 +125,8 @@ class ApitallyMiddleware:
|
|
111
125
|
consumer = self.get_consumer(request)
|
112
126
|
consumer_identifier = consumer.identifier if consumer else None
|
113
127
|
self.client.consumer_registry.add_or_update_consumer(consumer)
|
128
|
+
if response_status == 0 and exception is not None:
|
129
|
+
response_status = 500
|
114
130
|
self.client.request_counter.add_request(
|
115
131
|
consumer=consumer_identifier,
|
116
132
|
method=request.method,
|
@@ -118,7 +134,7 @@ class ApitallyMiddleware:
|
|
118
134
|
status_code=response_status,
|
119
135
|
response_time=response_time,
|
120
136
|
request_size=request.headers.get("Content-Length"),
|
121
|
-
response_size=response_headers.get("Content-Length"),
|
137
|
+
response_size=response_size or response_headers.get("Content-Length"),
|
122
138
|
)
|
123
139
|
if response_status == 422 and response_body and response_headers.get("Content-Type") == "application/json":
|
124
140
|
with contextlib.suppress(json.JSONDecodeError):
|
@@ -1,49 +1,64 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.0
|
4
4
|
Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
Author-email: hello@apitally.io
|
9
|
-
|
5
|
+
Project-URL: Homepage, https://apitally.io
|
6
|
+
Project-URL: Documentation, https://docs.apitally.io
|
7
|
+
Project-URL: Repository, https://github.com/apitally/apitally-py
|
8
|
+
Author-email: Apitally <hello@apitally.io>
|
9
|
+
License: MIT License
|
10
|
+
License-File: LICENSE
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
12
|
+
Classifier: Environment :: Web Environment
|
11
13
|
Classifier: Framework :: Django
|
12
14
|
Classifier: Framework :: FastAPI
|
13
15
|
Classifier: Framework :: Flask
|
14
16
|
Classifier: Intended Audience :: Developers
|
17
|
+
Classifier: Intended Audience :: Information Technology
|
15
18
|
Classifier: License :: OSI Approved :: MIT License
|
19
|
+
Classifier: Programming Language :: Python
|
16
20
|
Classifier: Programming Language :: Python :: 3
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
17
22
|
Classifier: Programming Language :: Python :: 3.8
|
18
23
|
Classifier: Programming Language :: Python :: 3.9
|
19
24
|
Classifier: Programming Language :: Python :: 3.10
|
20
25
|
Classifier: Programming Language :: Python :: 3.11
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
27
|
+
Classifier: Topic :: Internet
|
21
28
|
Classifier: Topic :: Internet :: WWW/HTTP
|
22
29
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
|
30
|
+
Classifier: Topic :: Software Development
|
31
|
+
Classifier: Topic :: System :: Monitoring
|
23
32
|
Classifier: Typing :: Typed
|
33
|
+
Requires-Python: <4.0,>=3.8
|
34
|
+
Requires-Dist: backoff>=2.0.0
|
24
35
|
Provides-Extra: django-ninja
|
36
|
+
Requires-Dist: django-ninja>=0.18.0; extra == 'django-ninja'
|
37
|
+
Requires-Dist: django<5,>=2.2; (python_version < '3.10') and extra == 'django-ninja'
|
38
|
+
Requires-Dist: django>=2.2; (python_version >= '3.10') and extra == 'django-ninja'
|
39
|
+
Requires-Dist: requests>=2.26.0; extra == 'django-ninja'
|
25
40
|
Provides-Extra: django-rest-framework
|
41
|
+
Requires-Dist: django<5,>=2.2; (python_version < '3.10') and extra == 'django-rest-framework'
|
42
|
+
Requires-Dist: django>=2.2; (python_version >= '3.10') and extra == 'django-rest-framework'
|
43
|
+
Requires-Dist: djangorestframework>=3.10.0; extra == 'django-rest-framework'
|
44
|
+
Requires-Dist: inflection>=0.5.1; extra == 'django-rest-framework'
|
45
|
+
Requires-Dist: requests>=2.26.0; extra == 'django-rest-framework'
|
46
|
+
Requires-Dist: uritemplate>=3.0.0; extra == 'django-rest-framework'
|
26
47
|
Provides-Extra: fastapi
|
48
|
+
Requires-Dist: fastapi>=0.88.0; extra == 'fastapi'
|
49
|
+
Requires-Dist: httpx>=0.22.0; extra == 'fastapi'
|
50
|
+
Requires-Dist: starlette<1.0.0,>=0.22.0; extra == 'fastapi'
|
27
51
|
Provides-Extra: flask
|
52
|
+
Requires-Dist: flask>=2.0.0; extra == 'flask'
|
53
|
+
Requires-Dist: requests>=2.26.0; extra == 'flask'
|
28
54
|
Provides-Extra: litestar
|
55
|
+
Requires-Dist: httpx>=0.22.0; extra == 'litestar'
|
56
|
+
Requires-Dist: litestar>=2.0.0; extra == 'litestar'
|
29
57
|
Provides-Extra: sentry
|
58
|
+
Requires-Dist: sentry-sdk>=2.2.0; extra == 'sentry'
|
30
59
|
Provides-Extra: starlette
|
31
|
-
Requires-Dist:
|
32
|
-
Requires-Dist:
|
33
|
-
Requires-Dist: django (>=2.2,<5) ; (python_version < "3.10") and (extra == "django-ninja" or extra == "django-rest-framework")
|
34
|
-
Requires-Dist: django-ninja (>=0.18.0) ; extra == "django-ninja"
|
35
|
-
Requires-Dist: djangorestframework (>=3.10.0) ; extra == "django-rest-framework"
|
36
|
-
Requires-Dist: fastapi (>=0.87.0) ; extra == "fastapi"
|
37
|
-
Requires-Dist: flask (>=2.0.0) ; extra == "flask"
|
38
|
-
Requires-Dist: httpx (>=0.22.0) ; extra == "fastapi" or extra == "litestar" or extra == "starlette"
|
39
|
-
Requires-Dist: inflection (>=0.5.1) ; extra == "django-rest-framework"
|
40
|
-
Requires-Dist: litestar (>=2.0.0) ; extra == "litestar"
|
41
|
-
Requires-Dist: requests (>=2.26.0) ; extra == "django-ninja" or extra == "django-rest-framework" or extra == "flask"
|
42
|
-
Requires-Dist: sentry-sdk (>=2.2.0) ; extra == "sentry"
|
43
|
-
Requires-Dist: starlette (>=0.21.0,<1.0.0) ; extra == "fastapi" or extra == "starlette"
|
44
|
-
Requires-Dist: uritemplate (>=3.0.0) ; extra == "django-rest-framework"
|
45
|
-
Project-URL: Documentation, https://docs.apitally.io
|
46
|
-
Project-URL: Repository, https://github.com/apitally/apitally-py
|
60
|
+
Requires-Dist: httpx>=0.22.0; extra == 'starlette'
|
61
|
+
Requires-Dist: starlette<1.0.0,>=0.21.0; extra == 'starlette'
|
47
62
|
Description-Content-Type: text/markdown
|
48
63
|
|
49
64
|
<p align="center">
|
@@ -193,4 +208,3 @@ on GitHub or
|
|
193
208
|
## License
|
194
209
|
|
195
210
|
This library is licensed under the terms of the MIT license.
|
196
|
-
|
@@ -1,19 +1,19 @@
|
|
1
|
-
apitally/__init__.py,sha256=
|
2
|
-
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
apitally/client/asyncio.py,sha256=Y5sbRLRnJCIJx9VQ2DGgQsYNKGvURV2U1y3VxHuPhgQ,4874
|
4
|
-
apitally/client/base.py,sha256=v4LSOYNIOoeL3KTVyBlXBY5LCXc79Lvu9yK5Y_KILSQ,15442
|
5
|
-
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
-
apitally/client/threading.py,sha256=cASa0C9nyRp5gf5IzCDj6TE-v8t8SW4zJ38W6NdJ3Q8,5204
|
1
|
+
apitally/__init__.py,sha256=ShXQBVjyiSOHxoQJS2BvNG395W4KZfqMxZWBAR0MZrE,22
|
7
2
|
apitally/common.py,sha256=GbVmnXxhRvV30d7CfCQ9r0AeXj14Mv9Jm_Yd1bRWP28,1088
|
8
3
|
apitally/django.py,sha256=Zw8a971UwGKaEMPUtmlBbjufAYwMkSjRSQlss8FDY-E,13795
|
9
4
|
apitally/django_ninja.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
10
5
|
apitally/django_rest_framework.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
11
6
|
apitally/fastapi.py,sha256=hEyYZsvIaA3OXZSSFdey5iqeEjfBPHgfNbyX8pLm7GI,123
|
12
7
|
apitally/flask.py,sha256=7TJIoAT91-bR_7gZkL0clDk-Whl-V21hbo4nASaDmB4,6447
|
13
|
-
apitally/litestar.py,sha256=
|
8
|
+
apitally/litestar.py,sha256=O9bSzwJC-dN6ukRqyVNYBhUqxEpzie-bR2bFojcvvMI,9547
|
14
9
|
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
apitally/starlette.py,sha256=
|
16
|
-
apitally
|
17
|
-
apitally
|
18
|
-
apitally
|
19
|
-
apitally
|
10
|
+
apitally/starlette.py,sha256=B1QTvw5nf9pdnuQda5XsCfConMN81ze8WQ0ldmiTdkc,9589
|
11
|
+
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
+
apitally/client/asyncio.py,sha256=Y5sbRLRnJCIJx9VQ2DGgQsYNKGvURV2U1y3VxHuPhgQ,4874
|
13
|
+
apitally/client/base.py,sha256=BC_KNDuhDQcjTasvztPey9879ITcuARhgCm6HSYbGTI,15663
|
14
|
+
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
15
|
+
apitally/client/threading.py,sha256=cASa0C9nyRp5gf5IzCDj6TE-v8t8SW4zJ38W6NdJ3Q8,5204
|
16
|
+
apitally-0.13.0.dist-info/METADATA,sha256=-MZ4Xne70XtcDIT3waW4QvrMGnLdu-Sfz-CghqyJcf4,7599
|
17
|
+
apitally-0.13.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
18
|
+
apitally-0.13.0.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
19
|
+
apitally-0.13.0.dist-info/RECORD,,
|
File without changes
|