diff --git a/python/damask/_config.py b/python/damask/_config.py index 2f5904cc6..5f7453b11 100644 --- a/python/damask/_config.py +++ b/python/damask/_config.py @@ -2,25 +2,34 @@ import copy from io import StringIO from collections.abc import Iterable import abc +from pathlib import Path +from typing import Union, Dict, Any, Type, TypeVar import numpy as np import yaml +from ._typehints import FileHandle from . import Rotation +MyType = TypeVar('MyType', bound='Config') + class NiceDumper(yaml.SafeDumper): """Make YAML readable for humans.""" - def write_line_break(self, data=None): + def write_line_break(self, + data: str = None): super().write_line_break(data) if len(self.indents) == 1: super().write_line_break() - def increase_indent(self, flow=False, indentless=False): + def increase_indent(self, + flow: bool = False, + indentless: bool = False): return super().increase_indent(flow, False) - def represent_data(self, data): + def represent_data(self, + data: Any): """Cast Config objects and its subclasses to dict.""" if isinstance(data, dict) and type(data) != dict: return self.represent_data(dict(data)) @@ -31,14 +40,17 @@ class NiceDumper(yaml.SafeDumper): else: return super().represent_data(data) - def ignore_aliases(self, data): + def ignore_aliases(self, + data: Any) -> bool: """Do not use references to existing objects.""" return True class Config(dict): """YAML-based configuration.""" - def __init__(self,yml=None,**kwargs): + def __init__(self, + yml: Union[str, Dict[str, Any]] = None, + **kwargs): """Initialize from YAML, dict, or key=value pairs.""" if isinstance(yml,str): kwargs.update(yaml.safe_load(yml)) @@ -47,7 +59,7 @@ class Config(dict): super().__init__(**kwargs) - def __repr__(self): + def __repr__(self) -> str: """Show as in file.""" output = StringIO() self.save(output) @@ -55,14 +67,15 @@ class Config(dict): return ''.join(output.readlines()) - def __copy__(self): + def __copy__(self: MyType) -> MyType: """Create deep copy.""" return copy.deepcopy(self) copy = __copy__ - def __or__(self,other): + def __or__(self: MyType, + other) -> MyType: """ Update configuration with contents of other. @@ -76,18 +89,24 @@ class Config(dict): updated : damask.Config Updated configuration. + Note + ---- + This functionality is a backport for Python 3.8 + """ duplicate = self.copy() duplicate.update(other) return duplicate - def __ior__(self,other): + def __ior__(self: MyType, + other) -> MyType: """Update configuration with contents of other.""" return self.__or__(other) - def delete(self,keys): + def delete(self: MyType, + keys: Union[Iterable, str]) -> MyType: """ Remove configuration keys. @@ -109,7 +128,8 @@ class Config(dict): @classmethod - def load(cls,fname): + def load(cls: Type[MyType], + fname: FileHandle) -> MyType: """ Load from yaml file. @@ -124,14 +144,15 @@ class Config(dict): Configuration from file. """ - try: + if isinstance(fname, (str, Path)): fhandle = open(fname) - except TypeError: + else: fhandle = fname return cls(yaml.safe_load(fhandle)) - - def save(self,fname,**kwargs): + def save(self, + fname: FileHandle, + **kwargs): """ Save to yaml file. @@ -143,9 +164,9 @@ class Config(dict): Keyword arguments parsed to yaml.dump. """ - try: + if isinstance(fname, (str, Path)): fhandle = open(fname,'w',newline='\n') - except TypeError: + else: fhandle = fname if 'width' not in kwargs: diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 1b4d4439c..01d85f6a4 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -1,10 +1,14 @@ import numpy as np import h5py +from typing import Sequence, Dict, Any, Collection +from ._typehints import FileHandle from . import Config from . import Rotation from . import Orientation from . import util +from . import Table + class ConfigMaterial(Config): """ @@ -17,7 +21,9 @@ class ConfigMaterial(Config): """ - def __init__(self,d=None,**kwargs): + def __init__(self, + d: Dict[str, Any] = None, + **kwargs): """ New material configuration. @@ -30,14 +36,17 @@ class ConfigMaterial(Config): Initial content specified as pairs of key=value. """ + default: Collection if d is None: - for section,default in {'material':[],'homogenization':{},'phase':{}}.items(): + for section, default in {'material':[],'homogenization':{},'phase':{}}.items(): if section not in kwargs: kwargs.update({section:default}) super().__init__(d,**kwargs) - def save(self,fname='material.yaml',**kwargs): + def save(self, + fname: FileHandle = 'material.yaml', + **kwargs): """ Save to yaml file. @@ -53,7 +62,8 @@ class ConfigMaterial(Config): @classmethod - def load(cls,fname='material.yaml'): + def load(cls, + fname: FileHandle = 'material.yaml') -> 'ConfigMaterial': """ Load from yaml file. @@ -72,10 +82,14 @@ class ConfigMaterial(Config): @staticmethod - def load_DREAM3D(fname, - grain_data=None,cell_data=None,cell_ensemble_data='CellEnsembleData', - phases='Phases',Euler_angles='EulerAngles',phase_names='PhaseName', - base_group=None): + def load_DREAM3D(fname: str, + grain_data: str = None, + cell_data: str = None, + cell_ensemble_data: str = 'CellEnsembleData', + phases: str = 'Phases', + Euler_angles: str = 'EulerAngles', + phase_names: str = 'PhaseName', + base_group: str = None) -> 'ConfigMaterial': """ Load DREAM.3D (HDF5) file. @@ -154,7 +168,8 @@ class ConfigMaterial(Config): @staticmethod - def from_table(table,**kwargs): + def from_table(table: Table, + **kwargs) -> 'ConfigMaterial': """ Generate from an ASCII table. @@ -207,7 +222,7 @@ class ConfigMaterial(Config): @property - def is_complete(self): + def is_complete(self) -> bool: """ Check for completeness. @@ -267,12 +282,11 @@ class ConfigMaterial(Config): if homogenization - set(self['homogenization']): print(f'Homogenization(s) {homogenization-set(self["homogenization"])} missing') ok = False - return ok @property - def is_valid(self): + def is_valid(self) -> bool: """ Check for valid content. @@ -316,7 +330,10 @@ class ConfigMaterial(Config): return ok - def material_rename_phase(self,mapping,ID=None,constituent=None): + def material_rename_phase(self, + mapping: Dict[str, str], + ID: Sequence[int] = None, + constituent: Sequence[int] = None) -> 'ConfigMaterial': """ Change phase name in material. @@ -347,7 +364,9 @@ class ConfigMaterial(Config): return dup - def material_rename_homogenization(self,mapping,ID=None): + def material_rename_homogenization(self, + mapping: Dict[str, str], + ID: Sequence[int] = None) -> 'ConfigMaterial': """ Change homogenization name in material. @@ -374,7 +393,8 @@ class ConfigMaterial(Config): return dup - def material_add(self,**kwargs): + def material_add(self, + **kwargs: Any) -> 'ConfigMaterial': """ Add material entries. @@ -453,7 +473,7 @@ class ConfigMaterial(Config): N = max(N,s[0]) if len(s)>0 else N n = max(n,s[1]) if len(s)>1 else n - mat = [{'constituents':[{} for _ in range(n)]} for _ in range(N)] + mat: Sequence[dict] = [{'constituents':[{} for _ in range(n)]} for _ in range(N)] if 'v' not in kwargs: shaped['v'] = np.broadcast_to(1/n,(N,n)) @@ -461,7 +481,7 @@ class ConfigMaterial(Config): map_shape = {'O':(N,n,4),'F_i':(N,n,3,3)} for k,v in shaped.items(): target = map_shape.get(k,(N,n)) - obj = np.broadcast_to(v.reshape(util.shapeshifter(v.shape,target,mode='right')),target) + obj = np.broadcast_to(v.reshape(util.shapeshifter(v.shape, target, mode = 'right')), target) for i in range(N): if k in ['phase','O','v','F_i']: for j in range(n): diff --git a/python/damask/util.py b/python/damask/util.py index 0bbc1e7b5..b4c287884 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -9,7 +9,7 @@ import re import fractions from collections import abc from functools import reduce -from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, Optional +from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, SupportsIndex, Sequence from pathlib import Path import numpy as np @@ -427,7 +427,7 @@ def hybrid_IA(dist: np.ndarray, def shapeshifter(fro: Tuple[int, ...], to: Tuple[int, ...], mode: Literal['left','right'] = 'left', - keep_ones: bool = False) -> Tuple[Optional[int], ...]: + keep_ones: bool = False) -> Sequence[SupportsIndex]: """ Return dimensions that reshape 'fro' to become broadcastable to 'to'. @@ -483,14 +483,14 @@ def shapeshifter(fro: Tuple[int, ...], grp = match.groups() except AssertionError: raise ValueError(f'Shapes can not be shifted {fro} --> {to}') - fill: Tuple[Optional[int], ...] = () + fill: Any = () for g,d in zip(grp,fro+(None,)): fill += (1,)*g.count(',')+(d,) return fill[:-1] def shapeblender(a: Tuple[int, ...], - b: Tuple[int, ...]) -> Tuple[int, ...]: + b: Tuple[int, ...]) -> Sequence[SupportsIndex]: """ Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'.