Metadata-Version: 2.1
Name: thresult
Version: 0.9.2
Summary: TangledHub Result
Home-page: https://gitlab.com/tangledlabs/thresult
License: BSD 3-clause
Keywords: tangled,tangledhub,tangledlabs,thresult
Author: TangledHub
Author-email: info@tangledgroup.com
Requires-Python: >=3.10,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Project-URL: Repository, https://gitlab.com/tangledlabs/thresult
Description-Content-Type: text/markdown

![[License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
![Build](https://img.shields.io/badge/build-success-brightgreen)
![Coverage](https://img.shields.io/badge/Coverage-100%25-green)

# thresult

## Overview
TangledHub library for handling returned values from functions/methods and handling errors.

## Versioning 
Starting with version 0.9.0

## Licencing
thresult is licensed under the BSD license. Check the [LICENSE file](LICENCE.md) for details

## Installation

## Testing
```bash
docker-compose build thresult-test ; docker-compose run --rm thresult-test
```

## Building
```bash
docker-compose build thresult-build ; docker-compose run --rm thresult-build
```

## Publish
```bash
docker-compose build thresult-publish ; docker-compose run --rm -e PYPI_USERNAME=__token__ -e PYPI_PASSWORD=__SECRET__ thresult-publish
```

### Run examples from Docker
```bash
docker run -it -v $PWD/examples:/code -v $PWD:/deps/thresult -e PYTHONPATH=/deps python:3.10 /bin/bash
cd /code
python -B traceback_0.py
python -B traceback_1.py
python -B traceback_2.py
python -B traceback_3.py
```


## Usage
thresult provides type system for handling values and exceptions in Python.
Expressions can return value of type Result and variables can hold value of type Result.
Result can be Ok or Err. Ok type has attribute v and Err type has attribute e.
If some expression doesn't rise exception, it will return Ok type where v attribute will be result of expression.
If error occurs, Err type will be returned, and e attribute of that type will contain error message.
Custom types can be created, and they must extend Result, Ok or Err type.

Example when returned value is Ok type
```python
from thresult import Result, Ok, Err

def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res

r: Result = f(1.0, 20.0) # r is Ok[<class 'float'>] object
```

Example when returned value is Err type
```python
from thresult import Result, Ok, Err

def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a/b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)

    return res

# divide with 0.0 will not rise exception,
# object type Err[<class 'Exception'>] is returned
r: Result = f(1.0, 0.0) # r is Err[<class 'Exception'>] object
```

To obtain a value from Ok or Err type
- structural pattern matching
- unwrap()
- unwrap_value()
- unwrap_or(v: Any)

There are different ways to handle exceptions

Obtain a value from Ok or Err type 
with structural pattern matching
```python
from thresult import Result, Ok, Err

def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 0.0)

match r:
    case Ok(v):
        print('Ok, value: ', v)
    case Err(e):
        print('Err, error: ', e) # float division by zero
```

Using structural pattern matching to handle exceptions.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


match f(1.0, 0.0):
    case Ok(v):
        print('Ok, value: ', v)
    case Err(e):
        print('Err, error: ', e) # float division by zero
```

Using *unwrap* to handle exceptions.
In case of error, exception is thrown.
```python
from thresult import Result, Ok, Err

def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res

r: Result = f(1.0, 0.0)
z: float = r.unwrap() # ZeroDivisionError: float division by zero
```

Using *unwrap_value* to handle exceptions.
In case of error, unwrap_value returns error message and doesn't terminate program.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 0.0)
v: str = r.unwrap_value()
# v will contain error message, float division by zero
```

Using *unwrap_or* to handle exceptions.
In case of error, unwrap_or returns value passed as argument and doesn't terminate program.
In this example it is 0.0.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 0.0)
v: float = r.unwrap_or(0.0)
# v will contain value 0.0
```

Obtain a value from Ok type using structural pattern matching.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 10.0)

match r:
    case Ok(v):
        # v holds value of division operation - 0.1
        print('Ok, value: ', v)
    case Err(e):
        print('Err, error: ', e)
```

Obtain a value from Ok type using structural pattern matching.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


match f(1.0, 10.0):
    case Ok(v):
        # v holds value of division operation - 0.1
        print('Ok, value: ', v)
    case Err(e):
        print('Err, error: ', e) 
```

Obtain a value from Ok type using *unwrap* method on variable.
Variable holds a value of Ok type.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 10.0)
v: float = r.unwrap() # v is 0.1
```

Obtain a value from Ok type using *unwrap* method on expression. 
Expression returns a value of Ok type.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


v: float = (f(1.0, 10.0)).unwrap() # v is 0.1
```


Obtain a value from Ok type using *unwrap_value* method on variable.
Variable holds a value of Ok type. If value is Ok type, *unwrap_value* has no effects, 
it is same as *unwrap* method.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a/b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res

    
r: Result = f(1.0, 10.0)
v: float = r.unwrap_value() # v is 0.1
```

Obtain a value from Ok type using *unwrap* method on expression. 
Expression returns a value of Ok type. If value is Ok type, *unwrap_value* has no effects, 
it is same as *unwrap* method.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


v: float = (f(1.0, 10.0)).unwrap_value() # v is 0.1
```

Obtain a value from Ok type using *unwrap_or* method on variable.
Variable holds a value of Ok type. If value is Ok type, *unwrap_or* has no effects, 
it is same as *unwrap* or *unwrap_value* method.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a/b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


r: Result = f(1.0, 10.0)
v: float = r.unwrap_or(10.1) # v is 0.1
```

Obtain a value from Ok type using *unwrap_or* method on expression.
Expression returns a value of Ok type. If value is Ok type, *unwrap_or* has no effects, 
it is same as *unwrap* or *unwrap_value* method.
```python
from thresult import Result, Ok, Err


def f(a: float, b: float) -> Result[float, Exception]:
    try:
        r: float = a / b
        res = Ok[float](r)
    except Exception as e:
        res = Err[Exception](e)
    
    return res


v: float = (f(1.0, 10.0)).unwrap_or(10.1) # v is 0.1
```

thresult provides type checking
```python
from thresult import Result, Ok, Err


class A:
    pass

    
a = A()
at: Ok[A] = Ok[A](a) 
at: Ok[A] = Ok[A](4) # TypeError: Got <class 'int'> but expected <class 'A'>
```

create custom types by extending Ok and Err
```python
from thresult import Result, Ok, Err


class AOk(Ok):
    pass


class AErr(Err):
    pass


CustomResult: type = AOk[float] | AErr[str]


r: CustomResult = AOk[int](5)
v: int = r.unwrap_value() # v holds value of 5
```

Using decorators to obtain Ok type
```python
from thresult import Result, Ok, Err


@Result[float, str]
def f(n: float):
    return 1/n


v = f(10.0) # Ok[<class 'float'>]
f: float = v.unwrap() # 0.1
```

Using decorators to obtain Err type
```python
from thresult import Result, Ok, Err


@Result[float, str]
def f(n: float):
    return 1 / n


v = f(0.0)
m: str = v.unwrap_value() # float division by zero
```

Using decorators to obtain Ok 
Custom result type
```python
from thresult import Result, Ok, Err


# custom result type
CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
f: float = v.unwrap() # 0.1
```

Using decorators to obtain Ok 
Custom result type
Using unwrap_value
```python
from thresult import Result, Ok, Err


# custom result type
CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
f: float = v.unwrap_value() # 0.1
```

Using decorators to obtain Ok 
Custom result type
Using unwrap_or
```python
from thresult import Result, Ok, Err


# custom result type
CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
f: float = v.unwrap_or(10.1) # 0.1
```

Using decorators to obtain Err 
Custom result type
```python
from thresult import Result, Ok, Err


CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]
# panic: raising exception
f: float = v.unwrap() # ZeroDivisionError: float division by zero
```

Using decorators to obtain Err 
Custom result type
```python
from thresult import Result, Ok, Err


CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]

# doesn't panic, returns exception's message as string
f: float = v.unwrap_value() # string value: float division by zero
```

Using decorators to obtain Err. 
Custom result type
Using unwrap_or()
```python
from thresult import Result, Ok, Err


CustomResult: type = Ok[float] | Err[str] 


@CustomResult[float, 'str']
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]

# doesn't panic, returns custom exception's message as string
f: float = v.unwrap_or('Zero division: Custom message') # string value: Zero division: Custom message 
```

Using decorators to obtain Ok.
Custom result type and custom Ok type
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass

class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
```

Using decorators to obtain Ok.
Custom result type and custom Ok type.
Obtain value with *unwrap*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
r: float = v.unwrap() # 0.1
```

Using decorators to obtain Ok.
Custom result type and custom Ok type.
Obtain value with *unwrap_value*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
r: float = v.unwrap_value() # 0.1
```

Using decorators to obtain Ok.
Custom result type and custom Ok type.
Obtain value with *unwrap_or*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 

@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(10.0) # Ok[<class 'float'>]
r: float = v.unwrap_or(10.1) # 0.1
```

Using decorators to obtain Err.
Custom result type and custom Err type
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n

v = f(0.0) # Err[<class 'str'>]
```

Using decorators to obtain Err.
Custom result type and custom Err type. 
Handling exceptions using *unwrap*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]

try:
    v.unwrap()
except ZeroDivisionError as e:
    pass
```

Using decorators to obtain Err.
Custom result type and custom Err type.
Handling exceptions using *unwrap_value*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]
message: str = v.unwrap_value() # float division by zero
```

Using decorators to obtain Err.
Custom result type and custom Err type.
Handling exceptions using *unwrap_or*
```python
from thresult import Result, Ok, Err


class CustomOk(Ok):
    pass


class CustomErr(Err):
    pass


CustomResult: type = CustomOk[float] | CustomErr[str] 


@CustomResult[float, str]
def f(n: float):
    return 1 / n


v = f(0.0) # Err[<class 'str'>]
message: str = v.unwrap_or('Custom message') # Custom message
```

