All Projects â†’ alice-biometrics â†’ meiga

alice-biometrics / meiga

Licence: MIT license
🧙 A simple, typed and monad-based Result type for Python.

Programming Languages

python
139335 projects - #7 most used programming language

Projects that are alternatives of or similar to meiga

PredictEd
A rich edit control based text editor with text prediction and other smart features.
Stars: ✭ 32 (+33.33%)
Mutual labels:  typing
operators-service
Service Object based on Either Monad
Stars: ✭ 27 (+12.5%)
Mutual labels:  monads
type
A FOSS typing.io clone
Stars: ✭ 58 (+141.67%)
Mutual labels:  typing
flake8-mypy
A plugin for flake8 integrating Mypy.
Stars: ✭ 103 (+329.17%)
Mutual labels:  typing
flask-parameter-validation
Get and validate all Flask input parameters with ease.
Stars: ✭ 16 (-33.33%)
Mutual labels:  typing
keymacs
Ergonomic keyboard layout for nonergonomic keyboards.
Stars: ✭ 22 (-8.33%)
Mutual labels:  typing
typist
Web app practice and learn touch-typing.
Stars: ✭ 36 (+50%)
Mutual labels:  typing
computering
🎹 💨 Pretends you can type really fast
Stars: ✭ 23 (-4.17%)
Mutual labels:  typing
code-type
Practice code-typing with top 1000 keywords of the most popular programming languages.
Stars: ✭ 31 (+29.17%)
Mutual labels:  typing
clausejs
Write contract once. Get data & function validators & conformers, an accurate & readable project contract, auto-generated API documentation, generative test coverage, plus more. A tool that enables a more predictable workflow for developing your JavaScript projects.
Stars: ✭ 29 (+20.83%)
Mutual labels:  typing
futils
Utilities for generic functional programming
Stars: ✭ 21 (-12.5%)
Mutual labels:  monads
coqpit
Simple but maybe too simple config management through python data classes. We use it for machine learning.
Stars: ✭ 67 (+179.17%)
Mutual labels:  typing
pytermgui
Python TUI framework with mouse support, modular widget system, customizable and rapid terminal markup language and more!
Stars: ✭ 1,270 (+5191.67%)
Mutual labels:  typing
pyMonet
High abstract python library for functional programming. Contains algebraic data structures known (or unknown) from Haskell or Scala.
Stars: ✭ 31 (+29.17%)
Mutual labels:  monads
Forbind
Functional chaining and promises in Swift
Stars: ✭ 44 (+83.33%)
Mutual labels:  monads
aioapi
Yet another way to build APIs using AIOHTTP framework
Stars: ✭ 14 (-41.67%)
Mutual labels:  typing
freestyle-cassandra
Freestyle Cassandra
Stars: ✭ 17 (-29.17%)
Mutual labels:  monads
prisma-client-py
Prisma Client Python is an auto-generated and fully type-safe database client designed for ease of use
Stars: ✭ 739 (+2979.17%)
Mutual labels:  typing
woodwork
Woodwork is a Python library that provides robust methods for managing and communicating data typing information.
Stars: ✭ 97 (+304.17%)
Mutual labels:  typing
json2python-models
Generate Python model classes (pydantic, attrs, dataclasses) based on JSON datasets with typing module support
Stars: ✭ 119 (+395.83%)
Mutual labels:  typing

meiga 🧙 version ci pypi codecov

A simple, typed and monad-based Result type for Python.

Installation 💻

pip install meiga

Getting Started 📈

meiga 🧙 provides a simple and clear way of handling errors in Python without using Exceptions. This package can help you to dry your code helping on modeling the output of your classes and method.

This package provides a new type class, the Result[Type, Type] This Result type allows to simplify a wide range of problems, like handling potential undefined values, or reduce complexity handling exceptions. Additionally, code can be simplified following a semantic pipeline reducing the visual noise of checking data types, controlling runtime flow and side-effects.

This package is based in another solutions from another modern languages as this swift-based Result implementation.

Example

The best way to illustrate how meiga can help you is with an example.

Consider the following example of a function that tries to extract a String (str) for a given key from a Dict.

from meiga import Result, Error


class NoSuchKey(Error):
    pass


class TypeMismatch(Error):
    pass


def string_from_key(dictionary: dict, key: str) -> Result[str, Error]:
    if key not in dictionary.keys():
        return Result(failure=NoSuchKey())

    value = dictionary[key]
    if not isinstance(value, str):
        return Result(failure=TypeMismatch())

    return Result(success=value)

Returned value Result type provides a robust wrapper around the functions and methods. Rather than throw an exception, it returns a Result that either contains the str value for the given key, or an typed Error detailing what went wrong (Result[str, Error]).

Features

Result

Result[T, Error] 👉 A discriminated union that encapsulates successful outcome with a value of type T or a failure with an arbitrary Error exception.

Functions

Functions Definition
throw() Throws the encapsulated failure value if this instance derive from Error or BaseException.
unwrap() Returns the encapsulated value if this instance represents success or None if it is failure.
unwrap_or_throw() Returns the encapsulated value if this instance represents success or throws the encapsulated exception if it is failure.
unwrap_or_return() Returns the encapsulated value if this instance represents success or return Result as long as @meiga decorator wraps the function.
unwrap_or(failure_value) Returns the encapsulated value if this instance represents success or the selected failure_value if it is failure.
unwrap_or_else(on_failure) Returns the encapsulated value if this instance represents success or execute the on_failure function when it is failure.
unwrap_and(on_success) Returns the encapsulated value if this instance represents success and execute the on_success function when it is success.
handle(on_success,on_failure) Returns itself and execute the on_successfunction when the instance represemts success and the on_failure function when it is failure.
map(transform) Returns a transformed result applying transform function applied to encapsulated value if this instance represents success or failure

Properties

Properties Definition
value Returns the encapsulated value whether it's success or failure
is_success Returns true if this instance represents successful outcome. In this case is_failure returns false.
is_failure Returns true if this instance represents failed outcome. In this case is_success returns false

Let's image we have a dictionary that represent a user info data

>>> user_info = {"first_name": "Rosalia", "last_name": "De Castro", "age": 60}

And we try to obtain first_name

>>> result = string_from_key(dictionary=user_info, key="first_name")
Result[status: success | value: Rosalia]

You can check the status of the result

>>> result.is_success
True
>>> result.is_failure
False

If the result is a success you can get the expected value

>>> result.value
Rosalia 

Otherwise, if we try to access an invalid key or a non string value, returned result will be a failure.

>>> result = string_from_key(dictionary=user_info, key="invalid_key")
Result[status: failure | value: NoSuchKey]
>>> result.is_failure
True
>>> result.value
NoSuchKey() // Error 

Or

>>> result = string_from_key(dictionary=user_info, key="age")
Result[status: failure | value: TypeMismatch]
>>> result.is_failure
True
>>> result.value
TypeMismatch() // Error 

Alias

Use meiga aliases to improve the semantics of your code.

For success result you can use:

result = Result(success="Rosalia")
result = Success("Rosalia") # it is equivalent

If return value is a bool you can use:

result = Success()
result = Success(True)
result = isSuccess

For failure results:

class NoSuchKey(Error):
    pass

result = Result(failure=NoSuchKey())
result = Failure(NoSuchKey())

If you don't want to specify the error, you can use default value with:

result = Failure()
result = Failure(Error())
result = isFailure # Only valid for a failure result with non-specific Error() value

Bringing previous example back. that is the way you can use the alias:

from meiga import Result, Error, Success, Failure,


class NoSuchKey(Error):
    pass


class TypeMismatch(Error):
    pass


def string_from_key(dictionary: dict, key: str) -> Result[str, Error]:
    if key not in dictionary.keys():
        return Failure(NoSuchKey())

    value = dictionary[key]
    if not isinstance(value, str):
        return Failure(TypeMismatch())

    return Success(value)

Furthermore, there is a available a useful alias: NotImplementedMethodError

Use it when define abstract method that returns Result type

from meiga import Result, Error, NotImplementedMethodError

from abc import ABCMeta, abstractmethod

class AuthService:

    __metaclass__ = ABCMeta

    @abstractmethod
    def __init__(self, base_url: str):
        self.base_url = base_url

    @abstractmethod
    def create_token(self, client: str, client_id: str) -> Result[str, Error]:
        return NotImplementedMethodError

Advance Usage 🚀

Decorator

Use @meiga as a decorator to protect your results and prevent from unexpected exceptions. It allways returns a Result object.

@meiga
def create_user(user_id: UserId) -> BoolResult:
     user = user_creator.execute(user_id).unwrap_or_return()
     return repository.save(user)

When decorate staticmethod and classmethod check the order, otherwise it will raise an error (UnexpectedDecorationOrderError) as these kind of methods are not callable

class UserCreatorFactory:

    @staticmethod
    @meiga
    def from_version(version: str) -> Result[UserCreator, Error]:
        if version == "migration_v1":
            creator = UserCreator.build()
        else:
            creator = LegacyUserCreator.build()
        return Success(creator)

Unwrap Result

If you wrap a Result object, its will return a valid value if it is success. Otherwise, it will return None.

result = Result(success="Hi!")
value = result.unwrap()
assert value == "Hi!"

result = Failure(Error())
value = result.unwrap()

assert value is None

You can use unwrap_or_returnin combination with @meiga decorator. If something wrong happens unwraping your Result, the unwrap_or_return function will raise an Exception (ReturnErrorOnFailure). @meiga decorator allows to handle the exception in case of error and unwrap the value in case of success. The following example illustrate this:

from meiga import Result, Error
from meiga.decorators import meiga

@meiga
def handling_result(key: str) -> Result:
    user_info = {"first_name": "Rosalia", "last_name": "De Castro", "age": 60}
    first_name = string_from_key(dictionary=user_info, key=key).unwrap_or_return() 
    # Do whatever with the name
    name = first_name.lower()
    return Result(success=name)

If key is valid success value would be returned. Otherwise, an Error would be returned.

If you need to return a specific value if fails, you can do it with meiga:

first_name = string_from_key(dictionary=user_info, key=key).unwrap_or_return(return_value_on_failure=isSuccess) 

Handle Result

This framework also allows a method for handling Result type. handle method returns itself and execute the on_success function when the instance represemts success and the on_failure function when it is failure.

When the operations is executed with its happy path, handle function returns the success value, as with result.value.

>>> result = string_from_key(dictionary=user_info, key="first_name")
Result[status: success | value: Rosalia]
>>> first_name = result.handle()
Rosalia

In addition, you can call another function after evaluate the result. Use optional parameters success_handler and failure_handler (Callable functions).

def success_handler():
    print("Do my successful stuff here!")

def failure_handler():
     print("Do my failure stuff here!")


result = string_from_key(dictionary=user_info, key="first_name")

result.handle(on_success=success_handler, on_failure=failure_handler)
Additional parameters

If you need to add some arguments as a parameters, use success_args and failure_args:

def success_handler(param_1):
    print(f"param_1: {param_1}")

def failure_handler(param_1, param_2):
    print(f"param_1: {param_1}")
    print(f"param_2: {param_2}")


result = string_from_key(dictionary=user_info, key="first_name")

result.handle(on_success=success_handler, 
              on_failure=failure_handler,
              success_args=1,
              failure_args=(1, 2))
Additional parameters in combination with the Result itself

Sometimes a handle function will need information about external parameters and also about the result itself. Now, is possible this combination thanks to Result.__id__ identifier.

    parameters = (1, Result.__id__, 2)

    def on_success(param_1: int, result: Result, param_2: int):
        assert param_1 == 1
        assert isinstance(result, Result)
        assert result.value is True
        assert param_2 == 2

    def on_failure(param_1: int, result: Result, param_2: int):
        assert param_1 == 1
        assert isinstance(result, Result)
        assert result.value == Error()
        assert param_2 == 2

    @meiga
    def run():
        result.handle(
            on_success=on_success,
            on_failure=on_failure,
            success_args=parameters,
            failure_args=parameters,
        )

    run()

Test Assertions

To help us on testing functions that returns Result, meiga provide us two functions: assert_success and access_failure.

Check the following pytest-based test for more information: tests/unit/test_result_assertions.py

Contact 📬

[email protected]

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].