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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.12.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
- for line in traceback.format_exception(exception)[::-1]:
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 response_time, response_status, response_headers, response_body
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
- elif message["type"] == "http.response.body" and response_status == 400:
84
- response_body += message["body"]
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 = 0.0
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 response_time, response_status, response_headers, response_body
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
- elif message["type"] == "http.response.body" and response_status == 422:
74
- response_body += message["body"]
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
- self.add_request(
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
- else:
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
1
+ Metadata-Version: 2.3
2
2
  Name: apitally
3
- Version: 0.12.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
- Home-page: https://apitally.io
6
- License: MIT
7
- Author: Apitally
8
- Author-email: hello@apitally.io
9
- Requires-Python: >=3.8,<4.0
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: backoff (>=2.0.0)
32
- Requires-Dist: django (>=2.2) ; (python_version >= "3.10") and (extra == "django-ninja" or extra == "django-rest-framework")
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=eHjt9DPsMbptabS2yGx9Yhbyzq5hFSUHXb7zc8Q_8-o,23
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=sQcrHw-JV9AlpnXlrczmaDe0k6tD9PYQsc8nyQul8Ko,8802
8
+ apitally/litestar.py,sha256=O9bSzwJC-dN6ukRqyVNYBhUqxEpzie-bR2bFojcvvMI,9547
14
9
  apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- apitally/starlette.py,sha256=PA_0BTy9aVtrY2_QrWU7Js5vjW1uxZ476rmg95fXq2g,8881
16
- apitally-0.12.0.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
17
- apitally-0.12.0.dist-info/METADATA,sha256=gGryZ9u9sLV8mYrcoRhvYK4lQ4bFf9vhbvrCENA6T-I,6994
18
- apitally-0.12.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
19
- apitally-0.12.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.7.0
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any