rust-ok 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, pesap
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
rust_ok-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.3
2
+ Name: rust-ok
3
+ Version: 0.1.0
4
+ Summary: Rust-inspired Result/Ok/Err primitives for Python
5
+ Keywords: result,error-handling,rust,functional,typing
6
+ Author: pesap
7
+ Author-email: pesap <pesap@users.noreply.github.com>
8
+ License: BSD 3-Clause License
9
+
10
+ Copyright (c) 2025, pesap
11
+
12
+ Redistribution and use in source and binary forms, with or without
13
+ modification, are permitted provided that the following conditions are met:
14
+
15
+ 1. Redistributions of source code must retain the above copyright notice, this
16
+ list of conditions and the following disclaimer.
17
+
18
+ 2. Redistributions in binary form must reproduce the above copyright notice,
19
+ this list of conditions and the following disclaimer in the documentation
20
+ and/or other materials provided with the distribution.
21
+
22
+ 3. Neither the name of the copyright holder nor the names of its
23
+ contributors may be used to endorse or promote products derived from
24
+ this software without specific prior written permission.
25
+
26
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
30
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
+ Classifier: Development Status :: 3 - Alpha
37
+ Classifier: Intended Audience :: Developers
38
+ Classifier: License :: OSI Approved :: BSD License
39
+ Classifier: Natural Language :: English
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Programming Language :: Python :: 3.14
45
+ Classifier: Typing :: Typed
46
+ Requires-Python: >=3.11, <3.15
47
+ Description-Content-Type: text/markdown
48
+
49
+ # rust-ok
50
+ Rust-style `Result`, `Ok`, and `Err` primitives for Python projects.
51
+
52
+ ## Installation
53
+ ```bash
54
+ pip install rust-ok
55
+ ```
56
+
57
+ ## Usage
58
+ ```python
59
+ from rust_ok import Result, Ok, Err
60
+
61
+ def parse_int(raw: str) -> Result[int, str]:
62
+ try:
63
+ return Ok(int(raw))
64
+ except ValueError as exc:
65
+ return Err(str(exc))
66
+
67
+ result = parse_int("42")
68
+ print(result.unwrap_or(0)) # -> 42
69
+ ```
70
+
71
+ ### Formatting exception chains
72
+ ```python
73
+ from rust_ok import Err, Ok, format_exception_chain
74
+
75
+ try:
76
+ Err(ValueError("boom")).unwrap()
77
+ except Exception as exc:
78
+ print(format_exception_chain(exc))
79
+ ```
80
+
81
+ ### Iterating over results
82
+ ```python
83
+ from rust_ok import Err, Ok, is_ok
84
+
85
+ results = [Ok(1), Err("bad"), Ok(3)]
86
+
87
+ for res in results:
88
+ if is_ok(res):
89
+ print("value:", res.unwrap())
90
+ ```
@@ -0,0 +1,42 @@
1
+ # rust-ok
2
+ Rust-style `Result`, `Ok`, and `Err` primitives for Python projects.
3
+
4
+ ## Installation
5
+ ```bash
6
+ pip install rust-ok
7
+ ```
8
+
9
+ ## Usage
10
+ ```python
11
+ from rust_ok import Result, Ok, Err
12
+
13
+ def parse_int(raw: str) -> Result[int, str]:
14
+ try:
15
+ return Ok(int(raw))
16
+ except ValueError as exc:
17
+ return Err(str(exc))
18
+
19
+ result = parse_int("42")
20
+ print(result.unwrap_or(0)) # -> 42
21
+ ```
22
+
23
+ ### Formatting exception chains
24
+ ```python
25
+ from rust_ok import Err, Ok, format_exception_chain
26
+
27
+ try:
28
+ Err(ValueError("boom")).unwrap()
29
+ except Exception as exc:
30
+ print(format_exception_chain(exc))
31
+ ```
32
+
33
+ ### Iterating over results
34
+ ```python
35
+ from rust_ok import Err, Ok, is_ok
36
+
37
+ results = [Ok(1), Err("bad"), Ok(3)]
38
+
39
+ for res in results:
40
+ if is_ok(res):
41
+ print("value:", res.unwrap())
42
+ ```
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "rust-ok"
3
+ version = "0.1.0"
4
+ description = "Rust-inspired Result/Ok/Err primitives for Python"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "pesap", email = "pesap@users.noreply.github.com" }
8
+ ]
9
+ requires-python = ">=3.11,<3.15"
10
+ license = { file = "LICENSE.txt" }
11
+ keywords = [
12
+ "result",
13
+ "error-handling",
14
+ "rust",
15
+ "functional",
16
+ "typing",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: BSD License",
22
+ "Natural Language :: English",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Typing :: Typed",
29
+ ]
30
+ dependencies = []
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "mypy>=1.18.2",
35
+ "pre-commit>=4.5.0,<5.0.0",
36
+ "pytest>=8.3.0,<9.0.0",
37
+ "pytest-cov>=5.0.0,<6.0.0",
38
+ "ruff>=0.6.8,<0.7.0",
39
+ ]
40
+
41
+ [tool.pytest.ini_options]
42
+ pythonpath = ["src"]
43
+ testpaths = ["tests"]
44
+ addopts = [
45
+ "--cov=rust_ok",
46
+ "--cov-report=term-missing",
47
+ ]
48
+
49
+ [build-system]
50
+ requires = ["uv_build>=0.9.10,<0.10.0"]
51
+ build-backend = "uv_build"
@@ -0,0 +1,21 @@
1
+ """Public API for rust-ok."""
2
+
3
+ from .exceptions import IsNotError, RustOkError, UnwrapError
4
+ from .err import Err
5
+ from .ok import Ok
6
+ from .guards import is_err, is_ok
7
+ from .result import Result
8
+ from .trace import format_exception_chain, iter_causes
9
+
10
+ __all__ = [
11
+ "Err",
12
+ "IsNotError",
13
+ "Ok",
14
+ "Result",
15
+ "RustOkError",
16
+ "UnwrapError",
17
+ "format_exception_chain",
18
+ "iter_causes",
19
+ "is_err",
20
+ "is_ok",
21
+ ]
@@ -0,0 +1,92 @@
1
+ """Implementation of the Err variant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Optional, TypeVar
6
+
7
+ from .exceptions import UnwrapError
8
+ from .result import Result
9
+
10
+ T = TypeVar("T")
11
+ E = TypeVar("E")
12
+ U = TypeVar("U")
13
+ F = TypeVar("F")
14
+
15
+
16
+ class Err(Result[T, E]):
17
+ """Error result containing an error value."""
18
+
19
+ __slots__ = ("_error_value",)
20
+ __match_args__ = ("error",)
21
+
22
+ def __init__(self, error: E) -> None:
23
+ self._error_value = error
24
+
25
+ def __repr__(self) -> str:
26
+ return f"Err({self._error_value!r})"
27
+
28
+ def __str__(self) -> str:
29
+ return f"Err({self._error_value})"
30
+
31
+ def __eq__(self, other: object) -> bool:
32
+ if isinstance(other, Err):
33
+ return bool(self._error_value == other._error_value)
34
+ return False
35
+
36
+ def __hash__(self) -> int:
37
+ return hash(("Err", self._error_value))
38
+
39
+ def __bool__(self) -> bool:
40
+ return False
41
+
42
+ def unwrap(self) -> T:
43
+ raise UnwrapError(f"Called unwrap on Err: {self._error_value}")
44
+
45
+ def unwrap_err(self) -> E:
46
+ return self._error_value
47
+
48
+ def unwrap_or(self, default: T) -> T:
49
+ return default
50
+
51
+ def unwrap_or_else(self, func: Callable[[E], T]) -> T:
52
+ return func(self._error_value)
53
+
54
+ def expect(self, msg: str) -> T:
55
+ raise UnwrapError(f"{msg}: {self._error_value}")
56
+
57
+ def is_ok(self) -> bool:
58
+ return False
59
+
60
+ def is_err(self) -> bool:
61
+ return True
62
+
63
+ def map(self, func: Callable[[T], U]) -> Result[U, E]:
64
+ return Err(self._error_value)
65
+
66
+ def map_err(self, func: Callable[[E], F]) -> Result[T, F]:
67
+ return Err(func(self._error_value))
68
+
69
+ def and_then(self, func: Callable[[T], Result[U, E]]) -> Result[U, E]:
70
+ return Err(self._error_value)
71
+
72
+ def or_else(self, func: Callable[[E], Result[T, F]]) -> Result[T, F]:
73
+ return func(self._error_value)
74
+
75
+ def ok(self) -> T | None:
76
+ return None
77
+
78
+ def err(self) -> E:
79
+ return self._error_value
80
+
81
+ def unwrap_or_raise(
82
+ self,
83
+ exc_type: type[BaseException] = Exception,
84
+ context: Optional[str] = None,
85
+ ) -> T:
86
+ payload = self._error_value
87
+ msg = context if context is not None else str(payload)
88
+
89
+ if isinstance(payload, BaseException):
90
+ raise exc_type(msg) from payload
91
+
92
+ raise exc_type(f"{msg}: {payload!r}")
@@ -0,0 +1,13 @@
1
+ """Custom exceptions for rust-ok."""
2
+
3
+
4
+ class RustOkError(Exception):
5
+ """Base exception for all rust-ok errors."""
6
+
7
+
8
+ class UnwrapError(RustOkError):
9
+ """Raised when accessing an Ok/Err value in an invalid way."""
10
+
11
+
12
+ class IsNotError(RustOkError):
13
+ """Raised when attempting to retrieve an error from an Ok result."""
@@ -0,0 +1,22 @@
1
+ """Type-guard helpers for Result values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypeGuard, TypeVar
6
+
7
+ from .err import Err
8
+ from .ok import Ok
9
+ from .result import Result
10
+
11
+ T = TypeVar("T")
12
+ E = TypeVar("E")
13
+
14
+
15
+ def is_ok(result: Result[T, E]) -> TypeGuard[Ok[T, E]]:
16
+ """Return True if the result is Ok."""
17
+ return isinstance(result, Ok)
18
+
19
+
20
+ def is_err(result: Result[T, E]) -> TypeGuard[Err[T, E]]:
21
+ """Return True if the result is Err."""
22
+ return isinstance(result, Err)
@@ -0,0 +1,94 @@
1
+ """Implementation of the Ok variant."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Literal, Optional, TypeVar, cast, overload
6
+
7
+ from .exceptions import IsNotError, UnwrapError
8
+ from .result import Result
9
+
10
+ T = TypeVar("T")
11
+ E = TypeVar("E")
12
+ U = TypeVar("U")
13
+ F = TypeVar("F")
14
+
15
+
16
+ class Ok(Result[T, E]):
17
+ """Success result containing a value."""
18
+
19
+ __slots__ = ("value",)
20
+ __match_args__ = ("value",)
21
+
22
+ value: T
23
+
24
+ @overload
25
+ def __init__(self: "Ok[None, E]", value: Literal[None] = None) -> None: ...
26
+
27
+ @overload
28
+ def __init__(self: "Ok[T, E]", value: T) -> None: ...
29
+
30
+ def __init__(self, value: Optional[T] = None) -> None:
31
+ self.value = cast(T, value)
32
+
33
+ def __repr__(self) -> str:
34
+ return f"Ok({self.value!r})"
35
+
36
+ def __str__(self) -> str:
37
+ return f"Ok({self.value})"
38
+
39
+ def __eq__(self, other: object) -> bool:
40
+ if isinstance(other, Ok):
41
+ return bool(self.value == other.value)
42
+ return False
43
+
44
+ def __hash__(self) -> int:
45
+ return hash(("Ok", self.value))
46
+
47
+ def __bool__(self) -> bool:
48
+ return True
49
+
50
+ def unwrap(self) -> T:
51
+ return self.value
52
+
53
+ def unwrap_err(self) -> E:
54
+ raise UnwrapError("Called unwrap_err on Ok")
55
+
56
+ def unwrap_or(self, default: T) -> T:
57
+ return self.value
58
+
59
+ def unwrap_or_else(self, func: Callable[[E], T]) -> T:
60
+ return self.value
61
+
62
+ def expect(self, msg: str) -> T:
63
+ return self.value
64
+
65
+ def is_ok(self) -> bool:
66
+ return True
67
+
68
+ def is_err(self) -> bool:
69
+ return False
70
+
71
+ def map(self, func: Callable[[T], U]) -> Result[U, E]:
72
+ return Ok(func(self.value))
73
+
74
+ def map_err(self, func: Callable[[E], F]) -> Result[T, F]:
75
+ return Ok(self.value)
76
+
77
+ def and_then(self, func: Callable[[T], Result[U, E]]) -> Result[U, E]:
78
+ return func(self.value)
79
+
80
+ def or_else(self, func: Callable[[E], Result[T, F]]) -> Result[T, F]:
81
+ return Ok(self.value)
82
+
83
+ def ok(self) -> T:
84
+ return self.value
85
+
86
+ def err(self) -> E:
87
+ raise IsNotError
88
+
89
+ def unwrap_or_raise(
90
+ self,
91
+ exc_type: type[BaseException] = Exception,
92
+ context: str | None = None,
93
+ ) -> T:
94
+ return self.value
File without changes
@@ -0,0 +1,85 @@
1
+ """Base Result primitives and helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Generic, Optional, TypeVar
6
+
7
+ T = TypeVar("T")
8
+ E = TypeVar("E")
9
+ U = TypeVar("U")
10
+ F = TypeVar("F")
11
+
12
+
13
+ class Result(Generic[T, E]):
14
+ """Base type for Ok/Err results."""
15
+
16
+ __slots__ = ()
17
+
18
+ def unwrap(self) -> T:
19
+ """Return the contained value if successful, else raise in subclass."""
20
+ raise NotImplementedError # pragma: no cover
21
+
22
+ def unwrap_err(self) -> E:
23
+ """Return the contained error if Err, else raise in subclass."""
24
+ raise NotImplementedError # pragma: no cover
25
+
26
+ def unwrap_or(self, default: T) -> T:
27
+ """Return the contained value if Ok, otherwise return the default."""
28
+ raise NotImplementedError # pragma: no cover
29
+
30
+ def unwrap_or_else(self, func: Callable[[E], T]) -> T:
31
+ """Return the contained value if Ok, otherwise compute a default."""
32
+ raise NotImplementedError # pragma: no cover
33
+
34
+ def expect(self, msg: str) -> T:
35
+ """Return the contained value if Ok, otherwise raise with custom message."""
36
+ raise NotImplementedError # pragma: no cover
37
+
38
+ def is_ok(self) -> bool: # pragma: no cover
39
+ """Return True if this is Ok."""
40
+ from .ok import Ok
41
+
42
+ return isinstance(self, Ok)
43
+
44
+ def is_err(self) -> bool: # pragma: no cover
45
+ """Return True if this is Err."""
46
+ from .err import Err
47
+
48
+ return isinstance(self, Err)
49
+
50
+ def map(self, func: Callable[[T], U]) -> Result[U, E]:
51
+ """Apply func to the contained value if Ok, returning a new Result."""
52
+ raise NotImplementedError # pragma: no cover
53
+
54
+ def map_err(self, func: Callable[[E], F]) -> Result[T, F]:
55
+ """Apply func to the error if Err, returning a new Result."""
56
+ raise NotImplementedError # pragma: no cover
57
+
58
+ def and_then(self, func: Callable[[T], Result[U, E]]) -> Result[U, E]:
59
+ """Chain another computation on the contained value if Ok."""
60
+ raise NotImplementedError # pragma: no cover
61
+
62
+ def or_else(self, func: Callable[[E], Result[T, F]]) -> Result[T, F]:
63
+ """Handle the error by calling func if Err, returning a new Result."""
64
+ raise NotImplementedError # pragma: no cover
65
+
66
+ def ok(self) -> T | None:
67
+ """Return the success value if Ok, otherwise None."""
68
+ raise NotImplementedError # pragma: no cover
69
+
70
+ def err(self) -> E:
71
+ """Return the error value if Err, otherwise raise in subclass."""
72
+ raise NotImplementedError # pragma: no cover
73
+
74
+ @property
75
+ def error(self) -> E | None:
76
+ """Return the error value if Err, otherwise None."""
77
+ return self.err()
78
+
79
+ def unwrap_or_raise(
80
+ self,
81
+ exc_type: type[BaseException] = Exception,
82
+ context: Optional[str] = None,
83
+ ) -> T:
84
+ """Return the Ok value or raise `exc_type`."""
85
+ raise NotImplementedError # pragma: no cover
@@ -0,0 +1,40 @@
1
+ """Helpers for formatting chained exceptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from io import StringIO
6
+ from typing import Iterator
7
+
8
+
9
+ def iter_causes(exc: BaseException) -> Iterator[BaseException]:
10
+ """Yield an exception and its chained causes/contexts in order."""
11
+ current: BaseException | None = exc
12
+ while current is not None:
13
+ yield current
14
+ if current.__cause__ is not None:
15
+ current = current.__cause__
16
+ elif current.__context__ is not None and not current.__suppress_context__:
17
+ current = current.__context__
18
+ else:
19
+ current = None
20
+
21
+
22
+ def format_exception_chain(exc: BaseException) -> str:
23
+ """Return a readable string for an exception and its chain."""
24
+ from traceback import format_exception # lazy import keeps module light
25
+
26
+ buffer = StringIO()
27
+ first = True
28
+ for cause in iter_causes(exc):
29
+ if not first:
30
+ buffer.write("\n\n")
31
+ first = False
32
+ chunk = "".join(
33
+ format_exception(
34
+ cause.__class__,
35
+ cause,
36
+ cause.__traceback__,
37
+ )
38
+ ).rstrip()
39
+ buffer.write(chunk)
40
+ return buffer.getvalue()