diff --git a/README.md b/README.md index 655c693..f98dada 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # Serox -_Rusty abstractions for Python._ +_Rusty abstractions for Python_ -`Serox` defines a emulates a suite of commonly-used Rust abstractions in a manner that is near-fully +`serox` provides a suite of commonly-used Rust abstractions in a manner that is near-fully static-type-checker compliant, the exceptions being cases involving higher-kinded types (HKTs; e.g. -`Iterator.collect`) as these are not currently supported by Python's type system. Namely: +`Iterator.collect`) as these are not currently supported by Python's type system. + +The subset of abstractions most broadly-applicable are importable from `serox.prelude`. + +## Features 1. `Iterator` combinators that allow for the seamless chaining of operations over data with [rayon]-inspired functionality for effortless parallelism. @@ -18,23 +22,54 @@ static-type-checker compliant, the exceptions being cases involving higher-kinde redresses this. 4. The `qmark` decorator emulates the '?' (error/null short-circuiting) operator, allowing for - propagation of error and null values without interrupting the control flow. Without this, one has + propagation of error and null values without disrupting the control flow. Without this, one has to resort to awkward pattern-matching to perform common operations such as `unwrap_or` (setting `Null` to a default value) or `map` (applying a function to the contained value if `Some`). +## Example + +Early exiting (in the fashion of Rust's `?` operator) an Option/Result-returning function is enabled +by the `qmark` ('question mark') decorator: + ```python -from serox import Option, qmark +from serox.prelude import * @qmark -def some_function(foo: Option[str]) -> Option[str]: - foo_bar: str = value.map(lambda x: x + "bar").q - return Some(foo_bar + "_baz") +def some_function(value: Option[int]) -> Option[float]: + squared: int = value.map(lambda x: x ** 2).q + # The above expands to the rather verbose: + # match value: + # case Null(): + # return Null[float]() + # case Some(x): + # squared = value ** 2 + + return Some(1.0 / squared) ``` -[rayon]: https://github.com/rayon-rs/rayon +## Requirements + +Python version `>=3.12.3` is required for typing purposes. + +## Installation + +`serox` is available on PyPI and thus the latest version can be installed via `pip` with + +```sh +pip install serox +``` + +or via [uv] with + +```sh +uv add serox +``` ## Acknowledgements Credit to [result](https://github.com/rustedpy/result) and [rustshed](https://github.com/pawelrubin/rustshed/) for laying the groundwork for the `Result` and `qmark` implementations. + +[rayon]: https://github.com/rayon-rs/rayon +[uv]: https://docs.astral.sh/uv/ diff --git a/pyproject.toml b/pyproject.toml index da4d1b0..df04b9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,6 @@ lint.ignore = [ "PLW2901", # overwriting loop variable "PLC0105", # covariant typevars have to end in "_co" ] -lint.per-file-ignores = { "__init__.py" = ["F403"] } lint.select = [ "E", # pycodestyle "F", # pyflakes @@ -122,6 +121,10 @@ lint.select = [ ] target-version = "py310" +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F403"] +"prelude" = ["F403"] + [tool.ruff.lint.isort] case-sensitive = true classes = [] diff --git a/requirements-dev.lock b/requirements-dev.lock index 4a9128d..de93f1e 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,6 +12,7 @@ -e file:. black==24.4.2 bump2version==1.0.1 + # via serox cfgv==3.4.0 # via pre-commit click==8.1.7 diff --git a/requirements.lock b/requirements.lock index bd086ff..e6c9c04 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,6 +10,8 @@ # universal: false -e file:. +bump2version==1.0.1 + # via serox joblib==1.4.2 # via serox numpy==2.0.1 diff --git a/serox/__init__.py b/serox/__init__.py index 63e291d..4bc6981 100644 --- a/serox/__init__.py +++ b/serox/__init__.py @@ -1,9 +1,9 @@ from .collections import * -from .io import * from .iter import * from .misc import * from .option import * from .optional import * +from .path import * from .question_mark import * from .range import * from .result import * diff --git a/serox/io.py b/serox/path.py similarity index 100% rename from serox/io.py rename to serox/path.py diff --git a/serox/prelude.py b/serox/prelude.py new file mode 100644 index 0000000..a3a1d15 --- /dev/null +++ b/serox/prelude.py @@ -0,0 +1,32 @@ +# pyright: reportUnusedImport=false +from .cmp import Ord, PartialOrd +from .iter import Bridge, DoubleEndedIterator, Iterator +from .misc import Clone, Dupe +from .option import Null, Option, Some +from .optional import none, some +from .question_mark import qmark +from .range import Range +from .result import Err, Ok, Result +from .vec import Vec + +# export all imports +__all__ = [ + "Bridge", + "Clone", + "DoubleEndedIterator", + "Dupe", + "Err", + "Iterator", + "Null", + "Ok", + "Option", + "Ord", + "PartialOrd", + "Range", + "Result", + "Some", + "Vec", + "none", + "qmark", + "some", +] diff --git a/uv.lock b/uv.lock index fc65ddd..e5f12dd 100644 --- a/uv.lock +++ b/uv.lock @@ -351,7 +351,7 @@ wheels = [ [[package]] name = "serox" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "bump2version" },