Skip to main content

mark's result class for safe value retrieval

Project description

mares

mark's result class for safe data retrieval

or, as i learnt from opus 4.5, a railway-oriented two-track result pattern for explicit success/failure handling

the class

@dataclass(frozen=True, slots=True)
class Result(Generic[T]):
    """
    `dataclasses.dataclass` representing a result for safe value retrieval

    attributes:
        `value: T`
            value to return or fallback value if erroneous
        `error: BaseException | None = None`
            exception if any

    methods:
        `def __bool__(self) -> bool: ...`
            method for boolean comparison for exception safety
        `def get(self) -> T: ...`
            method that raises or returns an error if the Result is erroneous
        `def map(self, func: Callable[[T], U]) -> Result[U]: ...`
            method that maps the value when not erroneous
        `def bind(self, func: Callable[[T], Result[U]]) -> Result[U]: ...`
            method that binds to another Result-returning function
        `def cry(self, string: bool = False) -> str: ...`
            method that returns the result value or raises an error
    """

    value: T
    error: BaseException | None = None

    def __bool__(self) -> bool:
        """
        method for boolean comparison for easier exception handling

        returns: `bool`
            that returns True if `self.error` is not None
        """
        return self.error is None

    def cry(self, string: bool = False) -> str:  # noqa: FBT001, FBT002
        """
        method that raises or returns an error if the Result is erroneous

        arguments:
            `string: bool = False`
                if `self.error` is an Exception, returns it as a string
                error message

        returns: `str`
            returns `self.error` as a string if `string` is True,
            or returns an empty string if `self.error` is None
        """

        if isinstance(self.error, BaseException):
            if string:
                message = f"{self.error}"
                name = self.error.__class__.__name__
                return f"{message} ({name})" if (message != "") else name

            raise self.error

        return ""

    def get(self) -> T:
        """
        method that returns the result value or raises an error

        returns: `T`
            returns `self.value` if `self.error` is None

        raises: `BaseException`
            if `self.error` is not None
        """
        if self.error is not None:
            raise self.error
        return self.value

    def map(self, func: Callable[[T], U]) -> "Result[U]":
        """
        method that maps the value when not erroneous

        arguments:
            `func: Callable[[T], U]`
                function to transform the value

        returns: `Result[U]`
            returns a new Result with the transformed value, or the same error
        """
        if self.error is not None:
            return Result(cast(U, self.value), error=self.error)
        return Result(func(self.value))

    def bind(self, func: Callable[[T], "Result[U]"]) -> "Result[U]":
        """
        method that binds to another Result-returning function

        arguments:
            `func: Callable[[T], Result[U]]`
                function to transform the value into a Result

        returns: `Result[U]`
            returns the bound Result, or the same error
        """
        if self.error is not None:
            return Result(cast(U, self.value), error=self.error)
        return func(self.value)

    @staticmethod
    def wrap(default: R) -> Callable[[Callable[P, R]], Callable[P, "Result[R]"]]:
        """decorator that wraps a non-Result-returning function to return a Result"""

        def result_decorator(func: Callable[P, R]) -> Callable[P, Result[R]]:
            @wraps(func)
            def wrapper(*args: P.args, **kwargs: P.kwargs) -> Result[R]:
                try:
                    return Result(func(*args, **kwargs))
                except Exception as exc:
                    return Result(default, error=exc)

            return wrapper

        return result_decorator

an example

def fetch_json(url: str) -> Result[dict[str, Any]]:
    try:
        with urllib.request.urlopen(url, timeout=10) as response:
            data = response.read().decode("utf-8")
        return Result(json.loads(data))
    except (OSError, ValueError, json.JSONDecodeError) as exc:
        return Result({}, error=exc)

def require_field(data: dict[str, Any], field: str) -> Result[dict[str, Any]]:
    if field not in data:
        return Result({}, error=KeyError(f"Missing field: {field}"))
    return Result(data)

return (
    fetch_json(f"https://jsonplaceholder.typicode.com/users/{randint(1, 10)}")
    .bind(lambda payload: require_field(payload, "name"))
    .map(lambda payload: str(payload["name"]))
)

bonus! this is available as a library, and as a quick insertion tool.

pip install mares

uv install mares

uvx mares inject path/to/file.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mares-2026.3.2.tar.gz (6.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mares-2026.3.2-py3-none-any.whl (9.2 kB view details)

Uploaded Python 3

File details

Details for the file mares-2026.3.2.tar.gz.

File metadata

  • Download URL: mares-2026.3.2.tar.gz
  • Upload date:
  • Size: 6.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mares-2026.3.2.tar.gz
Algorithm Hash digest
SHA256 f5c61bcb5a81b909e5286ed0f409abb8caed6cd9f1c6486217a58c02eb3758e8
MD5 0e43bb0cf8e2345e689d1a53e11d9f98
BLAKE2b-256 e2f8715ce9a7940e64162b2b6028ed28a1109300dd05127055be3a0e68a090bc

See more details on using hashes here.

File details

Details for the file mares-2026.3.2-py3-none-any.whl.

File metadata

  • Download URL: mares-2026.3.2-py3-none-any.whl
  • Upload date:
  • Size: 9.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mares-2026.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a885d58fe25634d81ba439996b72f1025bf7860e343ecdfeca40c064afb5b8a9
MD5 fdd5a1cc8f65e578206bd4709fd65cec
BLAKE2b-256 5d813a093c8572650b45d2d9ba3203f13db65bb1780b62dd3cdce8d866cdc612

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page