Python utilities for cross-project use
- clone the repo / download the source distribution
$ pip install <path to project directory>
- Install
pytestpackage andpytest-lazy-fixtureplugin either by running$ pip install <path>[test]or manually cdinto/testsdirectory- Test package:
- from source:
$ python -m pytest <options> - installed:
$ pytest <options>
- from source:
utils– small utilities and helper objects that do not need a dedicated moduletest– namespace class with different sampledict,listandsetcollectionsbytewise()– return string representation ofbyteseqas hexadecimal uppercase octets separated bysepbitwise()– return string representation ofbyteseqas binary octets separated bysep@deprecated– issueDeprecationWarningbefore invoking the wrapee functionautorepr()– generate canonical__repr__()method using providedmsgschain()– SmartChain – extendeditertools.chain()isdunder()– return whethernameis a '__double_underscore__' name (from enum module)issunder()– return whethernameis a '_single_underscore_' nameisiterable()– return whetherobjis iterable, consideringstrandbytesare notDisposable– descriptor that clears its value after each accessspy– iterator around given iterable with separate independent iterator branch for lookahead@getter– decorator implementing getter-only attribute descriptor@setter– decorator implementing setter-only attribute descriptor@legacy– decorator to mark wrapped function or method is out of usestack()– print given iterable in a columnDummy– mock no-op class returning itself on every attr access or method callnull– sentinel object for denoting the absence of a valueclipboard()– put given string into Windows clipboardignore– context manager for filtering specified errors@classproperty– decorator implementing a class-level read-only propertyTree– tree structure converter and tree-style renderer.render()– create tree-like visual representation string.convert()– build the tree starting from givenrootitem top-down following references to child nodes.build()– build the tree out ofitemscollection bottom-up following references to parent nodes
AttrEnum– enum with custom attributes + an automatic.indexattribute
bits– utilities intended for manipulating binary data on a sub-byte levelBits– wrapper aroundintthat treats a number as a bit sequence.set()– set bits on specifiedpositions(set to1).clear()– clear bits on specifiedpositions(set to0).mask()– set/clear bits according to positions of1s and0s inmaskstring.flag()– extract one-bit boolean from specified positionpos.flags()– convertnrightmost bits to tuple of booleans.compose()– construct aBitsobject out of given sequence of bits specified inflags.extract()– pull out one or multiple values on sub-byte basis according tomask.pack()– insert values specified innumsinto currentBitsobject according tomask
typechecking– module for annotation-driven function call typechecking with no inspection of content for container objects@check_args– decorator for typechecking wrapped function / method arguments specified inarguments
Small utilities and helper objects that do not need a dedicated module
Intended for use in both application development and interactive Python interpreter sessions
Namespace class with different sample dict, list and set collections
bytewise(byteseq: bytes, sep: str = ' ', limit: Union[int, NoneType] = None, show_len: bool = True) -> str
Return string representation of byteseq as hexadecimal uppercase octets separated by sep
Functionally is the inverse of bytes.fromhex()
In case the length of byteseq exceeds the value of specified limit argument, extra part of
output is collapsed to an ellipsis and only the last element is shown after it (see example)
If output is trimmed, show_len argument tells whether '(<n> bytes)' is appended to output
Raises ValueError if limit is less than 2
>>> assert bytewise(b'12345', sep='-') == '31-32-33-34-35'
>>> assert bytewise(bytes.fromhex('00 01 42 5A FF')) == '00 01 42 5A FF'
>>> assert bytewise(b'python', limit=5) == '70 79 74 .. 6E (6 bytes)'Return string representation of byteseq as binary octets separated by sep
>>> assert bitwise(b'abc') == '01100001 01100010 01100011'
>>> assert bitwise(bytes.fromhex('00 0A FF')) == '00000000 00001010 11111111'Issue DeprecationWarning before invoking the wrapee function
Note: Warning filters should be enabled in order for the warning to be displayed. Minimal required filter is 'default::DeprecationWarning:utils'
If reason argument is specified, it will be displayed after the warning message
>>> @deprecated('duck tape')
... def func(): ...
...
>>> func()
DeprecationWarning: Function 'func' is marked as deprecated (duck tape)Generate canonical __repr__() method using provided msg
>>> class Belarus:
... __repr__ = autorepr('deserves respect')
...
>>> repr(Belarus)
"<utils.autorepr.<locals>.Belarus deserves respect at 0x...>"SmartChain – extended itertools.chain()
- accepts singular objects as well as iterables
- treats
stras items, not iterables
>>> assert [*schain(-1, range(3), 8)] == [-1, 0, 1, 2, 8] # accepts non-iterable objects
>>> assert [*schain(('foo', 'bar'), 'solid')] == ['foo', 'bar', 'solid'] # does not tear strings apart
>>> assert [*schain(range(3), 3, [], 42)] == [0, 1, 2, 3, 42] # iterables and items could go in any orderReturn whether name is a '__double_underscore__' name (from enum module)
Return whether name is a '_single_underscore_' name
Return whether obj is iterable, considering str and bytes are not
Descriptor that clears its value after each access
>>> class Class:
... attr = Disposable(100500)
...
>>> obj = Class()
>>> assert obj.attr == 100500 # returns initial value
>>> obj.attr = 42 # descriptor value is set to 42
>>> assert obj.attr == 42 # first access returns value
>>> assert obj.attr is None # subsequent access returns NoneIterator around given iterable with separate independent iterator branch for lookahead
.lookahead() returns an iterator that advances the underlying iterable,
but does not influence main iteration branch
spy object itself works just as conventional iterable regardless of .lookahead() state
>>> iterator = spy(range(1, 3)) # spy object wraps range(5)
>>> lookahead = iterator.lookahead() # independent lookahead iterator is created
>>> assert lookahead.__next__() == 1
>>> assert iterator.__next__() == 1
>>> assert list(lookahead) == [2, 3]
>>> assert list(iterator) == [2, 3]
>>> assert list(lookahead) == [] # exhaustedDecorator implementing getter-only attribute descriptor
Wraps given getter function into descriptor object that uses its return value when instance attribute with the same name as was assigned to descriptor itself is acessed
Attribute setting and deletion procedures are left unaffected
Signature of decorated getter method should be getter(self, value) -> returned:
value– the actual value of requested instance attribute stored in instance__dict__returned– getter return value that is to be returned to outer code requesting the attribute
>>> class GetterExample:
... @getter
... def attr(self, value):
... # handle acquired value somehow...
... return str(value)
...
>>> instance = GetterExample()
>>> instance.attr = 42
>>> assert instance.__dict__['attr'] == 42 # store unchanged
>>> assert instance.attr == '42' # acquire modifiedDecorator implementing setter-only attribute descriptor
Wraps given setter function into descriptor object that assigns its return value to instance attribute with the same name as was assigned to descriptor itself
Attribute access and deletion procedures are left unaffected
Signature of decorated setter method should be setter(self, value) -> stored:
value– the value being set to instance attribute from outer codestored– return value that is to be actually assigned to instance attribute
>>> class SetterExample:
... @setter
... def attr(self, value):
... # handle reassignment somehow...
... return str(value)
...
>>> instance = SetterExample()
>>> instance.attr = 42
>>> assert instance.__dict__['attr'] == '42' # store modified
>>> assert instance.attr == '42' # acquire unchangedDecorator to mark wrapped function or method is out of use
Returns new function that raises RuntimeError when called
Print given iterable in a column
Mock no-op class returning itself on every attr access or method call
Intended for avoiding both if-checks and attribute errors when dealing with optional values
Evaluates to False on logical operations
>>> dummy = Dummy('whatever', accepts='any args')
>>> assert str(dummy) == 'Dummy'
>>> assert dummy.whatever is dummy
>>> assert dummy.method('any', 'args') is dummy
>>> assert dummy('any', 'args') is dummy
>>> assert bool(dummy) is FalseSentinel object for denoting the absence of a value
Evaluates to False on logical comparisons
Should not be used as a distinct value for some attribute or variable
Put given string into Windows clipboard
Raises subprocess.CalledProcessError if underlying clip utility returns non-zero exit code
Context manager for filtering specified errors
Accepts any amount of exception types, subclasses are respected
If no error type is provided, it returns nullcontext which does nothing –
that simplifies usage in case exception types are calculated dymamically
>>> with ignore(LookupError):
... raise KeyError() # KeyError is a subclass of LookupError, so it is filtered out
...>>> with ignore(LookupError):
... raise RuntimeError('message') # RuntimeError does not pass a filter, so it is raised
...
RuntimeError: message>>> with ignore():
... raise Exception('message') # no exception types are being passed, so nothing is filtered
...
Exception: messageDecorator implementing a class-level read-only property
Tree structure converter and tree-style renderer
Intended to be used mainly for display purposes
Does not handle cycle references for now
>>> exceptions = [...] # list of all python exceptions
>>> tree = Tree.build(items=exceptions, naming='__name__', parents='__base__')
>>> assert str(tree) == tree.render()
>>> tree.render()
object
└── BaseException
├── Exception
│ ├── ArithmeticError
│ │ ├── FloatingPointError
│ │ ├── OverflowError
│ │ └── ZeroDivisionError
│ ├── AssertionError
│ ├── AttributeError
...Create tree-like visual representation string
Strings used for visualising tree branches are determined by
styleargumentEmpty tree representation is specified by
emptyargument
.convert(root: 'Item', naming: 'Union[str, NameHandle]', children: 'Union[str, ChildrenHandle]') -> 'Tree'Build the tree starting from given
rootitem top-down following references to child nodesThe name for each generated node is determined by
namingargument, which can be:
- string – defines the name of an item's attribute, so that
node.name = item.<name>- callable – defines a callable of a single argument, so that
node.name = <callable>(item)Similarly,
childrenargument defines a handle for acquiring a list of item's children. It could be whether a item's attribute name or a single-argument callable hook
.build(items: 'Iterable[Item]', naming: 'Union[str, NameHandle]', parent: 'Union[str, ParentHandle]' = None) -> 'Tree'Build the tree out of
itemscollection bottom-up following references to parent nodesSemantics of
namingandparentarguments is similar to corresponding arguments of.convert()methodElements of
itemscollection should be hashable
Enum with custom attributes + an automatic .index attribute
AttrEnum attributes are declared by assigning desired names to special __fields__ variable
on the very first line of enum class body (somewhat similar to Python __slots__)
Attribute values are set by assigning each AttrEnum member with a tuple of values,
that correspond to specified __fields__; missing values fallback to None
.index attribute is set automatically and defaults to enum member index number within order of declaration
Both .value and .index attributes may be overridden by providing their names in __fields__
If __fields__ tuple is not specified, only .index attribute is added to enum member implicitly;
besides that the class would generally behave like conventional Enum
>>> class Sample(AttrEnum):
... __fields__ = 'attr1', 'attr2', 'attr3'
... A = 'data_A', 10, True
... B = 'data_B', 42
... C = 'data_C', 77
...
>>> member = Sample.B
>>> assert member.name == 'B'
>>> assert member.index == 1 # counts from 0 in order of declaration
>>> assert member.value == ('data_B', 42, None) # values are filled up to match __fields__
>>> assert member.attr1 == 'data_B'
>>> assert member.attr2 == 42
>>> assert member.attr3 is None # if attr is not specified, it defaults to None
>>> assert repr(member) == "<Sample.B: attr1='data_B', attr2=42, attr3=None>">>> class ValueSample(AttrEnum):
... __fields__ = 'index', 'value'
... A = 1, 'data_A'
... B = 3, 'data_B'
... C = 2, 'data_C'
...
>>> member = ValueSample.B
>>> assert member.name == 'B'
>>> assert member.index == 3 # index is overridden
>>> assert member.value == 'data_B' # value is overridden as well
>>> assert repr(member) == "<ValueSample.B: index=3, value='data_B'>" # repr keeps unified format>>> class VoidSample(AttrEnum):
... A = 2
... B = 7
... C = 9
...
>>> member = VoidSample.B
>>> assert member.name == 'B'
>>> assert member.index == 1 # .index defaults to enum member index number
>>> assert member.value == 7 # .value defaults to whatever member is assigned to
>>> assert repr(member) == "<VoidSample.B: 7>"Utilities intended for manipulating binary data on a sub-byte level
Wrapper around int treating a number as a bit sequence
Provides a set of tools to manipulate, extract, insert, split, combine individual bits and bit sequences within a processed number
All methods intended to modify the value in-place return a newly created Bits object
since there's no way of manipulating underlying int value directly
Set bits on specified
positions(set to1)Position indexes start from zero and are counted from rightmost bit to the left
>>> Bits(0b01).set(1) == Bits(0b11) >>> Bits(0b1010).set(0, 2) == Bits(0b1111) >>> Bits(0b0).set(3) == Bits(0b1000)Clear bits on specified
positions(set to0)Position indexes start from zero and are counted from rightmost bit to the left
>>> Bits(0b01).clear(0) == Bits(0b0) >>> Bits(0b1010).clear(3) == Bits(0b0010) >>> Bits(0b11).clear(0, 2) == Bits(0b10)Set/clear bits according to positions of
1s and0s inmaskstringMask consists of 3 types of markers:
1– sets the the bit on corresponding position0– clears the corresponding bit-(dash) – leaves the corresponding bit unchangedMask may use an arbitrary character distinct from
0and1to denote position of a bit to be skippedMask is right-aligned with processed number (to match least-significant bit)
>>> Bits(0b0101).mask('-01-') == Bits(0b0011) >>> Bits(0b1100).mask('11') == Bits(0b1111) >>> Bits(0b1111).mask('-') == Bits(0b1111)Extract one-bit boolean from specified position
posPosition index starts from zero and is counted from rightmost bit to the left
>>> Bits(0b0100).flag(2) == TrueConvert
nrightmost bits to tuple of booleansFunctionally is the inverse of
.compose()methodResulting flags order conforms to default bit indexing - right to left
>>> Bits(0b0100).flags(3) == (False, False, True)Construct a
Bitsobject out of given sequence of bits specified inflagsFunctionally is the inverse of
.flags()methodThe bits placing order conforms to default bit indexing – right to left
>>> Bits.compose(False, False, True, False) == Bits(0x100)Pull out one or multiple values on sub-byte basis according to
maskMask consists of 3 types of markers:
- number marker – digits 0..9
- delimiter – any character (except for digit or specified separator character)
- separator – character specified by
sep(could not be another marker character)Each group of adjacent number markers holds position of a separate number to be extracted from current
Bitsobject (represented as a bit sequence)The order of extraction is defined by value of marker digits themselves
Multiple groups with the same digit would raise a
ValueErrorDelimiter characters denote positions of the bits to be skipped (dash is recommended)
Separator characters are intended to enhance readability and are simply ignored
Mask is right-aligned with processed number (to match least-significant bit)
>>> Bits(0b0001_1100).extract('111--') == (0b111,) >>> Bits(0b1100_0110).extract('2221 11-3') == (0b1, 0b110, 0b0) >>> Bits(0b0011_1010).extract('1122 2-13') -> ValueError # duplicating marker group: 1Insert values specified in
numsinto currentBitsobject according tomaskFunctionally is the inverse of
.extract()method on specified positions denoted bymaskMask consists of 3 types of markers:
- index marker – digits from 0 to
len(nums)- delimiter – any character (except for digit or specified separator character)
- separator – character specified by
sep(could not be another marker character)Each group of adjacent index markers holds position of a separate number to be inserted into current
Bitsobject (represented as a bit sequence)The value of marker digits themselves defines 0-based index of number from
numsargument to be inserted into designated positionMultiple groups of index markers with the same digit would cause corresponding number to be inserted into multiple positions, possibly stripping it to the length of its respective group if required
Delimiter characters denote positions of the bits to be skipped (dash is recommended)
Separator characters are intended to enhance readability and are simply ignored
Mask is right-aligned with processed number (to match least-significant bit)
>>> Bits().pack('0000--1-', 0b100, 1) == Bits(0b0100_0010) >>> Bits(0b10).pack('0011 22--', 0b11, 0, 1) == Bits(0b1100_0110) >>> Bits(0b1001_1010).pack('00- 111-', 0b11, 1) == Bits(0b1111_0010)
Module for annotation-driven function call typechecking with no inspection of content for container objects
Ideologically intended for human-bound use cases such as rough user input viability checks
Click to expand
API:
@check_args– decorator that performs typechecking on specified (or all) arguments of the decorated function / method immediately before the actual call
Typecheck machinery glossary:
- type annotation (annotation) - entire annotation expression:
Dict[str, Union[List[int], int]], etc. - type specificator (typespec) - any structural component of type annotation:
Iterable[int],Tuple,Union[int, Collection[int]],Any,type, etc. - type origin (basetype) – upper component of some given subscriptable typespec:
Union,List,int,TypeVar, etc. - type arguments (typeargs) - set of arguments of some given subscriptable typespec:
[str, Dict[str, int]],[],[Optional[Callable]],[bool, ...], etc.
Supported features:
- first-level typechecking against provided type annotation
- all type specificators provided by
typingmodule for Python 3.8 - structure checks for
Tuples, excluding homogeneous tuples (likeTuple[str, ...]) - structure checks for
TypedDicts - subclass checks for
NamedTuples - typechecking against bound types and constraints of
TypeVars - runtime-checkable
Protocols - simplified
IOclass checks - automatic
None–>NoneTypeconversion (bytyping.get_type_hints()used under the hood)
Behaviours that this module was NOT designed to support:
- inspecting of contents for iterables and containers, including homogeneous tuple typespecs
- inspecting callable signatures
- inspecting annotations of interface and protocol classes
- checks that involve applying a specific type to generic classes
- rigorous subclass checks of complex type specificators inside
Type[...] - complicated
IOtype checks - resolving
ForwardRefs
Supported type specifications:
- Bare types, including
NoneType SpecialForms:Any,ClassVar,Final,Literal,Optional,Union- Interfaces:
Awaitable,Callable,ContextManager,AsyncContextManager,Coroutine,Generator,AsyncGenerator,Hashable,Iterable,AsyncIterable,Iterator,AsyncIterator,Reversible,Sized - Protocols:
SupportsAbs,SupportsBytes,SupportsComplex,SupportsFloat,SupportsIndex,SupportsInt,SupportsRound - Custom runtime-checkable protocols (derived from
Protocol) - Containers:
ChainMap,Collection,Container,Counter,Deque,Dict,DefaultDict,OrderedDict,ItemsView,KeysView,ValuesView,List,Mapping,MutableMapping,MappingView,Sequence,MutableSequence,Set,FrozenSet,AbstractSet,MutableSet,Tuple - Type references:
Type,ByteString,Pattern,Match,IO,TextIO,BinaryIO GenericclassesTypedDictsNamedTuples andNamedTupleclass itselfTypeVars
Decorator for typechecking wrapped function / method arguments specified in arguments
Raises TypecheckError if typecheck fails, otherwise returns None
Checks are performed against argument annotations of wrapped callable at the time it is being called
If arguments are omitted, typeckecking is performed on all the parameters being passed
Default values of function / method arguments are not typechecked unless check_defaults=True is provided
If wrapped function / method is already decorated, @check_args should be applied beforehand in most cases
Though classmethods and staticmehtods are treated properly and may be simply decorated over
If some argument of wrapped callable has default value set to None, its annotation is
automatically converted to Optional[<annotation>] (by typing.get_type_hints() used under the hood)
>>> @check_args
... def func(a: Union[int, Dict[str, int], Tuple[Any, str]]):
... ...
...
>>> func(1) # typechecks: `1` is an `int`
>>> func(True) # typechecks: `bool` is a subclass of `int`
>>> func({}) # typechecks: empty dict is a `dict`
>>> func({1: True, 2: 's'}) # typechecks: dict contents are not inspected
>>> func((object, 's')) #typechecks: argument is a `tuple` and its structure matches annotation signature
>>> func(None) # fails: `NoneType` does not match any one of given specifications
>>> func(['s', 1]) # fails: `list` is not an `int`, nor a `dict`, nor a `tuple`
>>> func(('s', 0)) # fails: the second item of the tuple does not match given `str` specification
>>> func((0, 's', 'extra')) # fails: tuple has an extra element>>> @check_args('a', 'b')
... def func(a: Any, b: int, c: bool):
... ...
...
>>> func(object, 1, 's') # typechecks: only 'a' and 'b' arguments are checked