diff --git a/nomad/metainfo/__init__.py b/nomad/metainfo/__init__.py index e9a217aa25570b17a0e72576fa42eb2082a4366e..e27cf60e6009aa7ba83d8375543060f26f47f73b 100644 --- a/nomad/metainfo/__init__.py +++ b/nomad/metainfo/__init__.py @@ -1,2 +1,2 @@ from .metainfo import MSection, MCategory, Definition, Property, Quantity, SubSection, \ - Section, Category, Package, Enum, m_package, units + Section, Category, Package, Enum, Datetime, m_package, units diff --git a/nomad/metainfo/example.py b/nomad/metainfo/example.py index 14ac880dd2a37837d1ed89d652615698c50a87b3..9c30137dae6f1f8e3acad16d85671e0d148e41e0 100644 --- a/nomad/metainfo/example.py +++ b/nomad/metainfo/example.py @@ -1,8 +1,9 @@ """ An example metainfo package. """ import numpy as np +from datetime import datetime -from nomad.metainfo import MSection, MCategory, Section, Quantity, Package, SubSection, Enum, units +from nomad.metainfo import MSection, MCategory, Section, Quantity, Package, SubSection, Enum, Datetime, units m_package = Package(links=['http://metainfo.nomad-coe.eu']) @@ -18,6 +19,7 @@ class Parsing(MSection): parser_version = Quantity(type=str) nomad_version = Quantity(type=str) warnings = Quantity(type=str, shape=['0..*']) + parse_time = Quantity(type=Datetime) class System(MSection): @@ -93,6 +95,10 @@ if __name__ == '__main__': run = Run() run.code_name = 'VASP' run.code_version = '1.0.0' + + parsing = run.m_create(Parsing) + parsing.parse_time = datetime.now() + run.m_as(VaspRun).x_vasp_raw_format = 'outcar' # The same as run.x_vasp_raw_format = 'outcar' # type: ignore @@ -117,4 +123,4 @@ if __name__ == '__main__': run = Run.m_from_dict(serializable) print(run.sccs[0].system) - print(m_package.m_to_dict()) # type: ignore, pylint: disable=undefined-variable + # print(m_package.m_to_json(indent=2)) # type: ignore, pylint: disable=undefined-variable diff --git a/nomad/metainfo/metainfo.py b/nomad/metainfo/metainfo.py index 227b751605c0db7576f28d8a34274936cd80deab..f85bd37df4a015f23088a360137ccafe1f4eaab5 100644 --- a/nomad/metainfo/metainfo.py +++ b/nomad/metainfo/metainfo.py @@ -134,7 +134,7 @@ See the reference of classes :class:`Section` and :class:`Quantities` for detail .. autoclass:: Quantity """ -# TODO validation +# TODO validation and constraints from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict, Set, \ Callable as TypingCallable, cast @@ -148,6 +148,9 @@ import itertools import numpy as np from pint.unit import _Unit from pint import UnitRegistry +import aniso8601 +from datetime import datetime +import pytz m_package: 'Package' = None @@ -384,13 +387,40 @@ class Reference(DataType): return MProxy(value) +class __Datetime(DataType): + + def __parse(self, datetime_str: str) -> datetime: + try: + try: + return aniso8601.parse_datetime(datetime_str) + except ValueError: + date = aniso8601.parse_date(datetime_str) + return datetime(date.year, date.month, date.day) + except Exception: + raise TypeError('Invalid date literal "{0}"'.format(datetime_str)) + + def set_normalize(self, section: 'MSection', quantity_def: 'Quantity', value: Any) -> Any: + if isinstance(value, str): + value = self.__parse(value) + + if not isinstance(value, datetime): + raise TypeError('%s is not a datetime.' % value) + + return value + + def serialize(self, section: 'MSection', quantity_def: 'Quantity', value: Any) -> Any: + value.replace(tzinfo=pytz.utc) + return value.isoformat() + + def deserialize(self, section: 'MSection', quantity_def: 'Quantity', value: Any) -> Any: + return self.__parse(value) + + Dimension = __Dimension() Unit = __Unit() QuantityType = __QuantityType() Callable = __Callable() - - -# TODO class Datetime(DataType) +Datetime = __Datetime() class MObjectMeta(type): @@ -737,10 +767,6 @@ class MSection(metaclass=MObjectMeta): 'The value %s is not an enum value for quantity %s.' % (value, quantity_def)) - elif quantity_def in [Quantity.type, Quantity.derived]: - # TODO check these special cases for Quantity quantities - pass - elif quantity_def.type == Any: pass @@ -999,45 +1025,43 @@ class MSection(metaclass=MObjectMeta): dct.pop('m_parent_index', None) dct.pop('m_parent_sub_section', None) - def items(): - for name, sub_section_def in section_def.all_sub_sections.items(): - if name in dct: - sub_section_value = dct.pop(name) - if sub_section_def.repeats: - yield name, [ - sub_section_def.sub_section.section_cls.m_from_dict(sub_section_dct) - for sub_section_dct in sub_section_value] + section = cls() + + for name, sub_section_def in section_def.all_sub_sections.items(): + if name in dct: + sub_section_value = dct.pop(name) + if sub_section_def.repeats: + for sub_section_dct in sub_section_value: + sub_section = sub_section_def.sub_section.section_cls.m_from_dict(sub_section_dct) + section.m_add_sub_section(sub_section_def, sub_section) + + else: + sub_section = sub_section_def.sub_section.section_cls.m_from_dict(sub_section_value) + section.m_add_sub_section(sub_section_def, sub_section) + + for name, quantity_def in section_def.all_quantities.items(): + if name in dct: + quantity_value = dct[name] + + if type(quantity_def.type) == np.dtype: + quantity_value = np.asarray(quantity_value) + + if isinstance(quantity_def.type, DataType): + dimensions = len(quantity_def.shape) + if dimensions == 0: + quantity_value = quantity_def.type.deserialize( + section, quantity_def, quantity_value) + elif dimensions == 1: + quantity_value = list( + quantity_def.type.deserialize(section, quantity_def, item) + for item in quantity_value) else: - yield name, sub_section_def.sub_section.section_cls.m_from_dict(sub_section_value) - - for name, quantity_def in section_def.all_quantities.items(): - if name in dct: - quantity_value = dct[name] - - if type(quantity_def.type) == np.dtype: - quantity_value = np.asarray(quantity_value) - - if isinstance(quantity_def.type, DataType): - dimensions = len(quantity_def.shape) - # TODO hand in the context, which is currently create after! - if dimensions == 0: - quantity_value = quantity_def.type.deserialize( - None, quantity_def, quantity_value) - elif dimensions == 1: - quantity_value = list( - quantity_def.type.deserialize(None, quantity_def, item) - for item in quantity_value) - else: - raise MetainfoError( - 'Only numpy quantities can have more than 1 dimension.') + raise MetainfoError( + 'Only numpy quantities can have more than 1 dimension.') - yield name, quantity_value + section.m_data.dct[name] = quantity_value # type: ignore - dct = {key: value for key, value in items()} - section_instance = cast(MSectionBound, section_def.section_cls()) - # TODO !do not update, but set directly! - section_instance.m_update(**dct) - return section_instance + return section def m_to_json(self, **kwargs): """Returns the data of this section as a json string. """ @@ -1271,9 +1295,6 @@ class Quantity(Property): virtual: 'Quantity' = None # TODO derived_from = Quantity(type=Quantity, shape=['0..*']) - # TODO categories = Quantity(type=Category, shape=['0..*']) - # TODO converter = Quantity(type=Converter), a class with set of functions for - # normalizing, (de-)serializing values. def __get__(self, obj, cls): if obj is None: