From 3b8f6847782160f8a167cf7f2b7e35bdf40f1966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl> Date: Sun, 29 Dec 2019 02:04:58 +0100 Subject: [PATCH] 2.1.11 --- CHANGELOG.md | 3 +- docs/coding/concurrent.rst | 3 ++ docs/coding/functions.rst | 19 +++++++++++ docs/coding/structures.rst | 34 +++++++++++++++++++ docs/index.rst | 2 ++ docs/json.rst | 18 ++++++++++ docs/posix.rst | 3 ++ satella/__init__.py | 2 +- satella/coding/__init__.py | 2 +- satella/coding/algos.py | 5 +++ satella/coding/decorators.py | 23 ++++++------- satella/coding/fun_static.py | 25 +++++--------- satella/coding/recast_exceptions.py | 20 +++++------ satella/coding/structures/singleton.py | 10 +++--- satella/coding/structures/structures.py | 30 ++++++++++++---- satella/coding/structures/typednamedtuple.py | 6 ++-- satella/configuration/schema/descriptors.py | 24 ++++++------- satella/exception_handling/dump_to_file.py | 4 +++ .../exception_handling/exception_handlers.py | 6 ++-- satella/exception_handling/global_eh.py | 1 + satella/instrumentation/trace_back.py | 12 +++---- satella/json.py | 1 + 22 files changed, 175 insertions(+), 78 deletions(-) create mode 100644 docs/coding/functions.rst create mode 100644 docs/json.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e1cccce..40969bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # v2.1.11 -* _TBA_ +* bugfix release: __all__ in [coding](satella/coding/__init__.py) fixed +* more docs # v2.1.10 diff --git a/docs/coding/concurrent.rst b/docs/coding/concurrent.rst index d7f2aaac..78bdc2bb 100644 --- a/docs/coding/concurrent.rst +++ b/docs/coding/concurrent.rst @@ -1,3 +1,6 @@ +Concurrent data structures +========================== + CallableGroup ------------- diff --git a/docs/coding/functions.rst b/docs/coding/functions.rst new file mode 100644 index 00000000..3c5c4bcb --- /dev/null +++ b/docs/coding/functions.rst @@ -0,0 +1,19 @@ +Functions +========= + +.. autofunction:: satella.coding.merge_dicts + +.. autofunction:: satella.coding.static_var + +.. autofunction:: satella.coding.silence_excs + +.. autoclass:: satella.coding.rethrow_as + :members: + +.. autofunction:: satella.coding.precondition + +.. autofunction:: satella.coding.for_argument + + + + diff --git a/docs/coding/structures.rst b/docs/coding/structures.rst index 25b881ec..a385f711 100644 --- a/docs/coding/structures.rst +++ b/docs/coding/structures.rst @@ -1,3 +1,8 @@ +Structures +========== + +The following is a guide to all the data structures that Satella defines. + Heap ---- @@ -25,7 +30,36 @@ try to assign something else there. .. autofunction:: satella.coding.typednamedtuple + +OmniHashableMixin +----------------- + +If you need quick __hash__ and __eq__ operators from listed fields of the class. + +.. autoclass:: satella.coding.OmniHashableMixin + :members: + +.. autofunction:: satella.coding.typednamedtuple + + Singleton --------- +Makes the resulting object's ``__init__()`` be called at most once, then caches the object and returns the same +upon each instantiation. + .. autofunction:: satella.coding.Singleton + +DictObject +---------- + +DictObject is an object constructed out of a dict, that allows it's values to be obtained as getattr(), and not only +getitem(). + +.. autoclass:: satella.coding.DictObject + :members: + +You can use the following function to recursively turn every dict into a DictObject + +.. autofunction:: satella.coding.apply_dict_object + diff --git a/docs/index.rst b/docs/index.rst index 8b02f2ea..bea56862 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,10 +9,12 @@ Welcome to satella's documentation! configuration/schema configuration/sources coding/monitor + coding/functions coding/structures coding/concurrent instrumentation/traceback instrumentation/metrics + json posix recipes diff --git a/docs/json.rst b/docs/json.rst new file mode 100644 index 00000000..5cab7d12 --- /dev/null +++ b/docs/json.rst @@ -0,0 +1,18 @@ +JSON +==== + +In order to better support JSON, you should declare each of your class that supports being converted to JSON +with + +.. autoclass:: satella.json.JSONAble + :members: + +Then you can convert structures made out of standard serializable Python JSON objects, such as dicts +and lists, and also JSONAble objects, by this all + +.. autofunction:: satella.json.json_encode + +You might also want to check out the JSONEncoder satella uses to do it. + +.. autoclass:: satella.json.JSONEncoder + :members: diff --git a/docs/posix.rst b/docs/posix.rst index fef31514..7e9e789f 100644 --- a/docs/posix.rst +++ b/docs/posix.rst @@ -1,3 +1,6 @@ +POSIX-specifics +=============== + suicide ------- diff --git a/satella/__init__.py b/satella/__init__.py index 4c95db2c..e314eee2 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1,3 +1,3 @@ # coding=UTF-8 -__version__ = '2.1.11a1' +__version__ = '2.1.11' diff --git a/satella/coding/__init__.py b/satella/coding/__init__.py index e1022583..f6087502 100644 --- a/satella/coding/__init__.py +++ b/satella/coding/__init__.py @@ -12,7 +12,7 @@ from .fun_static import static_var __all__ = [ - 'typednamedtuple', 'OmniHashableMixin' + 'typednamedtuple', 'OmniHashableMixin', 'TimeBasedHeap', 'Heap', 'CallableGroup', 'DictObject', 'apply_dict_object', 'Monitor', 'RMonitor', 'CallableGroup', 'LockedDataset', 'merge_dicts', 'for_argument', diff --git a/satella/coding/algos.py b/satella/coding/algos.py index db2f1d6b..d81879b2 100644 --- a/satella/coding/algos.py +++ b/satella/coding/algos.py @@ -4,6 +4,11 @@ __all__ = ['merge_dicts'] def merge_dicts(v1: tp.Any, v2: tp.Any) -> tp.Any: + """ + Try to merge two dicts/list together. If key collision is found, value from v2 will be taken. + + Lists will be concatenated, and dicts updated. v1 will be updated in-place! + """ if isinstance(v1, dict) and isinstance(v2, dict): for k in v2.keys(): try: diff --git a/satella/coding/decorators.py b/satella/coding/decorators.py index c503a3eb..d6705787 100644 --- a/satella/coding/decorators.py +++ b/satella/coding/decorators.py @@ -17,15 +17,15 @@ def precondition(*t_ops): You can do it like this: - @precondition(lambda x: x == 1) - def return_two(x): - return x*2 + >>> @precondition(lambda x: x == 1) + >>> def return_two(x): + >>> return x*2 or - @precondition('x == 1') - def return_two(x): - .. + >>> @precondition('x == 1') + >>> def return_two(x): + >>> .. If None is passed then argument will be always assumed to be True. You can use all standard locals in precondition. @@ -69,17 +69,16 @@ def precondition(*t_ops): def for_argument(*t_ops, **t_kwops): """ - Calls a callable for each of the arguments. + Calls a callable for each of the arguments. Pass None if you do not wish to process given argument. returns is a special keyword, a callable to process the result through Use like: - @for_argument(int, str, typed=bool, returns=int) - def check(val1, val2, typed='True'): - if typed: - return val1 + int(val2) - + >>> @for_argument(int, str, typed=bool, returns=int) + >>> def check(val1, val2, typed='True'): + >>> if typed: + >>> return val1 + int(val2) """ t_ops = [_NOP if op == 'self' else op for op in t_ops] returns = t_kwops.pop('returns', _NOP) diff --git a/satella/coding/fun_static.py b/satella/coding/fun_static.py index a43cd27b..547c0f9e 100644 --- a/satella/coding/fun_static.py +++ b/satella/coding/fun_static.py @@ -1,28 +1,21 @@ -import logging -import typing -import functools - -logger = logging.getLogger(__name__) - - -def static_var(var_name, value): +def static_var(var_name: str, starting_value): """ Declare a static variable for given function Use it like: - @static_var('counter', 2) - def count(): - count.counter += 1 + >>> @static_var('counter', 2) + >>> def count(): + >>> count.counter += 1 or: - class MyClass: - @static_var('counter', 2) - def count(): - MyClass.counter += 1 + >>> class MyClass: + >>> @static_var('counter', 2) + >>> def count(): + >>> MyClass.counter += 1 """ def decorate(func): - setattr(func, var_name, value) + setattr(func, var_name, starting_value) return func return decorate diff --git a/satella/coding/recast_exceptions.py b/satella/coding/recast_exceptions.py index e76b6906..2d238695 100644 --- a/satella/coding/recast_exceptions.py +++ b/satella/coding/recast_exceptions.py @@ -1,8 +1,6 @@ import functools -import logging import typing as tp -logger = logging.getLogger(__name__) __all__ = [ 'rethrow_as', @@ -24,20 +22,20 @@ class rethrow_as: Transform some exceptions into others. Either a decorator or a context manager - """ - def __init__(self, *pairs, **kwargs): - """ - Pass tuples of (exception to catch - exception to transform to). + New exception will be created by calling exception to transform to with + repr of current one. - New exception will be created by calling exception to transform to with - repr of current one. + You can also provide just two exceptions, eg. - You can also provide just two exceptions, eg. + >>> rethrow_as(NameError, ValueError) - rethrow_as(NameError, ValueError) + If the second value is a None, exception will be silenced. + """ - If the second value is a None, exception will be silenced. + def __init__(self, *pairs, **kwargs): + """ + Pass tuples of (exception to catch - exception to transform to). :param exception_preprocessor: other callable/1 to use instead od repr. Should return a text diff --git a/satella/coding/structures/singleton.py b/satella/coding/structures/singleton.py index 99225b85..2a4cea52 100644 --- a/satella/coding/structures/singleton.py +++ b/satella/coding/structures/singleton.py @@ -1,6 +1,3 @@ -# coding=UTF-8 -from __future__ import print_function, absolute_import, division - import functools @@ -8,6 +5,7 @@ __all__ = [ 'Singleton', ] + # Taken from https://wiki.python.org/moin/PythonDecoratorLibrary def Singleton(cls): """ @@ -15,9 +13,9 @@ def Singleton(cls): Usage: - @Singleton - class MyClass(object): - ... + >>> @Singleton + >>> class MyClass(object): + >>> ... """ cls.__new_old__ = cls.__new__ diff --git a/satella/coding/structures/structures.py b/satella/coding/structures/structures.py index c3d0e12a..aff8d9eb 100644 --- a/satella/coding/structures/structures.py +++ b/satella/coding/structures/structures.py @@ -16,13 +16,31 @@ __all__ = [ class OmniHashableMixin: + """ + A mix-in. Provides hashing and equal comparison for your own class using specified fields. + + Example of use: + + class Point2D(OmniHashableMixin): + _HASH_FIELDS_TO_USE = ['x', 'y'] + + def __init__(self, x, y): + ... + + and now class Point2D has defined __hash__ and __eq__ by these fields. + Do everything in your power to make specified fields immutable, as mutating them will result + in a different hash. + """ _HASH_FIELDS_TO_USE = [] def __hash__(self): return functools.reduce(operator.xor, (hash(getattr(self, fname)) \ for fname in self._HASH_FIELDS_TO_USE)) - def __eq__(self, other): + def __eq__(self, other: 'OmniHashableMixin') -> bool: + """ + Note that this will only compare _HASH_FIELDS_TO_USE + """ cons = lambda p: [getattr(p, fname) for fname in self._HASH_FIELDS_TO_USE] if cons(self) == cons(other): return True @@ -32,7 +50,7 @@ class OmniHashableMixin: else: return False - def __ne__(self, other): + def __ne__(self, other: 'OmniHashableMixin') -> bool: return not self.__eq__(other) @@ -93,6 +111,7 @@ class Heap(object): def pop(self) -> tp.Any: """ Return smallest element of the heap. + :raises IndexError: on empty heap """ return heapq.heappop(self.heap) @@ -120,7 +139,6 @@ class Heap(object): """ Return an iterator returning all elements in this heap sorted ascending. State of the heap is not changed - :return: Iterator """ heap = copy.copy(self.heap) while heap: @@ -130,17 +148,16 @@ class Heap(object): """ Return an iterator returning all elements in this heap sorted descending. State of the heap is not changed - :return: Iterator """ return reversed(list(self.iter_ascending())) def __len__(self) -> int: return len(self.heap) - def __str__(self): + def __str__(self) -> str: return '<satella.coding.Heap: %s elements>' % (len(self, )) - def __repr__(self): + def __repr__(self) -> str: return u'<satella.coding.Heap>' def __contains__(self, item) -> bool: @@ -204,6 +221,7 @@ class TimeBasedHeap(Heap): Return all elements less (sharp inequality) than particular value. This changes state of the heap + :param less: value to compare against :return: Iterator """ diff --git a/satella/coding/structures/typednamedtuple.py b/satella/coding/structures/typednamedtuple.py index a005b03e..ab8a7615 100644 --- a/satella/coding/structures/typednamedtuple.py +++ b/satella/coding/structures/typednamedtuple.py @@ -25,10 +25,10 @@ def typednamedtuple(cls_name, *arg_name_type): For example: - tnt = typednamedtuple('tnt', ('x', float), ('y', float)) - a = tnt('5.0', y=2) + >>> tnt = typednamedtuple('tnt', ('x', float), ('y', float)) + >>> a = tnt('5.0', y=2) - a.x is float, a.y is float too + a.x is float, a.y is float too """ fieldnames = [] diff --git a/satella/configuration/schema/descriptors.py b/satella/configuration/schema/descriptors.py index 6fc817b3..89272588 100644 --- a/satella/configuration/schema/descriptors.py +++ b/satella/configuration/schema/descriptors.py @@ -202,14 +202,14 @@ class Dict(Descriptor): Use like: - Dict([ - create_key(String(), 'key_s'), - create_key(Integer(), 'key_i'), - create_key(Float(), 'key_f'), - create_key(String(), 'key_not_present', optional=True, - default='hello world'), - create_key(IPv4(), 'ip_addr') - ]) + >>> Dict([ + >>> create_key(String(), 'key_s'), + >>> create_key(Integer(), 'key_i'), + >>> create_key(Float(), 'key_f'), + >>> create_key(String(), 'key_not_present', optional=True, + >>> default='hello world'), + >>> create_key(IPv4(), 'ip_addr') + >>>]) """ BASIC_MAKER = dict CHECKERS = [must_be_type(dict)] @@ -312,11 +312,11 @@ def register_custom_descriptor(name: str): Use like: - @register_custom_descriptor('ipv6') - class IPv6(Regexp): - REGEXP = '(\A([0-9a-f]{1,4}:)' ... + >>> @register_custom_descriptor('ipv6') + >>> class IPv6(Regexp): + >>> REGEXP = '(\A([0-9a-f]{1,4}:)' ... - name -- name under which it is supposed to be invokable + :param name: under which it is supposed to be invokable """ def inner(cls): BASE_LOOKUP_TABLE[name] = cls diff --git a/satella/exception_handling/dump_to_file.py b/satella/exception_handling/dump_to_file.py index 1b7c7593..1ade0230 100644 --- a/satella/exception_handling/dump_to_file.py +++ b/satella/exception_handling/dump_to_file.py @@ -27,6 +27,8 @@ class AsStream: def __init__(self, o: AsStreamTypeAccept, human_readable: bool): """ + A stream to dump to + :param o: stream, or a file name to use, or None to use /dev/null :param human_readable: whether the output should be human-readable or a pickle (False for pickle) @@ -84,6 +86,8 @@ class DumpToFileHandler(BaseExceptionHandler): def __init__(self, human_readables: tp.Iterable[AsStreamTypeAcceptHR], trace_pickles: tp.Iterable[AsStreamTypeAcceptpIN] = None): """ + Handler that dumps an exception to a file. + :param human_readables: iterable of either a file-like objects, or paths where human-readable files will be output :param trace_pickles: iterable of either a file-like objects, or paths where pickles with stack status diff --git a/satella/exception_handling/exception_handlers.py b/satella/exception_handling/exception_handlers.py index 1c0c0130..cecc9f68 100644 --- a/satella/exception_handling/exception_handlers.py +++ b/satella/exception_handling/exception_handlers.py @@ -51,9 +51,9 @@ def exception_handler(priority: int = NORMAL_PRIORITY): """ Convert a callable to an FunctionExceptionHandler. Usage - @exception_handler(priority=-10) - def handle_exc(type, val, traceback): - ... + >>> @exception_handler(priority=-10) + >>> def handle_exc(type, val, traceback): + >>> ... :return: ExceptionHandler instance """ diff --git a/satella/exception_handling/global_eh.py b/satella/exception_handling/global_eh.py index c452d5b3..f253f11f 100644 --- a/satella/exception_handling/global_eh.py +++ b/satella/exception_handling/global_eh.py @@ -29,6 +29,7 @@ class GlobalExcepthook: def remove_hook(self, hook: BaseExceptionHandler): """ Unregister a hook + :param hook: hook to remove :raise ValueError: if hook not in list """ diff --git a/satella/instrumentation/trace_back.py b/satella/instrumentation/trace_back.py index bb094b85..611f6510 100644 --- a/satella/instrumentation/trace_back.py +++ b/satella/instrumentation/trace_back.py @@ -11,12 +11,12 @@ but that may involve an import. Use in such a way: - try: - ... - except WhateverError as e: - tp = Traceback() - print(tp.pretty_print()) - # you can now pickle it if you wish to +>>> try: +>>> ... +>>> except WhateverError as e: +>>> tp = Traceback() +>>> print(tp.pretty_print()) +>>> # you can now pickle it if you wish to """ import inspect import io diff --git a/satella/json.py b/satella/json.py index e2adaa0c..4881ad3f 100644 --- a/satella/json.py +++ b/satella/json.py @@ -21,6 +21,7 @@ class JSONEncoder(json.JSONEncoder): def json_encode(x) -> str: """ Convert an object to JSON. Will properly handle subclasses of JSONAble + :param x: object to convert """ return JSONEncoder().encode(x) -- GitLab