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