diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e1cccce107dcfc60d0429cc126efdb4f79ce159..40969bf5ec771daa587adb4d99691a97e52ac277 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 d7f2aaaca0c06825190f832c3804b85314afb5a8..78bdc2bbe55a5b1c67875ce374ce0c60906877c4 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 0000000000000000000000000000000000000000..3c5c4bcb067d5db552075f653f2876a7867d0c7e --- /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 25b881ec50876c5ee456b9ba4148afaee79956ac..a385f7118325f21c89e905c5fcdb5763b47350ef 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 8b02f2ea49a261cb0eee25c86c68aacebc060cae..bea568620e6a1a322e7845644b4fb1734e2aef5f 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 0000000000000000000000000000000000000000..5cab7d1281149e98dcbd3663e6feda7e01e448f2 --- /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 fef31514d3f04930c5bcb564e3dda717c4170c72..7e9e789f6ff6525141eb400bd4fa941d562b42dc 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 4c95db2cc2112f2786d0486efd82f772a323769b..e314eee206fd0a518482fb650457609b8fedd842 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 e1022583b78f740fb26bd460d287c9f95c0b2c41..f6087502b4f7dcb957fecd13e46cc8eb0bca1833 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 db2f1d6bc777b7ec8f6edc5d52cd57ef62e73aab..d81879b20c58b2534f7865e0e086c771cd196d71 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 c503a3ebfa46fc8f860262571705f338ef7639f7..d670578727e5d75308a3ca8d7d9370c7c147461d 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 a43cd27b515dcb94a315fd60db0aa09d7025418d..547c0f9e8c19940469fa8bcacfbfd3a4eb6da31e 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 e76b6906ce451a5ce0cbc77784760eff928c39bc..2d2386952aabf7b23e6d5aaa7fcaf4750cc9967a 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 99225b858ee35625eef2781a6c38b4257afd4502..2a4cea52dae2fee9203bcce1cd9d995a4aeaad43 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 c3d0e12a2f542762913d8a66ae42b06fb62435bd..aff8d9eb6cd9c4c7347f2c6be35b0b3b0a712902 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 a005b03e3c75ef63069cb2e2cbcaecc45f99cd4a..ab8a7615e9bdf827100908e057efc81669eb297c 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 6fc817b32152063c8acc465802d4068569dbdaae..8927258822185b64b0055b3574e7111bb23b9deb 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 1b7c759342421b12ef89cb2d11337f116b0b8196..1ade023034d7ba06ccbb8795171212447255599e 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 1c0c01307aa57643502230b1bc9234987539ad0d..cecc9f68ee07162cf4d557c07fd9b2b511a74681 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 c452d5b321dc4423e536f4e4fde354dd12efc9ea..f253f11ffa952616ccb1569f7d845d886bbc6e1d 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 bb094b855e2b10371c548b861932ea3aaf7a42b2..611f651042442e1a959c7ceab44a276b10276146 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 e2adaa0c5f73989f1bfe55e27ce1d91c32f3bd7e..4881ad3f2afb81ff8ca51acb8eb67d98ea3bb83e 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)