Commit 7b431349 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Metainfo refactorings.

parent afcf9216
......@@ -17,7 +17,7 @@ import datetime
from elasticsearch_dsl import Keyword
from nomad import utils, config
from nomad.metainfo import MObject
from nomad.metainfo import MSection
class UploadWithMetadata():
......@@ -113,7 +113,7 @@ class CalcWithMetadata():
if value is None or key in ['backend']:
continue
if isinstance(value, MObject):
if isinstance(value, MSection):
value = value.m_to_dict()
yield key, value
......
from .metainfo import MObject, Section, Quantity, Enum, units
from .metainfo import MSection, Section, Quantity, Enum, units
......@@ -3,7 +3,7 @@ Some playground to try the API_CONCEPT.md ideas.
"""
class MObject:
class MSection:
def __init__(self, m_definition: 'MElementDef', m_def: 'MSection' = None):
self.m_definition = m_definition
self.m_def = m_def
......@@ -26,7 +26,7 @@ class MObject:
subsection.append(self)
class MSection(MObject):
class MSection(MSection):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
......@@ -60,7 +60,7 @@ class MSection(MObject):
return ':%s' % self.m_definition.name
class MProperty(MObject):
class MProperty(MSection):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
......
......@@ -27,10 +27,10 @@ Here is a simple example that demonstrates the definition of System related quan
.. code-block:: python
class Run(MObject):
class Run(MSection):
pass
class System(MObject):
class System(MSection):
\"\"\"
A system section includes all quantities that describe a single a simulated
system (a.k.a. geometry).
......@@ -109,18 +109,18 @@ quantities:
All meta-info definitions and classes for meta-info data objects (i.e. section instances)
inherit from :class:` MObject`. This base-class provides common functions and attributes
inherit from :class:` MSection`. This base-class provides common functions and attributes
for all meta-info data objects. Names of these common parts are prefixed with ``m_``
to distinguish them from user defined quantities. This also constitute's the `reflection`
interface (in addition to Python's build in ``getattr``, ``setattr``) that allows to
create and manipulate meta-info data, without prior program time knowledge of the underlying
definitions.
.. autoclass:: MObject
.. autoclass:: MSection
The following classes can be used to define and structure meta-info data:
- sections are defined by sub-classes :class:`MObject` and using :class:`Section` to
- sections are defined by sub-classes :class:`MSection` and using :class:`Section` to
populate the classattribute `m_def`
- quantities are defined by assigning classattributes of a section with :class:`Quantity`
instances
......@@ -150,8 +150,8 @@ from pint.unit import _Unit
from pint import UnitRegistry
import inflection
__module__ = sys.modules[__name__]
MObjectBound = TypeVar('MObjectBound', bound='MObject')
is_bootstrapping = True
MSectionBound = TypeVar('MSectionBound', bound='MSection')
# Reflection
......@@ -212,22 +212,22 @@ class MObjectMeta(type):
def __new__(self, cls_name, bases, dct):
cls = super().__new__(self, cls_name, bases, dct)
init = getattr(cls, '__init_section_cls__')
if init is not None:
init = getattr(cls, '__init_cls__')
if init is not None and not is_bootstrapping:
init()
return cls
Content = Tuple[MObjectBound, Union[List[MObjectBound], MObjectBound], str, MObjectBound]
SectionDef = Union[str, 'Section', Type[MObjectBound]]
Content = Tuple[MSectionBound, Union[List[MSectionBound], MSectionBound], str, MSectionBound]
SectionDef = Union[str, 'Section', Type[MSectionBound]]
class MObject(metaclass=MObjectMeta):
"""Base class for all section objects on all meta-info levels.
class MSection(metaclass=MObjectMeta):
"""Base class for all section instances on all meta-info levels.
All metainfo objects instantiate classes that inherit from ``MObject``. Each
section or quantity definition is an ``MObject``, each actual (meta-)data carrying
section is an ``MObject``. This class consitutes the reflection interface of the
All metainfo objects instantiate classes that inherit from ``MSection``. Each
section or quantity definition is an ``MSection``, each actual (meta-)data carrying
section is an ``MSection``. This class consitutes the reflection interface of the
meta-info, since it allows to manipulate sections (and therefore all meta-info data)
without having to know the specific sub-class.
......@@ -262,9 +262,9 @@ class MObject(metaclass=MObjectMeta):
m_def: 'Section' = None
def __init__(self, m_def: 'Section' = None, m_parent: 'MObject' = None, _bs: bool = False, **kwargs):
def __init__(self, m_def: 'Section' = None, m_parent: 'MSection' = None, _bs: bool = False, **kwargs):
self.m_def: 'Section' = m_def
self.m_parent: 'MObject' = m_parent
self.m_parent: 'MSection' = m_parent
self.m_parent_index = -1
cls = self.__class__
......@@ -291,14 +291,10 @@ class MObject(metaclass=MObjectMeta):
# self.m_update(**kwargs)
@classmethod
def __init_section_cls__(cls):
# only works after bootstrapping, since functionality is still missing
if not all([hasattr(__module__, cls) for cls in ['Quantity', 'Section', 'Package', 'Category', 'sub_section']]):
return
def __init_cls__(cls):
# ensure that the m_def is defined
m_def = cls.m_def
if m_def is None and cls != MObject:
if m_def is None:
m_def = Section()
setattr(cls, 'm_def', m_def)
......@@ -353,7 +349,7 @@ class MObject(metaclass=MObjectMeta):
raise TypeError('Value has wrong type.')
elif isinstance(definition.type, Section):
if not isinstance(value, MObject) or value.m_def != definition.type:
if not isinstance(value, MSection) or value.m_def != definition.type:
raise TypeError('The value is not a section of wrong section definition')
else:
......@@ -404,7 +400,7 @@ class MObject(metaclass=MObjectMeta):
return section
def m_sub_sections(self, definition: SectionDef) -> List[MObjectBound]:
def m_sub_sections(self, definition: SectionDef) -> List[MSectionBound]:
"""Returns all sub sections for the given section definition
Args:
......@@ -425,7 +421,7 @@ class MObject(metaclass=MObjectMeta):
else:
return [m_data_value]
def m_sub_section(self, definition: SectionDef, parent_index: int = -1) -> MObjectBound:
def m_sub_section(self, definition: SectionDef, parent_index: int = -1) -> MSectionBound:
"""Returns the sub section for the given section definition and possible
parent_index (for repeatable sections).
......@@ -465,7 +461,7 @@ class MObject(metaclass=MObjectMeta):
return m_data_value
def m_add_sub_section(self, sub_section: MObjectBound) -> MObjectBound:
def m_add_sub_section(self, sub_section: MSectionBound) -> MSectionBound:
"""Adds the given section instance as a sub section to this section."""
section_def = sub_section.m_def
......@@ -480,7 +476,7 @@ class MObject(metaclass=MObjectMeta):
return sub_section
def m_create(self, definition: SectionDef, **kwargs) -> 'MObject':
def m_create(self, definition: SectionDef, **kwargs) -> 'MSection':
"""Creates a subsection and adds it this this section
Args:
......@@ -518,7 +514,7 @@ class MObject(metaclass=MObjectMeta):
quantity = self.__resolve_quantity(definition)
MObject.m_type_check(quantity, value, check_item=True)
MSection.m_type_check(quantity, value, check_item=True)
m_data_values = self.m_data.setdefault(quantity.name, [])
m_data_values.append(value)
......@@ -529,7 +525,7 @@ class MObject(metaclass=MObjectMeta):
quantity = self.__resolve_quantity(definition)
for value in values:
MObject.m_type_check(quantity, value, check_item=True)
MSection.m_type_check(quantity, value, check_item=True)
m_data_values = self.m_data.setdefault(quantity.name, [])
for value in values:
......@@ -582,7 +578,7 @@ class MObject(metaclass=MObjectMeta):
return {key: value for key, value in items()}
@classmethod
def m_from_dict(cls: Type[MObjectBound], dct: Dict[str, Any]) -> MObjectBound:
def m_from_dict(cls: Type[MSectionBound], dct: Dict[str, Any]) -> MSectionBound:
section_def = cls.m_def
# remove m_def and m_parent_index, they set themselves automatically
......@@ -604,7 +600,7 @@ class MObject(metaclass=MObjectMeta):
yield key, value
dct = {key: value for key, value in items()}
section_instance = cast(MObjectBound, section_def.section_cls())
section_instance = cast(MSectionBound, section_def.section_cls())
section_instance.m_update(**dct)
return section_instance
......@@ -625,10 +621,10 @@ class MObject(metaclass=MObjectMeta):
for name, attr in self.m_data.items():
if isinstance(attr, list):
for value in attr:
if isinstance(value, MObject):
if isinstance(value, MSection):
yield value, attr, name, self
elif isinstance(attr, MObject):
elif isinstance(attr, MSection):
yield value, value, name, self
def __repr__(self):
......@@ -640,6 +636,29 @@ class MObject(metaclass=MObjectMeta):
return '%s:%s' % (name, m_section_name)
class MCategory(metaclass=MObjectMeta):
m_def: 'Category' = None
@classmethod
def __init_cls__(cls):
# ensure that the m_def is defined
m_def = cls.m_def
if m_def is None:
m_def = Category()
setattr(cls, 'm_def', m_def)
# transfer name and description to m_def
m_def.name = cls.__name__
if cls.__doc__ is not None:
m_def.description = inspect.cleandoc(cls.__doc__)
# add section cls' section to the module's package
module_name = cls.__module__
pkg = Package.from_module(module_name)
pkg.m_add_sub_section(cls.m_def)
# M3, the definitions that are used to write definitions. These are the section definitions
# for sections Section and Quantity.They define themselves; i.e. the section definition
# for Section is the same section definition.
......@@ -680,9 +699,9 @@ class cached_property:
return value
class Definition(MObject):
class Definition(MSection):
__all_definitions: Dict[Type[MObject], List[MObject]] = {}
__all_definitions: Dict[Type[MSection], List[MSection]] = {}
name: 'Quantity' = None
description: 'Quantity' = None
......@@ -699,9 +718,9 @@ class Definition(MObject):
definitions.append(self)
@classmethod
def all_definitions(cls: Type[MObjectBound]) -> Iterable[MObjectBound]:
def all_definitions(cls: Type[MSectionBound]) -> Iterable[MSectionBound]:
""" Returns all definitions of this definition class. """
return cast(Iterable[MObjectBound], Definition.__all_definitions.get(cls, []))
return cast(Iterable[MSectionBound], Definition.__all_definitions.get(cls, []))
@cached_property
def all_categories(self):
......@@ -770,7 +789,7 @@ class Quantity(Definition):
elif type(value) == np.ndarray:
value = value.tolist()
MObject.m_type_check(self, value)
MSection.m_type_check(self, value)
obj.m_data[self.__name] = value
def __delete__(self, obj):
......@@ -796,7 +815,7 @@ class Section(Definition):
'section class'.
"""
section_cls: Type[MObject] = None
section_cls: Type[MSection] = None
""" The section class that corresponse to this section definition. """
repeats: 'Quantity' = None
......@@ -848,7 +867,7 @@ class Section(Definition):
.. code-block:: Python
class System(MObject):
class System(MSection):
pass
System.m_def.add_quantity(Quantity(name='n_atoms', type=int))
......@@ -885,11 +904,11 @@ class sub_section:
def __init__(self, section: SectionDef, **kwargs):
if isinstance(section, type):
self.section_def = cast(MObject, section).m_def
self.section_def = cast(MSection, section).m_def
else:
self.section_def = cast(Section, section)
def __get__(self, obj: MObject, type=None) -> Union[MObject, Section]:
def __get__(self, obj: MSection, type=None) -> Union[MSection, Section]:
if obj is None:
# the class attribute case
return self.section_def
......@@ -903,7 +922,7 @@ class sub_section:
return m_data_value
def __set__(self, obj: MObject, value: Union[MObject, List[MObject]]):
def __set__(self, obj: MSection, value: Union[MSection, List[MSection]]):
raise NotImplementedError('Sub sections cannot be set directly. Use m_create.')
def __delete__(self, obj):
......@@ -920,11 +939,6 @@ class Category(Definition):
In the old meta-info this was known as `abstract types`.
"""
def __init__(self, module_name, *args, **kwargs):
super().__init__(*args, **kwargs)
Package.from_module(module_name).m_add_sub_section(self)
@cached_property
def definitions(self) -> Iterable[Definition]:
""" All definitions that are directly or indirectly in this category. """
......@@ -1021,10 +1035,12 @@ Section.m_def.parent = Package.m_def
Category.m_def = Section(repeats=True, parent=Package.m_def)
Package.__init_section_cls__()
Category.__init_section_cls__()
Section.__init_section_cls__()
Quantity.__init_section_cls__()
is_bootstrapping = False
Package.__init_cls__()
Category.__init_cls__()
Section.__init_cls__()
Quantity.__init_cls__()
units = UnitRegistry()
""" The default pint unit registry that should be used to give units to quantity definitions. """
......@@ -2,7 +2,7 @@ from ase.data import chemical_symbols
from elasticsearch_dsl import Keyword, Integer, Float, InnerDoc, Nested
import numpy as np
from nomad.metainfo import MObject, Section, Quantity, Enum, units
from nomad.metainfo import MSection, Section, Quantity, Enum, units
def optimade_links(section: str):
......@@ -27,7 +27,7 @@ class Optimade():
pass
class OptimadeStructureEntry(MObject):
class OptimadeStructureEntry(MSection):
m_def = Section(
links=optimade_links('h.6.2'),
a_flask=dict(skip_none=True),
......@@ -166,7 +166,7 @@ class OptimadeStructureEntry(MObject):
''')
class Species(MObject):
class Species(MSection):
"""
Used to describe the species of the sites of this structure. Species can be pure
chemical elements, or virtual-crystal atoms representing a statistical occupation of a
......@@ -244,11 +244,11 @@ def elastic_mapping(section: Section, base_cls: type) -> type:
return type(section.name, (base_cls,), dct)
def elastic_obj(source: MObject, target_cls: type):
def elastic_obj(source: MSection, target_cls: type):
if source is None:
return None
assert isinstance(source, MObject)
assert isinstance(source, MSection)
target = target_cls()
......
......@@ -23,7 +23,7 @@ from nomadcore.local_backend import LocalBackend as LegacyLocalBackend
from nomadcore.local_backend import Section, Results
from nomad.utils import get_logger
from nomad.metainfo import MObject, Section as MI2Section
from nomad.metainfo import MSection, Section as MI2Section
logger = get_logger(__name__)
......@@ -337,7 +337,7 @@ class LocalBackend(LegacyParserBackend):
delegate = LegacyLocalBackend(*args, **kwargs)
super().__init__(delegate)
self.mi2_data: Dict[str, MObject] = {}
self.mi2_data: Dict[str, MSection] = {}
self.reset_status()
......@@ -349,7 +349,7 @@ class LocalBackend(LegacyParserBackend):
self._known_attributes = ['results']
self.fileOut = io.StringIO()
def add_mi2_section(self, section: MObject):
def add_mi2_section(self, section: MSection):
""" Allows to mix a metainfo2 style section into backend. """
self.mi2_data[section.m_def.name] = section
......
......@@ -15,7 +15,7 @@
import pytest
import numpy as np
from nomad.metainfo.metainfo import MObject, Section, Quantity, Definition, Category, Package, sub_section
from nomad.metainfo.metainfo import MSection, MCategory, Section, Quantity, Definition, Category, Package, sub_section
def assert_section_def(section_def: Section):
......@@ -35,7 +35,7 @@ def assert_section_def(section_def: Section):
assert section_def.parent is not None
def assert_section_instance(section: MObject):
def assert_section_instance(section: MSection):
assert_section_def(section.m_def)
if section.m_parent is not None:
......@@ -72,7 +72,7 @@ class TestPureReflection:
test_section_def = Section(name='TestSection')
test_section_def.m_create(Quantity, name='test_quantity')
obj = MObject(m_def=test_section_def)
obj = MSection(m_def=test_section_def)
assert obj.m_def.name == 'TestSection'
# FIXME assert obj.m_get('test_quantity') is None
setattr(obj, 'test_quantity', 'test_value')
......@@ -81,12 +81,13 @@ class TestPureReflection:
m_package = Package(description='package doc')
material_defining = Category(
__name__, name='material_defining',
description='Quantities that add to what constitutes a different material.')
class MaterialDefining(MCategory):
"""Quantities that add to what constitutes a different material."""
pass
class Run(MObject):
class Run(MSection):
""" This is the description.
And some more description.
......@@ -98,14 +99,14 @@ class Run(MObject):
''')
class System(MObject):
class System(MSection):
m_def = Section(repeats=True, parent=Run.m_def)
n_atoms = Quantity(type=int, default=0, categories=[material_defining])
atom_label = Quantity(type=str, shape=['n_atoms'], categories=[material_defining])
n_atoms = Quantity(type=int, default=0, categories=[MaterialDefining.m_def])
atom_label = Quantity(type=str, shape=['n_atoms'], categories=[MaterialDefining.m_def])
atom_positions = Quantity(type=np.dtype('f8'), shape=['n_atoms', 3])
class Parsing(MObject):
class Parsing(MSection):
m_def = Section(parent=Run.m_def)
......@@ -166,8 +167,8 @@ class TestM2:
def test_direct_category(self):
assert len(System.atom_label.categories)
assert material_defining in System.atom_label.categories
assert System.atom_label in material_defining.definitions
assert MaterialDefining.m_def in System.atom_label.categories
assert System.atom_label in MaterialDefining.m_def.definitions
def test_package(self):
assert m_package.name == __name__
......@@ -180,7 +181,7 @@ class TestM1:
""" Test for meta-info instances. """
def test_run(self):
class Run(MObject):
class Run(MSection):
pass
run = Run()
......@@ -192,7 +193,7 @@ class TestM1:
assert_section_instance(run)
def test_system(self):
class System(MObject):
class System(MSection):
m_def = Section()
atom_labels = Quantity(type=str, shape=['1..*'])
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment