apitally 0.12.1__tar.gz → 0.13.0__tar.gz
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-0.13.0/.github/workflows/publish.yaml +24 -0
- apitally-0.13.0/.github/workflows/summary.yaml +16 -0
- apitally-0.13.0/.github/workflows/tests.yaml +97 -0
- apitally-0.13.0/.gitignore +58 -0
- apitally-0.13.0/.pre-commit-config.yaml +19 -0
- apitally-0.13.0/Makefile +17 -0
- {apitally-0.12.1 → apitally-0.13.0}/PKG-INFO +38 -24
- apitally-0.13.0/apitally/__init__.py +1 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/client/base.py +6 -1
- {apitally-0.12.1 → apitally-0.13.0}/apitally/starlette.py +9 -11
- apitally-0.13.0/pyproject.toml +145 -0
- apitally-0.13.0/renovate.json +5 -0
- apitally-0.13.0/tests/__init__.py +0 -0
- apitally-0.13.0/tests/conftest.py +27 -0
- apitally-0.13.0/tests/constants.py +2 -0
- apitally-0.13.0/tests/django_ninja_urls.py +37 -0
- apitally-0.13.0/tests/django_rest_framework_urls.py +36 -0
- apitally-0.13.0/tests/test_client_asyncio.py +98 -0
- apitally-0.13.0/tests/test_client_base.py +179 -0
- apitally-0.13.0/tests/test_client_threading.py +95 -0
- apitally-0.13.0/tests/test_django_ninja.py +168 -0
- apitally-0.13.0/tests/test_django_rest_framework.py +138 -0
- apitally-0.13.0/tests/test_fastapi.py +65 -0
- apitally-0.13.0/tests/test_flask.py +107 -0
- apitally-0.13.0/tests/test_litestar.py +178 -0
- apitally-0.13.0/tests/test_starlette.py +250 -0
- apitally-0.13.0/uv.lock +2301 -0
- apitally-0.12.1/apitally/__init__.py +0 -1
- apitally-0.12.1/pyproject.toml +0 -125
- {apitally-0.12.1 → apitally-0.13.0}/LICENSE +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/README.md +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/client/__init__.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/client/asyncio.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/client/logging.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/client/threading.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/common.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/django.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/django_ninja.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/django_rest_framework.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/fastapi.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/flask.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/litestar.py +0 -0
- {apitally-0.12.1 → apitally-0.13.0}/apitally/py.typed +0 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Publish
|
2
|
+
on:
|
3
|
+
release:
|
4
|
+
types: [published]
|
5
|
+
permissions:
|
6
|
+
id-token: write
|
7
|
+
contents: read
|
8
|
+
jobs:
|
9
|
+
publish:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
environment: release
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v4
|
14
|
+
with:
|
15
|
+
fetch-depth: 0
|
16
|
+
- name: Install uv
|
17
|
+
uses: astral-sh/setup-uv@v3
|
18
|
+
with:
|
19
|
+
version: "0.4.29"
|
20
|
+
enable-cache: true
|
21
|
+
- name: Build package
|
22
|
+
run: uv build
|
23
|
+
- name: Publish package to PyPI
|
24
|
+
run: uv publish
|
@@ -0,0 +1,16 @@
|
|
1
|
+
name: Summary
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
branches-ignore:
|
5
|
+
- main
|
6
|
+
jobs:
|
7
|
+
wait-for-triggered-checks:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
permissions:
|
10
|
+
checks: read
|
11
|
+
steps:
|
12
|
+
- name: Wait for all triggered status checks
|
13
|
+
uses: poseidon/wait-for-status-checks@v0.6.0
|
14
|
+
with:
|
15
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
16
|
+
ignore_pattern: ^codecov/.+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
name: Tests
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
paths-ignore:
|
5
|
+
- .gitignore
|
6
|
+
- LICENSE
|
7
|
+
- README.md
|
8
|
+
concurrency:
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
10
|
+
cancel-in-progress: true
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
check-pre-commit:
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
- uses: actions/setup-python@v5
|
18
|
+
with:
|
19
|
+
python-version: "3.12"
|
20
|
+
- uses: pre-commit/action@v3.0.1
|
21
|
+
|
22
|
+
test-coverage:
|
23
|
+
runs-on: ubuntu-latest
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v4
|
26
|
+
- name: Install uv
|
27
|
+
uses: astral-sh/setup-uv@v3
|
28
|
+
with:
|
29
|
+
version: "0.4.29"
|
30
|
+
enable-cache: true
|
31
|
+
- name: Install Python
|
32
|
+
run: uv python install 3.12
|
33
|
+
- name: Install dependencies
|
34
|
+
run: uv sync --all-extras --frozen
|
35
|
+
- name: Run checks
|
36
|
+
run: uv run make check
|
37
|
+
- name: Run tests and create coverage report
|
38
|
+
run: uv run make test-coverage
|
39
|
+
- name: Upload coverage report to Codecov
|
40
|
+
uses: codecov/codecov-action@v4
|
41
|
+
with:
|
42
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
43
|
+
|
44
|
+
test-matrix:
|
45
|
+
runs-on: ubuntu-latest
|
46
|
+
strategy:
|
47
|
+
fail-fast: false
|
48
|
+
matrix:
|
49
|
+
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
50
|
+
deps:
|
51
|
+
- starlette
|
52
|
+
- fastapi starlette
|
53
|
+
- fastapi==0.109.0 starlette
|
54
|
+
- fastapi==0.100.1 starlette
|
55
|
+
- fastapi==0.88.0 starlette
|
56
|
+
- flask
|
57
|
+
- flask==2.3.*
|
58
|
+
- flask==2.0.3 Werkzeug==2.*
|
59
|
+
- djangorestframework django uritemplate inflection
|
60
|
+
- djangorestframework django==4.2.* uritemplate inflection
|
61
|
+
- djangorestframework==3.12.* django==3.2.* uritemplate
|
62
|
+
- djangorestframework==3.10.* django==2.2.* uritemplate
|
63
|
+
- django-ninja django
|
64
|
+
- django-ninja==0.22.* django
|
65
|
+
- django-ninja==0.18.0 django
|
66
|
+
- litestar
|
67
|
+
- litestar==2.6.1
|
68
|
+
- litestar==2.3.0
|
69
|
+
- litestar==2.0.1
|
70
|
+
exclude:
|
71
|
+
- python: "3.12"
|
72
|
+
deps: fastapi==0.100.1 starlette
|
73
|
+
- python: "3.12"
|
74
|
+
deps: fastapi==0.87.0 starlette
|
75
|
+
- python: "3.12"
|
76
|
+
deps: djangorestframework==3.12.* django==3.2.* uritemplate
|
77
|
+
- python: "3.12"
|
78
|
+
deps: djangorestframework==3.10.* django==2.2.* uritemplate
|
79
|
+
- python: "3.12"
|
80
|
+
deps: litestar==2.0.1
|
81
|
+
steps:
|
82
|
+
- uses: actions/checkout@v4
|
83
|
+
- name: Install uv
|
84
|
+
uses: astral-sh/setup-uv@v3
|
85
|
+
with:
|
86
|
+
version: "0.4.29"
|
87
|
+
enable-cache: true
|
88
|
+
- name: Install Python
|
89
|
+
run: uv python install ${{ matrix.python }}
|
90
|
+
- name: Build Python package wheel
|
91
|
+
run: uv build --wheel
|
92
|
+
- name: Install test dependencies
|
93
|
+
run: uv sync --no-install-project --only-group test --frozen
|
94
|
+
- name: Install app dependencies
|
95
|
+
run: uv pip install ${{ matrix.deps }} ./dist/apitally-*.whl
|
96
|
+
- name: Run tests
|
97
|
+
run: uv run make test
|
@@ -0,0 +1,58 @@
|
|
1
|
+
notebooks/
|
2
|
+
|
3
|
+
# Byte-compiled / optimized / DLL files
|
4
|
+
__pycache__/
|
5
|
+
*.py[cod]
|
6
|
+
*$py.class
|
7
|
+
|
8
|
+
# Distribution / packaging
|
9
|
+
.Python
|
10
|
+
build/
|
11
|
+
develop-eggs/
|
12
|
+
dist/
|
13
|
+
downloads/
|
14
|
+
eggs/
|
15
|
+
.eggs/
|
16
|
+
lib/
|
17
|
+
lib64/
|
18
|
+
parts/
|
19
|
+
sdist/
|
20
|
+
var/
|
21
|
+
wheels/
|
22
|
+
share/python-wheels/
|
23
|
+
*.egg-info/
|
24
|
+
.installed.cfg
|
25
|
+
*.egg
|
26
|
+
MANIFEST
|
27
|
+
|
28
|
+
# Unit test / coverage reports
|
29
|
+
.coverage
|
30
|
+
.cache
|
31
|
+
coverage.xml
|
32
|
+
.pytest_cache/
|
33
|
+
|
34
|
+
# Jupyter Notebook
|
35
|
+
.ipynb_checkpoints
|
36
|
+
|
37
|
+
# Environments
|
38
|
+
.venv/
|
39
|
+
|
40
|
+
# mypy
|
41
|
+
.mypy_cache/
|
42
|
+
.dmypy.json
|
43
|
+
dmypy.json
|
44
|
+
|
45
|
+
# ruff
|
46
|
+
.ruff_cache/
|
47
|
+
|
48
|
+
# LSP config files
|
49
|
+
pyrightconfig.json
|
50
|
+
|
51
|
+
# Visual Studio Code
|
52
|
+
.vscode/
|
53
|
+
|
54
|
+
# Local History for Visual Studio Code
|
55
|
+
.history/
|
56
|
+
|
57
|
+
# macOS
|
58
|
+
.DS_Store
|
@@ -0,0 +1,19 @@
|
|
1
|
+
default_language_version:
|
2
|
+
python: "3.12"
|
3
|
+
repos:
|
4
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
5
|
+
rev: v5.0.0
|
6
|
+
hooks:
|
7
|
+
- id: end-of-file-fixer
|
8
|
+
- id: trailing-whitespace
|
9
|
+
- id: mixed-line-ending
|
10
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
11
|
+
rev: v0.7.2
|
12
|
+
hooks:
|
13
|
+
- id: ruff
|
14
|
+
args: ["--fix", "--exit-non-zero-on-fix"]
|
15
|
+
- id: ruff-format
|
16
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
17
|
+
rev: 0.4.29
|
18
|
+
hooks:
|
19
|
+
- id: uv-lock
|
apitally-0.13.0/Makefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
.PHONY: format check test test-coverage
|
2
|
+
|
3
|
+
format:
|
4
|
+
ruff check apitally tests --fix --select I
|
5
|
+
ruff format apitally tests
|
6
|
+
|
7
|
+
check:
|
8
|
+
uv lock --locked
|
9
|
+
ruff check apitally tests
|
10
|
+
ruff format --diff apitally tests
|
11
|
+
mypy --install-types --non-interactive apitally tests
|
12
|
+
|
13
|
+
test:
|
14
|
+
pytest -v --tb=short
|
15
|
+
|
16
|
+
test-coverage:
|
17
|
+
pytest -v --tb=short --cov --cov-report=xml
|
@@ -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
|
-
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.0.0"
|
@@ -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
|
@@ -59,11 +59,12 @@ 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
65
|
response_size = 0
|
66
66
|
response_chunked = False
|
67
|
+
exception: Optional[BaseException] = None
|
67
68
|
start_time = time.perf_counter()
|
68
69
|
|
69
70
|
async def send_wrapper(message: Message) -> None:
|
@@ -92,17 +93,11 @@ class ApitallyMiddleware:
|
|
92
93
|
try:
|
93
94
|
await self.app(scope, receive, send_wrapper)
|
94
95
|
except BaseException as e:
|
95
|
-
|
96
|
-
request=request,
|
97
|
-
response_status=500,
|
98
|
-
response_time=time.perf_counter() - start_time,
|
99
|
-
response_headers=response_headers,
|
100
|
-
response_body=response_body,
|
101
|
-
response_size=response_size,
|
102
|
-
exception=e,
|
103
|
-
)
|
96
|
+
exception = e
|
104
97
|
raise e from None
|
105
|
-
|
98
|
+
finally:
|
99
|
+
if response_time is None:
|
100
|
+
response_time = time.perf_counter() - start_time
|
106
101
|
self.add_request(
|
107
102
|
request=request,
|
108
103
|
response_status=response_status,
|
@@ -110,6 +105,7 @@ class ApitallyMiddleware:
|
|
110
105
|
response_headers=response_headers,
|
111
106
|
response_body=response_body,
|
112
107
|
response_size=response_size,
|
108
|
+
exception=exception,
|
113
109
|
)
|
114
110
|
else:
|
115
111
|
await self.app(scope, receive, send) # pragma: no cover
|
@@ -129,6 +125,8 @@ class ApitallyMiddleware:
|
|
129
125
|
consumer = self.get_consumer(request)
|
130
126
|
consumer_identifier = consumer.identifier if consumer else None
|
131
127
|
self.client.consumer_registry.add_or_update_consumer(consumer)
|
128
|
+
if response_status == 0 and exception is not None:
|
129
|
+
response_status = 500
|
132
130
|
self.client.request_counter.add_request(
|
133
131
|
consumer=consumer_identifier,
|
134
132
|
method=request.method,
|
@@ -0,0 +1,145 @@
|
|
1
|
+
[project]
|
2
|
+
name = "apitally"
|
3
|
+
description = "Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar."
|
4
|
+
authors = [
|
5
|
+
{ name = "Apitally", email = "hello@apitally.io" },
|
6
|
+
]
|
7
|
+
readme = "README.md"
|
8
|
+
license = { text = "MIT License" }
|
9
|
+
classifiers = [
|
10
|
+
"Development Status :: 5 - Production/Stable",
|
11
|
+
"Environment :: Web Environment",
|
12
|
+
"Framework :: Django",
|
13
|
+
"Framework :: FastAPI",
|
14
|
+
"Framework :: Flask",
|
15
|
+
"Intended Audience :: Developers",
|
16
|
+
"Intended Audience :: Information Technology",
|
17
|
+
"License :: OSI Approved :: MIT License",
|
18
|
+
"Programming Language :: Python",
|
19
|
+
"Programming Language :: Python :: 3",
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
21
|
+
"Programming Language :: Python :: 3.8",
|
22
|
+
"Programming Language :: Python :: 3.9",
|
23
|
+
"Programming Language :: Python :: 3.10",
|
24
|
+
"Programming Language :: Python :: 3.11",
|
25
|
+
"Programming Language :: Python :: 3.12",
|
26
|
+
"Topic :: Internet",
|
27
|
+
"Topic :: Internet :: WWW/HTTP",
|
28
|
+
"Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
|
29
|
+
"Topic :: Software Development",
|
30
|
+
"Topic :: System :: Monitoring",
|
31
|
+
"Typing :: Typed",
|
32
|
+
]
|
33
|
+
requires-python = ">=3.8,<4.0"
|
34
|
+
dependencies = [
|
35
|
+
"backoff>=2.0.0",
|
36
|
+
]
|
37
|
+
dynamic = ["version"]
|
38
|
+
|
39
|
+
[project.optional-dependencies]
|
40
|
+
django_ninja = [
|
41
|
+
"django>=2.2,<5; python_version<'3.10'",
|
42
|
+
"django>=2.2; python_version>='3.10'",
|
43
|
+
"django-ninja>=0.18.0",
|
44
|
+
"requests>=2.26.0",
|
45
|
+
]
|
46
|
+
django_rest_framework = [
|
47
|
+
"django>=2.2,<5; python_version<'3.10'",
|
48
|
+
"django>=2.2; python_version>='3.10'",
|
49
|
+
"djangorestframework>=3.10.0",
|
50
|
+
"uritemplate>=3.0.0", # required for schema generation
|
51
|
+
"inflection>=0.5.1", # required for schema generation
|
52
|
+
"requests>=2.26.0",
|
53
|
+
]
|
54
|
+
fastapi = [
|
55
|
+
"fastapi>=0.88.0",
|
56
|
+
"starlette>=0.22.0,<1.0.0",
|
57
|
+
"httpx>=0.22.0",
|
58
|
+
]
|
59
|
+
flask = [
|
60
|
+
"flask>=2.0.0",
|
61
|
+
"requests>=2.26.0",
|
62
|
+
]
|
63
|
+
litestar = [
|
64
|
+
"litestar>=2.0.0",
|
65
|
+
"httpx>=0.22.0",
|
66
|
+
]
|
67
|
+
sentry = [
|
68
|
+
"sentry-sdk>=2.2.0",
|
69
|
+
]
|
70
|
+
starlette = [
|
71
|
+
"starlette>=0.21.0,<1.0.0",
|
72
|
+
"httpx>=0.22.0",
|
73
|
+
]
|
74
|
+
|
75
|
+
[project.urls]
|
76
|
+
Homepage = "https://apitally.io"
|
77
|
+
Documentation = "https://docs.apitally.io"
|
78
|
+
Repository = "https://github.com/apitally/apitally-py"
|
79
|
+
|
80
|
+
[dependency-groups]
|
81
|
+
dev = [
|
82
|
+
"ipykernel~=6.29.0",
|
83
|
+
"mypy~=1.13.0",
|
84
|
+
"pre-commit~=3.5.0; python_version<'3.9'",
|
85
|
+
"pre-commit~=4.0.1; python_version>='3.9'",
|
86
|
+
"ruff~=0.7.0",
|
87
|
+
]
|
88
|
+
test = [
|
89
|
+
"pytest~=7.4.4; python_version<'3.9'",
|
90
|
+
"pytest~=8.3.3; python_version>='3.9'",
|
91
|
+
"pytest-asyncio~=0.21.2",
|
92
|
+
"pytest-cov~=5.0.0; python_version<'3.9'",
|
93
|
+
"pytest-cov~=6.0.0; python_version>='3.9'",
|
94
|
+
"pytest-httpx~=0.22.0; python_version<'3.9'",
|
95
|
+
"pytest-httpx~=0.33.0; python_version>='3.9'",
|
96
|
+
"pytest-mock~=3.14.0",
|
97
|
+
"requests-mock~=1.12.1",
|
98
|
+
]
|
99
|
+
types = [
|
100
|
+
"django-types",
|
101
|
+
"djangorestframework-types",
|
102
|
+
"types-colorama",
|
103
|
+
"types-docutils",
|
104
|
+
"types-pygments",
|
105
|
+
"types-pyyaml",
|
106
|
+
"types-requests",
|
107
|
+
"types-setuptools",
|
108
|
+
"types-six",
|
109
|
+
"types-ujson",
|
110
|
+
]
|
111
|
+
|
112
|
+
[build-system]
|
113
|
+
requires = ["hatchling", "hatch-vcs"]
|
114
|
+
build-backend = "hatchling.build"
|
115
|
+
|
116
|
+
[tool.hatch.version]
|
117
|
+
source = "vcs"
|
118
|
+
|
119
|
+
[tool.uv]
|
120
|
+
default-groups = ["dev", "test", "types"]
|
121
|
+
|
122
|
+
[tool.ruff]
|
123
|
+
line-length = 120
|
124
|
+
target-version = "py312"
|
125
|
+
|
126
|
+
[tool.ruff.lint]
|
127
|
+
ignore = ["E501"]
|
128
|
+
select = ["E", "F", "W", "I"]
|
129
|
+
|
130
|
+
[tool.ruff.lint.isort]
|
131
|
+
lines-after-imports = 2
|
132
|
+
|
133
|
+
[tool.mypy]
|
134
|
+
python_version = "3.12"
|
135
|
+
check_untyped_defs = true
|
136
|
+
|
137
|
+
[tool.pytest.ini_options]
|
138
|
+
asyncio_mode = "auto"
|
139
|
+
testpaths = ["tests"]
|
140
|
+
|
141
|
+
[tool.coverage.run]
|
142
|
+
source = ["apitally"]
|
143
|
+
|
144
|
+
[tool.coverage.report]
|
145
|
+
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING"]
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import os
|
5
|
+
from asyncio import AbstractEventLoop
|
6
|
+
from typing import Iterator
|
7
|
+
|
8
|
+
import pytest
|
9
|
+
|
10
|
+
|
11
|
+
if os.getenv("PYTEST_RAISE", "0") != "0":
|
12
|
+
|
13
|
+
@pytest.hookimpl(tryfirst=True)
|
14
|
+
def pytest_exception_interact(call):
|
15
|
+
raise call.excinfo.value
|
16
|
+
|
17
|
+
@pytest.hookimpl(tryfirst=True)
|
18
|
+
def pytest_internalerror(excinfo):
|
19
|
+
raise excinfo.value
|
20
|
+
|
21
|
+
|
22
|
+
@pytest.fixture(scope="module")
|
23
|
+
def event_loop() -> Iterator[AbstractEventLoop]:
|
24
|
+
policy = asyncio.get_event_loop_policy()
|
25
|
+
loop = policy.new_event_loop()
|
26
|
+
yield loop
|
27
|
+
loop.close()
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from django.http import HttpRequest
|
2
|
+
from django.urls import path
|
3
|
+
from ninja import NinjaAPI
|
4
|
+
|
5
|
+
|
6
|
+
api = NinjaAPI()
|
7
|
+
|
8
|
+
|
9
|
+
@api.get("/foo", summary="Foo", description="Foo")
|
10
|
+
def foo(request: HttpRequest) -> str:
|
11
|
+
return "foo"
|
12
|
+
|
13
|
+
|
14
|
+
@api.get("/foo/{bar}")
|
15
|
+
def foo_bar(request: HttpRequest, bar: int) -> str:
|
16
|
+
return f"foo: {bar}"
|
17
|
+
|
18
|
+
|
19
|
+
@api.post("/bar")
|
20
|
+
def bar(request: HttpRequest) -> str:
|
21
|
+
return "bar"
|
22
|
+
|
23
|
+
|
24
|
+
@api.put("/baz")
|
25
|
+
def baz(request: HttpRequest) -> str:
|
26
|
+
request.apitally_consumer = "baz" # type: ignore[attr-defined]
|
27
|
+
raise ValueError("baz")
|
28
|
+
|
29
|
+
|
30
|
+
@api.get("/val")
|
31
|
+
def val(request: HttpRequest, foo: int) -> str:
|
32
|
+
return "val"
|
33
|
+
|
34
|
+
|
35
|
+
urlpatterns = [
|
36
|
+
path("api/", api.urls), # type: ignore[arg-type]
|
37
|
+
]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from django.urls import path
|
2
|
+
from rest_framework.decorators import api_view
|
3
|
+
from rest_framework.request import Request
|
4
|
+
from rest_framework.response import Response
|
5
|
+
from rest_framework.views import APIView
|
6
|
+
|
7
|
+
|
8
|
+
class FooView(APIView):
|
9
|
+
"""Foo"""
|
10
|
+
|
11
|
+
def get(self, request: Request) -> Response:
|
12
|
+
return Response("foo")
|
13
|
+
|
14
|
+
|
15
|
+
class FooBarView(APIView):
|
16
|
+
def get(self, request: Request, bar: int) -> Response:
|
17
|
+
request._request.apitally_consumer = "test" # type: ignore[attr-defined]
|
18
|
+
return Response(f"foo: {bar}")
|
19
|
+
|
20
|
+
|
21
|
+
@api_view(["POST"])
|
22
|
+
def bar(request: Request) -> Response:
|
23
|
+
return Response("bar")
|
24
|
+
|
25
|
+
|
26
|
+
@api_view(["PUT"])
|
27
|
+
def baz(request: Request) -> Response:
|
28
|
+
raise ValueError("baz")
|
29
|
+
|
30
|
+
|
31
|
+
urlpatterns = [
|
32
|
+
path("foo/", FooView.as_view()),
|
33
|
+
path("foo/<int:bar>/", FooBarView.as_view()),
|
34
|
+
path("bar/", bar),
|
35
|
+
path("baz/", baz),
|
36
|
+
]
|