Functions and decorators

class satella.coding.expect_exception(exc_to_except: Type[Exception] | Tuple[Type[Exception]], else_raise: Type[Exception], *args, **kwargs)

A context manager to use as following:

>>> a = {'test': 2}
>>> with expect_exception(KeyError, ValueError, 'KeyError not raised'):
>>>     a['test2']

If other exception than the expected is raised, it is passed through

Parameters:
  • exc_to_except – a list of exceptions or a single exception to expect

  • else_raise – raise a particular exception if no exception is raised. This should be a callable that accepts provided args and kwargs and returns an exception instance.

  • args – args to provide to constructor

  • kwargs – kwargs to provide to constructor

satella.coding.enum_value(value)

If value is an enum, extract and return it’s value.

Otherwise, return it as-is.

Parameters:

value – value to extract enum from

Returns:

value

satella.coding.contains(needle, haystack) bool

A syntactic sugar for the following:

>>> for item in haystack:
>>>     if needle == item:
>>>         return True
>>> return False

Note that this is very like Python’s in operator, however it’s not quite same, since in doesn’t involve the __eq__ operator at every step!

This function for example allows you to circumvent Python’s limitations concerning ComparableEnum

Parameters:
  • needle – needle to check for

  • haystack – haystack to check against

Returns:

whether haystack contains the element

class satella.coding.class_or_instancemethod

A decorator to make your methods both classmethods (they will receive an instance of type as their first argument) or normal methods (they will receive an instance of their type).

Use like:

>>> class MyClass:
>>>     @class_or_instancemethod
>>>     def method(self_or_cls):
>>>         if isinstance(self_or_cls, MyClass):
>>>             # method code
>>>         else:
>>>             # classmethod code
satella.coding.chain_callables(callable1: Callable, callable2: Callable) Callable

Link two callables together. callable2, if it takes an argument, will receive callables’1 result, and if it takes no arguments it will received nothing.

Parameters:
  • callable1 – first callable to call

  • callable2 – callable to call with callable1’s result

Returns:

result of callable2

satella.coding.source_to_function(src: Callable | str) Callable[[Any], Any]

If src is callable, return it as-is Transform a string containing a Python expression with a variable x to a lambda.

It will be treated as if it was appended to ‘lambda x: ‘

WARNING: Do not run untrusted data. Familiarize yourself with the dangers of passing unvalidated data to exec() or eval()!

Parameters:

src – a callable or a Python string expression

Returns:

a callable

satella.coding.call_with_arguments(function: Callable, arguments: Dict[str, Any]) Any

Call a function, but with giving it arguments via a dictionary.

Dictionary should be a mapping of argument name to it’s value.

Parameters:
  • function – function to call

  • arguments – a dict of arguments : argument name => argument value. This dictionary will be modified!

Returns:

return value of the function

Raises:
  • TypeError – too few arguments, or some arguments required were missing

  • ValueError – too many arguments given

satella.coding.decorators.replace_argument_if(arg_name: str, structure: dict | list | tuple | PredicateClass, instance_of: Type | Tuple[Type, ...] | None = None, predicate: Callable[[T], bool] | None = None)

Examine arguments of the callable that will be decorated with this.

If argument arg_name is found to be an instance of instance_of, it will be replaced by a structure defined a structure.

Parameters:
  • arg_name – argument to replace

  • instance_of – type

  • predicate – alternative condition of replacement. If this is given, predicate is called on the value of the argument and replacement is done if it returns True

  • structure – a callable that takes original argument and returns new, or a structure made of these

satella.coding.get_arguments(function: Callable, *args, **kwargs) Dict[str, Any]

Return local variables that would be defined for given function if called with provided arguments.

Note that this function will not return the “self” argument of methods and it won’t return the class of “cls” of classmethods.

Parameters:
  • function – callable to examine

  • args – arguments to provide

  • kwargs – keyword arguments to provide

Returns:

a dictionary of local variables with their values, as they would appear in function if called with provided arguments

Raises:

TypeError – the dictionary cannot be created with provided arguments

satella.coding.queue_iterator(queue: Queue) Iterator

Syntactic sugar for

>>> while queue.qsize() > 0:
>>>     yield queue.get()
satella.coding.update_key_if_not_none(dictionary: ~typing.Dict, key: ~typing.Hashable | ~typing.Dict, value: ~typing.Any | ~typing.Type[~satella.coding.misc._BLANK] = <class 'satella.coding.misc._BLANK'>) Dict

Syntactic sugar for

>>> if value is not None:
>>>     dictionary[key] = value

If value is passed, else

>>> for key, value in key.items():
>>>     if value is not None:
>>>         dictionary[key] = value
Parameters:
  • dictionary – dictionary to update

  • key – key to use or a dictionary of items

  • value – value to use

Returns:

the dictionary itself

satella.coding.update_key_if_true(dictionary: ~typing.Dict, key: ~typing.Hashable, value: ~typing.Any, flag: bool | ~typing.Type[~satella.coding.misc._BLANK] = <class 'satella.coding.misc._BLANK'>) Dict

If flag is True, execute dictionary[key] = value

Parameters:
  • dictionary – dictionary to mutate

  • key – dictionary key to use

  • value – dictionary value to set

  • flag – whether to execute the setting operation. If let at default, flag will be calculated from boolean of the value

Returns:

the dict itself

satella.coding.update_attr_if_none(obj: object, attr: str, value: Any, on_attribute_error: bool = True, if_value_is_not_none: bool = False) object

Updates the object attribute, if it’s value is None, or if it yields AttributeError (customizable as per on_attribute_error parameter)

Parameters:
  • obj – object to alter

  • attr – attribute to set

  • value – value to set

  • on_attribute_error – whether to proceed with setting the value on AttributeError while trying to read given attribute. If False, AttributeError will be raised.

  • if_value_is_not_none – update object unconditionally, if only value is not None

Returns:

obj

satella.coding.merge_dicts(v1: Any, v2: Any) Any

Try to merge two dicts/list together. If key collision is found, value from v2 will be taken.

If the objects aren’t dicts or lists, v2 will be returned.

Lists will be concatenated, and dicts updated. v1 will be updated in-place!

satella.coding.static_var(var_name: str, starting_value: Any | None = None)

Declare a static variable for given function

Use it like:

>>> @static_var('counter', 2)
>>> def count():
>>>     count.counter += 1

or:

>>> class MyClass:
>>>     @static_var('counter', 2)
>>>     def count(self):
>>>         MyClass.count.counter += 1
satella.coding.silence_excs(*exc_types: Type[Exception], returns=None, returns_factory: Callable[[], Any] | None = None)

Silence given exception types.

Can be either a decorator or a context manager.

If you are using it as a decorator, you can specify what value should the function return by using the returns kwarg:

>>> @silence_excs(KeyError, returns=5)
>>> def returns_5():
>>>     raise KeyError()
>>> assert returns_5() == 5

Or if you want to you can specify a callable that will return the value you want to return

>>> @silence_excs(KeyError, returns_factory=lambda: 5)
>>> def returns_5():
>>>     raise KeyError()
>>> assert returns_5() == 5
Raises:

ValueError – you gave both returns and returns_factory. You can only pass one of them!

class satella.coding.log_exceptions(logger: ~logging.Logger, severity: int = 40, format_string: str = '{e}', locals_: ~typing.Dict | None = None, exc_types: ~typing.Type[Exception] | ~typing.Sequence[~typing.Type[Exception]] = <class 'Exception'>, swallow_exception: bool = False)

Decorator/context manager to log your exceptions into the log.

The exception will be logged and re-raised.

Logger will be passed the exception instance as exc_info.

Parameters:
  • logger – a logger to which the exception has to be logged

  • severity – a severity level

  • format_string

    a format string with fields: - e : the exception instance itself - args : positional arguments with which the function was called, unavailable if context

    manager

    • kwargskeyword arguments with which the function was called, unavailable if context

      manager

    You can specify additional fields providing the locals_ argument Example: “{exc_type} occurred with message {exc_val} with traceback {exc_tb}”

  • locals – local variables to add to the format string. args and kwargs will be overwritten by this, but e will never be overwritten.

  • exc_types – logger will log only on those exceptions. Default is None which means log on all exceptions

  • swallow_exception – if True, exception will be swallowed

class satella.coding.rethrow_as(*pairs: ~typing.Type[Exception] | ~typing.Tuple[~typing.Type[Exception]], exception_preprocessor: ~typing.Callable[[Exception], str] | None = <built-in function repr>, returns=None, returns_factory: ~typing.Callable[[], ~typing.Any] | None = None)

Transform some exceptions into others.

Either a decorator or a context manager

New exception will be created by calling exception to transform to with repr of current one.

Note

This checks if exception matches directly via isinstance, so defining your own subclassing hierarchy by __isinstance__ or __issubclass__ will work here.

You can also provide just two exceptions, eg.

>>> rethrow_as(NameError, ValueError)

You can also provide a pairwise translation, eg. from NameError to ValueError and from OSError to IOError

>>> rethrow_as((NameError, ValueError), (OSError, IOError))

If the second value is a None, exception will be silenced.

Pass tuples of (exception to catch - exception to transform to).

Warning

Try to use reraise_as instead. However, during to richer set of switches and capability to return a value this is not deprecated.

Parameters:
  • exception_preprocessor – other callable/1 to use instead of repr. Should return a str, a text description of the exception

  • returns – what value should the function return if this is used as a decorator

  • returns_factory – a callable that returns the value this function should return is this is used as as decorator

Raises:

ValueError – you specify both returns and returns_factory

class satella.coding.reraise_as(source_exc: Type[Exception] | Tuple[Type[Exception]], target_exc: Type[Exception] | None, *args, **kwargs)

Transform some exceptions into others.

Either a decorator or a context manager

New exception will be created by calling exception to transform to with repr of current one.

You can also provide just two exceptions, eg.

>>> reraise_as(NameError, ValueError, 'a value error!')

You can also provide a catch-all:

>>> reraise_as((NameError, ValueError), OSError, 'an OS error!')

New exception will be raised from the one caught!

Note

This checks if exception matches directly via isinstance, so defining your own subclassing hierarchy by __isinstance__ or __issubclass__ will work here.

This is meant as an improvement of rethrow_as

Parameters:
  • source_exc – source exception or a tuple of exceptions to catch

  • target_exc – target exception to throw. If given a None, the exception will be silently swallowed.

  • args – arguments to constructor of target exception

  • kwargs – keyword arguments to constructor of target exception

satella.coding.catch_exception(exc_class: Type[Exception] | Tuple[Type[Exception], ...], clb: Callable[[], T | None], return_instead: T | None = None, return_value_on_no_exception: bool = False) Exception | T

Catch exception of given type and return it. Functionally equivalent to:

>>> try:
>>>     v = clb()
>>>     if return_value_on_no_exception:
>>>         return v
>>> except exc_class as e:
>>>     if return_instead:
>>>         return return_instead
>>>     return e

If a different class of exception is caught, it will be propagated.

Parameters:
  • exc_class – Exception classes to catch

  • clb – callable/0 to call to raise the exception

  • return_instead – what to return instead of the function result if it didn’t end in an exception

  • return_value_on_no_exception – whether to return the function result if exception didn’t happen

Raises:

ValueError – an exception was not thrown

satella.coding.raises_exception(exc_class: Type[Exception] | Tuple[Type[Exception], ...], clb: Callable[[], None]) bool

Does the callable raise a given exception?

Wrapping classes with something

Sometimes you need to wrap all methods/properties in given class with a common decorator. Here’s the function you can use:

satella.coding.wrap_with(callables: ~typing.Callable[[~typing.Callable], ~typing.Callable] = <function <lambda>>, properties: ~typing.Callable[[property], property] = <function <lambda>>, selector_callables: ~typing.Callable[[~typing.Callable], bool] = <function <lambda>>, selector_properties: ~typing.Callable[[property], bool] = <function <lambda>>)

A metaclass that wraps all elements discovered in this class with something

Example:

>>> def make_double(fun):
>>>     return lambda self, x: fun(x)*2
>>> class Doubles(metaclass=wrap_all_methods_with(make_double)):
>>>     def return_four(self, x):
>>>         return 2
>>> assert Doubles().return_four(4) == 4

Note that every callable that appears in the class namespace, ie. object that has __call__ will be considered for wrapping.

This is compatible with the abc.ABCMeta metaclass

Parameters:
  • callables – function to wrap all callables with given class with

  • properties – function to wrap all properties with given class with

  • selector_callables – additional criterion to be ran on given callable before deciding to wrap it. It must return True for wrapping to proceed.

  • selector_properties – additional criterion to be ran on given property before deciding to wrap it. It must return True for wrapping to proceed.

In order to more easily construct functions that will wrap properties, the following was provided:

satella.coding.wrap_property(getter: ~typing.Callable[[~typing.Callable[[object], ~typing.Any]], ~typing.Callable[[object], ~typing.Any]] = <function <lambda>>, setter: ~typing.Callable[[~typing.Callable[[object, ~typing.Any], None]], ~typing.Callable[[object, ~typing.Any], None]] = <function <lambda>>, deleter: ~typing.Callable[[~typing.Callable[[object], None]], ~typing.Callable[[object], None]] = <function <lambda>>)

Construct a property wrapper.

This will return a function, that if given a property, will wrap it’s getter, setter and deleter with provided functions.

Getter, setter and deleter are extracted from fget, fset and fdel, so only native properties, please, not descriptor-objects.

Parameters:
  • getter – callable that accepts a callable(instance) -> value, and returns the same. Getter will be wrapped by this

  • setter – callable that accepts a callable(instance, value) and returns the same. Setter will be wrapped by this

  • deleter – callable that accepts a callable(instance), and returns the same. Deleter will be wrapped by this

You can also decorate given callables in order not to be wrapped with

satella.coding.dont_wrap(fun)

A special decorator to save given class member from being mulched by wrap_with

Function overloading

Warning

This is coded for cases where the function prototypes differ significantly, for ex. matches

only one prototype. For cases where a single call might match multiple prototypes, and if it’s desired that the implementation tells them apart, this implementation might not be of sufficient complexity.

Go file a ticket that you cannot use Satella with some implementation. Just type down what kind of implementation that was.

class satella.coding.overload(fun: Callable)

A class used for method overloading.

Warning

This feature is scheduled for an overhaul and may not work as promised. Keep that in mind.

Note that methods can be only overloaded by their positional, or positional-and-keyword arguments. Overload distinguishment will be done at the level of positional arguments only.

Note that typing checks will be done via isinstance().

Use like this:

>>> @overload
>>> def what_type(x: str):
>>>     print('String')
>>> @what_type.overload
>>> def what_type(x: int):
>>>     print('Int')
>>> what_type(5)
>>> what_type('string')

Note that this instance’s __wrapped__ will refer to the first function. TypeError will be called if no signatures match arguments.

property all_functions: Iterable[object]

Return a list of all functions registered within this overload

overload(fun)
Raises:

ValueError – this signature already has an overload

class satella.coding.TypeSignature(t_sign: Signature)

A type signature.

You can compare signatures:

>>> def a(y: object):
>>>     pass
>>> def b(y: int):
>>>     pass
>>> TypeSignature.from_fun(a) < TypeSignature(b)
can_be_called_with_args(*args, **kwargs) bool

Can this type signature be called with following arguments?

static from_fun(fun) TypeSignature

Return a type signature from a function

is_more_generic_than(b: TypeSignature) bool

Is this type signature more generic than an other?

matches(*args, **kwargs) bool

Does this invocation match this signature?

DocsFromParent

satella.coding.DocsFromParent(name: str, bases: Tuple[type], dictionary: dict) Type

A metaclass that fetches missing docstring’s for methods from the classes’ bases, looked up BFS. This will fetch the class’s docstring itself, if available and not present in the child.

>>> class Father:
>>>     def test(self):
>>>         '''my docstring'''
>>> class Child(Father, metaclass=DocsFromParent):
>>>     def test(self):
>>>         ...
>>> assert Child.test.__doc__ == 'my docstring'

CopyDocsFrom

satella.coding.CopyDocsFrom(target_cls: Type)

A metaclass to copy documentation from some other class for respective methods.

>>> class Source:
>>>     def test(self):
>>>        'docstring'
>>> class Target(metaclass=CopyDocsFrom(Source)):
>>>     def test(self):
>>>         ...
>>> assert Target.test.__doc__ == Source.test.__doc__
Parameters:

target_cls – class from which to copy the docs

metaclass_maker

Now, give the following type structure:

class MetaA(type):
    pass

class MetaB(type):
    pass

class A(metaclass=MetaA):
    pass

class B(metaclass=MetaB):
    pass

You just can’t construct the following class

class C(A,B):
    pass

Without running into TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.

Following function will help with that:

satella.coding.metaclass_maker(name: str, bases: tuple, a_dict: dict) Type

Automatically construct a compatible meta-class like interface. Use like:

>>> class C(A, B, metaclass=metaclass_maker):
>>>     pass

Deep comparison

To analyze why two objects don’t compare the same, you can use the following functions:

satella.coding.assert_equal(a, b)

Assert that two values are equal. If not, an satella.coding.Inequality exception will be thrown.

Objects are tried to compare using it’s __eq__.

Parameters:
  • a – first value to compare

  • b – second value to compare

Raises:

Inequal – objects were not equal

class satella.coding.Inequal(obj1, obj2, reason: InequalityReason)

An exception raised by deep_compare() if two objects don’t match

Variables:
  • obj1 – first object that was not equal, or key name

  • obj2 – second object that was not equal, or None

  • reason – (InequalityReason) reason for inequality

class satella.coding.InequalityReason(value)

An enumeration.

KEY_NOT_FOUND = 2

key given as obj1 was not found

LENGTH_MISMATCH = 1

length didn’t match

NOT_EQUAL = 0

direct eq yielded not equal