import time
from functools import wraps
import inspect
from typing import Generator, Union, Dict, Any, List
import numpy as np
from isimple.core.log import get_logger
log = get_logger(__name__)
[docs]def restrict(val, minval, maxval):
"""https://stackoverflow.com/questions/4092528
"""
if val < minval:
return minval
if val > maxval:
return maxval
return val
[docs]def rotations(sequence) -> list: # todo: clean up
"""Returns all rotations of a list.
"""
def rotate(seq, n: int) -> list:
return seq[n:] + seq[:n]
rotation_list = []
for N in range(len(sequence)):
rotation_list.append(rotate(sequence, N))
return rotation_list
[docs]def timing(f):
"""Function decorator to measure elapsed time.
:param f: function
"""
@wraps(f)
def wrap(*args, **kwargs):
ts = time.time()
result = f(*args, **kwargs)
te = time.time()
log.info(f"{f.__name__}() --> {te-ts} s elapsed.")
return result
return wrap
[docs]def describe_function(f):
name = f.__name__
if inspect.ismethod(f):
if hasattr(f, '__self__'):
classes = [f.__self__.__class__]
else:
#unbound method or regular function
classes = [f.im_class]
while classes:
c = classes.pop()
if name in c.__dict__:
return f'{f.__module__}.{c.__name__}.{name}'
else:
classes = list(c.__bases__) + classes
return f'{f.__module__}{name}'
[docs]def bases(c: type) -> List[type]:
b = [base for base in c.__bases__]
for base in b:
b += bases(base)
return list(set(b))
[docs]def nbases(c: type) -> int:
if c is None or c is type(None):
return 0
else:
return len(bases(c))
[docs]def all_attributes(
t: Union[object, type],
include_under: bool = True,
include_methods: bool = True,
include_mro: bool = False,
) -> List[str]:
if not isinstance(t, type):
t = t.__class__
b = [t] + bases(t)
attributes: list = []
for base in b:
attributes += base.__dict__
attributes = list(set(attributes))
if not include_under:
attributes = [a for a in attributes if a[0] != '_']
if not include_methods:
attributes = [a for a in attributes if not hasattr(getattr(t,a),'__call__')] # todo: this is hacky
if not include_mro:
attributes = [a for a in attributes if a[0:3] != 'mro']
return attributes
[docs]def all_annotations(t: Union[object, type]) -> Dict[str, type]:
if not isinstance(t, type):
t = object.__class__
b = [t] + bases(t)
annotations: Dict[str, Any] = {}
# todo: in order to get the correct annotation, bases must be ordered from most generic to most specific
for base in sorted(b, key=nbases):
try:
annotations.update(base.__annotations__)
except AttributeError:
pass
return annotations
[docs]def get_overridden_methods(c, m) -> list:
b = [c] + bases(c)
implementations = []
for base in b:
if m.__name__ in base.__dict__:
implementations.append(getattr(base, m.__name__))
return implementations
[docs]def frame_number_iterator(total: int,
Nf: int = None,
dt: float = None, fps: float = None) \
-> Generator[int, None, None]:
if Nf is not None and (dt is None and fps is None): # todo: a bit awkward, make two methods instead?
Nf = min(Nf, total)
for f in np.linspace(0, total, Nf):
yield int(f)
elif (dt is not None and fps is not None) and Nf is None:
df = restrict(dt * fps, 1, total)
for f in np.arange(0, total, df):
yield int(f)
else:
ValueError()
[docs]def before_version(version_a, version_b):
"""Check whether `version_a` precedes `version_b`.
Only handles numerics, i.e. no '1.25b.3v7'
"""
return tuple(int(s) for s in version_a.split('.')) \
< tuple(int(s) for s in version_b.split('.'))
[docs]def after_version(version_a, version_b):
return not before_version(version_a, version_b)