diff --git a/README.md b/README.md index 32860a6..cf864ef 100644 --- a/README.md +++ b/README.md @@ -1 +1,251 @@ # wird + +`wird` is a library that provides basic monads in python. Core idea is to provide +mechanics for writing purely python pipeline-styled code. + +> **Why wird?** Wird is a misspelling of Anglo-Saxon / Old North word "wyrd". It means +> fate, but not totally predefined, more like a consequence of previous deeds. + +## Pipelines + +Before getting into `wird` API it's worth explaining concept of pipeline-styled code. +Mainly our code is imperative - we describe what we do to achieve some result in steps, +one by one. It's not worth to reject imperative code in favor of declarative one (where +we describe the result instead of steps for getting it), as most languages are generally +imperative and it's more convenient to provide better ways to write it. + +Different languages provide pipelines in different forms. For example in C# or Java it +is provided with so called Fluent API (sometimes method chaining). Example: + +```csharp +int[] numbers = [ 5, 10, 8, 3, 6, 12 ]; + +IEnumerable evenNumbersSorted = numbers + .Where(num => num % 2 == 0) + .OrderBy(num => num); +``` + +There we write some class that allows us to chain method execution in order to perform +some action. This is quite nice approach, however it's not really extensible and does +not suit to most of the business cases where we want to separate bits of logic into +different entities. + +Mostly this kind of syntax is used for builder pattern: + +```csharp +var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + +host.Run(); +``` + +In functional languages you can find so called "pipe operator" - `|>`. Let's take a look +at simple case - we want to put to square some number, that convert that to string and +reverse it. In F# you might write that like: + +```fsharp +let result = rev (toStr (square 512)) +``` + +Problem of this piece of code is that despite or algorithm is simple and direct, when we +write code it steps are written in reverse order and we need to "unwrap" function calls. + +With pipe operator same code becomes much more elegant: + +```fsharp +let result = 512 + |> square + |> toStr + |> rev +``` + +All actions are written one-by-one in the same order as they executed. This is much more +readable code. + +Basically `wird` is written to provide this mechanic to python language in some +opinionated form inspired by Rust language. + +## Monads + +### `Value` + +Container for sync value that provides pipe-styled execution of arbitrary functions. +Let's look at the example: + +```python +import operator + +from wird import Value + + +res = ( + Value(3) + .map(operator.add, 1) # 3 + 1 -> 4 + .map(operator.mul, 3) # 4 * 3 -> 12 + .map(operator.truediv, 2) # 12 / 2 -> 6 + .inspect(print) # print 6.0 & pass next + .unwrap(as_type=int) # extract 6.0 from container +) +``` + +`Value` is a simple wrapper around passed value with special methods (`map` / +`map_async` / `inspect` / `inspect_async`) that bind passed function to container value +(read as invoke / apply). Thus it is basically is a simplest monad. + +`Value` provides the following interface: + +- `Value.unwrap` - method for extracting internally stored value with optional type + casting (only for type checker, not actual casting happens) +- `Value.map` - binding method for sync functions +- `Value.map_async` - binding method for async functions +- `Value.inspect` - binding method for sync side-effect functions +- `Value.inspect_async` - binding method for async side-effect functions + +Main different between `map` and `inspect` is that `map` wraps the result of the +executed function into `Value` container and `inspect` just invokes function passing +stored value next. If stored value is mutable, `inspect` can be used to modify it via +side effect. + +### `Future` + +Container for async values. It is similar to `Value` and provides nearly the same +interface. When we invoke any of async methods in `Value` we actually return `Future` +container, as now stored value is computed asynchronously and requires `await`. + +```python +import operator + +from wird import Value + +async def mul_async(x: int, y: int) -> int: + return x * y + +async def truediv_async(x: int, y: int) -> float: + return x / y + +async def main(): + res = await ( + Value(3) + .map(operator.add, 1) # 3 + 1 -> 4 (Value) + .map_async(mul_async, 3) # 4 * 3 -> 12 (Future) + .map_async(truediv_async, 2) # 12 / 2 -> 6.0 (Future) + .inspect(print) # print 6.0 & pass next (Future) + .unwrap() # extract awaitable 6.0 from container + ) + +if __name__ == "__main__": + import asyncio + asyncio.run(main()) +``` + +`Future` provides the following interface: + +- `Future.unwrap` - extract internally stored awaitable value +- `Future.map` - binding method for sync functions +- `Future.map_async` - binding method for async functions +- `Future.inspect` - binding method for sync side-effect functions +- `Future.inspect_async` - binding method for async side-effect functions +- `Future.from_` - static method for creating awaitable object from sync value + +Also `Future` is awaitable by itself, so one can just await `Future` itself instead of +calling `Future.unwrap`, but to stay uniform it is recommended to use `Future.unwrap`. + +### `Maybe` + +Despite `Value` and `Future`, `Maybe` is not a single container, but rather a pair of +containers - `Some` and `Empty`. Each resembles additional property of data - its +presence. + +`Some` indicates that data is present allowing it to be processed. `Empty` on the other +hand marks that there is not data and we can't perform execution ignoring that. +Basically it hides explicit `is None` checks, taking it as internal rule of function +mapping. + +Possible relevant case of usage is database patch / update operations, when we +intentionally want to provide some abstract interface that allows optional column +update. For example we store in SQL database following data structure: + +```python +from dataclasses import dataclass +from datetime import date + + +@dataclass +class Customer: + uid: int + first_name: str + second_name: str + birthdate: date | None = None +``` + +We provide HTTP route to update this entity in DB. If we've provided a field in request +body, then this field must be updated. Commonly one will make each field in DTO (except +for ID) optional with default `None` value, but what to do with `birthdate`? When +parsing we will propagate default `None` so we do not know if this `None` was passed +explicitly or we've implicitly set it via DTO default. + +`Maybe` allows to explicitly separate this cases, allowing us to have `None` as present value: + +```python +from dataclasses import dataclass +from datetime import date + +from wird import Empty, Maybe + + +@dataclass +class CustomerUpdate: + uid: int + first_name: Maybe[str] = Empty() + second_name: Maybe[str] = Empty() + birthdate: Maybe[date | None] = Empty() +``` + +Thus when `birthdate` is `Empty` we know that we do not have to update this column at +all, and when it is `Some` we can safely set `None` as desired value. + +`Maybe` provides the following interface: + +- `Maybe.unwrap` - extract internally stored value on `Some`, raise `EmptyUnwrapError` + on `Empty` +- `Maybe.unwrap_or` - extract internally stored value on `Some` or return passed + replacement value on `Empty` +- `Maybe.unwrap_or_none` - extract internally stored value on `Some` or return `None` + on `Empty` +- `Maybe.unwrap_or_else` - extract internally stored value on `Some` or return result of + execution of factory function for replacement value on `Empty` +- `Maybe.unwrap_or_else_async` - same as `Maybe.unwrap_or_else`, but for async factory + function +- `Maybe.map` - binding method for sync functions, applies only on `Some` +- `Maybe.map_async` - same as `Maybe.map`, but for async functions +- `Maybe.inspect` - binding method for sync side-effect functions, applies only on + `Some` +- `Maybe.inspect_async` - same as `Maybe.inspect`, but for async functions +- `Maybe.and_` - logical AND for 2 `Maybe` values, replaces self `Maybe` with passed + `Maybe` if first one is `Some` +- `Maybe.and_then` - same as `Maybe.map`, but for sync functions that return `Maybe` +- `Maybe.and_then_async` - same as `Maybe.and_then`, but for async functions +- `Maybe.or_` - logical OR for 2 `Maybe` values, replaces self `Maybe` with passed + `Maybe` if first one is `Empty` +- `Maybe.or_else` - replaces `Empty` with `Maybe` result of passed sync function +- `Maybe.or_else_async` - same as `Maybe.or_else`, but for async functions +- `Maybe.is_some` - `True` on `Some` container +- `Maybe.is_some_and` - `True` on `Some` container and passed predicate being `True` +- `Maybe.is_some_and_async` - same as `Maybe.is_some_and`, but for async predicates +- `Maybe.is_empty` - `True` on `Empty` container +- `Maybe.is_empty_or` - `True` on `Empty` container or passed predicate being `True` +- `Maybe.is_empty_or_async` - same as `Maybe.is_empty_or`, but for async predicates +- `Maybe.filter` - if predicate is `False` replaces `Maybe` with `Empty` +- `Maybe.filter_async` - same as `Maybe.filter`, but for async predicates + +In order to provide seamless experience, instead of making developer to work with +`Future[Maybe[T]]` we provide `FutureMaybe` container that provides exactly the same +interface as sync `Maybe`. Worth noting that `FutureMaybe` is awaitable, like `Future`, +and returns internally stored `Maybe` instance. + +Also in some cases one might need point-free versions of `Maybe` interface methods, so +one can access them via `maybe` module. For `FutureMaybe` point-free functions one can +use `future_maybe` module. diff --git a/pyproject.toml b/pyproject.toml index 909885e..4035bab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "module" [tool.coverage.run] -omit = ["tests/*"] +omit = ["tests/*", "wird/maybe.py", "wird/future_maybe.py"] branch = true [tool.coverage.report] @@ -56,6 +56,7 @@ target-version = "py313" [tool.ruff.lint] fixable = ["ALL"] +extend-select = ["RUF022"] [tool.ruff.format] quote-style = "double" diff --git a/tests/test_maybe.py b/tests/test_maybe.py new file mode 100644 index 0000000..e48038d --- /dev/null +++ b/tests/test_maybe.py @@ -0,0 +1,246 @@ +import operator + +import pytest + +from wird import Empty, Future, Maybe, Some +from wird._maybe import EmptyUnwrapError, FutureMaybe + + +async def test_some_unwraps() -> None: + m = Some(1).as_maybe() + + assert m.unwrap() == 1 + assert m.unwrap_or(2) == 1 + assert m.unwrap_or_none() == 1 + assert m.unwrap_or_else(lambda: 2) == 1 + assert await m.unwrap_or_else_async(lambda: Future.from_(2)) == 1 + + assert await FutureMaybe.from_(m).unwrap() == 1 + assert await FutureMaybe.from_(m).unwrap_or(2) == 1 + assert await FutureMaybe.from_(m).unwrap_or_none() == 1 + assert await FutureMaybe.from_(m).unwrap_or_else(lambda: 2) == 1 + assert await FutureMaybe.from_(m).unwrap_or_else_async(lambda: Future.from_(2)) == 1 + + +async def test_empty_unwraps() -> None: + m = Empty().as_maybe(int) + + with pytest.raises(EmptyUnwrapError, match="expected Some, got Empty"): + m.unwrap() + + with pytest.raises(EmptyUnwrapError, match="on empty"): + m.unwrap(on_empty="on empty") + + assert m.unwrap_or(2) == 2 + assert m.unwrap_or_none() is None + assert m.unwrap_or_else(lambda: 2) == 2 + assert await m.unwrap_or_else_async(lambda: Future.from_(2)) == 2 + + with pytest.raises(EmptyUnwrapError, match="expected Some, got Empty"): + await FutureMaybe.from_(m).unwrap() + + with pytest.raises(EmptyUnwrapError, match="on empty"): + await FutureMaybe.from_(m).unwrap(on_empty="on empty") + + assert await FutureMaybe.from_(m).unwrap_or(2) == 2 + assert await FutureMaybe.from_(m).unwrap_or_none() is None + assert await FutureMaybe.from_(m).unwrap_or_else(lambda: 2) == 2 + assert await FutureMaybe.from_(m).unwrap_or_else_async(lambda: Future.from_(2)) == 2 + + +@pytest.mark.parametrize( + ("maybe", "target"), + [ + (Some(1), Some(5)), + (Empty(), Empty()), + ], +) +async def test_maybe_map(maybe: Maybe[int], target: Maybe[int]) -> None: + assert ( + await maybe.map(operator.add, 1) + .map_async(lambda x: Future.from_(x + 1)) + .map(operator.add, 1) + .map_async(lambda x: Future.from_(x + 1)) + == target + ) + + +@pytest.mark.parametrize( + ("maybe", "target"), + [ + (Some({}), Some({"a": 1, "b": 2, "c": 3, "d": 4})), + (Empty(), Empty()), + ], +) +async def test_maybe_inspect(maybe: Maybe[dict], target: Maybe[dict]) -> None: + async def set_item(data: dict, key: str, value: int) -> None: + data[key] = value + + assert await ( + maybe.inspect(operator.setitem, "a", 1) + .inspect_async(set_item, "b", 2) + .inspect(operator.setitem, "c", 3) + .inspect_async(set_item, "d", 4) + ) + + +async def test_some_predicates() -> None: + m = Some(1).as_maybe() + + assert m.is_some() + assert not m.is_empty() + assert m.is_some_and(lambda x: x % 2 == 1) + assert m.is_empty_or(lambda x: x % 2 == 1) + assert await m.is_some_and_async(lambda x: Future.from_(x % 2 == 1)) + assert await m.is_empty_or_async(lambda x: Future.from_(x % 2 == 1)) + + assert await FutureMaybe.from_(m).is_some() + assert not await FutureMaybe.from_(m).is_empty() + assert await FutureMaybe.from_(m).is_some_and(lambda x: x % 2 == 1) + assert await FutureMaybe.from_(m).is_empty_or(lambda x: x % 2 == 1) + assert await FutureMaybe.from_(m).is_some_and_async( + lambda x: Future.from_(x % 2 == 1) + ) + assert await FutureMaybe.from_(m).is_empty_or_async( + lambda x: Future.from_(x % 2 == 1) + ) + + +async def test_empty_predicates() -> None: + m = Empty().as_maybe(int) + + assert not m.is_some() + assert m.is_empty() + assert not m.is_some_and(lambda x: x % 2 == 1) + assert m.is_empty_or(lambda x: x % 2 == 1) + assert not await m.is_some_and_async(lambda x: Future.from_(x % 2 == 1)) + assert await m.is_empty_or_async(lambda x: Future.from_(x % 2 == 1)) + + assert not await FutureMaybe.from_(m).is_some() + assert await FutureMaybe.from_(m).is_empty() + assert not await FutureMaybe.from_(m).is_some_and(lambda x: x % 2 == 1) + assert await FutureMaybe.from_(m).is_empty_or(lambda x: x % 2 == 1) + assert not await FutureMaybe.from_(m).is_some_and_async( + lambda x: Future.from_(x % 2 == 1) + ) + assert await FutureMaybe.from_(m).is_empty_or_async( + lambda x: Future.from_(x % 2 == 1) + ) + + +async def test_some_and() -> None: + m = Some(1).as_maybe() + + assert m.and_(Some(2)) == Some(2) + assert m.and_(Empty()) == Empty() + assert m.and_then(lambda _: Some(2)) == Some(2) + assert m.and_then(lambda _: Empty()) == Empty() + assert await m.and_then_async(lambda _: Future.from_(Some(2))) == Some(2) + assert await m.and_then_async(lambda _: Future.from_(Empty())) == Empty() + + assert await FutureMaybe.from_(m).and_(Some(2)) == Some(2) + assert await FutureMaybe.from_(m).and_(Empty()) == Empty() + assert await FutureMaybe.from_(m).and_then(lambda _: Some(2)) == Some(2) + assert await FutureMaybe.from_(m).and_then(lambda _: Empty()) == Empty() + assert await FutureMaybe.from_(m).and_then_async( + lambda _: Future.from_(Some(2)) + ) == Some(2) + assert ( + await FutureMaybe.from_(m).and_then_async(lambda _: Future.from_(Empty())) + == Empty() + ) + + +async def test_empty_and() -> None: + m = Empty().as_maybe(int) + + assert m.and_(Some(2)) == Empty() + assert m.and_(Empty()) == Empty() + assert m.and_then(lambda _: Some(2)) == Empty() + assert m.and_then(lambda _: Empty()) == Empty() + assert await m.and_then_async(lambda _: Future.from_(Some(2))) == Empty() + assert await m.and_then_async(lambda _: Future.from_(Empty())) == Empty() + + assert await FutureMaybe.from_(m).and_(Some(2)) == Empty() + assert await FutureMaybe.from_(m).and_(Empty()) == Empty() + assert await FutureMaybe.from_(m).and_then(lambda _: Some(2)) == Empty() + assert await FutureMaybe.from_(m).and_then(lambda _: Empty()) == Empty() + assert ( + await FutureMaybe.from_(m).and_then_async(lambda _: Future.from_(Some(2))) + == Empty() + ) + assert ( + await FutureMaybe.from_(m).and_then_async(lambda _: Future.from_(Empty())) + == Empty() + ) + + +async def test_some_or() -> None: + m = Some(1).as_maybe() + + assert m.or_(Some(2)) == Some(1) + assert m.or_(Empty()) == Some(1) + assert m.or_else(lambda: Some(2)) == Some(1) + assert m.or_else(lambda: Empty()) == Some(1) + assert await m.or_else_async(lambda: Future.from_(Some(2))) == Some(1) + assert await m.or_else_async(lambda: Future.from_(Empty())) == Some(1) + + assert await FutureMaybe.from_(m).or_(Some(2)) == Some(1) + assert await FutureMaybe.from_(m).or_(Empty()) == Some(1) + assert await FutureMaybe.from_(m).or_else(lambda: Some(2)) == Some(1) + assert await FutureMaybe.from_(m).or_else(lambda: Empty()) == Some(1) + assert await FutureMaybe.from_(m).or_else_async( + lambda: Future.from_(Some(2)) + ) == Some(1) + assert await FutureMaybe.from_(m).or_else_async( + lambda: Future.from_(Empty()) + ) == Some(1) + + +async def test_empty_or() -> None: + m = Empty().as_maybe(int) + + assert m.or_(Some(2)) == Some(2) + assert m.or_(Empty()) == Empty() + assert m.or_else(lambda: Some(2)) == Some(2) + assert m.or_else(lambda: Empty()) == Empty() + assert await m.or_else_async(lambda: Future.from_(Some(2))) == Some(2) + assert await m.or_else_async(lambda: Future.from_(Empty())) == Empty() + + assert await FutureMaybe.from_(m).or_(Some(2)) == Some(2) + assert await FutureMaybe.from_(m).or_(Empty()) == Empty() + assert await FutureMaybe.from_(m).or_else(lambda: Some(2)) == Some(2) + assert await FutureMaybe.from_(m).or_else(lambda: Empty()) == Empty() + assert await FutureMaybe.from_(m).or_else_async( + lambda: Future.from_(Some(2)) + ) == Some(2) + assert ( + await FutureMaybe.from_(m).or_else_async(lambda: Future.from_(Empty())) + == Empty() + ) + + +@pytest.mark.parametrize( + ("maybe", "target"), + [ + (Some(1), Some(1)), + (Some(2), Empty()), + (Empty(), Empty()), + ], +) +async def test_filter(maybe: Maybe[int], target: Maybe[int]) -> None: + assert maybe.filter(lambda x: x % 2 == 1) == target + assert await maybe.filter_async(lambda x: Future.from_(x % 2 == 1)) == target + + assert await FutureMaybe.from_(maybe).filter(lambda x: x % 2 == 1) == target + assert ( + await FutureMaybe.from_(maybe).filter_async(lambda x: Future.from_(x % 2 == 1)) + == target + ) + + +def test_some_ctors() -> None: + assert Some.from_optional(1) == Some(1) + assert Some.from_optional(None) == Empty() + assert Some.from_if(1, lambda x: x % 2 == 1) == Some(1) + assert Some.from_if(2, lambda x: x % 2 == 1) == Empty() diff --git a/uv.lock b/uv.lock index 6b398a5..a62dc06 100644 --- a/uv.lock +++ b/uv.lock @@ -13,63 +13,71 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.2" +version = "7.13.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, - { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, - { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, - { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, - { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, - { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, - { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, - { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, - { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, - { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, - { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, - { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, - { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, - { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, - { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, - { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, - { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, - { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, - { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, - { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, - { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, - { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, - { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, - { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] @@ -83,43 +91,49 @@ wheels = [ [[package]] name = "librt" -version = "0.7.8" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, - { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, - { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, - { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, - { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, - { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, - { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, - { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, - { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, - { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, - { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, - { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, - { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, - { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, - { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, - { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, - { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, - { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, ] [[package]] @@ -238,52 +252,51 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] name = "ty" -version = "0.0.14" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/57/22c3d6bf95c2229120c49ffc2f0da8d9e8823755a1c3194da56e51f1cc31/ty-0.0.14.tar.gz", hash = "sha256:a691010565f59dd7f15cf324cdcd1d9065e010c77a04f887e1ea070ba34a7de2", size = 5036573, upload-time = "2026-01-27T00:57:31.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/95/8de69bb98417227b01f1b1d743c819d6456c9fd140255b6124b05b17dfd6/ty-0.0.20.tar.gz", hash = "sha256:ebba6be7974c14efbb2a9adda6ac59848f880d7259f089dfa72a093039f1dcc6", size = 5262529, upload-time = "2026-03-02T15:51:36.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/cb/cc6d1d8de59beb17a41f9a614585f884ec2d95450306c173b3b7cc090d2e/ty-0.0.14-py3-none-linux_armv6l.whl", hash = "sha256:32cf2a7596e693094621d3ae568d7ee16707dce28c34d1762947874060fdddaa", size = 10034228, upload-time = "2026-01-27T00:57:53.133Z" }, - { url = "https://files.pythonhosted.org/packages/f3/96/dd42816a2075a8f31542296ae687483a8d047f86a6538dfba573223eaf9a/ty-0.0.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f971bf9805f49ce8c0968ad53e29624d80b970b9eb597b7cbaba25d8a18ce9a2", size = 9939162, upload-time = "2026-01-27T00:57:43.857Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b4/73c4859004e0f0a9eead9ecb67021438b2e8e5fdd8d03e7f5aca77623992/ty-0.0.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:45448b9e4806423523268bc15e9208c4f3f2ead7c344f615549d2e2354d6e924", size = 9418661, upload-time = "2026-01-27T00:58:03.411Z" }, - { url = "https://files.pythonhosted.org/packages/58/35/839c4551b94613db4afa20ee555dd4f33bfa7352d5da74c5fa416ffa0fd2/ty-0.0.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94a9b747ff40114085206bdb3205a631ef19a4d3fb89e302a88754cbbae54c", size = 9837872, upload-time = "2026-01-27T00:57:23.718Z" }, - { url = "https://files.pythonhosted.org/packages/41/2b/bbecf7e2faa20c04bebd35fc478668953ca50ee5847ce23e08acf20ea119/ty-0.0.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6756715a3c33182e9ab8ffca2bb314d3c99b9c410b171736e145773ee0ae41c3", size = 9848819, upload-time = "2026-01-27T00:57:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/be/60/3c0ba0f19c0f647ad9d2b5b5ac68c0f0b4dc899001bd53b3a7537fb247a2/ty-0.0.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89d0038a2f698ba8b6fec5cf216a4e44e2f95e4a5095a8c0f57fe549f87087c2", size = 10324371, upload-time = "2026-01-27T00:57:29.291Z" }, - { url = "https://files.pythonhosted.org/packages/24/32/99d0a0b37d0397b0a989ffc2682493286aa3bc252b24004a6714368c2c3d/ty-0.0.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c64a83a2d669b77f50a4957039ca1450626fb474619f18f6f8a3eb885bf7544", size = 10865898, upload-time = "2026-01-27T00:57:33.542Z" }, - { url = "https://files.pythonhosted.org/packages/1a/88/30b583a9e0311bb474269cfa91db53350557ebec09002bfc3fb3fc364e8c/ty-0.0.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:242488bfb547ef080199f6fd81369ab9cb638a778bb161511d091ffd49c12129", size = 10555777, upload-time = "2026-01-27T00:58:05.853Z" }, - { url = "https://files.pythonhosted.org/packages/cd/a2/cb53fb6325dcf3d40f2b1d0457a25d55bfbae633c8e337bde8ec01a190eb/ty-0.0.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4790c3866f6c83a4f424fc7d09ebdb225c1f1131647ba8bdc6fcdc28f09ed0ff", size = 10412913, upload-time = "2026-01-27T00:57:38.834Z" }, - { url = "https://files.pythonhosted.org/packages/42/8f/f2f5202d725ed1e6a4e5ffaa32b190a1fe70c0b1a2503d38515da4130b4c/ty-0.0.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:950f320437f96d4ea9a2332bbfb5b68f1c1acd269ebfa4c09b6970cc1565bd9d", size = 9837608, upload-time = "2026-01-27T00:57:55.898Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/59a2a0521640c489dafa2c546ae1f8465f92956fede18660653cce73b4c5/ty-0.0.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a0ec3ee70d83887f86925bbc1c56f4628bd58a0f47f6f32ddfe04e1f05466df", size = 9884324, upload-time = "2026-01-27T00:57:46.786Z" }, - { url = "https://files.pythonhosted.org/packages/03/95/8d2a49880f47b638743212f011088552ecc454dd7a665ddcbdabea25772a/ty-0.0.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1a4e6b6da0c58b34415955279eff754d6206b35af56a18bb70eb519d8d139ef", size = 10033537, upload-time = "2026-01-27T00:58:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/e9/40/4523b36f2ce69f92ccf783855a9e0ebbbd0f0bb5cdce6211ee1737159ed3/ty-0.0.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dc04384e874c5de4c5d743369c277c8aa73d1edea3c7fc646b2064b637db4db3", size = 10495910, upload-time = "2026-01-27T00:57:26.691Z" }, - { url = "https://files.pythonhosted.org/packages/08/d5/655beb51224d1bfd4f9ddc0bb209659bfe71ff141bcf05c418ab670698f0/ty-0.0.14-py3-none-win32.whl", hash = "sha256:b20e22cf54c66b3e37e87377635da412d9a552c9bf4ad9fc449fed8b2e19dad2", size = 9507626, upload-time = "2026-01-27T00:57:41.43Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d9/c569c9961760e20e0a4bc008eeb1415754564304fd53997a371b7cf3f864/ty-0.0.14-py3-none-win_amd64.whl", hash = "sha256:e312ff9475522d1a33186657fe74d1ec98e4a13e016d66f5758a452c90ff6409", size = 10437980, upload-time = "2026-01-27T00:57:36.422Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/186829654f5bfd9a028f6648e9caeb11271960a61de97484627d24443f91/ty-0.0.14-py3-none-win_arm64.whl", hash = "sha256:b6facdbe9b740cb2c15293a1d178e22ffc600653646452632541d01c36d5e378", size = 9885831, upload-time = "2026-01-27T00:57:49.747Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2c/718abe48393e521bf852cd6b0f984766869b09c258d6e38a118768a91731/ty-0.0.20-py3-none-linux_armv6l.whl", hash = "sha256:7cc12769c169c9709a829c2248ee2826b7aae82e92caeac813d856f07c021eae", size = 10333656, upload-time = "2026-03-02T15:51:56.461Z" }, + { url = "https://files.pythonhosted.org/packages/41/0e/eb1c4cc4a12862e2327b72657bcebb10b7d9f17046f1bdcd6457a0211615/ty-0.0.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b777c1bf13bc0a95985ebb8a324b8668a4a9b2e514dde5ccf09e4d55d2ff232", size = 10168505, upload-time = "2026-03-02T15:51:51.895Z" }, + { url = "https://files.pythonhosted.org/packages/89/7f/10230798e673f0dd3094dfd16e43bfd90e9494e7af6e8e7db516fb431ddf/ty-0.0.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b2a4a7db48bf8cba30365001bc2cad7fd13c1a5aacdd704cc4b7925de8ca5eb3", size = 9678510, upload-time = "2026-03-02T15:51:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/59d9159577494edd1728f7db77b51bb07884bd21384f517963114e3ab5f6/ty-0.0.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6846427b8b353a43483e9c19936dc6a25612573b44c8f7d983dfa317e7f00d4c", size = 10162926, upload-time = "2026-03-02T15:51:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a8/b7273eec3e802f78eb913fbe0ce0c16ef263723173e06a5776a8359b2c66/ty-0.0.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245ceef5bd88df366869385cf96411cb14696334f8daa75597cf7e41c3012eb8", size = 10171702, upload-time = "2026-03-02T15:51:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/5f1144f2f04a275109db06e3498450c4721554215b80ae73652ef412eeab/ty-0.0.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4d21d1cdf67a444d3c37583c17291ddba9382a9871021f3f5d5735e09e85efe", size = 10682552, upload-time = "2026-03-02T15:51:33.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/db/9f1f637310792f12bd6ed37d5fc8ab39ba1a9b0c6c55a33865e9f1cad840/ty-0.0.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd4ffd907d1bd70e46af9e9a2f88622f215e1bf44658ea43b32c2c0b357299e4", size = 11242605, upload-time = "2026-03-02T15:51:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/1a/68/cc9cae2e732fcfd20ccdffc508407905a023fc8493b8771c392d915528dc/ty-0.0.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6594b58d8b0e9d16a22b3045fc1305db4b132c8d70c17784ab8c7a7cc986807", size = 10974655, upload-time = "2026-03-02T15:51:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/b9e3e3f28fe63486331e653f6aeb4184af8b1fe80542fcf74d2dda40a93d/ty-0.0.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3662f890518ce6cf4d7568f57d03906912d2afbf948a01089a28e325b1ef198c", size = 10761325, upload-time = "2026-03-02T15:51:26.818Z" }, + { url = "https://files.pythonhosted.org/packages/39/9e/67db935bdedf219a00fb69ec5437ba24dab66e0f2e706dd54a4eca234b84/ty-0.0.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e3ffbae58f9f0d17cdc4ac6d175ceae560b7ed7d54f9ddfb1c9f31054bcdc2c", size = 10145793, upload-time = "2026-03-02T15:51:38.562Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/b0eb815d4dc5a819c7e4faddc2a79058611169f7eef07ccc006531ce228c/ty-0.0.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:176e52bc8bb00b0e84efd34583962878a447a3a0e34ecc45fd7097a37554261b", size = 10189640, upload-time = "2026-03-02T15:51:50.202Z" }, + { url = "https://files.pythonhosted.org/packages/b8/71/63734923965cbb70df1da3e93e4b8875434e326b89e9f850611122f279bf/ty-0.0.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2bc73025418e976ca4143dde71fb9025a90754a08ac03e6aa9b80d4bed1294b", size = 10370568, upload-time = "2026-03-02T15:51:42.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/a532c2048533347dff48e9ca98bd86d2c224356e101688a8edaf8d6973fb/ty-0.0.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52f7c9ec6e363e094b3c389c344d5a140401f14a77f0625e3f28c21918552f5", size = 10853999, upload-time = "2026-03-02T15:51:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/48/88/36c652c658fe96658043e4abc8ea97801de6fb6e63ab50aaa82807bff1d8/ty-0.0.20-py3-none-win32.whl", hash = "sha256:c7d32bfe93f8fcaa52b6eef3f1b930fd7da410c2c94e96f7412c30cfbabf1d17", size = 9744206, upload-time = "2026-03-02T15:51:54.183Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/a4a13bed1d7fd9d97aaa3c5bb5e6d3e9a689e6984806cbca2ab4c9233cac/ty-0.0.20-py3-none-win_amd64.whl", hash = "sha256:a5e10f40fc4a0a1cbcb740a4aad5c7ce35d79f030836ea3183b7a28f43170248", size = 10711999, upload-time = "2026-03-02T15:51:29.212Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/6bfd748a9f4ff9267ed3329b86a0f02cdf6ab49f87bc36c8a164852f99fc/ty-0.0.20-py3-none-win_arm64.whl", hash = "sha256:53f7a5c12c960e71f160b734f328eff9a35d578af4b67a36b0bb5990ac5cdc27", size = 10150143, upload-time = "2026-03-02T15:51:31.283Z" }, ] [[package]] diff --git a/wird/__init__.py b/wird/__init__.py index eb82ec8..5b3fd88 100644 --- a/wird/__init__.py +++ b/wird/__init__.py @@ -1,7 +1,16 @@ +from . import maybe, future_maybe # noqa from ._future import Future +from ._maybe import Empty, EmptyUnwrapError, FutureMaybe, Maybe, Some from ._value import Value __all__ = ( + "Empty", + "EmptyUnwrapError", "Future", + "FutureMaybe", + "Maybe", + "Some", "Value", + "future_maybe", + "maybe", ) diff --git a/wird/_future.py b/wird/_future.py index f0d3363..4989f3f 100644 --- a/wird/_future.py +++ b/wird/_future.py @@ -20,16 +20,25 @@ class Future[T]: @staticmethod def from_[V](value: V) -> Future[V]: + """Creates Future from sync value.""" + async def _identity() -> V: return value return Future(_identity()) @overload - def unwrap(self) -> Awaitable[T]: ... + def unwrap(self) -> Awaitable[T]: + """Returns the container value.""" + ... @overload - def unwrap[R](self, *, as_type: Type[R]) -> Awaitable[R]: ... + def unwrap[R](self, *, as_type: Type[R]) -> Awaitable[R]: + """Returns the contained value casted to passed type. + + No actual casting is performed, type change affects only type checkers. + """ + ... def unwrap(self, **_) -> Awaitable[Any]: return self.internal @@ -40,6 +49,7 @@ def map[**P, R]( *args: P.args, **kwargs: P.kwargs, ) -> Future[R]: + """Maps a Future[T] to Future[R] by applying a function to a contained value.""" return Future(_map(self.internal, fn, *args, **kwargs)) def inspect[**P]( @@ -48,6 +58,10 @@ def inspect[**P]( *args: P.args, **kwargs: P.kwargs, ) -> Future[T]: + """Calls a function with a reference to the contained value. + + Returns the original Future. + """ return Future(_inspect(self.internal, fn, *args, **kwargs)) def map_async[**P, R]( @@ -56,6 +70,8 @@ def map_async[**P, R]( *args: P.args, **kwargs: P.kwargs, ) -> Future[R]: + """Maps a Future[T] to Future[R] by applying a async function to a contained + value.""" return Future(_map_async(self.internal, fn, *args, **kwargs)) def inspect_async[**P]( @@ -64,6 +80,10 @@ def inspect_async[**P]( *args: P.args, **kwargs: P.kwargs, ) -> Future[T]: + """Calls an async function with a reference to the contained value. + + Returns the original Future. + """ return Future(_inspect_async(self.internal, fn, *args, **kwargs)) def __await__(self) -> Generator[Any, Any, T]: diff --git a/wird/_maybe.py b/wird/_maybe.py new file mode 100644 index 0000000..48339ef --- /dev/null +++ b/wird/_maybe.py @@ -0,0 +1,914 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import ( + Any, + Awaitable, + Callable, + Concatenate, + NoReturn, + Protocol, + Type, + cast, + overload, +) + +from . import _future as f, maybe + +__all__ = ( + "Empty", + "EmptyUnwrapError", + "FutureMaybe", + "Maybe", + "Some", +) + + +class EmptyUnwrapError(ValueError): + """Exception raised on attempt to unwrap Empty container.""" + + +class Maybe[T](Protocol): # pragma: no cover + @overload + def unwrap(self, *, on_empty: str = "expected Some, got Empty") -> T: + """Returns the contained Some value. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + """ + ... + + @overload + def unwrap[R]( + self, + *, + as_type: Type[R], + on_empty: str = "expected Some, got Empty", + ) -> R: + """Returns the contained Some value casted to passed type. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + + No actual casting is performed, type change affects only type checkers. + """ + ... + + def unwrap_or(self, /, default: T) -> T: + """Returns the contained Some value or a provided default. + + Arguments passed to unwrap_or are eagerly evaluated; if you are passing the + result of a function call, it is recommended to use unwrap_or_else, which is + lazily evaluated. + """ + ... + + def unwrap_or_none(self) -> T | None: + """Returns the contained Some value or None.""" + ... + + def unwrap_or_else[**P]( + self, + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: + """Returns the contained Some value or computes it from a sync closure.""" + ... + + def unwrap_or_else_async[**P]( + self, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[T]: + """Returns the contained Some value or computes it from a async closure.""" + ... + + def map[**P, R]( + self, + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + """Maps a Maybe[T] to Maybe[R] by applying a function to a contained value (if + Some) or returns Empty (if Empty).""" + ... + + def inspect[**P]( + self, + fn: Callable[Concatenate[T, P], Any], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + """Calls a function with a reference to the contained value if Some. + + Returns the original Maybe. + """ + ... + + def is_some(self) -> bool: + """Returns true if the Maybe is a Some value.""" + ... + + def is_empty(self) -> bool: + """Returns true if the Maybe is a Empty value.""" + ... + + def is_some_and[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + """Returns true if the Maybe is a Some and the value inside of it matches a + predicate.""" + ... + + def is_empty_or[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + """Returns true if the Maybe is a Empty or the value inside of it matches a + predicate.""" + ... + + def and_[R](self, other: Maybe[R]) -> Maybe[R]: + """Returns Empty if the Maybe is Empty, otherwise returns other. + + Arguments passed to and_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use and_then, which is lazily evaluated. + """ + ... + + def or_(self, other: Maybe[T]) -> Maybe[T]: + """Returns the Maybe if it contains a value, otherwise returns other. + + Arguments passed to or_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use or_else, which is lazily evaluated. + """ + ... + + def and_then[**P, R]( + self, + fn: Callable[Concatenate[T, P], Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls fn with the wrapped + value and returns the result. + + Some languages call this operation flatmap. + """ + ... + + def or_else[**P]( + self, + fn: Callable[P, Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + """Returns the Maybe if it contains a value, otherwise calls fn and returns the + result.""" + ... + + def filter[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + """Returns Empty if the Maybe is Empty, otherwise calls predicate with the + wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + ... + + def map_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Maps a Maybe[T] to FutureMaybe[R] by applying an async function to a + contained value (if Some) or returns Empty (if Empty).""" + ... + + def inspect_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Calls an async function with a reference to the contained value if Some. + + Returns the original Maybe as FutureMaybe. + """ + ... + + def is_some_and_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Some and the value inside of it + matches a async predicate.""" + ... + + def is_empty_or_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Empty or the value inside of it + matches a async predicate.""" + ... + + def and_then_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls an async fn with the + wrapped value and returns the result. + + Some languages call this operation flatmap. + """ + ... + + def or_else_async[**P]( + self, + fn: Callable[P, Awaitable[Maybe[T]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns the Maybe as FutureMaybe if it contains a value, otherwise calls an + async fn and returns the result.""" + ... + + def filter_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns Empty as FutureMaybe if the Maybe is Empty, otherwise calls an async + predicate with the wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + ... + + +@dataclass(slots=True, frozen=True) +class Some[T](Maybe[T]): + internal: T + + @staticmethod + def from_optional[V](value: V | None) -> Maybe[V]: + """Construct a new Maybe from passed value. + + If value is None, returns Empty, otherwise wraps value into Some. + """ + m = Some(value) if value is not None else Empty() + return cast(Maybe[V], m) + + @staticmethod + def from_if[**P, V]( + value: V, + fn: Callable[Concatenate[V, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[V]: + """Constructs a new Maybe from passed value, wrapped into Some if predicate is + True, otherwise returns Empty.""" + if fn(value, *args, **kwargs): + return Some(value) + + return Empty() + + def as_maybe(self) -> Maybe[T]: + return self + + def unwrap(self, **_) -> Any: + return self.internal + + def unwrap_or(self, /, default: T) -> T: + return self.internal + + def unwrap_or_none(self) -> T | None: + return self.internal + + def unwrap_or_else[**P]( + self, + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: + return self.internal + + def unwrap_or_else_async[**P]( + self, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[T]: + return f.Future.from_(self.internal) + + def map[**P, R]( + self, + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + return Some(fn(self.internal, *args, **kwargs)) + + def inspect[**P]( + self, + fn: Callable[Concatenate[T, P], Any], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + fn(self.internal, *args, **kwargs) + return self + + def is_some(self) -> bool: + return True + + def is_empty(self) -> bool: + return False + + def is_some_and[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + return fn(self.internal, *args, **kwargs) + + def is_empty_or[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + return fn(self.internal, *args, **kwargs) + + def and_[R](self, other: Maybe[R]) -> Maybe[R]: + return other + + def or_(self, other: Maybe[T]) -> Maybe[T]: + return self + + def and_then[**P, R]( + self, + fn: Callable[Concatenate[T, P], Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + return fn(self.internal, *args, **kwargs) + + def or_else[**P]( + self, + fn: Callable[P, Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + return self + + def filter[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + return self if fn(self.internal, *args, **kwargs) else Empty() + + def map_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + return FutureMaybe(_some_map_async(self.internal, fn, *args, **kwargs)) + + def inspect_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + return FutureMaybe(_some_inspect_async(self.internal, fn, *args, **kwargs)) + + def is_some_and_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + return f.Future(fn(self.internal, *args, **kwargs)) + + def is_empty_or_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + return f.Future(fn(self.internal, *args, **kwargs)) + + def and_then_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + return FutureMaybe(fn(self.internal, *args, **kwargs)) + + def or_else_async[**P]( + self, + fn: Callable[P, Awaitable[Maybe[T]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + return FutureMaybe.from_(self) + + def filter_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + return FutureMaybe(_some_filter_async(self.internal, fn, *args, **kwargs)) + + +async def _some_map_async[T, **P, R]( + value: T, + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, +) -> Maybe[R]: + return Some(await fn(value, *args, **kwargs)) + + +async def _some_inspect_async[T, **P]( + value: T, + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, +) -> Maybe[T]: + await fn(value, *args, **kwargs) + return Some(value) + + +async def _some_filter_async[T, **P]( + value: T, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> Maybe[T]: + if await fn(value, *args, **kwargs): + return Some(value) + + return Empty() + + +@dataclass(slots=True, frozen=True) +class Empty(Maybe[Any]): + def as_maybe[T](self, /, _: Type[T]) -> Maybe[T]: + return self + + def unwrap(self, *args, **kwargs) -> NoReturn: + raise EmptyUnwrapError(kwargs.get("on_empty", "expected Some, got Empty")) + + def unwrap_or(self, /, default: Any) -> Any: + return default + + def unwrap_or_none(self) -> Any | None: + return None + + def unwrap_or_else[**P, T]( + self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs + ) -> T: + return fn(*args, **kwargs) + + def unwrap_or_else_async[**P, T]( + self, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[T]: + return f.Future(fn(*args, **kwargs)) + + def map[**P, R]( + self, + fn: Callable[Concatenate[Any, P], R], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + return self + + def inspect[**P]( + self, + fn: Callable[Concatenate[Any, P], Any], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[Any]: + return self + + def is_some(self) -> bool: + return False + + def is_empty(self) -> bool: + return True + + def is_some_and[**P, R]( + self, + fn: Callable[Concatenate[Any, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + return False + + def is_empty_or[**P]( + self, + fn: Callable[Concatenate[Any, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> bool: + return True + + def and_[R](self, other: Maybe[R]) -> Maybe[R]: + return self + + def or_[T](self, other: Maybe[T]) -> Maybe[T]: + return other + + def and_then[**P, R]( + self, + fn: Callable[Concatenate[Any, P], Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[R]: + return self + + def or_else[**P, T]( + self, + fn: Callable[P, Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[T]: + return fn(*args, **kwargs) + + def filter[**P]( + self, + fn: Callable[Concatenate[Any, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> Maybe[Any]: + return self + + def map_async[**P, R]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + return FutureMaybe.from_(self) + + def inspect_async[**P]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[Any]: + return FutureMaybe.from_(self) + + def is_some_and_async[**P]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + return f.Future.from_(False) + + def is_empty_or_async[**P]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + return f.Future.from_(True) + + def and_then_async[**P, R]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + return FutureMaybe.from_(self) + + def or_else_async[**P]( + self, + fn: Callable[P, Awaitable[Maybe[Any]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[Any]: + return FutureMaybe(fn(*args, **kwargs)) + + def filter_async[**P]( + self, + fn: Callable[Concatenate[Any, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[Any]: + return FutureMaybe.from_(self) + + +@dataclass(slots=True, frozen=True) +class FutureMaybe[T]: + internal: f.Future[Maybe[T]] + + def __init__(self, internal: Awaitable[Maybe[T]]) -> None: + object.__setattr__( + self, + "internal", + f.Future(internal) if not isinstance(internal, f.Future) else internal, + ) + + @staticmethod + def from_[V](value: Maybe[V]) -> FutureMaybe[V]: + return FutureMaybe(f.Future.from_(value)) + + def __await__(self) -> f.Generator[Any, Any, Maybe[T]]: + return self.internal.__await__() + + @overload + def unwrap(self, *, on_empty: str = "expected Some, got Empty") -> f.Future[T]: + """Returns the contained Some value. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + """ + ... + + @overload + def unwrap[R]( + self, + *, + as_type: Type[R], + on_empty: str = "expected Some, got Empty", + ) -> f.Future[R]: + """Returns the contained Some value casted to passed type. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + + No actual casting is performed, type change affects only type checkers. + """ + ... + + def unwrap(self, **kwargs) -> Any: + return self.internal.map(maybe.unwrap, **kwargs) # type: ignore[arg-type] + + def unwrap_or(self, /, other: T) -> f.Future[T]: + """Returns the contained Some value or a provided default. + + Arguments passed to unwrap_or are eagerly evaluated; if you are passing the + result of a function call, it is recommended to use unwrap_or_else, which is + lazily evaluated. + """ + return self.internal.map(maybe.unwrap_or, other) + + def unwrap_or_none(self) -> f.Future[T | None]: + """Returns the contained Some value or None.""" + return self.internal.map(maybe.unwrap_or_none) + + def unwrap_or_else[**P]( + self, + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[T]: + """Returns the contained Some value or computes it from a sync closure.""" + return self.internal.map(maybe.unwrap_or_else, fn, *args, **kwargs) + + def unwrap_or_else_async[**P]( + self, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[T]: + """Returns the contained Some value or computes it from a async closure.""" + return self.internal.map_async(maybe.unwrap_or_else_async, fn, *args, **kwargs) + + def map[**P, R]( + self, + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Maps a Maybe[T] to Maybe[R] by applying a function to a contained value (if + Some) or returns Empty (if Empty).""" + return FutureMaybe(self.internal.map(maybe.map, fn, *args, **kwargs)) + + def inspect[**P]( + self, + fn: Callable[Concatenate[T, P], Any], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Calls a function with a reference to the contained value if Some. + + Returns the original Maybe. + """ + return FutureMaybe(self.internal.map(maybe.inspect, fn, *args, **kwargs)) + + def is_some(self) -> f.Future[bool]: + """Returns true if the Maybe is a Some value.""" + return self.internal.map(maybe.is_some) + + def is_empty(self) -> f.Future[bool]: + """Returns true if the Maybe is a Empty value.""" + return self.internal.map(maybe.is_empty) + + def is_some_and[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns true if the Maybe is a Some and the value inside of it matches a + predicate.""" + return self.internal.map(maybe.is_some_and, fn, *args, **kwargs) + + def is_empty_or[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns true if the Maybe is a Empty or the value inside of it matches a + predicate.""" + return self.internal.map(maybe.is_empty_or, fn, *args, **kwargs) + + def and_[R](self, other: Maybe[R]) -> FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise returns other. + + Arguments passed to and_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use and_then, which is lazily evaluated. + """ + return FutureMaybe(self.internal.map(maybe.and_, other)) + + def or_(self, other: Maybe[T]) -> FutureMaybe[T]: + """Returns the Maybe if it contains a value, otherwise returns other. + + Arguments passed to or_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use or_else, which is lazily evaluated. + """ + return FutureMaybe(self.internal.map(maybe.or_, other)) + + def and_then[**P, R]( + self, + fn: Callable[Concatenate[T, P], Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls fn with the wrapped + value and returns the result. + + Some languages call this operation flatmap. + """ + return FutureMaybe(self.internal.map(maybe.and_then, fn, *args, **kwargs)) + + def or_else[**P]( + self, + fn: Callable[P, Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns the Maybe if it contains a value, otherwise calls fn and returns the + result.""" + return FutureMaybe(self.internal.map(maybe.or_else, fn, *args, **kwargs)) + + def filter[**P]( + self, + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns Empty if the Maybe is Empty, otherwise calls predicate with the + wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return FutureMaybe(self.internal.map(maybe.filter, fn, *args, **kwargs)) + + def map_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Maps a Maybe[T] to FutureMaybe[R] by applying an async function to a + contained value (if Some) or returns Empty (if Empty).""" + return FutureMaybe( + self.internal.map_async(maybe.map_async, fn, *args, **kwargs) + ) + + def inspect_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Calls an async function with a reference to the contained value if Some. + + Returns the original Maybe as FutureMaybe. + """ + return FutureMaybe( + self.internal.map_async(maybe.inspect_async, fn, *args, **kwargs) + ) + + def is_some_and_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Some and the value inside of it + matches a async predicate.""" + return self.internal.map_async(maybe.is_some_and_async, fn, *args, **kwargs) + + def is_empty_or_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Empty or the value inside of it + matches a async predicate.""" + return self.internal.map_async(maybe.is_empty_or_async, fn, *args, **kwargs) + + def and_then_async[**P, R]( + self, + fn: Callable[Concatenate[T, P], Awaitable[Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls an async fn with the + wrapped value and returns the result. + + Some languages call this operation flatmap. + """ + return FutureMaybe( + self.internal.map_async(maybe.and_then_async, fn, *args, **kwargs) + ) + + def or_else_async[**P]( + self, + fn: Callable[P, Awaitable[Maybe[T]]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns the Maybe as FutureMaybe if it contains a value, otherwise calls an + async fn and returns the result.""" + return FutureMaybe( + self.internal.map_async(maybe.or_else_async, fn, *args, **kwargs) + ) + + def filter_async[**P]( + self, + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, + ) -> FutureMaybe[T]: + """Returns Empty as FutureMaybe if the Maybe is Empty, otherwise calls an async + predicate with the wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return FutureMaybe( + self.internal.map_async(maybe.filter_async, fn, *args, **kwargs) + ) diff --git a/wird/_value.py b/wird/_value.py index fc5c346..18232b7 100644 --- a/wird/_value.py +++ b/wird/_value.py @@ -13,10 +13,17 @@ class Value[T]: internal: T @overload - def unwrap(self) -> T: ... + def unwrap(self) -> T: + """Returns the container value.""" + ... @overload - def unwrap[R](self, *, as_type: Type[R]) -> R: ... + def unwrap[R](self, *, as_type: Type[R]) -> R: + """Returns the contained value casted to passed type. + + No actual casting is performed, type change affects only type checkers. + """ + ... def unwrap(self, **_) -> Any: return self.internal @@ -27,6 +34,7 @@ def map[**P, R]( *args: P.args, **kwargs: P.kwargs, ) -> Value[R]: + """Maps a Value[T] to Value[R] by applying a function to a contained value.""" return Value(fn(self.internal, *args, **kwargs)) def inspect[**P]( @@ -35,6 +43,10 @@ def inspect[**P]( *args: P.args, **kwargs: P.kwargs, ) -> Value[T]: + """Calls a function with a reference to the contained value. + + Returns the original Value. + """ fn(self.internal, *args, **kwargs) return self @@ -44,6 +56,9 @@ def map_async[**P, R]( *args: P.args, **kwargs: P.kwargs, ) -> f.Future[R]: + """Maps a Value[T] to Future[R] by applying a async function to a contained + value.""" + return f.Future(fn(self.internal, *args, **kwargs)) def inspect_async[**P]( @@ -52,6 +67,10 @@ def inspect_async[**P]( *args: P.args, **kwargs: P.kwargs, ) -> f.Future[T]: + """Calls an async function with a reference to the contained value. + + Returns the original value in Future container. + """ return f.Future(_inspect_async(self.internal, fn, *args, **kwargs)) diff --git a/wird/future_maybe.py b/wird/future_maybe.py new file mode 100644 index 0000000..4faa272 --- /dev/null +++ b/wird/future_maybe.py @@ -0,0 +1,303 @@ +from typing import Any, Awaitable, Callable, Concatenate, Type, overload + +from . import _future as f, _maybe as m + +__all__ = ( + "and_", + "and_then", + "and_then_async", + "filter", + "filter_async", + "inspect", + "inspect_async", + "is_empty", + "is_empty_or", + "is_empty_or_async", + "is_some", + "is_some_and", + "is_some_and_async", + "map", + "map_async", + "or_", + "or_else", + "or_else_async", + "unwrap", + "unwrap_or", + "unwrap_or_else", + "unwrap_or_else_async", + "unwrap_or_none", +) + + +@overload +def unwrap[T]( + future_maybe: m.FutureMaybe[T], *, on_empty: str = "expected Some, got Empty" +) -> f.Future[T]: + """Returns the contained Some value. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + """ + ... + + +@overload +def unwrap[T, R]( + future_maybe: m.FutureMaybe[T], + *, + as_type: Type[R], + on_empty: str = "expected Some, got Empty", +) -> f.Future[R]: + """Returns the contained Some value casted to passed type. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + + No actual casting is performed, type change affects only type checkers. + """ + ... + + +def unwrap[T](future_maybe: m.FutureMaybe[T], **kwargs) -> Any: + return future_maybe.unwrap(**kwargs) + + +def unwrap_or[T](future_maybe: m.FutureMaybe[T], /, other: T) -> f.Future[T]: + """Returns the contained Some value or a provided default. + + Arguments passed to unwrap_or are eagerly evaluated; if you are passing the + result of a function call, it is recommended to use unwrap_or_else, which is + lazily evaluated. + """ + return future_maybe.unwrap_or(other) + + +def unwrap_or_none[T](future_maybe: m.FutureMaybe[T]) -> f.Future[T | None]: + """Returns the contained Some value or None.""" + return future_maybe.unwrap_or_none() + + +def unwrap_or_else[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[T]: + """Returns the contained Some value or computes it from a sync closure.""" + return future_maybe.unwrap_or_else(fn, *args, **kwargs) + + +def unwrap_or_else_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[T]: + """Returns the contained Some value or computes it from a async closure.""" + return future_maybe.unwrap_or_else_async(fn, *args, **kwargs) + + +def map[T, **P, R]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Maps a Maybe[T] to Maybe[R] by applying a function to a contained value (if + Some) or returns Empty (if Empty).""" + return future_maybe.map(fn, *args, **kwargs) + + +def inspect[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Any], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Calls a function with a reference to the contained value if Some. + + Returns the original Maybe. + """ + return future_maybe.inspect_async(fn, *args, **kwargs) + + +def is_some[T](future_maybe: m.FutureMaybe[T]) -> f.Future[bool]: + """Returns true if the Maybe is a Some value.""" + return future_maybe.is_some() + + +def is_empty[T](future_maybe: m.FutureMaybe[T]) -> f.Future[bool]: + """Returns true if the Maybe is a Empty value.""" + return future_maybe.is_empty() + + +def is_some_and[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns true if the Maybe is a Some and the value inside of it matches a + predicate.""" + return future_maybe.is_some_and(fn, *args, **kwargs) + + +def is_empty_or[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns true if the Maybe is a Empty or the value inside of it matches a + predicate.""" + return future_maybe.is_empty_or(fn, *args, **kwargs) + + +def and_[T, R](future_maybe: m.FutureMaybe[T], other: m.Maybe[R]) -> m.FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise returns other. + + Arguments passed to and_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use and_then, which is lazily evaluated. + """ + return future_maybe.and_(other) + + +def or_[T](future_maybe: m.FutureMaybe[T], other: m.Maybe[T]) -> m.FutureMaybe[T]: + """Returns the Maybe if it contains a value, otherwise returns other. + + Arguments passed to or_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use or_else, which is lazily evaluated. + """ + return future_maybe.or_(other) + + +def and_then[T, **P, R]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], m.Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls fn with the wrapped + value and returns the result. + + Some languages call this operation flatmap. + """ + return future_maybe.and_then(fn, *args, **kwargs) + + +def or_else[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[P, m.Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns the Maybe if it contains a value, otherwise calls fn and returns the + result.""" + return future_maybe.or_else(fn, *args, **kwargs) + + +def filter[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns Empty if the Maybe is Empty, otherwise calls predicate with the + wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return future_maybe.filter(fn, *args, **kwargs) + + +def map_async[T, **P, R]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Maps a Maybe[T] to m.FutureMaybe[R] by applying an async function to a + contained value (if Some) or returns Empty (if Empty).""" + return future_maybe.map_async(fn, *args, **kwargs) + + +def inspect_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Calls an async function with a reference to the contained value if Some. + + Returns the original Maybe as m.FutureMaybe. + """ + return future_maybe.inspect_async(fn, *args, **kwargs) + + +def is_some_and_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Some and the value inside of it + matches a async predicate.""" + return future_maybe.is_some_and_async(fn, *args, **kwargs) + + +def is_empty_or_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Empty or the value inside of it + matches a async predicate.""" + return future_maybe.is_empty_or_async(fn, *args, **kwargs) + + +def and_then_async[T, **P, R]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[m.Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls an async fn with the + wrapped value and returns the result. + + Some languages call this operation flatmap. + """ + return future_maybe.and_then_async(fn, *args, **kwargs) + + +def or_else_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[P, Awaitable[m.Maybe[T]]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns the Maybe as m.FutureMaybe if it contains a value, otherwise calls an + async fn and returns the result.""" + return m.FutureMaybe(future_maybe.or_else_async(fn, *args, **kwargs)) + + +def filter_async[T, **P]( + future_maybe: m.FutureMaybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns Empty as m.FutureMaybe if the Maybe is Empty, otherwise calls an async + predicate with the wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return future_maybe.filter_async(fn, *args, **kwargs) diff --git a/wird/maybe.py b/wird/maybe.py new file mode 100644 index 0000000..06ff2d5 --- /dev/null +++ b/wird/maybe.py @@ -0,0 +1,301 @@ +from typing import Any, Awaitable, Callable, Concatenate, Type, overload + +from . import _future as f, _maybe as m + +__all__ = ( + "and_", + "and_then", + "and_then_async", + "filter", + "filter_async", + "inspect", + "inspect_async", + "is_empty", + "is_empty_or", + "is_empty_or_async", + "is_some", + "is_some_and", + "is_some_and_async", + "map", + "map_async", + "or_", + "or_else", + "or_else_async", + "unwrap", + "unwrap_or", + "unwrap_or_else", + "unwrap_or_else_async", + "unwrap_or_none", +) + + +@overload +def unwrap[T](maybe: m.Maybe[T], *, on_empty: str = "expected Some, got Empty") -> T: + """Returns the contained Some value. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + """ + ... + + +@overload +def unwrap[T, R]( + maybe: m.Maybe[T], + *, + as_type: Type[R], + on_empty: str = "expected Some, got Empty", +) -> R: + """Returns the contained Some value casted to passed type. + + Because this function may raise EmptyUnwrapError, its use is generally + discouraged. + + Instead, prefer to use pattern matching and handle the Empty case explicitly, or + call unwrap_or, unwrap_or_else. + + No actual casting is performed, type change affects only type checkers. + """ + ... + + +def unwrap[T](maybe: m.Maybe[T], **kwargs) -> Any: + return maybe.unwrap(**kwargs) + + +def unwrap_or[T](maybe: m.Maybe[T], /, default: T) -> T: + """Returns the contained Some value or a provided default. + + Arguments passed to unwrap_or are eagerly evaluated; if you are passing the + result of a function call, it is recommended to use unwrap_or_else, which is + lazily evaluated. + """ + return maybe.unwrap_or(default) + + +def unwrap_or_none[T](maybe: m.Maybe[T]) -> T | None: + """Returns the contained Some value or None.""" + return maybe.unwrap_or_none() + + +def unwrap_or_else[T, **P]( + maybe: m.Maybe[T], + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, +) -> T: + """Returns the contained Some value or computes it from a sync closure.""" + return maybe.unwrap_or_else(fn, *args, **kwargs) + + +def unwrap_or_else_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[T]: + """Returns the contained Some value or computes it from a async closure.""" + return maybe.unwrap_or_else_async(fn, *args, **kwargs) + + +def map[T, **P, R]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, +) -> m.Maybe[R]: + """Maps a Maybe[T] to Maybe[R] by applying a function to a contained value (if + Some) or returns Empty (if Empty).""" + return maybe.map(fn, *args, **kwargs) + + +def inspect[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Any], + *args: P.args, + **kwargs: P.kwargs, +) -> m.Maybe[T]: + """Calls a function with a reference to the contained value if Some. + + Returns the original Maybe. + """ + return maybe.inspect(fn, *args, **kwargs) + + +def is_some[T](maybe: m.Maybe[T]) -> bool: + """Returns true if the Maybe is a Some value.""" + return maybe.is_some() + + +def is_empty[T](maybe: m.Maybe[T]) -> bool: + """Returns true if the Maybe is a Empty value.""" + return maybe.is_empty() + + +def is_some_and[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> bool: + """Returns true if the Maybe is a Some and the value inside of it matches a + predicate.""" + return maybe.is_some_and(fn, *args, **kwargs) + + +def is_empty_or[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> bool: + """Returns true if the Maybe is a Empty or the value inside of it matches a + predicate.""" + return maybe.is_empty_or(fn, *args, **kwargs) + + +def and_[T, R](maybe: m.Maybe[T], other: m.Maybe[R]) -> m.Maybe[R]: + """Returns Empty if the Maybe is Empty, otherwise returns other. + + Arguments passed to and_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use and_then, which is lazily evaluated. + """ + return maybe.and_(other) + + +def or_[T](maybe: m.Maybe[T], other: m.Maybe[T]) -> m.Maybe[T]: + """Returns the Maybe if it contains a value, otherwise returns other. + + Arguments passed to or_ are eagerly evaluated; if you are passing the result of + a function call, it is recommended to use or_else, which is lazily evaluated. + """ + return maybe.or_(other) + + +def and_then[T, **P, R]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], m.Maybe[R]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.Maybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls fn with the wrapped + value and returns the result. + + Some languages call this operation flatmap. + """ + return maybe.and_then(fn, *args, **kwargs) + + +def or_else[T, **P]( + maybe: m.Maybe[T], + fn: Callable[P, m.Maybe[T]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.Maybe[T]: + """Returns the Maybe if it contains a value, otherwise calls fn and returns the + result.""" + return maybe.or_else(fn, *args, **kwargs) + + +def filter[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> m.Maybe[T]: + """Returns Empty if the Maybe is Empty, otherwise calls predicate with the + wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return maybe.filter(fn, *args, **kwargs) + + +def map_async[T, **P, R]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[R]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Maps a Maybe[T] to FutureMaybe[R] by applying an async function to a + contained value (if Some) or returns Empty (if Empty).""" + return maybe.map_async(fn, *args, **kwargs) + + +def inspect_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[Any]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Calls an async function with a reference to the contained value if Some. + + Returns the original Maybe as FutureMaybe. + """ + return maybe.inspect_async(fn, *args, **kwargs) + + +def is_some_and_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Some and the value inside of it + matches a async predicate.""" + return maybe.is_some_and_async(fn, *args, **kwargs) + + +def is_empty_or_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> f.Future[bool]: + """Returns Future-wrapped true if the Maybe is a Empty or the value inside of it + matches a async predicate.""" + return maybe.is_empty_or_async(fn, *args, **kwargs) + + +def and_then_async[T, **P, R]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[m.Maybe[R]]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[R]: + """Returns Empty if the Maybe is Empty, otherwise calls an async fn with the + wrapped value and returns the result. + + Some languages call this operation flatmap. + """ + return maybe.and_then_async(fn, *args, **kwargs) + + +def or_else_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[P, Awaitable[m.Maybe[T]]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns the Maybe as FutureMaybe if it contains a value, otherwise calls an + async fn and returns the result.""" + return maybe.or_else_async(fn, *args, **kwargs) + + +def filter_async[T, **P]( + maybe: m.Maybe[T], + fn: Callable[Concatenate[T, P], Awaitable[bool]], + *args: P.args, + **kwargs: P.kwargs, +) -> m.FutureMaybe[T]: + """Returns Empty as FutureMaybe if the Maybe is Empty, otherwise calls an async + predicate with the wrapped value and returns: + + - Some if predicate returns True + - Empty if predicate returns False. + """ + return maybe.filter_async(fn, *args, **kwargs)