tests: add test framework

This commit is contained in:
2026-04-17 23:12:07 +02:00
parent d0316dbd62
commit a8da6d6f91
6 changed files with 278 additions and 37 deletions
+4 -1
View File
@@ -3,4 +3,7 @@
.ruff_cache .ruff_cache
docker-compose.yml docker-compose.yml
.env .env
letsencrypt letsencrypt
.coverage
htmlcov
coverage.xml
+56 -20
View File
@@ -10,6 +10,8 @@ endif
UV ?= uv UV ?= uv
RUFF ?= $(UV) run --active ruff RUFF ?= $(UV) run --active ruff
TY ?= $(UV) run --active ty TY ?= $(UV) run --active ty
UNITTEST ?= $(UV) run --active -m unittest
COVERAGE ?= $(UV) run --active coverage
DOCKER ?= docker DOCKER ?= docker
DOCKER_TAG ?= localhost/stapler:latest DOCKER_TAG ?= localhost/stapler:latest
PORT ?= 8080 PORT ?= 8080
@@ -35,6 +37,36 @@ print-%:
.venv: uv.lock .venv: uv.lock
@$(MAKE) -s uv-sync @$(MAKE) -s uv-sync
# ACTIONS
.PHONY: install
install: uv-sync ## install project
.PHONY: update
update: uv-upgrade ## update project dependencies
.PHONY: format
format: ruff-fix ruff-format ## format project
.PHONY: lint
lint: ruff ruff-format-check ty ## lint project
.PHONY: build
build: docker-build ## build project
.PHONY: start
start: build docker-run ## start server in localhost
.PHONY: test
test: unittest ## test project
.PHONY: test-%
test-%: ## test project with specific test
@$(MAKE) -s unittest-$*
.PHONY: coverage
coverage: coverage-unittest coverage-xml coverage-report ## test project with coverage
# TOOLS # TOOLS
.PHONY: uv-sync .PHONY: uv-sync
@@ -65,6 +97,30 @@ ruff-format-check: .venv ## ruff format (check only)
ty: .venv ## ty check ty: .venv ## ty check
@$(TY) check @$(TY) check
.PHONY: unittest
unittest: .venv ## unittest
@$(UNITTEST) -v
.PHONY: unittest-%
unittest-%: .venv ## unittest -k [filter]
@$(UNITTEST) -v -k $*
.PHONY: coverage-unittest
coverage-unittest: .venv ## coverage run -m unittest
@$(COVERAGE) run -m unittest -v
.PHONY: coverage-report
coverage-report: .venv ## coverage report
@$(COVERAGE) report
.PHONY: coverage-html
coverage-html: .venv ## coverage html
@$(COVERAGE) html
.PHONY: coverage-xml
coverage-xml: .venv ## coverage xml
@$(COVERAGE) xml
.PHONY: docker-build .PHONY: docker-build
docker-build: ## docker build docker-build: ## docker build
@$(DOCKER) build . -t $(DOCKER_TAG) @$(DOCKER) build . -t $(DOCKER_TAG)
@@ -72,23 +128,3 @@ docker-build: ## docker build
.PHONY: docker-run .PHONY: docker-run
docker-run: docker-build ## docker run docker-run: docker-build ## docker run
@$(DOCKER) run -it -p $(PORT):80 -v ./data:/data $(DOCKER_TAG) --debug --no-certbot --no-https --host localhost:$(PORT) run @$(DOCKER) run -it -p $(PORT):80 -v ./data:/data $(DOCKER_TAG) --debug --no-certbot --no-https --host localhost:$(PORT) run
# ACTIONS
.PHONY: install
install: uv-sync ## install project
.PHONY: update
update: uv-upgrade ## update project dependencies
.PHONY: format
format: ruff-fix ruff-format ## format project
.PHONY: lint
lint: ruff ruff-format-check ty ## lint project
.PHONY: build
build: docker-build ## build project
.PHONY: start
start: build docker-run ## start server in localhost
+15 -15
View File
@@ -125,6 +125,15 @@ Usage: make [target1] [target2] ...
Commands/Targets: Commands/Targets:
help show this message help show this message
install install project
update update project dependencies
format format project
lint lint project
build build project
start start server in localhost
test test project
test-% test project with specific test
coverage test project with coverage
uv-sync uv sync uv-sync uv sync
uv-upgrade uv sync upgrade uv-upgrade uv sync upgrade
ruff ruff check ruff ruff check
@@ -132,21 +141,12 @@ ruff-fix ruff check (and fix)
ruff-format ruff format ruff-format ruff format
ruff-format-check ruff format (check only) ruff-format-check ruff format (check only)
ty ty check ty ty check
unittest unittest
unittest-% unittest -k [filter]
coverage-unittest coverage run -m unittest
coverage-report coverage report
coverage-html coverage html
coverage-xml coverage xml
docker-build docker build docker-build docker build
docker-run docker run docker-run docker run
install install project
update update project dependencies
format format project
lint lint project
build build project
start start server in localhost
Environment:
UV = uv
RUFF = uv run --active ruff
TY = uv run --active ty
DOCKER = docker
DOCKER_TAG = localhost/stapler:latest
TOKEN = secret
PORT = 8080
``` ```
+6 -1
View File
@@ -10,10 +10,15 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"coverage>=7.13.5",
"ruff>=0.15.10", "ruff>=0.15.10",
"ty>=0.0.29", "ty>=0.0.29",
] ]
[tool.ruff.lint] [tool.ruff.lint]
select = ["ALL"] select = ["ALL"]
ignore = ["D", "E501", "S104", "PLR2004", "ANN401", "BLE001", "COM812", "S603", "PLR0911"] ignore = ["D", "E501", "S104", "PLR2004", "ANN401", "BLE001", "COM812", "S603", "PLR0911", "S101", "PT"]
[tool.coverage.run]
source = ["src"]
omit = ["src/logs.py"]
+156
View File
@@ -0,0 +1,156 @@
import contextlib
import pathlib
import tempfile
import typing
import unittest
import unittest.mock
class BaseTestCase(unittest.TestCase):
@typing.override
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
self.mocks: list[unittest.mock.Mock] = []
self.tmp_dir: tempfile.TemporaryDirectory | None = None
self.tmp_path = pathlib.Path()
super().__init__(*args, **kwargs)
@typing.override
def tearDown(self) -> None:
if self.tmp_dir is not None:
self.tmp_dir.cleanup()
self.tmp_dir = None
super().tearDown()
def get_tmp_dir(self) -> str:
self.tmp_dir = tempfile.TemporaryDirectory(delete=False)
self.tmp_path = pathlib.Path(self.tmp_dir.name)
return self.tmp_dir.name
def mock(self, spec: type | None = None) -> unittest.mock.Mock:
mock = unittest.mock.Mock(spec)
self.mocks += [mock]
return mock
@contextlib.contextmanager
def patch(
self, target: str, return_value: typing.Any = None, count: int = 1
) -> typing.Iterator[unittest.mock.Mock]:
with unittest.mock.patch(
target, return_value=return_value, create=True
) as mock:
yield mock
self.assertEqual(mock.call_count, count, mock)
@contextlib.contextmanager
def patch_calls(
self,
target: str,
args: list[typing.Iterable[typing.Any]] | None = None,
return_values: list[typing.Any] | None = None,
) -> typing.Iterator[unittest.mock.Mock]:
if args is None:
args = [[]]
if return_values is None:
return_values = [None] * len(args)
with unittest.mock.patch(
target, side_effect=return_values, create=True
) as mock:
yield mock
self.__check_calls(mock, args)
@contextlib.contextmanager
def patch_call(
self,
target: str,
args: typing.Iterable[typing.Any] | None = None,
return_value: typing.Any = None,
) -> typing.Iterator[unittest.mock.Mock]:
if args is None:
args = []
with self.patch_calls(target, [args], [return_value]) as mock:
yield mock
@contextlib.contextmanager
def seal_mocks(self, *extra_mocks: unittest.mock.Mock) -> typing.Iterator[None]:
for mock in self.mocks:
unittest.mock.seal(mock)
for mock in extra_mocks:
unittest.mock.seal(mock)
yield
@contextlib.contextmanager
def mock_calls(
self,
mock: unittest.mock.Mock,
args: list[typing.Iterable[typing.Any]] | None = None,
return_values: list[typing.Any] | None = None,
) -> typing.Iterator[None]:
if args is None:
args = [[]]
if return_values is None:
return_values = [None] * len(args)
mock.side_effect = return_values
mock.reset_mock()
yield
self.__check_calls(mock, args)
@contextlib.contextmanager
def mock_call(
self,
mock: unittest.mock.Mock,
args: typing.Iterable[typing.Any] | None = None,
return_value: typing.Any = None,
) -> typing.Iterator[None]:
if args is None:
args = []
with self.mock_calls(mock, [args], [return_value]):
yield
@contextlib.contextmanager
def mock_calls_unchecked(
self,
mock: unittest.mock.Mock,
count: int = 1,
return_values: list[typing.Any] | None = None,
) -> typing.Iterator[None]:
if return_values is None:
return_values = [None] * count
mock.side_effect = return_values
mock.reset_mock()
yield
self.assertEqual(mock.call_count, count, mock)
@contextlib.contextmanager
def mock_call_unchecked(
self,
mock: unittest.mock.Mock,
return_value: typing.Any = None,
) -> typing.Iterator[None]:
with self.mock_calls_unchecked(mock, 1, [return_value]):
yield
def assert_file_content(self, file: pathlib.Path, *expected_content: str) -> None:
assert file.parent.is_dir(), file
assert file.exists(), file
assert file.is_file(), file
with file.open() as file_content:
self.assertEqual(file_content.read(), "\n".join(expected_content))
def __check_calls(
self,
mock: unittest.mock.Mock,
args: list[typing.Iterable[typing.Any]],
) -> None:
for i, values in enumerate(
zip(
mock.mock_calls
+ [None]
* (max(len(args), len(mock.method_calls)) - len(mock.mock_calls)),
args + [[]] * (max(len(args), len(mock.method_calls)) - len(args)),
strict=False,
)
):
real_call, expected_args = values
self.assertEqual(
real_call, unittest.mock.call(*expected_args), f"{i + 1}: {mock}"
)
Generated
+41
View File
@@ -2,6 +2,45 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.14" requires-python = ">=3.14"
[[package]]
name = "coverage"
version = "7.13.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" },
{ url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" },
{ url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" },
{ url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" },
{ url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" },
{ url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" },
{ url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" },
{ url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" },
{ url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" },
{ url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" },
{ url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" },
{ url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" },
{ url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" },
{ url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" },
{ url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" },
{ url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" },
{ url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" },
{ url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" },
{ url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" },
{ url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" },
{ url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" },
{ url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" },
{ url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" },
{ url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" },
{ url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" },
{ url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" },
{ url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" },
{ url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" },
{ url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" },
{ url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" },
{ url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.10" version = "0.15.10"
@@ -37,6 +76,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "coverage" },
{ name = "ruff" }, { name = "ruff" },
{ name = "ty" }, { name = "ty" },
] ]
@@ -46,6 +86,7 @@ requires-dist = [{ name = "toml", specifier = ">=0.10.2" }]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "coverage", specifier = ">=7.13.5" },
{ name = "ruff", specifier = ">=0.15.10" }, { name = "ruff", specifier = ">=0.15.10" },
{ name = "ty", specifier = ">=0.0.29" }, { name = "ty", specifier = ">=0.0.29" },
] ]