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 add 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.5.15.tar.gz (6.2 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.5.15-py3-none-any.whl (9.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mares-2026.5.15.tar.gz
  • Upload date:
  • Size: 6.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","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.5.15.tar.gz
Algorithm Hash digest
SHA256 dad136ea1b6976f8c41e043420bb2264274179ee64ffb7cf3fcd0c449f5972cf
MD5 72b783e716fc3c3237674c8ad093f309
BLAKE2b-256 f48a843599dbdef236b73598ef3a7792d582ba7e5ee2051d60fe99b4e95635d3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: mares-2026.5.15-py3-none-any.whl
  • Upload date:
  • Size: 9.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","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.5.15-py3-none-any.whl
Algorithm Hash digest
SHA256 6e919ecd7e70480a4f21a2f964485a4152d50ef43ab68b1b14f8327bbb034dd6
MD5 81444800669375a51e388e5346cbff91
BLAKE2b-256 03ccf5cf3009ed46353146b196577b748f4a0ee1091ede8b3d91bd4f091e55e5

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