Misc. pure-python utilities
Project description
Overview
========
.. image:: https://travis-ci.org/Infinidat/infi.pyutils.png?branch=master
*infi.pyutils* is a set of utilities for various tasks in Python.
Contents
========
Iteration Utilities
---------------
iterate
+++++++
*iterate* is a django-style for loop over elements, yielding convenient information about the iterated collection::
>>> from __future__ import print_function
>>> from infi.pyutils import iterate
>>> for iteration, element in iterate(range(3)):
... print(element)
... print(iteration.first)
... print(iteration.counter0)
... print(iteration.counter1)
... print(iteration.last)
0
True
0
1
False
1
False
1
2
False
2
False
2
3
True
This even works for generators and other iterables::
>>> list(iterate(i for i in range(3)))[-1][0].last
True
.. note:: For iterated collections which are not simple ones (i.e. generators or other types), some prefetching is made to determing last elements
renumerate
++++++++++
*renumerate* is like *enumerate*, only backwards. This is useful for popping from a list in-place::
>>> from infi.pyutils import renumerate
>>> l = [1, 2, 3, 4]
>>> for index, element in renumerate(l):
... if element % 2 == 0:
... unused = l.pop(index)
>>> l
[1, 3]
This also works for generators (although slightly less efficient):
>>> x = list(renumerate(i for i in range(3)))
>>> x
[(2, 2), (1, 1), (0, 0)]
*infi.pyutils* provides a set of mixin classes to make objects comparable and hashable based on a single key::
>>> from infi.pyutils import ComparableByKey
>>> class MyComparableType(ComparableByKey):
... def __init__(self, value):
... super(MyComparableType, self).__init__()
... self.value = value
... def _get_key(self):
... return self.value
>>> MyComparableType("c") > MyComparableType("b")
True
Reflection
----------
Method Map
++++++++++
Method maps is intended for the repeating pattern described below:
::
>>> class Handler(object):
... def handle_string(self, s):
... if s == 'some.string.1':
... return self._handle_string_1()
... elif s == 'some.string.2':
... return self._handle_string_2()
... raise NotImplementedError()
If the strings can be legitimate python variable names, one would interpolate the string in the handler name, and then use getattr to get them. Unfortunately, this is not possible with any string (and also not very explicit).
Using Method Maps is pretty straightforward:
>>> from infi.pyutils.method_map import MethodMap
>>> class Handler(object):
... HANDLERS = MethodMap()
... @HANDLERS.registering('some.string.1')
... def _handle_string_1(self):
... return 1
... @HANDLERS.registering('some.string.2')
... def _handle_string_2(self):
... return 2
... def handle_string(self, s):
... handler = self.HANDLERS.get(s, None)
... if handler is None:
... raise NotImplementedError()
... return handler()
>>> h = Handler()
>>> h.handle_string('some.string.1')
1
>>> h.handle_string('some.string.2')
2
>>> h.handle_string('bla') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
NotImplementedError
Functors
--------
*infi.pyutils.functors* is a utility package for working with function or function-like objects.
Misc. Functors
++++++++++++++
*Always* is used to constantly return a value:
::
>>> from infi.pyutils.functors import Always
>>> a = Always(5)
>>> a
<Always 5>
>>> a()
5
>>> a(1, 2, 3)
5
*Identity* is a functor that always returns its single argument:
::
>>> from infi.pyutils.functors import Identity
>>> Identity
<Identity>
>>> Identity(2)
2
>>> obj = object()
>>> Identity(obj) is obj
True
PASS
++++
PASS is a 'null functor'. You can always call it anyway you like, it will always return None::
>>> from infi.pyutils.functors import PASS
>>> PASS(1, 2, 3)
>>> PASS(666, a=2, c=4)
You can also use it as a context manager that does nothing::
>>> with PASS:
... pass
Predicates
++++++++++
Predicates are functors taking arguments and returning True/False
::
>>> from infi.pyutils.predicates import Predicate
>>> p = Predicate(lambda obj: obj is None)
>>> p(None)
True
>>> p(1)
False
AlwaysTrue and AlwaysFalse are available:
::
>>> from infi.pyutils.predicates import AlwaysTrue, AlwaysFalse
>>> AlwaysTrue(1)
True
>>> AlwaysTrue()
True
>>> AlwaysFalse(1)
False
>>> AlwaysFalse(343)
False
>>> AlwaysFalse()
False
Identity:
::
>>> from infi.pyutils.predicates import Identity
>>> is_none = Identity(None)
>>> is_none
<is None>
>>> is_none(None)
True
>>> is_none(1)
False
Equality:
::
>>> from infi.pyutils.predicates import Equality
>>> class NeverEquals(object):
... def __eq__(self, other):
... return False
>>> equals_to_1 = Equality(1)
>>> equals_to_1
< == 1>
>>> equals_to_1(1)
True
>>> equals_to_1(2)
False
>>> obj = NeverEquals()
>>> Equality(obj)(obj) # make sure it's not identity
False
Attribute checks:
::
>>> class SomeObject(object):
... pass
>>> a = SomeObject()
>>> a.x = 1
>>> a.y = 2
>>> a.z = 4
>>> b = SomeObject()
>>> b.x = 2
>>> b.y = 3
>>> b.z = 4
>>> from infi.pyutils.predicates import ObjectAttributes
>>> match = ObjectAttributes(z=4)
>>> match
<.z==4>
>>> match(a)
True
>>> match(b)
True
>>> match = ObjectAttributes(x=1, y=2)
>>> match(a)
True
>>> match(b)
False
>>> ObjectAttributes(missing_attribute=2)(a)
False
Dictionary items check:
>>> d = dict(a=1, b=2)
>>> from infi.pyutils.predicates import DictionaryItems
>>> match = DictionaryItems(a=1)
>>> match
<['a']==1>
>>>
>>> match(d)
True
>>> match(dict(a=2, b=2))
False
>>> match(dict())
False
>>> match(dict(b=2))
False
Logical aggregations are done with And, Or, Not:
::
>>> from infi.pyutils.predicates import And, Or, Not
>>> obj = object()
>>> is_none_or_obj = Or(Identity(obj), Identity(None))
>>> is_none_or_obj #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Or(<is <object object at 0x...>>, <is None>)
>>> is_none_or_obj(obj)
True
>>> is_none_or_obj(None)
True
>>> is_none_or_obj(1)
False
>>> is_not_none = Not(is_none)
>>> is_not_none
<not <is None>>
>>> is_not_none(None)
False
>>> is_not_none(1)
True
Lazy
----
*infi.pyutils.lazy* presents utilities for lazy computation and caching
cached_property and cached_method
+++++++++++++++++++++++++++++++++
>>> from infi.pyutils.lazy import cached_property
>>> class MyClass(object):
... called = False
... @cached_property
... def value(self):
... assert not self.called
... self.called = True
... return 1
>>> m = MyClass()
>>> m.value
1
>>> m.value
1
>>> from infi.pyutils.lazy import cached_method
>>> class MyClass(object):
... called = False
... @cached_method
... def get_value(self):
... assert not self.called
... self.called = True
... return 1
>>> m = MyClass()
>>> m.get_value()
1
>>> m.get_value()
1
Decorator Utilities
-------------------
The *infi.pyutils.decorators* package contains a specially-crafted *wraps* implementation (functools.wraps counterpart) preserving information on the originally wrapped function. It also patches *inspect.getargspec* and IPython's similar mechanisms in order to display proper argument information on wrapped functions. It is therefore recommended to use it instead of the default ones.
Context Utilities
-----------------
*infi.pyutils.contexts* contains *contextmanager*, a drop-in replacement for *contextlib.contextmanager*, using the crafted *wraps* implementation from *decorators*.
Import Utilities
----------------
*infi.pyutils.importing* contains **import_file**, a function for importing a module by its name::
>>> from infi.pyutils.importing import *
>>> import tempfile, os
>>> temp_dir = tempfile.mkdtemp()
>>> filename = os.path.join(temp_dir, "my_file.py")
>>> with open(filename, "w") as f:
... _ = f.write("a=2")
>>> module = import_file(filename)
>>> module.a
2
Reference Counters
------------------
*infi.pyutils.reference_counter* is an implementation of a reference counter with dependency support::
>>> from infi.pyutils.reference_counter import ReferenceCounter
>>> r = ReferenceCounter()
>>> r.get_reference_count()
0
>>> r.add_reference()
>>> r.get_reference_count()
1
Several reference counters can be chained via dependency::
>>> r1 = ReferenceCounter()
>>> r2 = ReferenceCounter()
>>> r1.depend_on_counter(r2)
>>> r1.add_reference()
>>> r2.get_reference_count()
1
Also, as a convenience, if you add a dependency to a counter that already has a nonzero refcount, the dependent counter is automatically increased as well::
>>> r1 = ReferenceCounter()
>>> r1.add_reference()
>>> r2 = ReferenceCounter()
>>> r1.depend_on_counter(r2)
>>> r2.get_reference_count()
1
You can add a callback to be called when the reference count drops to zero::
>>> r1 = ReferenceCounter()
>>> def callback(r):
... assert r is r1
... print("called!")
>>> r1.add_zero_refcount_callback(callback)
>>> r1.add_reference()
>>> r1.remove_reference()
called!
Misc. Utilities
---------------
recursive_getattr
+++++++++++++++++
>>> from infi.pyutils import recursive_getattr
>>> class Object(object):
... pass
>>> a = Object()
>>> a.b = Object()
>>> a.b.c = 2
>>> recursive_getattr(a, 'b.c')
2
>>> recursive_getattr(a, 'b.d', 4)
4
Reprify
+++++++
*Reprify* is intended to force a __repr__/__str__ on objects that don't properly support them::
>>> from infi.pyutils import Reprify
>>> class SomeObject(object):
... pass
>>> some_obj = SomeObject()
>>> s = Reprify(some_obj, repr='some_repr_here', str='some_str_here')
>>> repr(s)
'some_repr_here'
>>> str(s)
'some_str_here'
While still preserving the identity and attributes::
>>> some_obj.attr = 2
>>> s.attr
2
>>> isinstance(s, SomeObject)
True
Exception Retry Utilities
-------------------------
The *infi.pyutils.retry* package provides a mechanism to easily define when and how to retry a failed operation. It allows you to wrap methods in your class and plug in an error handling strategy (infinite retries, retry N times, expontential decay timeout, etc.) without changing your code.
========
.. image:: https://travis-ci.org/Infinidat/infi.pyutils.png?branch=master
*infi.pyutils* is a set of utilities for various tasks in Python.
Contents
========
Iteration Utilities
---------------
iterate
+++++++
*iterate* is a django-style for loop over elements, yielding convenient information about the iterated collection::
>>> from __future__ import print_function
>>> from infi.pyutils import iterate
>>> for iteration, element in iterate(range(3)):
... print(element)
... print(iteration.first)
... print(iteration.counter0)
... print(iteration.counter1)
... print(iteration.last)
0
True
0
1
False
1
False
1
2
False
2
False
2
3
True
This even works for generators and other iterables::
>>> list(iterate(i for i in range(3)))[-1][0].last
True
.. note:: For iterated collections which are not simple ones (i.e. generators or other types), some prefetching is made to determing last elements
renumerate
++++++++++
*renumerate* is like *enumerate*, only backwards. This is useful for popping from a list in-place::
>>> from infi.pyutils import renumerate
>>> l = [1, 2, 3, 4]
>>> for index, element in renumerate(l):
... if element % 2 == 0:
... unused = l.pop(index)
>>> l
[1, 3]
This also works for generators (although slightly less efficient):
>>> x = list(renumerate(i for i in range(3)))
>>> x
[(2, 2), (1, 1), (0, 0)]
*infi.pyutils* provides a set of mixin classes to make objects comparable and hashable based on a single key::
>>> from infi.pyutils import ComparableByKey
>>> class MyComparableType(ComparableByKey):
... def __init__(self, value):
... super(MyComparableType, self).__init__()
... self.value = value
... def _get_key(self):
... return self.value
>>> MyComparableType("c") > MyComparableType("b")
True
Reflection
----------
Method Map
++++++++++
Method maps is intended for the repeating pattern described below:
::
>>> class Handler(object):
... def handle_string(self, s):
... if s == 'some.string.1':
... return self._handle_string_1()
... elif s == 'some.string.2':
... return self._handle_string_2()
... raise NotImplementedError()
If the strings can be legitimate python variable names, one would interpolate the string in the handler name, and then use getattr to get them. Unfortunately, this is not possible with any string (and also not very explicit).
Using Method Maps is pretty straightforward:
>>> from infi.pyutils.method_map import MethodMap
>>> class Handler(object):
... HANDLERS = MethodMap()
... @HANDLERS.registering('some.string.1')
... def _handle_string_1(self):
... return 1
... @HANDLERS.registering('some.string.2')
... def _handle_string_2(self):
... return 2
... def handle_string(self, s):
... handler = self.HANDLERS.get(s, None)
... if handler is None:
... raise NotImplementedError()
... return handler()
>>> h = Handler()
>>> h.handle_string('some.string.1')
1
>>> h.handle_string('some.string.2')
2
>>> h.handle_string('bla') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
NotImplementedError
Functors
--------
*infi.pyutils.functors* is a utility package for working with function or function-like objects.
Misc. Functors
++++++++++++++
*Always* is used to constantly return a value:
::
>>> from infi.pyutils.functors import Always
>>> a = Always(5)
>>> a
<Always 5>
>>> a()
5
>>> a(1, 2, 3)
5
*Identity* is a functor that always returns its single argument:
::
>>> from infi.pyutils.functors import Identity
>>> Identity
<Identity>
>>> Identity(2)
2
>>> obj = object()
>>> Identity(obj) is obj
True
PASS
++++
PASS is a 'null functor'. You can always call it anyway you like, it will always return None::
>>> from infi.pyutils.functors import PASS
>>> PASS(1, 2, 3)
>>> PASS(666, a=2, c=4)
You can also use it as a context manager that does nothing::
>>> with PASS:
... pass
Predicates
++++++++++
Predicates are functors taking arguments and returning True/False
::
>>> from infi.pyutils.predicates import Predicate
>>> p = Predicate(lambda obj: obj is None)
>>> p(None)
True
>>> p(1)
False
AlwaysTrue and AlwaysFalse are available:
::
>>> from infi.pyutils.predicates import AlwaysTrue, AlwaysFalse
>>> AlwaysTrue(1)
True
>>> AlwaysTrue()
True
>>> AlwaysFalse(1)
False
>>> AlwaysFalse(343)
False
>>> AlwaysFalse()
False
Identity:
::
>>> from infi.pyutils.predicates import Identity
>>> is_none = Identity(None)
>>> is_none
<is None>
>>> is_none(None)
True
>>> is_none(1)
False
Equality:
::
>>> from infi.pyutils.predicates import Equality
>>> class NeverEquals(object):
... def __eq__(self, other):
... return False
>>> equals_to_1 = Equality(1)
>>> equals_to_1
< == 1>
>>> equals_to_1(1)
True
>>> equals_to_1(2)
False
>>> obj = NeverEquals()
>>> Equality(obj)(obj) # make sure it's not identity
False
Attribute checks:
::
>>> class SomeObject(object):
... pass
>>> a = SomeObject()
>>> a.x = 1
>>> a.y = 2
>>> a.z = 4
>>> b = SomeObject()
>>> b.x = 2
>>> b.y = 3
>>> b.z = 4
>>> from infi.pyutils.predicates import ObjectAttributes
>>> match = ObjectAttributes(z=4)
>>> match
<.z==4>
>>> match(a)
True
>>> match(b)
True
>>> match = ObjectAttributes(x=1, y=2)
>>> match(a)
True
>>> match(b)
False
>>> ObjectAttributes(missing_attribute=2)(a)
False
Dictionary items check:
>>> d = dict(a=1, b=2)
>>> from infi.pyutils.predicates import DictionaryItems
>>> match = DictionaryItems(a=1)
>>> match
<['a']==1>
>>>
>>> match(d)
True
>>> match(dict(a=2, b=2))
False
>>> match(dict())
False
>>> match(dict(b=2))
False
Logical aggregations are done with And, Or, Not:
::
>>> from infi.pyutils.predicates import And, Or, Not
>>> obj = object()
>>> is_none_or_obj = Or(Identity(obj), Identity(None))
>>> is_none_or_obj #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Or(<is <object object at 0x...>>, <is None>)
>>> is_none_or_obj(obj)
True
>>> is_none_or_obj(None)
True
>>> is_none_or_obj(1)
False
>>> is_not_none = Not(is_none)
>>> is_not_none
<not <is None>>
>>> is_not_none(None)
False
>>> is_not_none(1)
True
Lazy
----
*infi.pyutils.lazy* presents utilities for lazy computation and caching
cached_property and cached_method
+++++++++++++++++++++++++++++++++
>>> from infi.pyutils.lazy import cached_property
>>> class MyClass(object):
... called = False
... @cached_property
... def value(self):
... assert not self.called
... self.called = True
... return 1
>>> m = MyClass()
>>> m.value
1
>>> m.value
1
>>> from infi.pyutils.lazy import cached_method
>>> class MyClass(object):
... called = False
... @cached_method
... def get_value(self):
... assert not self.called
... self.called = True
... return 1
>>> m = MyClass()
>>> m.get_value()
1
>>> m.get_value()
1
Decorator Utilities
-------------------
The *infi.pyutils.decorators* package contains a specially-crafted *wraps* implementation (functools.wraps counterpart) preserving information on the originally wrapped function. It also patches *inspect.getargspec* and IPython's similar mechanisms in order to display proper argument information on wrapped functions. It is therefore recommended to use it instead of the default ones.
Context Utilities
-----------------
*infi.pyutils.contexts* contains *contextmanager*, a drop-in replacement for *contextlib.contextmanager*, using the crafted *wraps* implementation from *decorators*.
Import Utilities
----------------
*infi.pyutils.importing* contains **import_file**, a function for importing a module by its name::
>>> from infi.pyutils.importing import *
>>> import tempfile, os
>>> temp_dir = tempfile.mkdtemp()
>>> filename = os.path.join(temp_dir, "my_file.py")
>>> with open(filename, "w") as f:
... _ = f.write("a=2")
>>> module = import_file(filename)
>>> module.a
2
Reference Counters
------------------
*infi.pyutils.reference_counter* is an implementation of a reference counter with dependency support::
>>> from infi.pyutils.reference_counter import ReferenceCounter
>>> r = ReferenceCounter()
>>> r.get_reference_count()
0
>>> r.add_reference()
>>> r.get_reference_count()
1
Several reference counters can be chained via dependency::
>>> r1 = ReferenceCounter()
>>> r2 = ReferenceCounter()
>>> r1.depend_on_counter(r2)
>>> r1.add_reference()
>>> r2.get_reference_count()
1
Also, as a convenience, if you add a dependency to a counter that already has a nonzero refcount, the dependent counter is automatically increased as well::
>>> r1 = ReferenceCounter()
>>> r1.add_reference()
>>> r2 = ReferenceCounter()
>>> r1.depend_on_counter(r2)
>>> r2.get_reference_count()
1
You can add a callback to be called when the reference count drops to zero::
>>> r1 = ReferenceCounter()
>>> def callback(r):
... assert r is r1
... print("called!")
>>> r1.add_zero_refcount_callback(callback)
>>> r1.add_reference()
>>> r1.remove_reference()
called!
Misc. Utilities
---------------
recursive_getattr
+++++++++++++++++
>>> from infi.pyutils import recursive_getattr
>>> class Object(object):
... pass
>>> a = Object()
>>> a.b = Object()
>>> a.b.c = 2
>>> recursive_getattr(a, 'b.c')
2
>>> recursive_getattr(a, 'b.d', 4)
4
Reprify
+++++++
*Reprify* is intended to force a __repr__/__str__ on objects that don't properly support them::
>>> from infi.pyutils import Reprify
>>> class SomeObject(object):
... pass
>>> some_obj = SomeObject()
>>> s = Reprify(some_obj, repr='some_repr_here', str='some_str_here')
>>> repr(s)
'some_repr_here'
>>> str(s)
'some_str_here'
While still preserving the identity and attributes::
>>> some_obj.attr = 2
>>> s.attr
2
>>> isinstance(s, SomeObject)
True
Exception Retry Utilities
-------------------------
The *infi.pyutils.retry* package provides a mechanism to easily define when and how to retry a failed operation. It allows you to wrap methods in your class and plug in an error handling strategy (infinite retries, retry N times, expontential decay timeout, etc.) without changing your code.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
infi.pyutils-1.1.3.tar.gz
(44.2 kB
view details)
File details
Details for the file infi.pyutils-1.1.3.tar.gz
.
File metadata
- Download URL: infi.pyutils-1.1.3.tar.gz
- Upload date:
- Size: 44.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b869af08570429d6e1e00ffc2c86d97647bf4a060abef2e30d1d1e822e034070 |
|
MD5 | 590c96d5e8a7c5ea67518ad7ac8fa2d5 |
|
BLAKE2b-256 | fb09f721dd16a5a1204b5f1a7712f27b68b52c7e141652392bcaa3304c0f5c7f |