apitally 0.3.0__py3-none-any.whl → 0.3.2__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/asyncio.py +14 -2
- apitally/client/base.py +3 -2
- apitally/client/logging.py +17 -0
- apitally/client/threading.py +28 -16
- apitally/starlette.py +20 -2
- {apitally-0.3.0.dist-info → apitally-0.3.2.dist-info}/METADATA +25 -7
- apitally-0.3.2.dist-info/RECORD +17 -0
- apitally-0.3.0.dist-info/RECORD +0 -16
- {apitally-0.3.0.dist-info → apitally-0.3.2.dist-info}/LICENSE +0 -0
- {apitally-0.3.0.dist-info → apitally-0.3.2.dist-info}/WHEEL +0 -0
apitally/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.3.
|
1
|
+
__version__ = "0.3.2"
|
apitally/client/asyncio.py
CHANGED
@@ -16,14 +16,16 @@ from apitally.client.base import (
|
|
16
16
|
ApitallyClientBase,
|
17
17
|
ApitallyKeyCacheBase,
|
18
18
|
)
|
19
|
+
from apitally.client.logging import get_logger
|
19
20
|
|
20
21
|
|
21
|
-
logger =
|
22
|
+
logger = get_logger(__name__)
|
22
23
|
retry = partial(
|
23
24
|
backoff.on_exception,
|
24
25
|
backoff.expo,
|
25
26
|
httpx.HTTPError,
|
26
27
|
max_tries=3,
|
28
|
+
logger=logger,
|
27
29
|
giveup_log_level=logging.WARNING,
|
28
30
|
)
|
29
31
|
|
@@ -74,6 +76,13 @@ class ApitallyClient(ApitallyClientBase):
|
|
74
76
|
def stop_sync_loop(self) -> None:
|
75
77
|
self._stop_sync_loop = True
|
76
78
|
|
79
|
+
async def handle_shutdown(self) -> None:
|
80
|
+
if self._sync_loop_task is not None:
|
81
|
+
self._sync_loop_task.cancel()
|
82
|
+
# Send any remaining requests data before exiting
|
83
|
+
async with self.get_http_client() as client:
|
84
|
+
await self.send_requests_data(client)
|
85
|
+
|
77
86
|
def set_app_info(self, app_info: Dict[str, Any]) -> None:
|
78
87
|
self._app_info_sent = False
|
79
88
|
self._app_info_payload = self.get_info_payload(app_info)
|
@@ -109,7 +118,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
109
118
|
self.handle_keys_response(response_data)
|
110
119
|
self._keys_updated_at = time.time()
|
111
120
|
elif self.key_registry.salt is None: # pragma: no cover
|
112
|
-
logger.
|
121
|
+
logger.critical("Initial Apitally API key sync failed")
|
113
122
|
# Exit because the application will not be able to authenticate requests
|
114
123
|
sys.exit(1)
|
115
124
|
elif (self._keys_updated_at is not None and time.time() - self._keys_updated_at > MAX_QUEUE_TIME) or (
|
@@ -119,6 +128,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
119
128
|
|
120
129
|
@retry(raise_on_giveup=False)
|
121
130
|
async def _send_app_info(self, client: httpx.AsyncClient, payload: Dict[str, Any]) -> None:
|
131
|
+
logger.debug("Sending app info")
|
122
132
|
response = await client.post(url="/info", json=payload, timeout=REQUEST_TIMEOUT)
|
123
133
|
if response.status_code == 404 and "Client ID" in response.text:
|
124
134
|
self.stop_sync_loop()
|
@@ -130,11 +140,13 @@ class ApitallyClient(ApitallyClientBase):
|
|
130
140
|
|
131
141
|
@retry()
|
132
142
|
async def _send_requests_data(self, client: httpx.AsyncClient, payload: Dict[str, Any]) -> None:
|
143
|
+
logger.debug("Sending requests data")
|
133
144
|
response = await client.post(url="/requests", json=payload)
|
134
145
|
response.raise_for_status()
|
135
146
|
|
136
147
|
@retry(raise_on_giveup=False)
|
137
148
|
async def _get_keys(self, client: httpx.AsyncClient) -> Dict[str, Any]:
|
149
|
+
logger.debug("Updating API keys")
|
138
150
|
response = await client.get(url="/keys")
|
139
151
|
response.raise_for_status()
|
140
152
|
return response.json()
|
apitally/client/base.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import json
|
4
|
-
import logging
|
5
4
|
import os
|
6
5
|
import re
|
7
6
|
import threading
|
@@ -15,8 +14,10 @@ from math import floor
|
|
15
14
|
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast
|
16
15
|
from uuid import UUID, uuid4
|
17
16
|
|
17
|
+
from apitally.client.logging import get_logger
|
18
18
|
|
19
|
-
|
19
|
+
|
20
|
+
logger = get_logger(__name__)
|
20
21
|
|
21
22
|
HUB_BASE_URL = os.getenv("APITALLY_HUB_BASE_URL") or "https://hub.apitally.io"
|
22
23
|
HUB_VERSION = "v1"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
|
4
|
+
|
5
|
+
debug = os.getenv("APITALLY_DEBUG", "false").lower() in {"true", "yes", "y", "1"}
|
6
|
+
root_logger = logging.getLogger("apitally")
|
7
|
+
|
8
|
+
if debug:
|
9
|
+
root_logger.setLevel(logging.DEBUG)
|
10
|
+
console_handler = logging.StreamHandler()
|
11
|
+
formatter = logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s")
|
12
|
+
console_handler.setFormatter(formatter)
|
13
|
+
root_logger.addHandler(console_handler)
|
14
|
+
|
15
|
+
|
16
|
+
def get_logger(name: str) -> logging.Logger:
|
17
|
+
return logging.getLogger(name)
|
apitally/client/threading.py
CHANGED
@@ -17,14 +17,16 @@ from apitally.client.base import (
|
|
17
17
|
ApitallyClientBase,
|
18
18
|
ApitallyKeyCacheBase,
|
19
19
|
)
|
20
|
+
from apitally.client.logging import get_logger
|
20
21
|
|
21
22
|
|
22
|
-
logger =
|
23
|
+
logger = get_logger(__name__)
|
23
24
|
retry = partial(
|
24
25
|
backoff.on_exception,
|
25
26
|
backoff.expo,
|
26
27
|
requests.RequestException,
|
27
28
|
max_tries=3,
|
29
|
+
logger=logger,
|
28
30
|
giveup_log_level=logging.WARNING,
|
29
31
|
)
|
30
32
|
|
@@ -68,24 +70,31 @@ class ApitallyClient(ApitallyClientBase):
|
|
68
70
|
def start_sync_loop(self) -> None:
|
69
71
|
self._stop_sync_loop.clear()
|
70
72
|
if self._thread is None or not self._thread.is_alive():
|
71
|
-
self._thread = Thread(target=self._run_sync_loop)
|
73
|
+
self._thread = Thread(target=self._run_sync_loop, daemon=True)
|
72
74
|
self._thread.start()
|
73
75
|
register_exit(self.stop_sync_loop)
|
74
76
|
|
75
77
|
def _run_sync_loop(self) -> None:
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
78
|
+
try:
|
79
|
+
last_sync_time = 0.0
|
80
|
+
while not self._stop_sync_loop.is_set():
|
81
|
+
try:
|
82
|
+
now = time.time()
|
83
|
+
if (now - last_sync_time) > self.sync_interval:
|
84
|
+
with requests.Session() as session:
|
85
|
+
if self.sync_api_keys:
|
86
|
+
self.get_keys(session)
|
87
|
+
if not self._app_info_sent and last_sync_time > 0: # not on first sync
|
88
|
+
self.send_app_info(session)
|
89
|
+
self.send_requests_data(session)
|
90
|
+
last_sync_time = now
|
91
|
+
time.sleep(1)
|
92
|
+
except Exception as e: # pragma: no cover
|
93
|
+
logger.exception(e)
|
94
|
+
finally:
|
95
|
+
# Send any remaining requests data before exiting
|
96
|
+
with requests.Session() as session:
|
97
|
+
self.send_requests_data(session)
|
89
98
|
|
90
99
|
def stop_sync_loop(self) -> None:
|
91
100
|
self._stop_sync_loop.set()
|
@@ -125,7 +134,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
125
134
|
self.handle_keys_response(response_data)
|
126
135
|
self._keys_updated_at = time.time()
|
127
136
|
elif self.key_registry.salt is None: # pragma: no cover
|
128
|
-
logger.
|
137
|
+
logger.critical("Initial Apitally API key sync failed")
|
129
138
|
# Exit because the application will not be able to authenticate requests
|
130
139
|
sys.exit(1)
|
131
140
|
elif (self._keys_updated_at is not None and time.time() - self._keys_updated_at > MAX_QUEUE_TIME) or (
|
@@ -135,6 +144,7 @@ class ApitallyClient(ApitallyClientBase):
|
|
135
144
|
|
136
145
|
@retry(raise_on_giveup=False)
|
137
146
|
def _send_app_info(self, session: requests.Session, payload: Dict[str, Any]) -> None:
|
147
|
+
logger.debug("Sending app info")
|
138
148
|
response = session.post(url=f"{self.hub_url}/info", json=payload, timeout=REQUEST_TIMEOUT)
|
139
149
|
if response.status_code == 404 and "Client ID" in response.text:
|
140
150
|
self.stop_sync_loop()
|
@@ -146,11 +156,13 @@ class ApitallyClient(ApitallyClientBase):
|
|
146
156
|
|
147
157
|
@retry()
|
148
158
|
def _send_requests_data(self, session: requests.Session, payload: Dict[str, Any]) -> None:
|
159
|
+
logger.debug("Sending requests data")
|
149
160
|
response = session.post(url=f"{self.hub_url}/requests", json=payload, timeout=REQUEST_TIMEOUT)
|
150
161
|
response.raise_for_status()
|
151
162
|
|
152
163
|
@retry(raise_on_giveup=False)
|
153
164
|
def _get_keys(self, session: requests.Session) -> Dict[str, Any]:
|
165
|
+
logger.debug("Updating API keys")
|
154
166
|
response = session.get(url=f"{self.hub_url}/keys", timeout=REQUEST_TIMEOUT)
|
155
167
|
response.raise_for_status()
|
156
168
|
return response.json()
|
apitally/starlette.py
CHANGED
@@ -5,7 +5,17 @@ import json
|
|
5
5
|
import sys
|
6
6
|
import time
|
7
7
|
from importlib.metadata import PackageNotFoundError, version
|
8
|
-
from typing import
|
8
|
+
from typing import (
|
9
|
+
TYPE_CHECKING,
|
10
|
+
Any,
|
11
|
+
Callable,
|
12
|
+
Dict,
|
13
|
+
List,
|
14
|
+
Optional,
|
15
|
+
Tuple,
|
16
|
+
Type,
|
17
|
+
Union,
|
18
|
+
)
|
9
19
|
|
10
20
|
from httpx import HTTPStatusError
|
11
21
|
from starlette.authentication import (
|
@@ -59,6 +69,7 @@ class ApitallyMiddleware(BaseHTTPMiddleware):
|
|
59
69
|
)
|
60
70
|
self.client.start_sync_loop()
|
61
71
|
self.delayed_set_app_info(app_version, openapi_url)
|
72
|
+
_register_shutdown_handler(app, self.client.handle_shutdown)
|
62
73
|
super().__init__(app)
|
63
74
|
|
64
75
|
def delayed_set_app_info(self, app_version: Optional[str] = None, openapi_url: Optional[str] = None) -> None:
|
@@ -221,7 +232,7 @@ def _get_endpoint_info(app: ASGIApp) -> List[EndpointInfo]:
|
|
221
232
|
return schemas.get_endpoints(routes)
|
222
233
|
|
223
234
|
|
224
|
-
def _get_routes(app: ASGIApp) -> List[BaseRoute]:
|
235
|
+
def _get_routes(app: Union[ASGIApp, Router]) -> List[BaseRoute]:
|
225
236
|
if isinstance(app, Router):
|
226
237
|
return app.routes
|
227
238
|
elif hasattr(app, "app"):
|
@@ -229,6 +240,13 @@ def _get_routes(app: ASGIApp) -> List[BaseRoute]:
|
|
229
240
|
return [] # pragma: no cover
|
230
241
|
|
231
242
|
|
243
|
+
def _register_shutdown_handler(app: Union[ASGIApp, Router], shutdown_handler: Callable[[], Any]) -> None:
|
244
|
+
if isinstance(app, Router):
|
245
|
+
app.add_event_handler("shutdown", shutdown_handler)
|
246
|
+
elif hasattr(app, "app"):
|
247
|
+
_register_shutdown_handler(app.app, shutdown_handler)
|
248
|
+
|
249
|
+
|
232
250
|
def _get_versions(app_version: Optional[str]) -> Dict[str, str]:
|
233
251
|
versions = {
|
234
252
|
"python": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
4
4
|
Summary: Apitally client library for Python
|
5
5
|
Home-page: https://docs.apitally.io
|
6
6
|
License: MIT
|
@@ -30,7 +30,7 @@ Requires-Dist: fastapi (>=0.87.0) ; extra == "fastapi"
|
|
30
30
|
Requires-Dist: flask (>=2.0.0) ; extra == "flask"
|
31
31
|
Requires-Dist: httpx (>=0.22.0) ; extra == "fastapi" or extra == "starlette"
|
32
32
|
Requires-Dist: requests (>=2.26.0) ; extra == "django-rest-framework" or extra == "django-ninja" or extra == "flask"
|
33
|
-
Requires-Dist: starlette (>=0.21.0) ; extra == "fastapi" or extra == "starlette"
|
33
|
+
Requires-Dist: starlette (>=0.21.0,<1.0.0) ; extra == "fastapi" or extra == "starlette"
|
34
34
|
Project-URL: Documentation, https://docs.apitally.io
|
35
35
|
Project-URL: Repository, https://github.com/apitally/python-client
|
36
36
|
Description-Content-Type: text/markdown
|
@@ -45,7 +45,7 @@ Description-Content-Type: text/markdown
|
|
45
45
|
|
46
46
|
<p align="center"><b>Your refreshingly simple REST API companion.</b></p>
|
47
47
|
|
48
|
-
<p align="center"><i>Apitally offers
|
48
|
+
<p align="center"><i>Apitally offers busy engineering teams a simple and affordable API monitoring and API key management solution that is easy to set up and use with new and existing API projects.</i></p>
|
49
49
|
|
50
50
|
<p align="center">🔗 <b><a href="https://apitally.io" target="_blank">apitally.io</a></b></p>
|
51
51
|
|
@@ -53,13 +53,13 @@ Description-Content-Type: text/markdown
|
|
53
53
|
|
54
54
|
---
|
55
55
|
|
56
|
-
# Apitally client for Python
|
56
|
+
# Apitally client library for Python
|
57
57
|
|
58
58
|
[](https://github.com/apitally/python-client/actions)
|
59
59
|
[](https://codecov.io/gh/apitally/python-client)
|
60
60
|
[](https://pypi.org/project/apitally/)
|
61
61
|
|
62
|
-
This client library currently supports the following frameworks:
|
62
|
+
This client library for Apitally currently supports the following Python web frameworks:
|
63
63
|
|
64
64
|
- [FastAPI](https://docs.apitally.io/frameworks/fastapi)
|
65
65
|
- [Starlette](https://docs.apitally.io/frameworks/starlette)
|
@@ -67,6 +67,14 @@ This client library currently supports the following frameworks:
|
|
67
67
|
- [Django Ninja](https://docs.apitally.io/frameworks/django-ninja)
|
68
68
|
- [Django REST Framework](https://docs.apitally.io/frameworks/django-rest-framework)
|
69
69
|
|
70
|
+
Learn more about Apitally on our 🌎 [website](https://apitally.io) or check out the 📚 [documentation](https://docs.apitally.io).
|
71
|
+
|
72
|
+
## Key features
|
73
|
+
|
74
|
+
- Middleware for different frameworks to capture metadata about API endpoints, requests and responses (no sensitive data is captured)
|
75
|
+
- Non-blocking clients that aggregate and send captured data to Apitally and optionally synchronize API key hashes in 1 minute intervals
|
76
|
+
- Functions to easily secure endpoints with API key authentication and permission checks
|
77
|
+
|
70
78
|
## Install
|
71
79
|
|
72
80
|
Use `pip` to install and provide your framework of choice as an extra, for example:
|
@@ -79,10 +87,12 @@ The available extras are: `fastapi`, `starlette`, `flask`, `django_ninja` and `d
|
|
79
87
|
|
80
88
|
## Usage
|
81
89
|
|
82
|
-
|
90
|
+
Our [setup guides](https://docs.apitally.io/quickstart) include all the details you need to get started.
|
83
91
|
|
84
92
|
### FastAPI
|
85
93
|
|
94
|
+
This is an example of how to add the Apitally middleware to a FastAPI application. For further instructions, see our [setup guide for FastAPI](https://docs.apitally.io/frameworks/fastapi).
|
95
|
+
|
86
96
|
```python
|
87
97
|
from fastapi import FastAPI
|
88
98
|
from apitally.fastapi import ApitallyMiddleware
|
@@ -97,6 +107,8 @@ app.add_middleware(
|
|
97
107
|
|
98
108
|
### Starlette
|
99
109
|
|
110
|
+
This is an example of how to add the Apitally middleware to a Starlette application. For further instructions, see our [setup guide for Starlette](https://docs.apitally.io/frameworks/starlette).
|
111
|
+
|
100
112
|
```python
|
101
113
|
from starlette.applications import Starlette
|
102
114
|
from apitally.starlette import ApitallyMiddleware
|
@@ -111,6 +123,8 @@ app.add_middleware(
|
|
111
123
|
|
112
124
|
### Flask
|
113
125
|
|
126
|
+
This is an example of how to add the Apitally middleware to a Flask application. For further instructions, see our [setup guide for Flask](https://docs.apitally.io/frameworks/flask).
|
127
|
+
|
114
128
|
```python
|
115
129
|
from flask import Flask
|
116
130
|
from apitally.flask import ApitallyMiddleware
|
@@ -125,6 +139,8 @@ app.wsgi_app = ApitallyMiddleware(
|
|
125
139
|
|
126
140
|
### Django Ninja
|
127
141
|
|
142
|
+
This is an example of how to add the Apitally middleware to a Django Ninja application. For further instructions, see our [setup guide for Django Ninja](https://docs.apitally.io/frameworks/django-ninja).
|
143
|
+
|
128
144
|
In your Django `settings.py` file:
|
129
145
|
|
130
146
|
```python
|
@@ -140,6 +156,8 @@ APITALLY_MIDDLEWARE = {
|
|
140
156
|
|
141
157
|
### Django REST Framework
|
142
158
|
|
159
|
+
This is an example of how to add the Apitally middleware to a Django REST Framework application. For further instructions, see our [setup guide for Django REST Framework](https://docs.apitally.io/frameworks/django-rest-framework).
|
160
|
+
|
143
161
|
In your Django `settings.py` file:
|
144
162
|
|
145
163
|
```python
|
@@ -155,7 +173,7 @@ APITALLY_MIDDLEWARE = {
|
|
155
173
|
|
156
174
|
## Getting help
|
157
175
|
|
158
|
-
If you need help please
|
176
|
+
If you need help please [create a new discussion](https://github.com/orgs/apitally/discussions/categories/q-a) on GitHub.
|
159
177
|
|
160
178
|
## License
|
161
179
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
apitally/__init__.py,sha256=vNiWJ14r_cw5t_7UDqDQIVZvladKFGyHH2avsLpN7Vg,22
|
2
|
+
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
apitally/client/asyncio.py,sha256=uq1mK3LHSxhpvMY6FT-k9qzWced-v-bmuweBjXMBqkQ,5925
|
4
|
+
apitally/client/base.py,sha256=L_9mbabVDikBG0fYOUu-l1y0VjdsZ2qLu9VSmcQveTM,10622
|
5
|
+
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
+
apitally/client/threading.py,sha256=89C6RdyUTTo5dg_ZAG1qIp9cNzzfAa7zEYejyV_gw00,6471
|
7
|
+
apitally/django.py,sha256=U7lyjG97FrGGgA6_NHk3qHvWT69D2e5-37rtrS5PQDU,10251
|
8
|
+
apitally/django_ninja.py,sha256=TFltgr03FzTnl83sUAXJj7R32u_g9DTZ9p-HuVKs4ZE,2785
|
9
|
+
apitally/django_rest_framework.py,sha256=UmJvxxiKGRdaILSbg6jJY_cvAl-mpuPY1pM0FoQ4bg0,1587
|
10
|
+
apitally/fastapi.py,sha256=YjnrRis8UG2M6Q3lkwizbtDXU7nPfCA4mebxG8XwveY,3334
|
11
|
+
apitally/flask.py,sha256=7RI1odLYPRmlkQc-OU1HZJoHZxx2n249ftgsklpx2bM,6559
|
12
|
+
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
apitally/starlette.py,sha256=C5Mz77BV33jp3HpmDko5Bjl2t7lxq_WUC6Nj3pcX1AU,9771
|
14
|
+
apitally-0.3.2.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
15
|
+
apitally-0.3.2.dist-info/METADATA,sha256=DR6-sdXPUywSoGGFf4M0OiDmoszGRW_BKqCuACh2ekc,6745
|
16
|
+
apitally-0.3.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
17
|
+
apitally-0.3.2.dist-info/RECORD,,
|
apitally-0.3.0.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
apitally/__init__.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
|
2
|
-
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
apitally/client/asyncio.py,sha256=JR3WLUN9dfvIhLhYA2gNL3v1DqAxLHbUWqko5Z5CKHs,5440
|
4
|
-
apitally/client/base.py,sha256=dtFSHYTxpAFWqLcCSehzFL0qEWmNMjpBJQGDQMBxfQ4,10596
|
5
|
-
apitally/client/threading.py,sha256=Y90IdkFwu8ysSzKDOFgPnZYYaEFVvZNGzF4uTMoODhU,5890
|
6
|
-
apitally/django.py,sha256=U7lyjG97FrGGgA6_NHk3qHvWT69D2e5-37rtrS5PQDU,10251
|
7
|
-
apitally/django_ninja.py,sha256=TFltgr03FzTnl83sUAXJj7R32u_g9DTZ9p-HuVKs4ZE,2785
|
8
|
-
apitally/django_rest_framework.py,sha256=UmJvxxiKGRdaILSbg6jJY_cvAl-mpuPY1pM0FoQ4bg0,1587
|
9
|
-
apitally/fastapi.py,sha256=YjnrRis8UG2M6Q3lkwizbtDXU7nPfCA4mebxG8XwveY,3334
|
10
|
-
apitally/flask.py,sha256=7RI1odLYPRmlkQc-OU1HZJoHZxx2n249ftgsklpx2bM,6559
|
11
|
-
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
apitally/starlette.py,sha256=XOTpCe2Oiv7IvjljVx14w-hTnNzOllxnUpRXxCfeABY,9347
|
13
|
-
apitally-0.3.0.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
14
|
-
apitally-0.3.0.dist-info/METADATA,sha256=wsjv5c_GoE2bRby8FWekm4IpLk6IURgHlAFbkSRoh1Q,5453
|
15
|
-
apitally-0.3.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
16
|
-
apitally-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|