apitally 0.14.4__py3-none-any.whl → 0.14.6__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/client/client_asyncio.py +2 -0
- apitally/client/client_base.py +1 -0
- apitally/client/client_threading.py +1 -0
- apitally/client/request_logging.py +15 -12
- apitally/django.py +8 -2
- apitally/flask.py +6 -1
- apitally/litestar.py +10 -2
- apitally/starlette.py +10 -2
- {apitally-0.14.4.dist-info → apitally-0.14.6.dist-info}/METADATA +61 -31
- {apitally-0.14.4.dist-info → apitally-0.14.6.dist-info}/RECORD +12 -12
- {apitally-0.14.4.dist-info → apitally-0.14.6.dist-info}/WHEEL +0 -0
- {apitally-0.14.4.dist-info → apitally-0.14.6.dist-info}/licenses/LICENSE +0 -0
@@ -81,6 +81,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
81
81
|
self._stop_sync_loop = True
|
82
82
|
|
83
83
|
async def handle_shutdown(self) -> None:
|
84
|
+
self.enabled = False
|
84
85
|
if self._sync_loop_task is not None:
|
85
86
|
self._sync_loop_task.cancel()
|
86
87
|
# Send any remaining data before exiting
|
@@ -164,6 +165,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
164
165
|
|
165
166
|
def _handle_hub_response(self, response: httpx.Response) -> None:
|
166
167
|
if response.status_code == 404:
|
168
|
+
self.enabled = False
|
167
169
|
self.stop_sync_loop()
|
168
170
|
logger.error("Invalid Apitally client ID: %s", self.client_id)
|
169
171
|
elif response.status_code == 422:
|
apitally/client/client_base.py
CHANGED
@@ -190,6 +190,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
190
190
|
|
191
191
|
def _handle_hub_response(self, response: requests.Response) -> None:
|
192
192
|
if response.status_code == 404:
|
193
|
+
self.enabled = False
|
193
194
|
self.stop_sync_loop()
|
194
195
|
logger.error("Invalid Apitally client ID: %s", self.client_id)
|
195
196
|
elif response.status_code == 422:
|
@@ -5,7 +5,6 @@ import tempfile
|
|
5
5
|
import threading
|
6
6
|
import time
|
7
7
|
from collections import deque
|
8
|
-
from contextlib import suppress
|
9
8
|
from dataclasses import dataclass, field
|
10
9
|
from functools import lru_cache
|
11
10
|
from io import BufferedReader
|
@@ -29,14 +28,14 @@ MASKED = "******"
|
|
29
28
|
ALLOWED_CONTENT_TYPES = ["application/json", "text/plain"]
|
30
29
|
EXCLUDE_PATH_PATTERNS = [
|
31
30
|
r"/_?healthz?$",
|
32
|
-
r"/_?health[_
|
33
|
-
r"/_?heart[_
|
31
|
+
r"/_?health[\-_]?checks?$",
|
32
|
+
r"/_?heart[\-_]?beats?$",
|
34
33
|
r"/ping$",
|
35
34
|
r"/ready$",
|
36
35
|
r"/live$",
|
37
36
|
]
|
38
37
|
EXCLUDE_USER_AGENT_PATTERNS = [
|
39
|
-
r"health[_
|
38
|
+
r"health[\-_ ]?check",
|
40
39
|
r"microsoft-azure-application-lb",
|
41
40
|
r"googlehc",
|
42
41
|
r"kube-probe",
|
@@ -178,8 +177,6 @@ class RequestLogger:
|
|
178
177
|
|
179
178
|
query = self._mask_query_params(parsed_url.query) if self.config.log_query_params else ""
|
180
179
|
request["url"] = urlunparse(parsed_url._replace(query=query))
|
181
|
-
request["headers"] = self._mask_headers(request["headers"]) if self.config.log_request_headers else []
|
182
|
-
response["headers"] = self._mask_headers(response["headers"]) if self.config.log_response_headers else []
|
183
180
|
|
184
181
|
if not self.config.log_request_body or not self._has_supported_content_type(request["headers"]):
|
185
182
|
request["body"] = None
|
@@ -215,6 +212,9 @@ class RequestLogger:
|
|
215
212
|
if response["body"] is not None and len(response["body"]) > MAX_BODY_SIZE:
|
216
213
|
response["body"] = BODY_TOO_LARGE
|
217
214
|
|
215
|
+
request["headers"] = self._mask_headers(request["headers"]) if self.config.log_request_headers else []
|
216
|
+
response["headers"] = self._mask_headers(response["headers"]) if self.config.log_response_headers else []
|
217
|
+
|
218
218
|
item = {
|
219
219
|
"uuid": str(uuid4()),
|
220
220
|
"request": _skip_empty_values(request),
|
@@ -307,19 +307,22 @@ class RequestLogger:
|
|
307
307
|
@staticmethod
|
308
308
|
def _match_patterns(value: str, patterns: List[str]) -> bool:
|
309
309
|
for pattern in patterns:
|
310
|
-
|
311
|
-
|
312
|
-
return True
|
310
|
+
if re.search(pattern, value, re.I) is not None:
|
311
|
+
return True
|
313
312
|
return False
|
314
313
|
|
314
|
+
@staticmethod
|
315
|
+
def _get_user_agent(headers: List[Tuple[str, str]]) -> Optional[str]:
|
316
|
+
return next((v for k, v in headers if k.lower() == "user-agent"), None)
|
317
|
+
|
315
318
|
@staticmethod
|
316
319
|
def _has_supported_content_type(headers: List[Tuple[str, str]]) -> bool:
|
317
320
|
content_type = next((v for k, v in headers if k.lower() == "content-type"), None)
|
318
|
-
return
|
321
|
+
return RequestLogger.is_supported_content_type(content_type)
|
319
322
|
|
320
323
|
@staticmethod
|
321
|
-
def
|
322
|
-
return
|
324
|
+
def is_supported_content_type(content_type: Optional[str]) -> bool:
|
325
|
+
return content_type is not None and any(content_type.startswith(t) for t in ALLOWED_CONTENT_TYPES)
|
323
326
|
|
324
327
|
|
325
328
|
def _check_writable_fs() -> bool:
|
apitally/django.py
CHANGED
@@ -19,6 +19,7 @@ from apitally.client.logging import get_logger
|
|
19
19
|
from apitally.client.request_logging import (
|
20
20
|
BODY_TOO_LARGE,
|
21
21
|
MAX_BODY_SIZE,
|
22
|
+
RequestLogger,
|
22
23
|
RequestLoggingConfig,
|
23
24
|
)
|
24
25
|
from apitally.common import get_versions, parse_int
|
@@ -113,7 +114,7 @@ class ApitallyMiddleware:
|
|
113
114
|
)
|
114
115
|
|
115
116
|
def __call__(self, request: HttpRequest) -> HttpResponse:
|
116
|
-
if request.method is not None and request.method != "OPTIONS":
|
117
|
+
if self.client.enabled and request.method is not None and request.method != "OPTIONS":
|
117
118
|
timestamp = time.time()
|
118
119
|
request_size = parse_int(request.headers.get("Content-Length"))
|
119
120
|
request_body = b""
|
@@ -133,7 +134,12 @@ class ApitallyMiddleware:
|
|
133
134
|
else (len(response.content) if not response.streaming else None)
|
134
135
|
)
|
135
136
|
response_body = b""
|
136
|
-
|
137
|
+
response_content_type = response.get("Content-Type")
|
138
|
+
if (
|
139
|
+
self.capture_response_body
|
140
|
+
and not response.streaming
|
141
|
+
and RequestLogger.is_supported_content_type(response_content_type)
|
142
|
+
):
|
137
143
|
response_body = (
|
138
144
|
response.content if response_size is not None and response_size <= MAX_BODY_SIZE else BODY_TOO_LARGE
|
139
145
|
)
|
apitally/flask.py
CHANGED
@@ -17,6 +17,7 @@ from apitally.client.consumers import Consumer as ApitallyConsumer
|
|
17
17
|
from apitally.client.request_logging import (
|
18
18
|
BODY_TOO_LARGE,
|
19
19
|
MAX_BODY_SIZE,
|
20
|
+
RequestLogger,
|
20
21
|
RequestLoggingConfig,
|
21
22
|
)
|
22
23
|
from apitally.common import get_versions
|
@@ -74,6 +75,9 @@ class ApitallyMiddleware:
|
|
74
75
|
self.client.set_startup_data(data)
|
75
76
|
|
76
77
|
def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]:
|
78
|
+
if not self.client.enabled:
|
79
|
+
return self.wsgi_app(environ, start_response)
|
80
|
+
|
77
81
|
timestamp = time.time()
|
78
82
|
response_headers = Headers([])
|
79
83
|
status_code = 0
|
@@ -100,7 +104,8 @@ class ApitallyMiddleware:
|
|
100
104
|
response_time = time.perf_counter() - start_time
|
101
105
|
|
102
106
|
response_body = b""
|
103
|
-
|
107
|
+
response_content_type = response_headers.get("Content-Type")
|
108
|
+
if self.capture_response_body and RequestLogger.is_supported_content_type(response_content_type):
|
104
109
|
response_size = response_headers.get("Content-Length", type=int)
|
105
110
|
if response_size is not None and response_size > MAX_BODY_SIZE:
|
106
111
|
response_body = BODY_TOO_LARGE
|
apitally/litestar.py
CHANGED
@@ -19,6 +19,7 @@ from apitally.client.consumers import Consumer as ApitallyConsumer
|
|
19
19
|
from apitally.client.request_logging import (
|
20
20
|
BODY_TOO_LARGE,
|
21
21
|
MAX_BODY_SIZE,
|
22
|
+
RequestLogger,
|
22
23
|
RequestLoggingConfig,
|
23
24
|
)
|
24
25
|
from apitally.common import get_versions, parse_int
|
@@ -86,7 +87,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
86
87
|
|
87
88
|
def middleware_factory(self, app: ASGIApp) -> ASGIApp:
|
88
89
|
async def middleware(scope: Scope, receive: Receive, send: Send) -> None:
|
89
|
-
if scope["type"] == "http" and scope["method"] != "OPTIONS":
|
90
|
+
if self.client.enabled and scope["type"] == "http" and scope["method"] != "OPTIONS":
|
90
91
|
timestamp = time.time()
|
91
92
|
request = Request(scope)
|
92
93
|
request_size = parse_int(request.headers.get("Content-Length"))
|
@@ -99,6 +100,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
99
100
|
response_body_too_large = False
|
100
101
|
response_size: Optional[int] = None
|
101
102
|
response_chunked = False
|
103
|
+
response_content_type: Optional[str] = None
|
102
104
|
start_time = time.perf_counter()
|
103
105
|
|
104
106
|
async def receive_wrapper() -> Message:
|
@@ -119,6 +121,7 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
119
121
|
response_body, \
|
120
122
|
response_body_too_large, \
|
121
123
|
response_chunked, \
|
124
|
+
response_content_type, \
|
122
125
|
response_size
|
123
126
|
if message["type"] == "http.response.start":
|
124
127
|
response_time = time.perf_counter() - start_time
|
@@ -128,12 +131,17 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
128
131
|
response_headers.get("Transfer-Encoding") == "chunked"
|
129
132
|
or "Content-Length" not in response_headers
|
130
133
|
)
|
134
|
+
response_content_type = response_headers.get("Content-Type")
|
131
135
|
response_size = parse_int(response_headers.get("Content-Length")) if not response_chunked else 0
|
132
136
|
response_body_too_large = response_size is not None and response_size > MAX_BODY_SIZE
|
133
137
|
elif message["type"] == "http.response.body":
|
134
138
|
if response_chunked and response_size is not None:
|
135
139
|
response_size += len(message.get("body", b""))
|
136
|
-
if (
|
140
|
+
if (
|
141
|
+
(self.capture_response_body or response_status == 400)
|
142
|
+
and RequestLogger.is_supported_content_type(response_content_type)
|
143
|
+
and not response_body_too_large
|
144
|
+
):
|
137
145
|
response_body += message.get("body", b"")
|
138
146
|
if len(response_body) > MAX_BODY_SIZE:
|
139
147
|
response_body_too_large = True
|
apitally/starlette.py
CHANGED
@@ -20,6 +20,7 @@ from apitally.client.consumers import Consumer as ApitallyConsumer
|
|
20
20
|
from apitally.client.request_logging import (
|
21
21
|
BODY_TOO_LARGE,
|
22
22
|
MAX_BODY_SIZE,
|
23
|
+
RequestLogger,
|
23
24
|
RequestLoggingConfig,
|
24
25
|
)
|
25
26
|
from apitally.common import get_versions, parse_int
|
@@ -73,7 +74,7 @@ class ApitallyMiddleware:
|
|
73
74
|
self.client.set_startup_data(data)
|
74
75
|
|
75
76
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
76
|
-
if scope["type"] == "http" and scope["method"] != "OPTIONS":
|
77
|
+
if self.client.enabled and scope["type"] == "http" and scope["method"] != "OPTIONS":
|
77
78
|
timestamp = time.time()
|
78
79
|
request = Request(scope)
|
79
80
|
request_size = parse_int(request.headers.get("Content-Length"))
|
@@ -86,6 +87,7 @@ class ApitallyMiddleware:
|
|
86
87
|
response_body_too_large = False
|
87
88
|
response_size: Optional[int] = None
|
88
89
|
response_chunked = False
|
90
|
+
response_content_type: Optional[str] = None
|
89
91
|
exception: Optional[BaseException] = None
|
90
92
|
start_time = time.perf_counter()
|
91
93
|
|
@@ -107,6 +109,7 @@ class ApitallyMiddleware:
|
|
107
109
|
response_body, \
|
108
110
|
response_body_too_large, \
|
109
111
|
response_chunked, \
|
112
|
+
response_content_type, \
|
110
113
|
response_size
|
111
114
|
if message["type"] == "http.response.start":
|
112
115
|
response_time = time.perf_counter() - start_time
|
@@ -116,12 +119,17 @@ class ApitallyMiddleware:
|
|
116
119
|
response_headers.get("Transfer-Encoding") == "chunked"
|
117
120
|
or "Content-Length" not in response_headers
|
118
121
|
)
|
122
|
+
response_content_type = response_headers.get("Content-Type")
|
119
123
|
response_size = parse_int(response_headers.get("Content-Length")) if not response_chunked else 0
|
120
124
|
response_body_too_large = response_size is not None and response_size > MAX_BODY_SIZE
|
121
125
|
elif message["type"] == "http.response.body":
|
122
126
|
if response_chunked and response_size is not None:
|
123
127
|
response_size += len(message.get("body", b""))
|
124
|
-
if (
|
128
|
+
if (
|
129
|
+
(self.capture_response_body or response_status == 422)
|
130
|
+
and RequestLogger.is_supported_content_type(response_content_type)
|
131
|
+
and not response_body_too_large
|
132
|
+
):
|
125
133
|
response_body += message.get("body", b"")
|
126
134
|
if len(response_body) > MAX_BODY_SIZE:
|
127
135
|
response_body_too_large = True
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.14.
|
3
|
+
Version: 0.14.6
|
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
|
@@ -62,18 +62,19 @@ Requires-Dist: starlette<1.0.0,>=0.21.0; extra == 'starlette'
|
|
62
62
|
Description-Content-Type: text/markdown
|
63
63
|
|
64
64
|
<p align="center">
|
65
|
-
<
|
66
|
-
<
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
<a href="https://apitally.io" target="_blank">
|
66
|
+
<picture>
|
67
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://assets.apitally.io/logos/logo-vertical-dark.png">
|
68
|
+
<source media="(prefers-color-scheme: light)" srcset="https://assets.apitally.io/logos/logo-vertical-light.png">
|
69
|
+
<img alt="Apitally logo" src="https://assets.apitally.io/logos/logo-vertical-light.png" width="150">
|
70
|
+
</picture>
|
71
|
+
</a>
|
70
72
|
</p>
|
71
73
|
|
72
|
-
<p align="center"><b>
|
74
|
+
<p align="center"><b>Simple, privacy-focused API monitoring & analytics</b></p>
|
73
75
|
|
74
|
-
<p align="center"><i>Apitally helps you understand how your APIs are being used and alerts you when things go wrong.<br>
|
75
|
-
|
76
|
-
<p align="center">🔗 <b><a href="https://apitally.io" target="_blank">apitally.io</a></b></p>
|
76
|
+
<p align="center"><i>Apitally helps you understand how your APIs are being used and alerts you when things go wrong.<br>Just add two lines of code to your project to get started.</i></p>
|
77
|
+
<br>
|
77
78
|
|
78
79
|

|
79
80
|
|
@@ -89,10 +90,10 @@ This client library for Apitally currently supports the following Python web
|
|
89
90
|
frameworks:
|
90
91
|
|
91
92
|
- [FastAPI](https://docs.apitally.io/frameworks/fastapi)
|
92
|
-
- [Starlette](https://docs.apitally.io/frameworks/starlette)
|
93
|
-
- [Flask](https://docs.apitally.io/frameworks/flask)
|
94
|
-
- [Django Ninja](https://docs.apitally.io/frameworks/django-ninja)
|
95
93
|
- [Django REST Framework](https://docs.apitally.io/frameworks/django-rest-framework)
|
94
|
+
- [Django Ninja](https://docs.apitally.io/frameworks/django-ninja)
|
95
|
+
- [Flask](https://docs.apitally.io/frameworks/flask)
|
96
|
+
- [Starlette](https://docs.apitally.io/frameworks/starlette)
|
96
97
|
- [Litestar](https://docs.apitally.io/frameworks/litestar)
|
97
98
|
|
98
99
|
Learn more about Apitally on our 🌎 [website](https://apitally.io) or check out
|
@@ -100,10 +101,21 @@ the 📚 [documentation](https://docs.apitally.io).
|
|
100
101
|
|
101
102
|
## Key features
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
### API analytics
|
105
|
+
|
106
|
+
Track traffic, error and performance metrics for your API, each endpoint and individual API consumers, allowing you to make informed, data-driven engineering and product decisions.
|
107
|
+
|
108
|
+
### Error tracking
|
109
|
+
|
110
|
+
Understand which validation rules in your endpoints cause client errors. Capture error details and stack traces for 500 error responses, and have them linked to Sentry issues automatically.
|
111
|
+
|
112
|
+
### Request logging
|
113
|
+
|
114
|
+
Drill down from insights to individual requests or use powerful filtering to understand how consumers have interacted with your API. Configure exactly what is included in the logs to meet your requirements.
|
115
|
+
|
116
|
+
### API monitoring & alerting
|
117
|
+
|
118
|
+
Get notified immediately if something isn't right using custom alerts, synthetic uptime checks and heartbeat monitoring. Notifications can be delivered via email, Slack or Microsoft Teams.
|
107
119
|
|
108
120
|
## Install
|
109
121
|
|
@@ -140,6 +152,25 @@ app.add_middleware(
|
|
140
152
|
)
|
141
153
|
```
|
142
154
|
|
155
|
+
### Django
|
156
|
+
|
157
|
+
This is an example of how to add the Apitally middleware to a Django Ninja or
|
158
|
+
Django REST Framework application. For further instructions, see our
|
159
|
+
[setup guide for Django](https://docs.apitally.io/frameworks/django).
|
160
|
+
|
161
|
+
In your Django `settings.py` file:
|
162
|
+
|
163
|
+
```python
|
164
|
+
MIDDLEWARE = [
|
165
|
+
"apitally.django.ApitallyMiddleware",
|
166
|
+
# Other middleware ...
|
167
|
+
]
|
168
|
+
APITALLY_MIDDLEWARE = {
|
169
|
+
"client_id": "your-client-id",
|
170
|
+
"env": "dev", # or "prod" etc.
|
171
|
+
}
|
172
|
+
```
|
173
|
+
|
143
174
|
### Flask
|
144
175
|
|
145
176
|
This is an example of how to add the Apitally middleware to a Flask application.
|
@@ -158,23 +189,22 @@ app.wsgi_app = ApitallyMiddleware(
|
|
158
189
|
)
|
159
190
|
```
|
160
191
|
|
161
|
-
###
|
162
|
-
|
163
|
-
This is an example of how to add the Apitally middleware to a Django Ninja or
|
164
|
-
Django REST Framework application. For further instructions, see our
|
165
|
-
[setup guide for Django](https://docs.apitally.io/frameworks/django).
|
192
|
+
### Starlette
|
166
193
|
|
167
|
-
|
194
|
+
This is an example of how to add the Apitally middleware to a Starlette application.
|
195
|
+
For further instructions, see our
|
196
|
+
[setup guide for Starlette](https://docs.apitally.io/frameworks/starlette).
|
168
197
|
|
169
198
|
```python
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
]
|
174
|
-
|
175
|
-
|
176
|
-
"
|
177
|
-
|
199
|
+
from starlette.applications import Starlette
|
200
|
+
from apitally.starlette import ApitallyMiddleware
|
201
|
+
|
202
|
+
app = Starlette(routes=[...])
|
203
|
+
app.add_middleware(
|
204
|
+
ApitallyMiddleware,
|
205
|
+
client_id="your-client-id",
|
206
|
+
env="dev", # or "prod" etc.
|
207
|
+
)
|
178
208
|
```
|
179
209
|
|
180
210
|
### Litestar
|
@@ -1,24 +1,24 @@
|
|
1
1
|
apitally/__init__.py,sha256=ShXQBVjyiSOHxoQJS2BvNG395W4KZfqMxZWBAR0MZrE,22
|
2
2
|
apitally/common.py,sha256=Y8MRuTUHFUeQkcDrCLUxnqIPRpYIiW8S43T0QUab-_A,1267
|
3
|
-
apitally/django.py,sha256=
|
3
|
+
apitally/django.py,sha256=Tsw3uSM6EcfoCKpJuqliMFbc6eX0sVzJR9UyZck613M,16860
|
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
|
-
apitally/flask.py,sha256=
|
8
|
-
apitally/litestar.py,sha256=
|
7
|
+
apitally/flask.py,sha256=kOFkAZj62Zr-l5eJWRr8lmsMHfFW_kfi1kflH6mZseQ,9533
|
8
|
+
apitally/litestar.py,sha256=qV89DXFlG619-20294OLg52vGv4C4GxIm9IPoyY21L4,13514
|
9
9
|
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
apitally/starlette.py,sha256=
|
10
|
+
apitally/starlette.py,sha256=cMUUcQSqZ_meoxjyi8doZGarwP8RwNjFaFDmjoWvVK4,13240
|
11
11
|
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
apitally/client/client_asyncio.py,sha256=
|
13
|
-
apitally/client/client_base.py,sha256=
|
14
|
-
apitally/client/client_threading.py,sha256=
|
12
|
+
apitally/client/client_asyncio.py,sha256=9mdi9Hmb6-xn7dNdwP84e4PNAHGg2bYdMEgIfPUAtcQ,7003
|
13
|
+
apitally/client/client_base.py,sha256=DvivGeHd3dyOASRvkIo44Zh8RzdBMfH8_rROa2lFbgw,3799
|
14
|
+
apitally/client/client_threading.py,sha256=7JPu2Uulev7X2RiSLx4HJYfvAP6Z5zB_yuSevMfQC7I,7389
|
15
15
|
apitally/client/consumers.py,sha256=w_AFQhVgdtJVt7pVySBvSZwQg-2JVqmD2JQtVBoMkus,2626
|
16
16
|
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
17
|
-
apitally/client/request_logging.py,sha256=
|
17
|
+
apitally/client/request_logging.py,sha256=tVTMbTGtdf8OVULrnRPKNZxLuYqiMvO1NviRIfSNKnw,13134
|
18
18
|
apitally/client/requests.py,sha256=RdJyvIqQGVHvS-wjpAPUwcO7byOJ6jO8dYqNTU2Furg,3685
|
19
19
|
apitally/client/server_errors.py,sha256=axEhOxqV5SWjk0QCZTLVv2UMIaTfqPc81Typ4DXt66A,4646
|
20
20
|
apitally/client/validation_errors.py,sha256=6G8WYWFgJs9VH9swvkPXJGuOJgymj5ooWA9OwjUTbuM,1964
|
21
|
-
apitally-0.14.
|
22
|
-
apitally-0.14.
|
23
|
-
apitally-0.14.
|
24
|
-
apitally-0.14.
|
21
|
+
apitally-0.14.6.dist-info/METADATA,sha256=veFQGVno3OtfK3J6RLBTyfLxqUzNXR0YtJVpcBhWz4I,8665
|
22
|
+
apitally-0.14.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
apitally-0.14.6.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
24
|
+
apitally-0.14.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|