apitally 0.12.0__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.0 → apitally-0.13.0}/PKG-INFO +38 -24
  8. apitally-0.13.0/apitally/__init__.py +1 -0
  9. {apitally-0.12.0 → apitally-0.13.0}/apitally/client/base.py +6 -1
  10. {apitally-0.12.0 → apitally-0.13.0}/apitally/litestar.py +21 -4
  11. {apitally-0.12.0 → apitally-0.13.0}/apitally/starlette.py +30 -14
  12. apitally-0.13.0/pyproject.toml +145 -0
  13. apitally-0.13.0/renovate.json +5 -0
  14. apitally-0.13.0/tests/__init__.py +0 -0
  15. apitally-0.13.0/tests/conftest.py +27 -0
  16. apitally-0.13.0/tests/constants.py +2 -0
  17. apitally-0.13.0/tests/django_ninja_urls.py +37 -0
  18. apitally-0.13.0/tests/django_rest_framework_urls.py +36 -0
  19. apitally-0.13.0/tests/test_client_asyncio.py +98 -0
  20. apitally-0.13.0/tests/test_client_base.py +179 -0
  21. apitally-0.13.0/tests/test_client_threading.py +95 -0
  22. apitally-0.13.0/tests/test_django_ninja.py +168 -0
  23. apitally-0.13.0/tests/test_django_rest_framework.py +138 -0
  24. apitally-0.13.0/tests/test_fastapi.py +65 -0
  25. apitally-0.13.0/tests/test_flask.py +107 -0
  26. apitally-0.13.0/tests/test_litestar.py +178 -0
  27. apitally-0.13.0/tests/test_starlette.py +250 -0
  28. apitally-0.13.0/uv.lock +2301 -0
  29. apitally-0.12.0/apitally/__init__.py +0 -1
  30. apitally-0.12.0/pyproject.toml +0 -125
  31. {apitally-0.12.0 → apitally-0.13.0}/LICENSE +0 -0
  32. {apitally-0.12.0 → apitally-0.13.0}/README.md +0 -0
  33. {apitally-0.12.0 → apitally-0.13.0}/apitally/client/__init__.py +0 -0
  34. {apitally-0.12.0 → apitally-0.13.0}/apitally/client/asyncio.py +0 -0
  35. {apitally-0.12.0 → apitally-0.13.0}/apitally/client/logging.py +0 -0
  36. {apitally-0.12.0 → apitally-0.13.0}/apitally/client/threading.py +0 -0
  37. {apitally-0.12.0 → apitally-0.13.0}/apitally/common.py +0 -0
  38. {apitally-0.12.0 → apitally-0.13.0}/apitally/django.py +0 -0
  39. {apitally-0.12.0 → apitally-0.13.0}/apitally/django_ninja.py +0 -0
  40. {apitally-0.12.0 → apitally-0.13.0}/apitally/django_rest_framework.py +0 -0
  41. {apitally-0.12.0 → apitally-0.13.0}/apitally/fastapi.py +0 -0
  42. {apitally-0.12.0 → apitally-0.13.0}/apitally/flask.py +0 -0
  43. {apitally-0.12.0 → 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.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
- 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
@@ -72,16 +72,31 @@ class ApitallyPlugin(InitPluginProtocol):
72
72
  response_time = 0.0
73
73
  response_headers = Headers()
74
74
  response_body = b""
75
+ response_size = 0
76
+ response_chunked = False
75
77
  start_time = time.perf_counter()
76
78
 
77
79
  async def send_wrapper(message: Message) -> None:
78
- nonlocal response_time, response_status, response_headers, response_body
80
+ nonlocal \
81
+ response_time, \
82
+ response_status, \
83
+ response_headers, \
84
+ response_body, \
85
+ response_size, \
86
+ response_chunked
79
87
  if message["type"] == "http.response.start":
80
88
  response_time = time.perf_counter() - start_time
81
89
  response_status = message["status"]
82
90
  response_headers = Headers(message["headers"])
83
- elif message["type"] == "http.response.body" and response_status == 400:
84
- response_body += message["body"]
91
+ response_chunked = (
92
+ response_headers.get("Transfer-Encoding") == "chunked"
93
+ or "Content-Length" not in response_headers
94
+ )
95
+ elif message["type"] == "http.response.body":
96
+ if response_chunked:
97
+ response_size += len(message.get("body", b""))
98
+ if response_status == 400:
99
+ response_body += message.get("body", b"")
85
100
  await send(message)
86
101
 
87
102
  await app(scope, receive, send_wrapper)
@@ -91,6 +106,7 @@ class ApitallyPlugin(InitPluginProtocol):
91
106
  response_time=response_time,
92
107
  response_headers=response_headers,
93
108
  response_body=response_body,
109
+ response_size=response_size,
94
110
  )
95
111
  else:
96
112
  await app(scope, receive, send) # pragma: no cover
@@ -104,6 +120,7 @@ class ApitallyPlugin(InitPluginProtocol):
104
120
  response_time: float,
105
121
  response_headers: Headers,
106
122
  response_body: bytes,
123
+ response_size: int = 0,
107
124
  ) -> None:
108
125
  if response_status < 100 or not request.route_handler.paths:
109
126
  return # pragma: no cover
@@ -120,7 +137,7 @@ class ApitallyPlugin(InitPluginProtocol):
120
137
  status_code=response_status,
121
138
  response_time=response_time,
122
139
  request_size=request.headers.get("Content-Length"),
123
- response_size=response_headers.get("Content-Length"),
140
+ response_size=response_size or response_headers.get("Content-Length"),
124
141
  )
125
142
  if response_status == 400 and response_body and len(response_body) < 4096:
126
143
  with contextlib.suppress(json.JSONDecodeError):
@@ -59,40 +59,53 @@ 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
+ response_size = 0
66
+ response_chunked = False
67
+ exception: Optional[BaseException] = None
65
68
  start_time = time.perf_counter()
66
69
 
67
70
  async def send_wrapper(message: Message) -> None:
68
- nonlocal response_time, response_status, response_headers, response_body
71
+ nonlocal \
72
+ response_time, \
73
+ response_status, \
74
+ response_headers, \
75
+ response_body, \
76
+ response_size, \
77
+ response_chunked
69
78
  if message["type"] == "http.response.start":
70
79
  response_time = time.perf_counter() - start_time
71
80
  response_status = message["status"]
72
81
  response_headers = Headers(scope=message)
73
- elif message["type"] == "http.response.body" and response_status == 422:
74
- response_body += message["body"]
82
+ response_chunked = (
83
+ response_headers.get("Transfer-Encoding") == "chunked"
84
+ or "Content-Length" not in response_headers
85
+ )
86
+ elif message["type"] == "http.response.body":
87
+ if response_chunked:
88
+ response_size += len(message.get("body", b""))
89
+ if response_status == 422:
90
+ response_body += message.get("body", b"")
75
91
  await send(message)
76
92
 
77
93
  try:
78
94
  await self.app(scope, receive, send_wrapper)
79
95
  except BaseException as e:
80
- self.add_request(
81
- request=request,
82
- response_status=500,
83
- response_time=time.perf_counter() - start_time,
84
- response_headers=response_headers,
85
- response_body=response_body,
86
- exception=e,
87
- )
96
+ exception = e
88
97
  raise e from None
89
- else:
98
+ finally:
99
+ if response_time is None:
100
+ response_time = time.perf_counter() - start_time
90
101
  self.add_request(
91
102
  request=request,
92
103
  response_status=response_status,
93
104
  response_time=response_time,
94
105
  response_headers=response_headers,
95
106
  response_body=response_body,
107
+ response_size=response_size,
108
+ exception=exception,
96
109
  )
97
110
  else:
98
111
  await self.app(scope, receive, send) # pragma: no cover
@@ -104,6 +117,7 @@ class ApitallyMiddleware:
104
117
  response_time: float,
105
118
  response_headers: Headers,
106
119
  response_body: bytes,
120
+ response_size: int = 0,
107
121
  exception: Optional[BaseException] = None,
108
122
  ) -> None:
109
123
  path_template, is_handled_path = self.get_path_template(request)
@@ -111,6 +125,8 @@ class ApitallyMiddleware:
111
125
  consumer = self.get_consumer(request)
112
126
  consumer_identifier = consumer.identifier if consumer else None
113
127
  self.client.consumer_registry.add_or_update_consumer(consumer)
128
+ if response_status == 0 and exception is not None:
129
+ response_status = 500
114
130
  self.client.request_counter.add_request(
115
131
  consumer=consumer_identifier,
116
132
  method=request.method,
@@ -118,7 +134,7 @@ class ApitallyMiddleware:
118
134
  status_code=response_status,
119
135
  response_time=response_time,
120
136
  request_size=request.headers.get("Content-Length"),
121
- response_size=response_headers.get("Content-Length"),
137
+ response_size=response_size or response_headers.get("Content-Length"),
122
138
  )
123
139
  if response_status == 422 and response_body and response_headers.get("Content-Type") == "application/json":
124
140
  with contextlib.suppress(json.JSONDecodeError):
@@ -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