util.extend_docstring: proper layout for extended class (incl. current return type)

This commit is contained in:
Daniel Otto de Mentock 2022-11-19 08:10:00 +00:00 committed by Martin Diehl
parent dff8a5a7f9
commit 5017aabcea
5 changed files with 276 additions and 49 deletions

View File

@ -94,7 +94,7 @@ class Orientation(Rotation,Crystal):
""" """
@util.extend_docstring(_parameter_doc) @util.extend_docstring(extra_parameters=_parameter_doc)
def __init__(self, def __init__(self,
rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]),
*, *,
@ -300,84 +300,95 @@ class Orientation(Rotation,Crystal):
@classmethod @classmethod
@util.extended_docstring(Rotation.from_random, _parameter_doc) @util.extend_docstring(Rotation.from_random,
extra_parameters=_parameter_doc)
def from_random(cls, **kwargs) -> 'Orientation': def from_random(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random)
return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_quaternion,_parameter_doc) @util.extend_docstring(Rotation.from_quaternion,
extra_parameters=_parameter_doc)
def from_quaternion(cls, **kwargs) -> 'Orientation': def from_quaternion(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion)
return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_Euler_angles,_parameter_doc) @util.extend_docstring(Rotation.from_Euler_angles,
extra_parameters=_parameter_doc)
def from_Euler_angles(cls, **kwargs) -> 'Orientation': def from_Euler_angles(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles)
return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_axis_angle,_parameter_doc) @util.extend_docstring(Rotation.from_axis_angle,
extra_parameters=_parameter_doc)
def from_axis_angle(cls, **kwargs) -> 'Orientation': def from_axis_angle(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle)
return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_basis,_parameter_doc) @util.extend_docstring(Rotation.from_basis,
extra_parameters=_parameter_doc)
def from_basis(cls, **kwargs) -> 'Orientation': def from_basis(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis)
return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_matrix,_parameter_doc) @util.extend_docstring(Rotation.from_matrix,
extra_parameters=_parameter_doc)
def from_matrix(cls, **kwargs) -> 'Orientation': def from_matrix(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix)
return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_Rodrigues_vector,_parameter_doc) @util.extend_docstring(Rotation.from_Rodrigues_vector,
extra_parameters=_parameter_doc)
def from_Rodrigues_vector(cls, **kwargs) -> 'Orientation': def from_Rodrigues_vector(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector)
return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_homochoric,_parameter_doc) @util.extend_docstring(Rotation.from_homochoric,
extra_parameters=_parameter_doc)
def from_homochoric(cls, **kwargs) -> 'Orientation': def from_homochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric)
return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_cubochoric,_parameter_doc) @util.extend_docstring(Rotation.from_cubochoric,
extra_parameters=_parameter_doc)
def from_cubochoric(cls, **kwargs) -> 'Orientation': def from_cubochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric)
return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_spherical_component,_parameter_doc) @util.extend_docstring(Rotation.from_spherical_component,
extra_parameters=_parameter_doc)
def from_spherical_component(cls, **kwargs) -> 'Orientation': def from_spherical_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component)
return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extended_docstring(Rotation.from_fiber_component,_parameter_doc) @util.extend_docstring(Rotation.from_fiber_component,
extra_parameters=_parameter_doc)
def from_fiber_component(cls, **kwargs) -> 'Orientation': def from_fiber_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component) kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component)
return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori) return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(_parameter_doc) @util.extend_docstring(extra_parameters=_parameter_doc)
def from_directions(cls, def from_directions(cls,
uvw: FloatSequence, uvw: FloatSequence,
hkl: FloatSequence, hkl: FloatSequence,
@ -392,6 +403,10 @@ class Orientation(Rotation,Crystal):
hkl : numpy.ndarray, shape (...,3) hkl : numpy.ndarray, shape (...,3)
Lattice plane normal aligned with lab frame z-direction. Lattice plane normal aligned with lab frame z-direction.
Returns
-------
new : damask.Orientation
""" """
o = cls(**kwargs) o = cls(**kwargs)
x = o.to_frame(uvw=uvw) x = o.to_frame(uvw=uvw)
@ -538,8 +553,7 @@ class Orientation(Rotation,Crystal):
Notes Notes
----- -----
Currently requires same crystal family for both orientations. Requires same crystal family for both orientations.
For extension to cases with differing symmetry see A. Heinz and P. Neumann 1991 and 10.1107/S0021889808016373.
Examples Examples
-------- --------
@ -569,6 +583,8 @@ class Orientation(Rotation,Crystal):
>>> plt.show() >>> plt.show()
""" """
# For extension to cases with differing symmetry see
# https://doi.org/10.1107/S0021889808016373 and https://doi.org/10.1107/S0108767391006864
if self.family != other.family: if self.family != other.family:
raise NotImplementedError('disorientation between different crystal families') raise NotImplementedError('disorientation between different crystal families')

View File

@ -768,6 +768,10 @@ class Rotation:
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
Returns
-------
new : damask.Rotation
""" """
qu = np.array(q,dtype=float) qu = np.array(q,dtype=float)
if qu.shape[:-2:-1] != (4,): raise ValueError('invalid shape') if qu.shape[:-2:-1] != (4,): raise ValueError('invalid shape')
@ -800,6 +804,10 @@ class Rotation:
degrees : bool, optional degrees : bool, optional
Euler angles are given in degrees. Defaults to False. Euler angles are given in degrees. Defaults to False.
Returns
-------
new : damask.Rotation
Notes Notes
----- -----
Bunge Euler angles correspond to a rotation axis sequence of zx'z''. Bunge Euler angles correspond to a rotation axis sequence of zx'z''.
@ -834,6 +842,10 @@ class Rotation:
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
Returns
-------
new : damask.Rotation
""" """
ax = np.array(axis_angle,dtype=float) ax = np.array(axis_angle,dtype=float)
if ax.shape[:-2:-1] != (4,): raise ValueError('invalid shape') if ax.shape[:-2:-1] != (4,): raise ValueError('invalid shape')
@ -898,6 +910,10 @@ class Rotation:
R : numpy.ndarray, shape (...,3,3) R : numpy.ndarray, shape (...,3,3)
Rotation matrix with det(R) = 1 and R.T R = I. Rotation matrix with det(R) = 1 and R.T R = I.
Returns
-------
new : damask.Rotation
""" """
return Rotation.from_basis(R) return Rotation.from_basis(R)
@ -914,6 +930,10 @@ class Rotation:
b : numpy.ndarray, shape (...,2,3) b : numpy.ndarray, shape (...,2,3)
Corresponding three-dimensional vectors of second basis. Corresponding three-dimensional vectors of second basis.
Returns
-------
new : damask.Rotation
""" """
a_ = np.array(a,dtype=float) a_ = np.array(a,dtype=float)
b_ = np.array(b,dtype=float) b_ = np.array(b,dtype=float)
@ -946,6 +966,10 @@ class Rotation:
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
Returns
-------
new : damask.Rotation
""" """
ro = np.array(rho,dtype=float) ro = np.array(rho,dtype=float)
if ro.shape[:-2:-1] != (4,): raise ValueError('invalid shape') if ro.shape[:-2:-1] != (4,): raise ValueError('invalid shape')
@ -974,6 +998,10 @@ class Rotation:
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
Returns
-------
new : damask.Rotation
""" """
ho = np.array(h,dtype=float) ho = np.array(h,dtype=float)
if ho.shape[:-2:-1] != (3,): raise ValueError('invalid shape') if ho.shape[:-2:-1] != (3,): raise ValueError('invalid shape')
@ -999,6 +1027,10 @@ class Rotation:
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
Returns
-------
new : damask.Rotation
""" """
cu = np.array(x,dtype=float) cu = np.array(x,dtype=float)
if cu.shape[:-2:-1] != (3,): raise ValueError('invalid shape') if cu.shape[:-2:-1] != (3,): raise ValueError('invalid shape')
@ -1025,6 +1057,10 @@ class Rotation:
A seed to initialize the BitGenerator. A seed to initialize the BitGenerator.
Defaults to None, i.e. unpredictable entropy will be pulled from the OS. Defaults to None, i.e. unpredictable entropy will be pulled from the OS.
Returns
-------
new : damask.Rotation
""" """
rng = np.random.default_rng(rng_seed) rng = np.random.default_rng(rng_seed)
r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) # type: ignore r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) # type: ignore
@ -1066,6 +1102,10 @@ class Rotation:
A seed to initialize the BitGenerator. A seed to initialize the BitGenerator.
Defaults to None, i.e. unpredictable entropy will be pulled from the OS. Defaults to None, i.e. unpredictable entropy will be pulled from the OS.
Returns
-------
new : damask.Rotation
Notes Notes
----- -----
Due to the distortion of Euler space in the vicinity of ϕ = 0, probability densities, p, defined on Due to the distortion of Euler space in the vicinity of ϕ = 0, probability densities, p, defined on
@ -1150,7 +1190,7 @@ class Rotation:
sigma: float = 0., sigma: float = 0.,
shape: Union[int, IntSequence] = None, shape: Union[int, IntSequence] = None,
degrees: bool = False, degrees: bool = False,
rng_seed: NumpyRngSeed = None): rng_seed: NumpyRngSeed = None) -> 'Rotation':
""" """
Initialize with samples from a Gaussian distribution around a given direction. Initialize with samples from a Gaussian distribution around a given direction.
@ -1173,6 +1213,10 @@ class Rotation:
A seed to initialize the BitGenerator. A seed to initialize the BitGenerator.
Defaults to None, i.e. unpredictable entropy will be pulled from the OS. Defaults to None, i.e. unpredictable entropy will be pulled from the OS.
Returns
-------
new : damask.Rotation
Notes Notes
----- -----
The crystal direction for (θ=0,φ=0) is [0 0 1], The crystal direction for (θ=0,φ=0) is [0 0 1],

View File

@ -1,4 +1,3 @@
"""Functionality for generation of seed points for Voronoi or Laguerre tessellation.""" """Functionality for generation of seed points for Voronoi or Laguerre tessellation."""
from typing import Tuple as _Tuple from typing import Tuple as _Tuple

View File

@ -210,6 +210,14 @@ def open_text(fname: _FileHandle,
open(_Path(fname).expanduser(),mode,newline=('\n' if mode == 'w' else None)) open(_Path(fname).expanduser(),mode,newline=('\n' if mode == 'w' else None))
def execution_stamp(class_name: str,
function_name: str = None) -> str:
"""Timestamp the execution of a (function within a) class."""
now = _datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z')
_function_name = '' if function_name is None else f'.{function_name}'
return f'damask.{class_name}{_function_name} v{_version} ({now})'
def natural_sort(key: str) -> _List[_Union[int, str]]: def natural_sort(key: str) -> _List[_Union[int, str]]:
""" """
Natural sort. Natural sort.
@ -403,13 +411,6 @@ def project_equal_area(vector: _np.ndarray,
return _np.roll(_np.block([v[...,:2]/_np.sqrt(1.0+_np.abs(v[...,2:3])),_np.zeros_like(v[...,2:3])]), return _np.roll(_np.block([v[...,:2]/_np.sqrt(1.0+_np.abs(v[...,2:3])),_np.zeros_like(v[...,2:3])]),
-shift if keepdims else 0,axis=-1)[...,:3 if keepdims else 2] -shift if keepdims else 0,axis=-1)[...,:3 if keepdims else 2]
def execution_stamp(class_name: str,
function_name: str = None) -> str:
"""Timestamp the execution of a (function within a) class."""
now = _datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z')
_function_name = '' if function_name is None else f'.{function_name}'
return f'damask.{class_name}{_function_name} v{_version} ({now})'
def hybrid_IA(dist: _np.ndarray, def hybrid_IA(dist: _np.ndarray,
N: int, N: int,
@ -486,18 +487,18 @@ def shapeshifter(fro: _Tuple[int, ...],
final_shape: _List[int] = [] final_shape: _List[int] = []
index = 0 index = 0
for i,item in enumerate(_to): for i,item in enumerate(_to):
if item==_fro[index]: if item == _fro[index]:
final_shape.append(item) final_shape.append(item)
index+=1 index+=1
else: else:
final_shape.append(1) final_shape.append(1)
if _fro[index]==1 and not keep_ones: if _fro[index] == 1 and not keep_ones:
index+=1 index+=1
if index==len(_fro): if index == len(_fro):
final_shape = final_shape+[1]*(len(_to)-i-1) final_shape = final_shape+[1]*(len(_to)-i-1)
break break
if index!=len(_fro): raise ValueError(f'shapes cannot be shifted {fro} --> {to}') if index != len(_fro): raise ValueError(f'shapes cannot be shifted {fro} --> {to}')
return tuple(final_shape[::-1] if mode=='left' else final_shape) return tuple(final_shape[::-1] if mode == 'left' else final_shape)
def shapeblender(a: _Tuple[int, ...], def shapeblender(a: _Tuple[int, ...],
b: _Tuple[int, ...]) -> _Tuple[int, ...]: b: _Tuple[int, ...]) -> _Tuple[int, ...]:
@ -528,37 +529,82 @@ def shapeblender(a: _Tuple[int, ...],
return a + b[i:] return a + b[i:]
def extend_docstring(extra_docstring: str) -> _Callable: def _docstringer(docstring: _Union[str, _Callable],
extra_parameters: str = None,
# extra_examples: str = None,
# extra_notes: str = None,
return_type: _Union[str, _Callable] = None) -> str:
""" """
Decorator: Append to function's docstring. Extend a docstring.
Parameters Parameters
---------- ----------
extra_docstring : str docstring : str or callable, optional
Docstring to append. Docstring (of callable) to extend.
extra_parameters : str, optional
Additional information to append to Parameters section.
return_type : str or callable, optional
Type of return variable.
""" """
def _decorator(func): docstring_ = str( docstring if isinstance(docstring,str)
func.__doc__ += extra_docstring else docstring.__doc__ if hasattr(docstring,'__doc__')
return func else '')
return _decorator d = dict(Parameters=extra_parameters,
# Examples=extra_examples,
# Notes=extra_notes,
)
for key,extra in [(k,v) for (k,v) in d.items() if v is not None]:
if not (heading := _re.search(fr'^([ ]*){key}\s*\n\1{"-"*len(key)}',
docstring_,flags=_re.MULTILINE)):
raise RuntimeError(f"Docstring {docstring_} lacks a correctly formatted {key} section to insert values into")
content = [line for line in extra.split('\n') if line.strip()]
indent = len(heading.group(1))
shift = min([len(line)-len(line.lstrip(' '))-indent for line in content])
extra = '\n'.join([(line[shift:] if shift > 0 else
f'{" "*-shift}{line}') for line in content])
docstring_ = _re.sub(fr'(^([ ]*){key}\s*\n\2{"-"*len(key)}[\n ]*[A-Za-z0-9 ]*: ([^\n]+\n)*)',
fr'\1{extra}\n',
docstring_,flags=_re.MULTILINE)
if return_type is None:
return docstring_
else:
if isinstance(return_type,str):
return_type_ = return_type
else:
return_class = return_type.__annotations__.get('return','')
return_type_ = (_sys.modules[return_type.__module__].__name__.split('.')[0]
+'.'
+(return_class.__name__ if not isinstance(return_class,str) else return_class)
)
def extended_docstring(f: _Callable, return _re.sub(r'(^([ ]*)Returns\s*\n\2-------\s*\n[ ]*[A-Za-z0-9 ]*: )(.*)\n',
extra_docstring: str) -> _Callable: fr'\1{return_type_}\n',
docstring_,flags=_re.MULTILINE)
def extend_docstring(docstring: _Union[str, _Callable] = None,
extra_parameters: str = None) -> _Callable:
""" """
Decorator: Combine another function's docstring with a given docstring. Decorator: Extend the function's docstring.
Parameters Parameters
---------- ----------
f : function docstring : str or callable, optional
Function of which the docstring is taken. Docstring to extend. Defaults to that of decorated function.
extra_docstring : str extra_parameters : str, optional
Docstring to append. Additional information to append to Parameters section.
Notes
-----
Return type will become own type if docstring is callable.
""" """
def _decorator(func): def _decorator(func):
func.__doc__ = f.__doc__ + extra_docstring func.__doc__ = _docstringer(func.__doc__ if docstring is None else docstring,
extra_parameters,
func if isinstance(docstring,_Callable) else None,
)
return func return func
return _decorator return _decorator
@ -649,7 +695,6 @@ def Bravais_to_Miller(*,
[0,0,0,1]])) [0,0,0,1]]))
return _np.einsum('il,...l',basis,axis) return _np.einsum('il,...l',basis,axis)
def Miller_to_Bravais(*, def Miller_to_Bravais(*,
uvw: _np.ndarray = None, uvw: _np.ndarray = None,
hkl: _np.ndarray = None) -> _np.ndarray: hkl: _np.ndarray = None) -> _np.ndarray:
@ -706,7 +751,6 @@ def dict_prune(d: _Dict) -> _Dict:
return new return new
def dict_flatten(d: _Dict) -> _Dict: def dict_flatten(d: _Dict) -> _Dict:
""" """
Recursively remove keys of single-entry dictionaries. Recursively remove keys of single-entry dictionaries.

View File

@ -8,7 +8,6 @@ import h5py
from damask import util from damask import util
class TestUtil: class TestUtil:
@pytest.mark.xfail(sys.platform == 'win32', reason='echo is not a Windows command') @pytest.mark.xfail(sys.platform == 'win32', reason='echo is not a Windows command')
@ -208,3 +207,128 @@ class TestUtil:
@pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')])
def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais):
assert np.all(vector == util.Miller_to_Bravais(**{kw_Miller:util.Bravais_to_Miller(**{kw_Bravais:vector})})) assert np.all(vector == util.Miller_to_Bravais(**{kw_Miller:util.Bravais_to_Miller(**{kw_Bravais:vector})}))
@pytest.mark.parametrize('extra_parameters',["""
p2 : str, optional
p2 description 1
p2 description 2
""",
"""
p2 : str, optional
p2 description 1
p2 description 2
""",
"""
p2 : str, optional
p2 description 1
p2 description 2
"""])
@pytest.mark.parametrize('invalid_docstring',["""
Function description
Parameters ----------
p0 : numpy.ndarray, shape (...,4)
p0 description 1
p0 description 2
p1 : int, optional
p1 description
Remaining description
""",
"""
Function description
Parameters
----------
p0 : numpy.ndarray, shape (...,4)
p0 description 1
p0 description 2
p1 : int, optional
p1 description
Remaining description
""",])
def test_extend_docstring_parameters(self,extra_parameters,invalid_docstring):
test_docstring = """
Function description
Parameters
----------
p0 : numpy.ndarray, shape (...,4)
p0 description 1
p0 description 2
p1 : int, optional
p1 description
Remaining description
"""
invalid_docstring = """
Function description
Parameters ----------
p0 : numpy.ndarray, shape (...,4)
p0 description 1
p0 description 2
p1 : int, optional
p1 description
Remaining description
"""
expected = """
Function description
Parameters
----------
p0 : numpy.ndarray, shape (...,4)
p0 description 1
p0 description 2
p1 : int, optional
p1 description
p2 : str, optional
p2 description 1
p2 description 2
Remaining description
""".split("\n")
assert expected == util._docstringer(test_docstring,extra_parameters).split('\n')
with pytest.raises(RuntimeError):
util._docstringer(invalid_docstring,extra_parameters)
def test_replace_docstring_return_type(self):
class TestClassOriginal:
pass
def original_func() -> TestClassOriginal:
pass
class TestClassDecorated:
def decorated_func_bound(self) -> 'TestClassDecorated':
pass
def decorated_func() -> TestClassDecorated:
pass
original_func.__doc__ = """
Function description/Parameters
Returns
-------
Return value : test_util.TestClassOriginal
Remaining description
"""
expected = """
Function description/Parameters
Returns
-------
Return value : test_util.TestClassDecorated
Remaining description
"""
assert expected == util._docstringer(original_func,return_type=decorated_func)
assert expected == util._docstringer(original_func,return_type=TestClassDecorated.decorated_func_bound)