DAMASK_EICMD/python/damask/_yaml.py

245 lines
6.7 KiB
Python
Raw Normal View History

2021-01-03 16:33:40 +05:30
import copy
from io import StringIO
from collections.abc import Iterable
import abc
import platform
2022-11-23 02:56:15 +05:30
from typing import Optional, Union, Dict, Any, Type, TypeVar
import numpy as np
import yaml
try:
from yaml import CSafeLoader as SafeLoader
from yaml import CSafeDumper as SafeDumper
except ImportError:
from yaml import SafeLoader # type: ignore
from yaml import SafeDumper # type: ignore
2022-02-01 13:00:00 +05:30
from ._typehints import FileHandle
from . import Rotation
from . import util
MyType = TypeVar('MyType', bound='YAML')
2022-02-01 13:00:00 +05:30
class NiceDumper(SafeDumper):
"""Make YAML readable for humans."""
2022-02-01 13:00:00 +05:30
def write_line_break(self,
2022-11-23 02:56:15 +05:30
data: Optional[str] = None):
super().write_line_break(data) # type: ignore
if len(self.indents) == 1: # type: ignore
super().write_line_break() # type: ignore
2022-02-01 13:00:00 +05:30
def increase_indent(self,
flow: bool = False,
indentless: bool = False):
return super().increase_indent(flow, False) # type: ignore
2022-02-01 13:00:00 +05:30
def represent_data(self,
data: Any):
"""Cast YAML objects and its subclasses to dict."""
if isinstance(data, dict) and type(data) != dict:
return self.represent_data(dict(data))
if isinstance(data, np.ndarray):
return self.represent_data(data.tolist())
if isinstance(data, Rotation):
return self.represent_data(data.quaternion.tolist())
if isinstance(data, np.generic):
return self.represent_data(data.item())
return super().represent_data(data)
2022-02-01 13:00:00 +05:30
def ignore_aliases(self,
data: Any) -> bool:
"""Do not use references to existing objects."""
return True
class YAML(dict):
"""YAML-based configuration."""
2022-02-01 13:00:00 +05:30
def __init__(self,
config: Optional[Union[str, Dict[str, Any]]] = None,
2022-02-01 13:00:00 +05:30
**kwargs):
"""
New YAML-based configuration.
Parameters
----------
config : dict or str, optional
YAML. String needs to be valid YAML.
**kwargs: arbitray keyword-value pairs, optional
Top level entries of the configuration.
Notes
-----
Values given as keyword-value pairs take precedence
over entries with the same keyword in 'config'.
"""
if int(platform.python_version_tuple()[1]) >= 9:
if isinstance(config,str):
kwargs = yaml.load(config, Loader=SafeLoader) | kwargs
elif isinstance(config,dict):
kwargs = config | kwargs # type: ignore
super().__init__(**kwargs)
else:
if isinstance(config,str):
c = yaml.load(config, Loader=SafeLoader)
elif isinstance(config,dict):
c = config.copy()
else:
c = {}
c.update(kwargs)
super().__init__(**c)
2022-02-01 13:00:00 +05:30
def __repr__(self) -> str:
"""
Return repr(self).
Show as in file.
"""
output = StringIO()
self.save(output)
output.seek(0)
return ''.join(output.readlines())
2021-01-03 16:33:40 +05:30
2022-02-01 13:00:00 +05:30
def __copy__(self: MyType) -> MyType:
"""
Return deepcopy(self).
Create deep copy.
"""
2021-01-03 16:33:40 +05:30
return copy.deepcopy(self)
copy = __copy__
2022-02-01 13:00:00 +05:30
def __or__(self: MyType,
other) -> MyType:
"""
Return self|other.
Update configuration with contents of other.
Parameters
----------
other : damask.YAML or dict
Key-value pairs that update self.
2021-04-25 11:17:00 +05:30
Returns
-------
updated : damask.YAML
2021-04-25 11:17:00 +05:30
Updated configuration.
2022-02-01 13:00:00 +05:30
Note
----
This functionality is a backport for Python 3.8
"""
duplicate = self.copy()
duplicate.update(other)
return duplicate
2022-02-01 13:00:00 +05:30
def __ior__(self: MyType,
other) -> MyType:
"""
Return self|=other.
2022-08-11 00:21:50 +05:30
Update configuration with contents of other (in-place).
"""
return self.__or__(other)
2022-02-01 13:00:00 +05:30
def delete(self: MyType,
keys: Union[Iterable, str]) -> MyType:
"""
Remove configuration keys.
Parameters
----------
keys : iterable or scalar
Label of the key(s) to remove.
2021-04-25 11:17:00 +05:30
Returns
-------
updated : damask.YAML
2021-04-25 11:17:00 +05:30
Updated configuration.
"""
duplicate = self.copy()
for k in keys if isinstance(keys, Iterable) and not isinstance(keys, str) else [keys]:
del duplicate[k]
return duplicate
@classmethod
2022-02-01 13:00:00 +05:30
def load(cls: Type[MyType],
fname: FileHandle) -> MyType:
2020-09-30 12:19:55 +05:30
"""
Load from yaml file.
Parameters
----------
fname : file, str, or pathlib.Path
Filename or file for writing.
2021-04-25 11:17:00 +05:30
Returns
-------
loaded : damask.YAML
YAML from file.
2021-04-25 11:17:00 +05:30
2020-09-30 12:19:55 +05:30
"""
with util.open_text(fname) as fhandle:
return cls(yaml.load(fhandle, Loader=SafeLoader))
2022-02-01 13:00:00 +05:30
def save(self,
fname: FileHandle,
**kwargs):
"""
Save to yaml file.
Parameters
----------
fname : file, str, or pathlib.Path
2020-09-30 12:19:55 +05:30
Filename or file for writing.
**kwargs : dict
Keyword arguments parsed to yaml.dump.
"""
if 'width' not in kwargs:
kwargs['width'] = 256
if 'default_flow_style' not in kwargs:
kwargs['default_flow_style'] = None
2020-10-27 02:13:21 +05:30
if 'sort_keys' not in kwargs:
kwargs['sort_keys'] = False
with util.open_text(fname,'w') as fhandle:
try:
fhandle.write(yaml.dump(self,Dumper=NiceDumper,**kwargs))
except TypeError: # compatibility with old pyyaml
del kwargs['sort_keys']
fhandle.write(yaml.dump(self,Dumper=NiceDumper,**kwargs))
@property
@abc.abstractmethod
def is_complete(self):
"""Check for completeness."""
raise NotImplementedError
2021-01-03 16:33:40 +05:30
@property
@abc.abstractmethod
def is_valid(self):
"""Check for valid file layout."""
raise NotImplementedError