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.
Files changed (43) hide show
  1. apitally-0.13.0/.github/workflows/publish.yaml +24 -0
  2. apitally-0.13.0/.github/workflows/summary.yaml +16 -0
  3. apitally-0.13.0/.github/workflows/tests.yaml +97 -0
  4. apitally-0.13.0/.gitignore +58 -0
  5. apitally-0.13.0/.pre-commit-config.yaml +19 -0
  6. apitally-0.13.0/Makefile +17 -0
  7. {apitally-0.12.1 → apitally-0.13.0}/PKG-INFO +38 -24
  8. apitally-0.13.0/apitally/__init__.py +1 -0
  9. {apitally-0.12.1 → apitally-0.13.0}/apitally/client/base.py +6 -1
  10. {apitally-0.12.1 → apitally-0.13.0}/apitally/starlette.py +9 -11
  11. apitally-0.13.0/pyproject.toml +145 -0
  12. apitally-0.13.0/renovate.json +5 -0
  13. apitally-0.13.0/tests/__init__.py +0 -0
  14. apitally-0.13.0/tests/conftest.py +27 -0
  15. apitally-0.13.0/tests/constants.py +2 -0
  16. apitally-0.13.0/tests/django_ninja_urls.py +37 -0
  17. apitally-0.13.0/tests/django_rest_framework_urls.py +36 -0
  18. apitally-0.13.0/tests/test_client_asyncio.py +98 -0
  19. apitally-0.13.0/tests/test_client_base.py +179 -0
  20. apitally-0.13.0/tests/test_client_threading.py +95 -0
  21. apitally-0.13.0/tests/test_django_ninja.py +168 -0
  22. apitally-0.13.0/tests/test_django_rest_framework.py +138 -0
  23. apitally-0.13.0/tests/test_fastapi.py +65 -0
  24. apitally-0.13.0/tests/test_flask.py +107 -0
  25. apitally-0.13.0/tests/test_litestar.py +178 -0
  26. apitally-0.13.0/tests/test_starlette.py +250 -0
  27. apitally-0.13.0/uv.lock +2301 -0
  28. apitally-0.12.1/apitally/__init__.py +0 -1
  29. apitally-0.12.1/pyproject.toml +0 -125
  30. {apitally-0.12.1 → apitally-0.13.0}/LICENSE +0 -0
  31. {apitally-0.12.1 → apitally-0.13.0}/README.md +0 -0
  32. {apitally-0.12.1 → apitally-0.13.0}/apitally/client/__init__.py +0 -0
  33. {apitally-0.12.1 → apitally-0.13.0}/apitally/client/asyncio.py +0 -0
  34. {apitally-0.12.1 → apitally-0.13.0}/apitally/client/logging.py +0 -0
  35. {apitally-0.12.1 → apitally-0.13.0}/apitally/client/threading.py +0 -0
  36. {apitally-0.12.1 → apitally-0.13.0}/apitally/common.py +0 -0
  37. {apitally-0.12.1 → apitally-0.13.0}/apitally/django.py +0 -0
  38. {apitally-0.12.1 → apitally-0.13.0}/apitally/django_ninja.py +0 -0
  39. {apitally-0.12.1 → apitally-0.13.0}/apitally/django_rest_framework.py +0 -0
  40. {apitally-0.12.1 → apitally-0.13.0}/apitally/fastapi.py +0 -0
  41. {apitally-0.12.1 → apitally-0.13.0}/apitally/flask.py +0 -0
  42. {apitally-0.12.1 → apitally-0.13.0}/apitally/litestar.py +0 -0
  43. {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
@@ -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
1
+ Metadata-Version: 2.3
2
2
  Name: apitally
3
- Version: 0.12.1
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
-
@@ -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
- 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
@@ -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 = 0.0
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
- self.add_request(
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
- else:
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"]
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["github>apitally/renovate-config"],
4
+ "ignoreDeps": ["pytest-asyncio"]
5
+ }
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,2 @@
1
+ CLIENT_ID = "76b5cb91-a0a4-4ea0-a894-57d2b9fcb2c9"
2
+ ENV = "dev"
@@ -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
+ ]