py2http.util

This module contains various utilities including a lazyprop descriptor for lazy loading properties, a CreateProcess context manager for running functions in parallel processes, a ModuleFoundIgnore context manager for ignoring ModuleNotFoundErrors, and more.

The lazyprop descriptor is used to define properties that are computed once and then cached until the instance of the class is destroyed. It is useful for computing properties on-demand but only once.

The CreateProcess context manager is used to launch a parallel process and close it on exit. This is helpful when you need to execute tasks concurrently in a separate process.

The ModuleFoundIgnore context manager allows you to ignore ModuleNotFoundError exceptions in a block of code. This can be useful when importing optional modules that may or may not exist.

The TypeAsserter function generates a callable that asserts the expected type of a value based on a given kind. It can be used to validate the types of inputs based on predefined rules.

Overall, this module provides a collection of useful tools for handling lazy property loading, parallel processing, exception handling, type validation, and more.

Functions

call_with_inputs(func[, args, kwargs])

Call func(*args, **kwargs), passing the entries of kwargs that name positional-only parameters of func positionally, which JSON (a mapping) can only supply by name (i2mint/py2http#14).

conditional_logger([verbose, log_func])

deprecate([func, msg])

Decorator to emit a DeprecationWarning when the decorated function is called.

if_not_empty(obj[, if_empty_val])

obj_to_items_gen(obj, attrs[, ...])

Make a generator of (k, v) items extracted from an input object, given an iterable of attributes to extract

obj_to_path(obj)

Quasi-inverse of obj_to_path: Get a root_obj and attr_path from an object.

path_to_obj(root_obj, attr_path)

Get an object from a root object and "attribute path" specification.

py_obj_info(obj)

pyparam_to_dict(param[, kv_trans])

Get dict from a Parameter object

Classes

CreateProcess(proc_func[, process_name, ...])

A context manager to launch a parallel process and close it on exit.

Missing([val])

A class to use as a value to indicate that something was missing

ModuleNotFoundIgnore()

Context manager to ignore ModuleNotFoundErrors.

Skip()

Class to indicate if one should skip an item

TypeAsserter(types_for_kind[, if_kind_missing])

Makes a callable that asserts that a value v has the expected type(s) that it's kind k should be

lazyprop(func)

A descriptor implementation of lazyprop (cached property).

class py2http.util.CreateProcess(proc_func, process_name=None, wait_before_entering=2, verbose=False, args=(), **kwargs)[source]

Bases: object

A context manager to launch a parallel process and close it on exit.

class py2http.util.Missing(val=None)[source]

Bases: object

A class to use as a value to indicate that something was missing

class py2http.util.ModuleNotFoundIgnore[source]

Bases: object

Context manager to ignore ModuleNotFoundErrors.

When all goes well, code is executed normally:

>>> with ModuleNotFoundIgnore():
...     import os.path  # test when the module exists
...     # The following code is reached and executed
...     print('hi there!')
...     print(str(os.path.join)[:14] + '...')  # code is reached
hi there!
<function join...

But if you try to import a module that doesn’t exist on your system, the block will be skipped from that point onward, silently.

>>> with ModuleNotFoundIgnore():
...     import do.i.exist
...     # The following code is NEVER reached or executed
...     print(do.i.exist)
...     t = 0 / 0

But if there’s any other kind of error (other than ModuleNotFoundError that is, the error will be raised normally.

>>> with ModuleNotFoundIgnore():
...     t = 0/0
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
class py2http.util.Skip[source]

Bases: object

Class to indicate if one should skip an item

class py2http.util.TypeAsserter(types_for_kind, if_kind_missing='ignore')[source]

Bases: object

Makes a callable that asserts that a value v has the expected type(s) that it’s kind k should be

>>> assert_type = TypeAsserter({'foo': str, 'bar': (Callable, type(None))})
>>> assert_type('bar', lambda x: x)
>>> assert_type('bar', None)
>>> assert_type('foo', 'i am a string')
>>> assert_type('foo', list('i am not a string'))
Traceback (most recent call last):
  ...
AssertionError: Invalid foo type, must be a <class 'str'>, but was a <class 'list'>

If a kind wasn’t specified, the default is to ignore

>>> assert_type('not_a_kind', 'blah')
>>> assert_type = TypeAsserter({'foo': str, 'bar': (Callable, type(None))})  # nothing happens

But you can choose to warn or raise an exception instead

>>> assert_type = TypeAsserter({'foo': str, 'bar': list}, if_kind_missing='raise')
>>> assert_type('not_a_kind', 'blah')
Traceback (most recent call last):
    ...
ValueError: Unrecognized kind: not_a_kind. The ones I recognize: ['foo', 'bar']
py2http.util.call_with_inputs(func, args=(), kwargs=None)[source]

Call func(*args, **kwargs), passing the entries of kwargs that name positional-only parameters of func positionally, which JSON (a mapping) can only supply by name (i2mint/py2http#14).

>>> call_with_inputs({"a": 1}.__getitem__, kwargs={"key": "a"})
1
>>> d = {}
>>> call_with_inputs(d.__setitem__, kwargs={"key": "k", "value": 2}), d
(None, {'k': 2})
>>> call_with_inputs(lambda x, /, y=0: (x, y), kwargs={"x": 1, "y": 2})
(1, 2)

A positional-only argument that is neither required to be given nor given takes its default; a missing required one is an InputError (a client error), not a TypeError. A callable whose signature can’t be read gets **kwargs as is.

Python lets a function with **kwargs receive a keyword that is also the name of a positional-only parameter (Counter.update(iterable=3)): such an optional parameter is left in kwargs; only required ones are bound by name.

py2http.util.deprecate(func=None, *, msg=None)[source]

Decorator to emit a DeprecationWarning when the decorated function is called.

class py2http.util.lazyprop(func)[source]

Bases: object

A descriptor implementation of lazyprop (cached property). Made based on David Beazley’s “Python Cookbook” book and enhanced with boltons.cacheutils ideas.

>>> class Test:
...     def __init__(self, a):
...         self.a = a
...     @lazyprop
...     def len(self):
...         print('generating "len"')
...         return len(self.a)
>>> t = Test([0, 1, 2, 3, 4])
>>> t.__dict__
{'a': [0, 1, 2, 3, 4]}
>>> t.len
generating "len"
5
>>> t.__dict__
{'a': [0, 1, 2, 3, 4], 'len': 5}
>>> t.len
5
>>> # But careful when using lazyprop that no one will change the value of a without deleting the property first
>>> t.a = [0, 1, 2]  # if we change a...
>>> t.len  # ... we still get the old cached value of len
5
>>> del t.len  # if we delete the len prop
>>> t.len  # ... then len being recomputed again
generating "len"
3
py2http.util.obj_to_items_gen(obj, attrs, on_missing_attr=<class 'py2http.util.Missing'>, kv_trans=<function <lambda>>)[source]

Make a generator of (k, v) items extracted from an input object, given an iterable of attributes to extract

Parameters:
  • obj – A python object

  • attrs (Iterable[str]) – The iterable of attributes to extract from obj

  • on_missing_val –

    What to do if an attribute is missing:

    • Skip: Skip the item

    • Callable: Call a function with the attribute as an input

    • anything else: Just return that as a value

  • kv_trans (Optional[Callable])

Returns:

A generator

py2http.util.obj_to_path(obj)[source]

Quasi-inverse of obj_to_path: Get a root_obj and attr_path from an object. Obviously, would only be able to work with some types (only by-ref types?).

>>> class A:
...     def foo(self, x): ...
...     foo.x = 3
...     class B:
...         def bar(self, x): ...
...
>>> for t in [(A, ('foo',)), (A, ('B',)), (A, ('B', 'bar'))]:
...     print(obj_to_path(path_to_obj(*t)))
...     print(t)
...     print()
(<class 'util.A'>, ('foo',))
(<class 'util.A'>, ('foo',))

<class 'util.A.B'>
(<class 'util.A'>, ('B',))

(<class 'util.A'>, ('B', 'bar'))
(<class 'util.A'>, ('B', 'bar'))

# >>> for t in [(A, (‘foo’,)), (A, (‘B’,)), (A, (‘B’, ‘bar’))]: # … assert obj_to_path(path_to_obj(*t)) == t

py2http.util.path_to_obj(root_obj, attr_path)[source]

Get an object from a root object and “attribute path” specification.

>>> class A:
...     def foo(self, x): ...
...     foo.x = 3
...     class B:
...         def bar(self, x): ...
...
>>> obj = path_to_obj(A, ('foo',))
>>> assert callable(obj) and obj.__name__ == 'foo'
>>> path_to_obj(A, ('foo', 'x'))
3
>>> obj = path_to_obj(A, ('B', 'bar'))
>>> assert callable(obj) and obj.__qualname__ == 'A.B.bar'
py2http.util.pyparam_to_dict(param, kv_trans=<function _pyparam_kv_trans.skip_empties>)[source]

Get dict from a Parameter object

Parameters:
  • param – A inspect.Parameter instance

  • kv_trans (Callable) – A callable that will be called on the (k, v) attribute items of the Parameter instance

Returns:

A dict extracted from this Parameter

>>> from inspect import Parameter, Signature, signature
>>> from functools import partial
>>>
>>> def mult(x: float, /, y=1, *, z: int=1): ...
>>> params_dicts = map(pyparam_to_dict, signature(mult).parameters.values())
>>> # see that we can recover the original signature from these dicts
>>> assert Signature(map(lambda kw: Parameter(**kw), params_dicts)) == signature(mult)

Now what about the kv_trans? It’s default is made to return None when a value is equal to Parameter.empty (which is the way the inspect module distinguishes the None object from “it’s just not there”.

But we could provide our own kv_trans, which should be a function taking (k, v) pair (those k and v arg names are imposed!) and returns… well, what ever you want to return really. But you if return None, the (k, v) item will be skipped.

Look here how using kv_trans=pyparam_to_dict.kv_trans.with_str_kind does the job of skipping Parameter.empty items, but also cast the kind value to a string, so that it can be jsonizable.

>>> params_to_jdict = partial(pyparam_to_dict, kv_trans=pyparam_to_dict.kv_trans.with_str_kind)
>>> got = list(map(params_to_jdict, signature(mult).parameters.values()))
>>> expected = [
...     {'name': 'x', 'kind': 'POSITIONAL_ONLY', 'annotation': float},
...     {'name': 'y', 'kind': 'POSITIONAL_OR_KEYWORD', 'default': 1},
...     {'name': 'z', 'kind': 'KEYWORD_ONLY', 'default': 1, 'annotation': int}]
>>> assert got == expected, f"\n  got={got}\n  expected={expected}"