apitally 0.15.0__py3-none-any.whl → 0.15.1__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/common.py CHANGED
@@ -1,6 +1,8 @@
1
+ import gzip
2
+ import json
1
3
  import sys
2
4
  from importlib.metadata import PackageNotFoundError, version
3
- from typing import Dict, Optional, Union
5
+ from typing import Any, Dict, Optional, Union
4
6
 
5
7
 
6
8
  def parse_int(x: Union[str, int, None]) -> Optional[int]:
@@ -12,6 +14,18 @@ def parse_int(x: Union[str, int, None]) -> Optional[int]:
12
14
  return None
13
15
 
14
16
 
17
+ def try_json_loads(s: bytes, encoding: Optional[str] = None) -> Any:
18
+ if encoding is not None and encoding.lower() == "gzip":
19
+ try:
20
+ s = gzip.decompress(s)
21
+ except Exception:
22
+ pass
23
+ try:
24
+ return json.loads(s)
25
+ except Exception:
26
+ return None
27
+
28
+
15
29
  def get_versions(*packages, app_version: Optional[str] = None) -> Dict[str, str]:
16
30
  versions = _get_common_package_versions()
17
31
  for package in packages:
apitally/django.py CHANGED
@@ -22,7 +22,7 @@ from apitally.client.request_logging import (
22
22
  RequestLogger,
23
23
  RequestLoggingConfig,
24
24
  )
25
- from apitally.common import get_versions, parse_int
25
+ from apitally.common import get_versions, parse_int, try_json_loads
26
26
 
27
27
 
28
28
  if TYPE_CHECKING:
@@ -173,16 +173,15 @@ class ApitallyMiddleware:
173
173
  and content_type.startswith("application/json")
174
174
  ):
175
175
  try:
176
- with contextlib.suppress(json.JSONDecodeError):
177
- body = json.loads(response.content)
178
- if isinstance(body, dict) and "detail" in body and isinstance(body["detail"], list):
179
- # Log Django Ninja / Pydantic validation errors
180
- self.client.validation_error_counter.add_validation_errors(
181
- consumer=consumer_identifier,
182
- method=request.method,
183
- path=path,
184
- detail=body["detail"],
185
- )
176
+ body = try_json_loads(response.content, encoding=response.get("Content-Encoding"))
177
+ if isinstance(body, dict) and "detail" in body and isinstance(body["detail"], list):
178
+ # Log Django Ninja / Pydantic validation errors
179
+ self.client.validation_error_counter.add_validation_errors(
180
+ consumer=consumer_identifier,
181
+ method=request.method,
182
+ path=path,
183
+ detail=body["detail"],
184
+ )
186
185
  except Exception: # pragma: no cover
187
186
  logger.exception("Failed to log validation errors")
188
187
 
apitally/litestar.py CHANGED
@@ -1,4 +1,3 @@
1
- import contextlib
2
1
  import json
3
2
  import time
4
3
  from typing import Callable, Dict, List, Optional, Union
@@ -22,7 +21,7 @@ from apitally.client.request_logging import (
22
21
  RequestLogger,
23
22
  RequestLoggingConfig,
24
23
  )
25
- from apitally.common import get_versions, parse_int
24
+ from apitally.common import get_versions, parse_int, try_json_loads
26
25
 
27
26
 
28
27
  __all__ = ["ApitallyPlugin", "ApitallyConsumer", "RequestLoggingConfig"]
@@ -199,30 +198,29 @@ class ApitallyPlugin(InitPluginProtocol):
199
198
  )
200
199
 
201
200
  if response_status == 400 and response_body and len(response_body) < 4096:
202
- with contextlib.suppress(json.JSONDecodeError):
203
- parsed_body = json.loads(response_body)
204
- if (
205
- isinstance(parsed_body, dict)
206
- and "detail" in parsed_body
207
- and isinstance(parsed_body["detail"], str)
208
- and "validation" in parsed_body["detail"].lower()
209
- and "extra" in parsed_body
210
- and isinstance(parsed_body["extra"], list)
211
- ):
212
- self.client.validation_error_counter.add_validation_errors(
213
- consumer=consumer_identifier,
214
- method=request.method,
215
- path=path,
216
- detail=[
217
- {
218
- "loc": [error.get("source", "body")] + error["key"].split("."),
219
- "msg": error["message"],
220
- "type": "",
221
- }
222
- for error in parsed_body["extra"]
223
- if "key" in error and "message" in error
224
- ],
225
- )
201
+ body = try_json_loads(response_body, encoding=response_headers.get("Content-Encoding"))
202
+ if (
203
+ isinstance(body, dict)
204
+ and "detail" in body
205
+ and isinstance(body["detail"], str)
206
+ and "validation" in body["detail"].lower()
207
+ and "extra" in body
208
+ and isinstance(body["extra"], list)
209
+ ):
210
+ self.client.validation_error_counter.add_validation_errors(
211
+ consumer=consumer_identifier,
212
+ method=request.method,
213
+ path=path,
214
+ detail=[
215
+ {
216
+ "loc": [error.get("source", "body")] + error["key"].split("."),
217
+ "msg": error["message"],
218
+ "type": "",
219
+ }
220
+ for error in body["extra"]
221
+ if "key" in error and "message" in error
222
+ ],
223
+ )
226
224
 
227
225
  if response_status == 500 and "exception" in request.state:
228
226
  self.client.server_error_counter.add_server_error(
apitally/starlette.py CHANGED
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import contextlib
5
- import json
6
4
  import time
7
5
  from typing import Any, Callable, Dict, List, Optional, Union
8
6
  from warnings import warn
@@ -23,7 +21,7 @@ from apitally.client.request_logging import (
23
21
  RequestLogger,
24
22
  RequestLoggingConfig,
25
23
  )
26
- from apitally.common import get_versions, parse_int
24
+ from apitally.common import get_versions, parse_int, try_json_loads
27
25
 
28
26
 
29
27
  __all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig"]
@@ -191,16 +189,15 @@ class ApitallyMiddleware:
191
189
  response_size=response_size,
192
190
  )
193
191
  if response_status == 422 and response_body and response_headers.get("Content-Type") == "application/json":
194
- with contextlib.suppress(json.JSONDecodeError):
195
- body = json.loads(response_body)
196
- if isinstance(body, dict) and "detail" in body and isinstance(body["detail"], list):
197
- # Log FastAPI / Pydantic validation errors
198
- self.client.validation_error_counter.add_validation_errors(
199
- consumer=consumer_identifier,
200
- method=request.method,
201
- path=path,
202
- detail=body["detail"],
203
- )
192
+ body = try_json_loads(response_body, encoding=response_headers.get("Content-Encoding"))
193
+ if isinstance(body, dict) and "detail" in body and isinstance(body["detail"], list):
194
+ # Log FastAPI / Pydantic validation errors
195
+ self.client.validation_error_counter.add_validation_errors(
196
+ consumer=consumer_identifier,
197
+ method=request.method,
198
+ path=path,
199
+ detail=body["detail"],
200
+ )
204
201
  if response_status == 500 and exception is not None:
205
202
  self.client.server_error_counter.add_server_error(
206
203
  consumer=consumer_identifier,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apitally
3
- Version: 0.15.0
3
+ Version: 0.15.1
4
4
  Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.
5
5
  Project-URL: Homepage, https://apitally.io
6
6
  Project-URL: Documentation, https://docs.apitally.io
@@ -1,13 +1,13 @@
1
1
  apitally/__init__.py,sha256=ShXQBVjyiSOHxoQJS2BvNG395W4KZfqMxZWBAR0MZrE,22
2
- apitally/common.py,sha256=Y8MRuTUHFUeQkcDrCLUxnqIPRpYIiW8S43T0QUab-_A,1267
3
- apitally/django.py,sha256=1gLW5aVbIobjHoa5OzC3K1E85mC2a00PpJIAR9ATko4,16937
2
+ apitally/common.py,sha256=FMDBPlYHCqomgAq-Z8JiyTSMAoqJRycPsJzsxncQqQA,1598
3
+ apitally/django.py,sha256=zwe8svC8rfo7TyHfOlkYTeXptxPFoRjvt0bbYvgtJKM,16892
4
4
  apitally/django_ninja.py,sha256=-CmrwFFRv7thFOUK_OrOSouhHL9bm5sIBNIQlpyE_2c,166
5
5
  apitally/django_rest_framework.py,sha256=-CmrwFFRv7thFOUK_OrOSouhHL9bm5sIBNIQlpyE_2c,166
6
6
  apitally/fastapi.py,sha256=IfKfgsmIY8_AtnuMTW2sW4qnkya61CAE2vBoIpcc9tk,169
7
7
  apitally/flask.py,sha256=p_u33_FQq2i5AebWB8wYxXX0CPhcX8OJHGWj5dR4sPY,9622
8
- apitally/litestar.py,sha256=HThNH-gAnFtLyVU4Eh8L_obd0f3TNLYoTZ8IGgz1ZKE,13610
8
+ apitally/litestar.py,sha256=mHoMqBO_gyoopeHljY8e8GTcV29UDf3uhQMxY3GeNpA,13451
9
9
  apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- apitally/starlette.py,sha256=liK1KSEiQolHm1cNfJm6tkmE3uujoVqj8b-2prUVa3o,13277
10
+ apitally/starlette.py,sha256=iEcN--2eeUW9d78H42WolWEkss2idvXLjK2OQmvULdM,13218
11
11
  apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  apitally/client/client_asyncio.py,sha256=9mdi9Hmb6-xn7dNdwP84e4PNAHGg2bYdMEgIfPUAtcQ,7003
13
13
  apitally/client/client_base.py,sha256=DvivGeHd3dyOASRvkIo44Zh8RzdBMfH8_rROa2lFbgw,3799
@@ -19,7 +19,7 @@ apitally/client/requests.py,sha256=RdJyvIqQGVHvS-wjpAPUwcO7byOJ6jO8dYqNTU2Furg,3
19
19
  apitally/client/sentry.py,sha256=qMjHdI0V7c50ruo1WjmjWc8g6oGDv724vSCvcuZ8G9k,1188
20
20
  apitally/client/server_errors.py,sha256=4B2BKDFoIpoWc55UVH6AIdYSgzj6zxCdMNUW77JjhZw,3423
21
21
  apitally/client/validation_errors.py,sha256=6G8WYWFgJs9VH9swvkPXJGuOJgymj5ooWA9OwjUTbuM,1964
22
- apitally-0.15.0.dist-info/METADATA,sha256=CZxfxtM3p1RBy5ICdnuhjAWt4CESHhKtAIFEFtKCbjw,8643
23
- apitally-0.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
- apitally-0.15.0.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
25
- apitally-0.15.0.dist-info/RECORD,,
22
+ apitally-0.15.1.dist-info/METADATA,sha256=Z8Es_x6H-rxC5KB26S0i1BFGVBlISkQfNiYPglyS09E,8643
23
+ apitally-0.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ apitally-0.15.1.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
25
+ apitally-0.15.1.dist-info/RECORD,,