sain.error API documentation

# 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()

View Source

      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): View Source

     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 = '') View Source

    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]]': View Source

    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: View Source

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

Context for this error.

def to_string(self) -> str: View Source

    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]': View Source

    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: