diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e55e6c..24efb65 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,12 @@ repos: entry: ruff format language: system types_or: [ python, pyi, jupyter ] + - id: ty + name: ty check + entry: uv run ty check + language: system + pass_filenames: false + always_run: true - id: pytest name: pytest entry: uv run pytest -vv --cov=src --cov-report term-missing:skip-covered --cov-report=json:coverage.json diff --git a/pyproject.toml b/pyproject.toml index 4b93ae5..056daf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "danom" -version = "0.10.0" +version = "0.10.1" description = "Functional streams and monads" readme = "README.md" license = "MIT" @@ -24,6 +24,7 @@ build-backend = "uv_build" dev = [ "hypothesis>=6.148.2", "ipykernel>=7.1.0", + "papertrail>=0.1.2", "pre-commit>=4.5.0", "pytest>=9.0.1", "pytest-asyncio>=1.3.0", @@ -46,6 +47,9 @@ show_missing = true skip_covered = true fail_under = 95 +[project.entry-points.pytest11] +papertrail = "papertrail.__main__" + [tool.pytest] # addopts = ["--codspeed"] diff --git a/src/danom/_new_type.py b/src/danom/_new_type.py index 5e50aab..386c504 100644 --- a/src/danom/_new_type.py +++ b/src/danom/_new_type.py @@ -3,10 +3,12 @@ import inspect from collections.abc import Callable, Sequence from functools import wraps -from typing import TypeVar +from typing import ParamSpec, Self, TypeVar import attrs +T = TypeVar("T") + # skip return type because it makes Pylance think the returned type isn't a type def new_type( # noqa: ANN202 @@ -54,11 +56,11 @@ def has_len(email: str) -> bool: kwargs = _callables_to_kwargs(base_type, validators, converters) @attrs.define(frozen=frozen, eq=True, hash=frozen) - class _Wrapper[T]: - inner: T = attrs.field(**kwargs) + class _Wrapper: + inner: T = attrs.field(**kwargs) # ty: ignore[no-matching-overload] - def map(self, func: Callable[[T], T]) -> T: - return type(self)(func(self.inner)) + def map(self, func: Callable[[T], T]) -> Self: + return self.__class__(func(self.inner)) # ty: ignore[invalid-argument-type] locals().update(_create_forward_methods(base_type)) @@ -74,7 +76,7 @@ def _create_forward_methods(base_type: type) -> dict[str, Callable]: continue def make_forwarder(name: str) -> Callable: - def method[T](self, *args: tuple, **kwargs: dict) -> T: # noqa: ANN001 + def method(self, *args: tuple, **kwargs: dict) -> T: # noqa: ANN001 return getattr(self.inner, name)(*args, **kwargs) method.__name__ = name @@ -97,8 +99,11 @@ def _callables_to_kwargs( return {k: v for k, v in kwargs.items() if v} +P = ParamSpec("P") + + def _validate_bool_func[T]( - bool_fn: Callable[..., bool], + bool_fn: Callable[[T], bool], ) -> Callable[[attrs.AttrsInstance, attrs.Attribute, T], None]: if not callable(bool_fn): raise TypeError("provided boolean function must be callable") @@ -107,13 +112,13 @@ def _validate_bool_func[T]( def wrapper(_instance: attrs.AttrsInstance, attribute: attrs.Attribute, value: T) -> None: if not bool_fn(value): raise ValueError( - f"{attribute.name} does not return True for `{bool_fn.__name__}`, received `{value}`." + f"{attribute.name} does not return True for the given boolean function, received `{value}`." ) return wrapper -C = TypeVar("C", bound=Callable[..., object]) +C = TypeVar("C", bound=Callable[P, object]) def _to_list(value: C | Sequence[C] | None) -> list[C]: @@ -121,7 +126,7 @@ def _to_list(value: C | Sequence[C] | None) -> list[C]: return [] if callable(value): - return [value] + return [value] # ty: ignore[invalid-return-type] if isinstance(value, Sequence) and not all(callable(fn) for fn in value): raise TypeError(f"Given items are not all callable: {value = }") diff --git a/src/danom/_safe.py b/src/danom/_safe.py index 1497340..f968e97 100644 --- a/src/danom/_safe.py +++ b/src/danom/_safe.py @@ -1,14 +1,17 @@ import functools import traceback from collections.abc import Callable -from typing import ParamSpec +from typing import Concatenate, ParamSpec, TypeVar from danom._result import Err, Ok, Result +T = TypeVar("T") P = ParamSpec("P") +U = TypeVar("U") +E = TypeVar("E") -def safe[U, E](func: Callable[..., U]) -> Callable[..., Result[U, E]]: +def safe[**P, U](func: Callable[P, U]) -> Callable[P, Result[U, E]]: """Decorator for functions that wraps the function in a try except returns `Ok` on success else `Err`. .. code-block:: python @@ -32,7 +35,9 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Result[U, E]: return wrapper -def safe_method[U, E](func: Callable[..., U]) -> Callable[..., Result[U, E]]: +def safe_method[T, **P, U]( + func: Callable[Concatenate[T, P], U], +) -> Callable[Concatenate[T, P], Result[U, E]]: """The same as `safe` except it forwards on the `self` of the class instance to the wrapped function. .. code-block:: python @@ -51,7 +56,7 @@ def add_one(self, a: int) -> int: """ @functools.wraps(func) - def wrapper(self, *args: P.args, **kwargs: P.kwargs) -> Result[U, E]: # noqa: ANN001 + def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> Result[U, E]: try: return Ok(func(self, *args, **kwargs)) except Exception as e: # noqa: BLE001 diff --git a/src/danom/_stream.py b/src/danom/_stream.py index d6aa010..708c9c4 100644 --- a/src/danom/_stream.py +++ b/src/danom/_stream.py @@ -397,7 +397,7 @@ def par_collect(self, workers: int = 4, *, use_threads: bool = False) -> tuple[U batches = [ (list(chunk), self.ops) - for chunk in batched(self.seq, n=max(10, len(self.seq) // workers)) + for chunk in batched(self.seq, n=max(4, len(self.seq) // workers)) ] with executor_cls(max_workers=workers) as ex: diff --git a/src/danom/_utils.py b/src/danom/_utils.py index 398cd7d..549f8f1 100644 --- a/src/danom/_utils.py +++ b/src/danom/_utils.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Callable, Sequence +from functools import reduce from operator import not_ from typing import ParamSpec, TypeVar @@ -19,10 +20,11 @@ class _Compose: fns: Sequence[Composable] def __call__(self, initial: T_co) -> T_co | U_co: - value = initial - for fn in self.fns: - value = fn(value) - return value + return reduce(_apply, self.fns, initial) # ty: ignore[invalid-return-type] + + +def _apply[T_co](value: T_co, fn: Composable) -> T_co | U_co: + return fn(value) def compose(*fns: Composable) -> Composable: @@ -46,8 +48,8 @@ def compose(*fns: Composable) -> Composable: class _AllOf: fns: Sequence[Filterable] - def __call__(self, initial: T_co) -> bool: - return all(fn(initial) for fn in self.fns) + def __call__(self, item: T_co) -> bool: + return all(fn(item) for fn in self.fns) def all_of(*fns: Filterable) -> Filterable: @@ -67,8 +69,8 @@ def all_of(*fns: Filterable) -> Filterable: class _AnyOf: fns: Sequence[Filterable] - def __call__(self, initial: T_co) -> bool: - return any(fn(initial) for fn in self.fns) + def __call__(self, item: T_co) -> bool: + return any(fn(item) for fn in self.fns) def any_of(*fns: Filterable) -> Filterable: @@ -107,6 +109,21 @@ def identity[T_co](x: T_co) -> T_co: identity("abc") == "abc" identity(1) == 1 identity(ComplexDataType(a=1, b=2, c=3)) == ComplexDataType(a=1, b=2, c=3) + + Papertrail examples: + + >>> identity(1) == 1 + True + + >>> identity("abc") == "abc" + True + + >>> identity([0, 1, 2]) == [0, 1, 2] + True + + >>> identity(Ok(inner=1)) == Ok(inner=1) + True + :: """ return x diff --git a/tests/conftest.py b/tests/conftest.py index 1a20313..3b46ab1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import asyncio from multiprocessing.managers import ListProxy from pathlib import Path -from typing import Any, Never, Self +from typing import Any, Self from src.danom import safe, safe_method from src.danom._result import Err, Ok, Result @@ -81,7 +81,7 @@ def safe_get_error_type(exception: Exception) -> str: @safe -def div_zero(x: int) -> Never: +def div_zero(x: int) -> float: return x / 0 diff --git a/tests/test_new_type.py b/tests/test_new_type.py index 5807dd5..dbdf9e1 100644 --- a/tests/test_new_type.py +++ b/tests/test_new_type.py @@ -1,6 +1,6 @@ from contextlib import nullcontext -from types import SimpleNamespace +import attrs import hypothesis.strategies as st import pytest from hypothesis import given @@ -100,5 +100,21 @@ def test_new_type_map(initial_value, base_type, map_fn, get_attr, expected_inner def test_validate_bool_func(args): bool_fn, value, expected_context = args + attr = attrs.Attribute( + name="x", # ty: ignore[unknown-argument] + cmp=None, # ty: ignore[unknown-argument] + inherited=False, # ty: ignore[unknown-argument] + default=None, # ty: ignore[unknown-argument] + validator=None, # ty: ignore[unknown-argument] + repr=True, # ty: ignore[unknown-argument] + eq=True, # ty: ignore[unknown-argument] + order=True, # ty: ignore[unknown-argument] + hash=None, # ty: ignore[unknown-argument] + init=True, # ty: ignore[unknown-argument] + metadata={}, # ty: ignore[unknown-argument] + type=object, # ty: ignore[unknown-argument] + converter=None, # ty: ignore[unknown-argument] + ) + with expected_context: - _validate_bool_func(bool_fn)(object(), SimpleNamespace(name="x"), value) + _validate_bool_func(bool_fn)(object(), attr, value) diff --git a/tests/test_safe.py b/tests/test_safe.py index aec4237..008e8ef 100644 --- a/tests/test_safe.py +++ b/tests/test_safe.py @@ -60,11 +60,19 @@ def test_invalid_safe_method_pipeline(): def test_traceback(): - err = div_zero() - assert err.traceback.replace(str(REPO_ROOT), ".").splitlines() == [ + err = div_zero(1) + + expected_lines = [ "Traceback (most recent call last):", - ' File "./src/danom/_safe.py", line 28, in wrapper', + ' File "./src/danom/_safe.py", line 31, in wrapper', " return Ok(func(*args, **kwargs))", - " ^^^^^^^^^^^^^^^^^^^^^", - "TypeError: div_zero() missing 1 required positional argument: 'x'", + ' File "./tests/conftest.py", line 85, in div_zero', + " return x / 0", + "ZeroDivisionError: division by zero", ] + + tb_lines = err.traceback.replace(str(REPO_ROOT), ".").splitlines() + + missing_lines = [line for line in expected_lines if line not in tb_lines] + + assert missing_lines == [] diff --git a/tests/test_utils.py b/tests/test_utils.py index 4191193..39fc086 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,5 @@ import pytest +from papertrail import example from src.danom import Ok, compose, identity, invert from src.danom._utils import all_of, any_of, none_of @@ -65,7 +66,7 @@ def test_none_of(inp_args, fns, expected_result): ], ) def test_identity(x): - assert identity(x) == x + assert example(identity, x) == x @pytest.mark.parametrize( diff --git a/uv.lock b/uv.lock index 66172c2..1f1f27b 100644 --- a/uv.lock +++ b/uv.lock @@ -47,6 +47,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] +[[package]] +name = "black" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, + { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, + { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, + { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, +] + [[package]] name = "certifi" version = "2025.11.12" @@ -179,6 +211,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -273,7 +317,7 @@ wheels = [ [[package]] name = "danom" -version = "0.10.0" +version = "0.10.1" source = { editable = "." } dependencies = [ { name = "attrs" }, @@ -283,6 +327,7 @@ dependencies = [ dev = [ { name = "hypothesis" }, { name = "ipykernel" }, + { name = "papertrail" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -301,6 +346,7 @@ requires-dist = [{ name = "attrs", specifier = ">=25.4.0" }] dev = [ { name = "hypothesis", specifier = ">=6.148.2" }, { name = "ipykernel", specifier = ">=7.1.0" }, + { name = "papertrail", specifier = ">=0.1.2" }, { name = "pre-commit", specifier = ">=4.5.0" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, @@ -426,6 +472,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "io-adapters" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ac/43351192a306050a3c4cf7f96e5b95cfa16eac444b5cd6c1ec6d892fc5eb/io_adapters-0.2.2.tar.gz", hash = "sha256:e9507037cb1c3e96e5b56fa055c3c36bbfb4ccc226ca848236d17441c0f36c7e", size = 7379, upload-time = "2026-02-03T13:12:12.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/57/f07b8136df25c1d17e1b47333fbd45a0c14c6a263ce7b2d459e5cae0014e/io_adapters-0.2.2-py3-none-any.whl", hash = "sha256:f620ccc4a6e4cfd4f0b27884f75c02f4ea6cedacb2e056c1ed8856dcdcc3c756", size = 10421, upload-time = "2026-02-03T13:12:11.349Z" }, +] + [[package]] name = "ipykernel" version = "7.1.0" @@ -632,6 +690,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -659,6 +726,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "papertrail" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "black" }, + { name = "io-adapters" }, + { name = "pytest" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/45/5299ca269c96ae46c58a4e51f1d4a44941eabd5fbede9d56c06637fb312f/papertrail-0.1.2.tar.gz", hash = "sha256:f4497fd35f8fb102612112dc8231f0bab9a6d8244ac2ab14650e905dabed2ddd", size = 6781, upload-time = "2026-02-12T20:30:42.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/c5/fc531f78c9bc5e8cc29fe62ad1b6a7f454a2e32ecf6842b4cb6fea0fcaad/papertrail-0.1.2-py3-none-any.whl", hash = "sha256:d45260e555c641525248d6ed7e84a6d37a58b61e1e29bda6d538729ca9f0cbd3", size = 11538, upload-time = "2026-02-12T20:30:41.332Z" }, +] + [[package]] name = "parso" version = "0.8.5" @@ -668,6 +751,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, ] +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -790,7 +882,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.1" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -799,9 +891,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] @@ -865,6 +957,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3"