sain.error

Interfaces for working with Errors.

This exposes one abstract interface, Error that other errors can implement and use as an argument to match upon.

Usually this error is returned from a Result[T, Error] object.

Those errors can be converted into RuntimeError exceptions by calling sain.Result.unwrap and sain.Option.unwrap.

For an example

# Read the env variable, raises `RuntimeError` if it is not present.
path: Option[str] = Some(os.environ.get('SOME_PATH')).unwrap()
  1# BSD 3-Clause License
  2#
  3# Copyright (c) 2022-Present, nxtlo
  4# All rights reserved.
  5#
  6# Redistribution and use in source and binary forms, with or without
  7# modification, are permitted provided that the following conditions are met:
  8#
  9# * Redistributions of source code must retain the above copyright notice, this
 10#   list of conditions and the following disclaimer.
 11#
 12# * Redistributions in binary form must reproduce the above copyright notice,
 13#   this list of conditions and the following disclaimer in the documentation
 14#   and/or other materials provided with the distribution.
 15#
 16# * Neither the name of the copyright holder nor the names of its
 17#   contributors may be used to endorse or promote products derived from
 18#   this software without specific prior written permission.
 19#
 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 30"""Interfaces for working with Errors.
 31
 32This exposes one abstract interface, `Error` that other errors can implement and use as an argument to match upon.
 33
 34Usually this error is returned from a `Result[T, Error]` object.
 35
 36Those errors can be converted into `RuntimeError` exceptions by calling `sain.Result.unwrap` and `sain.Option.unwrap`.
 37
 38For an example
 39
 40```py
 41# Read the env variable, raises `RuntimeError` if it is not present.
 42path: Option[str] = Some(os.environ.get('SOME_PATH')).unwrap()
 43```
 44"""
 45
 46from __future__ import annotations
 47
 48__all__ = ("Error", "catch_unwind")
 49
 50import typing
 51
 52from sain import result as _result
 53from sain.macros import rustc_diagnostic_item
 54
 55from . import option as _option
 56from .convert import ToString
 57
 58if typing.TYPE_CHECKING:
 59    from collections.abc import Callable
 60
 61    from sain import Option
 62    from sain import result as _result
 63
 64R = typing.TypeVar("R", covariant=True)
 65
 66
 67@rustc_diagnostic_item("Error")
 68@typing.runtime_checkable
 69class Error(ToString, typing.Protocol):
 70    """`Error` is an interface usually used for values that returns `sain.Result[T, E]`
 71
 72    where `E` is an implementation of this interface.
 73
 74    Example
 75    -------
 76    ```py
 77    import requests
 78    import http
 79    from dataclasses import dataclass
 80
 81    from sain import Error
 82    from sain import Result, Ok, Err
 83
 84    # an http error.
 85    @dataclass
 86    class HTTPError(Error):
 87        response: requests.Response
 88        kind: http.HTTPStatus
 89        message: str = ""
 90
 91        def description(self) -> str:
 92            return f"HTTP Error [{self.response.status_code}, {self.kind}] for {self.response.url}"
 93
 94    # A simple request that handles [404] responses.
 95    def request(url: str, uid: int) -> Result[requests.Response, HTTPError]:
 96        response = requests.get(url, json={"user_id": uid})
 97        if response.status_code == 404:
 98            return Err(
 99                HTTPError(
100                    response,
101                    kind=http.HTTPStatus.NOT_FOUND,
102                    message=f"Resource not found for user_id {uid}",
103                )
104            )
105        return Ok(response)
106
107    # Execute the request
108    match request("some-url.com", 0):
109        case Ok(response):
110            # Deal with the response
111            ...
112        case Err(why):
113            # Deal with the error.
114            print(why)
115
116    ```
117    """
118
119    __slots__ = ("message",)
120
121    def __init__(self, message: str = "") -> None:
122        self.message = message
123        """A basic error message."""
124
125    def source(self) -> Option[type[Error]]:
126        """The source of this error, if any."""
127        return _option.NOTHING
128
129    def description(self) -> str:
130        """Context for this error."""
131        return ""
132
133    def to_string(self) -> str:
134        return self.__repr__()
135
136    def __repr__(self) -> str:
137        source = None if (src := self.source()).is_none() else src
138        return (
139            f'{type(self).__qualname__}(message: "{self.message}, source: {source!r})'
140        )
141
142    def __str__(self) -> str:
143        return self.message
144
145    # An error is always falsy.
146    def __bool__(self) -> typing.Literal[False]:
147        return False
148
149
150@rustc_diagnostic_item("catch_unwind")
151def catch_unwind(fn: Callable[[], R]) -> _result.Result[R, BaseException]:
152    """Invokes a closure, capturing exceptions if any one raised.
153
154    This function will return `Ok` with the closure's result if it doesn't raise any exceptions,
155    otherwise it will return `Err(cause)` with the exception.
156
157    You can treat this as an inline try-except block.
158
159    Notes
160    -----
161    This function also catch exceptions such as `KeyboardInterrupt` and `SystemExit`,
162    so try to use it with extreme caution.
163
164    Example
165    -------
166    ```py
167    from sain.error import catch_unwind
168
169    def request() -> str:
170        return requests.get("some url").text
171
172    def fetch() -> str:
173        raise RuntimeError from None
174
175    result = catch_unwind(request)
176    assert result.is_ok()
177
178    result = catch_unwind(fetch)
179    assert result.is_err()
180    ```
181
182    Parameters
183    ----------
184    fn: `Callable[[], R]`
185        The function to run.
186
187    Returns
188    -------
189    `Result[R, BaseException]`
190        Returns `Ok(value)` if the function ran successfully, otherwise `Err(cause)` with the exception.
191    """
192    try:
193        return _result.Ok(fn())
194    except BaseException as e:
195        return _result.Err(e)
@rustc_diagnostic_item('Error')
@typing.runtime_checkable
class Error(sain.convert.ToString, typing.Protocol):
 68@rustc_diagnostic_item("Error")
 69@typing.runtime_checkable
 70class Error(ToString, typing.Protocol):
 71    """`Error` is an interface usually used for values that returns `sain.Result[T, E]`
 72
 73    where `E` is an implementation of this interface.
 74
 75    Example
 76    -------
 77    ```py
 78    import requests
 79    import http
 80    from dataclasses import dataclass
 81
 82    from sain import Error
 83    from sain import Result, Ok, Err
 84
 85    # an http error.
 86    @dataclass
 87    class HTTPError(Error):
 88        response: requests.Response
 89        kind: http.HTTPStatus
 90        message: str = ""
 91
 92        def description(self) -> str:
 93            return f"HTTP Error [{self.response.status_code}, {self.kind}] for {self.response.url}"
 94
 95    # A simple request that handles [404] responses.
 96    def request(url: str, uid: int) -> Result[requests.Response, HTTPError]:
 97        response = requests.get(url, json={"user_id": uid})
 98        if response.status_code == 404:
 99            return Err(
100                HTTPError(
101                    response,
102                    kind=http.HTTPStatus.NOT_FOUND,
103                    message=f"Resource not found for user_id {uid}",
104                )
105            )
106        return Ok(response)
107
108    # Execute the request
109    match request("some-url.com", 0):
110        case Ok(response):
111            # Deal with the response
112            ...
113        case Err(why):
114            # Deal with the error.
115            print(why)
116
117    ```
118    """
119
120    __slots__ = ("message",)
121
122    def __init__(self, message: str = "") -> None:
123        self.message = message
124        """A basic error message."""
125
126    def source(self) -> Option[type[Error]]:
127        """The source of this error, if any."""
128        return _option.NOTHING
129
130    def description(self) -> str:
131        """Context for this error."""
132        return ""
133
134    def to_string(self) -> str:
135        return self.__repr__()
136
137    def __repr__(self) -> str:
138        source = None if (src := self.source()).is_none() else src
139        return (
140            f'{type(self).__qualname__}(message: "{self.message}, source: {source!r})'
141        )
142
143    def __str__(self) -> str:
144        return self.message
145
146    # An error is always falsy.
147    def __bool__(self) -> typing.Literal[False]:
148        return False

Error is an interface usually used for values that returns sain.Result[T, E]

where E is an implementation of this interface.

Example
import requests
import http
from dataclasses import dataclass

from sain import Error
from sain import Result, Ok, Err

# an http error.
@dataclass
class HTTPError(Error):
    response: requests.Response
    kind: http.HTTPStatus
    message: str = ""

    def description(self) -> str:
        return f"HTTP Error [{self.response.status_code}, {self.kind}] for {self.response.url}"

# A simple request that handles [404] responses.
def request(url: str, uid: int) -> Result[requests.Response, HTTPError]:
    response = requests.get(url, json={"user_id": uid})
    if response.status_code == 404:
        return Err(
            HTTPError(
                response,
                kind=http.HTTPStatus.NOT_FOUND,
                message=f"Resource not found for user_id {uid}",
            )
        )
    return Ok(response)

# Execute the request
match request("some-url.com", 0):
    case Ok(response):
        # Deal with the response
        ...
    case Err(why):
        # Deal with the error.
        print(why)

Implementations

This class implements Error in Rust.

Error(message: str = '')
122    def __init__(self, message: str = "") -> None:
123        self.message = message
124        """A basic error message."""
message

A basic error message.

def source(self) -> 'Option[type[Error]]':
126    def source(self) -> Option[type[Error]]:
127        """The source of this error, if any."""
128        return _option.NOTHING

The source of this error, if any.

def description(self) -> str:
130    def description(self) -> str:
131        """Context for this error."""
132        return ""

Context for this error.

def to_string(self) -> str:
134    def to_string(self) -> str:
135        return self.__repr__()

Converts the given value to a str.

Example
i = 5  # assume `int` implements `ToString`
five = "5"
assert five == i.to_string()
@rustc_diagnostic_item('catch_unwind')
def catch_unwind(fn: Callable[[], +R]) -> '_result.Result[R, BaseException]':
151@rustc_diagnostic_item("catch_unwind")
152def catch_unwind(fn: Callable[[], R]) -> _result.Result[R, BaseException]:
153    """Invokes a closure, capturing exceptions if any one raised.
154
155    This function will return `Ok` with the closure's result if it doesn't raise any exceptions,
156    otherwise it will return `Err(cause)` with the exception.
157
158    You can treat this as an inline try-except block.
159
160    Notes
161    -----
162    This function also catch exceptions such as `KeyboardInterrupt` and `SystemExit`,
163    so try to use it with extreme caution.
164
165    Example
166    -------
167    ```py
168    from sain.error import catch_unwind
169
170    def request() -> str:
171        return requests.get("some url").text
172
173    def fetch() -> str:
174        raise RuntimeError from None
175
176    result = catch_unwind(request)
177    assert result.is_ok()
178
179    result = catch_unwind(fetch)
180    assert result.is_err()
181    ```
182
183    Parameters
184    ----------
185    fn: `Callable[[], R]`
186        The function to run.
187
188    Returns
189    -------
190    `Result[R, BaseException]`
191        Returns `Ok(value)` if the function ran successfully, otherwise `Err(cause)` with the exception.
192    """
193    try:
194        return _result.Ok(fn())
195    except BaseException as e:
196        return _result.Err(e)

Invokes a closure, capturing exceptions if any one raised.

This function will return Ok with the closure's result if it doesn't raise any exceptions, otherwise it will return Err(cause) with the exception.

You can treat this as an inline try-except block.

Notes

This function also catch exceptions such as KeyboardInterrupt and SystemExit, so try to use it with extreme caution.

Example
from sain.error import catch_unwind

def request() -> str:
    return requests.get("some url").text

def fetch() -> str:
    raise RuntimeError from None

result = catch_unwind(request)
assert result.is_ok()

result = catch_unwind(fetch)
assert result.is_err()
Parameters
  • fn (Callable[[], R]): The function to run.
Returns
  • Result[R, BaseException]: Returns Ok(value) if the function ran successfully, otherwise Err(cause) with the exception.
  • # Implementations
  • **This function implements catch_unwind: