From 62449dfb40db5f37ad1b8be460d3ba8b321afee4 Mon Sep 17 00:00:00 2001
From: Lauri Himanen <lauri.himanen@gmail.com>
Date: Fri, 17 Jan 2020 12:30:51 +0200
Subject: [PATCH] Renamed Enum class to MEnum, added possibility to tie the
 enum values to variables.

---
 nomad/metainfo/CONCEPT.md  |  4 ++--
 nomad/metainfo/__init__.py |  6 ++---
 nomad/metainfo/example.py  |  4 ++--
 nomad/metainfo/legacy.py   |  4 ++--
 nomad/metainfo/metainfo.py | 45 ++++++++++++++++++++++++--------------
 nomad/metainfo/optimade.py |  6 ++---
 6 files changed, 41 insertions(+), 28 deletions(-)

diff --git a/nomad/metainfo/CONCEPT.md b/nomad/metainfo/CONCEPT.md
index a5c02f2001..9d1fb4324d 100644
--- a/nomad/metainfo/CONCEPT.md
+++ b/nomad/metainfo/CONCEPT.md
@@ -107,7 +107,7 @@ the instances of a sub section.
 A `Quantity` definition is a special and concrete `Property` definition:
 
 - `shape`, a list of either `int`, references to a dimension (quantity definition), or limits definitions (e.g. `'1..n'`, `'0..n'`.)
-- `type`, a primitive or Enum type
+- `type`, a primitive or MEnum type
 - `unit`, a (computed) units, e.g. `units.F * units.m`
 - `derived_from`, a list of references to other quantity definitions
 - `synonym`, a reference to another quantity definition
@@ -187,7 +187,7 @@ class System(MSection):
 
     atom_labels = Quantity(
         shape=['n_atoms'],
-        type=Enum(ase.data.chemical_symbols),
+        type=MEnum(ase.data.chemical_symbols),
         annotations=[ElasticSearchQuantity('keyword')])
     """
     Atom labels are ...
diff --git a/nomad/metainfo/__init__.py b/nomad/metainfo/__init__.py
index 05d759d772..9a54c3c36a 100644
--- a/nomad/metainfo/__init__.py
+++ b/nomad/metainfo/__init__.py
@@ -42,7 +42,7 @@ Starting example
             A Defines the number of atoms in the system.
             ''')
 
-        atom_labels = Quantity(type=Enum(ase.data.chemical_symbols), shape['n_atoms'])
+        atom_labels = Quantity(type=MEnum(ase.data.chemical_symbols), shape['n_atoms'])
         atom_positions = Quantity(type=float, shape=['n_atoms', 3], unit=Units.m)
         simulation_cell = Quantity(type=float, shape=[3, 3], unit=Units.m)
         pbc = Quantity(type=bool, shape=[3])
@@ -209,7 +209,7 @@ Custom data types
 .. autoclass:: DataType
     :members:
 
-.. autoclass:: Enum
+.. autoclass:: MEnum
 
 .. _metainfo-reflection
 
@@ -275,6 +275,6 @@ A more complex example
 """
 
 from .metainfo import MSection, MCategory, Definition, Property, Quantity, SubSection, \
-    Section, Category, Package, Environment, Enum, Datetime, MProxy, MetainfoError, DeriveError, \
+    Section, Category, Package, Environment, MEnum, Datetime, MProxy, MetainfoError, DeriveError, \
     MetainfoReferenceError, DataType, MData, MDataDict, Reference, MResource, m_package, \
     units
diff --git a/nomad/metainfo/example.py b/nomad/metainfo/example.py
index 009557b621..3be207f567 100644
--- a/nomad/metainfo/example.py
+++ b/nomad/metainfo/example.py
@@ -3,7 +3,7 @@
 import numpy as np
 from datetime import datetime
 
-from nomad.metainfo import MSection, MCategory, Section, Quantity, Package, SubSection, Enum, Datetime, units
+from nomad.metainfo import MSection, MCategory, Section, Quantity, Package, SubSection, MEnum, Datetime, units
 
 m_package = Package(links=['http://metainfo.nomad-coe.eu'])
 
@@ -82,7 +82,7 @@ class VaspRun(Run):
     m_def = Section(extends_base_section=True)
 
     x_vasp_raw_format = Quantity(
-        type=Enum(['xml', 'outcar']),
+        type=MEnum(['xml', 'outcar']),
         description='The file format of the parsed VASP mainfile.')
 
 
diff --git a/nomad/metainfo/legacy.py b/nomad/metainfo/legacy.py
index 0e0f345a13..3b0a3c4f84 100644
--- a/nomad/metainfo/legacy.py
+++ b/nomad/metainfo/legacy.py
@@ -9,7 +9,7 @@ import nomad_meta_info
 
 from nomad import utils
 from nomad.metainfo import Definition, Package, Category, Section, Quantity, SubSection, \
-    Environment, Enum, Reference, MSection, units
+    Environment, MEnum, Reference, MSection, units
 
 
 T = TypeVar('T', bound=Definition)
@@ -246,7 +246,7 @@ class LegacyMetainfoEnvironment:
             elif isinstance(definition.type, Reference):
                 dtype_str = 'r'
                 result['referencedSections'] = [definition.type.target_section_def.name]
-            elif isinstance(definition.type, Enum):
+            elif isinstance(definition.type, MEnum):
                 dtype_str = 'C'
             elif type(definition.type) == np.dtype:
                 dtype_str = definition.type.name[0]
diff --git a/nomad/metainfo/metainfo.py b/nomad/metainfo/metainfo.py
index 608cb01533..9c96ca4b20 100644
--- a/nomad/metainfo/metainfo.py
+++ b/nomad/metainfo/metainfo.py
@@ -20,6 +20,7 @@ import inspect
 import re
 import json
 import itertools
+
 import numpy as np
 import pint
 import pint.unit
@@ -56,14 +57,26 @@ class MetainfoReferenceError(MetainfoError):
 
 # Metainfo quantity data types
 
-class Enum(list):
-    """ Allows to define str types with values limited to a pre-set list of possible values. """
-    def __init__(self, *args):
+class MEnum():
+    """Allows to define str types with values limited to a pre-set list of possible values."""
+    def __init__(self, *args, **kwargs):
+        # Supports one big list in place of args
         if len(args) == 1 and isinstance(args[0], list):
-            super().__init__(args[0])
+            args = args[0]
 
-        else:
-            super().__init__(args)
+        # If non-named arguments are given, the default is to have them placed
+        # into a dictionary with their string value as both the enum name and
+        # the value.
+        for arg in args:
+            if arg in kwargs:
+                raise ValueError("Duplicate value '{}' provided for enum".format(arg))
+            kwargs[arg] = arg
+
+        self._values = set(kwargs.values())  # For allowing constant time member check
+        self._map = kwargs
+
+    def __getattr__(self, attr):
+        return self._map[attr]
 
 
 class MProxy():
@@ -181,7 +194,7 @@ class _QuantityType(DataType):
     - python build-in primitives: int, float, bool, str
     - numpy dtypes, e.g. f, int32
     - a section definition to define references
-    - an Enum instance to use it's values as possible str values
+    - an MEnum instance to use it's values as possible str values
     - a custom datatype, i.e. instance of :class:`DataType`
     - Any
     """
@@ -190,10 +203,10 @@ class _QuantityType(DataType):
         if value in [str, int, float, bool]:
             return value
 
-        if isinstance(value, Enum):
-            for enum_value in value:
+        if isinstance(value, MEnum):
+            for enum_value in value._values:
                 if not isinstance(enum_value, str):
-                    raise TypeError('Enum value %s is not a string.' % enum_value)
+                    raise TypeError('MEnum value %s is not a string.' % enum_value)
             return value
 
         if type(value) == np.dtype:
@@ -221,7 +234,7 @@ class _QuantityType(DataType):
         if value is str or value is int or value is float or value is bool:
             return dict(type_kind='python', type_data=value.__name__)
 
-        if isinstance(value, Enum):
+        if isinstance(value, MEnum):
             return dict(type_kind='Enum', type_data=list(value))
 
         if type(value) == np.dtype:
@@ -789,8 +802,8 @@ class MSection(metaclass=MObjectMeta):
                     'The value %s for quantity %s does not follow %s' %
                     (value, quantity_def, quantity_def.type))
 
-        elif isinstance(quantity_def.type, Enum):
-            if value not in quantity_def.type:
+        elif isinstance(quantity_def.type, MEnum):
+            if value not in quantity_def.type._values:
                 raise TypeError(
                     'The value %s is not an enum value for quantity %s.' %
                     (value, quantity_def))
@@ -1055,7 +1068,7 @@ class MSection(metaclass=MObjectMeta):
                     elif type(quantity.type) == np.dtype:
                         pass
 
-                    elif isinstance(quantity.type, Enum):
+                    elif isinstance(quantity.type, MEnum):
                         pass
 
                     elif quantity.type == Any:
@@ -1273,7 +1286,7 @@ class MSection(metaclass=MObjectMeta):
 
         if type(value) == np.ndarray:
             value_shape = value.shape
-        if isinstance(value, list) and not isinstance(value, Enum):
+        if isinstance(value, list) and not isinstance(value, MEnum):
             value_shape = [len(value)]
         else:
             value_shape = []
@@ -1467,7 +1480,7 @@ class Quantity(Property):
             The `type` can be one of:
 
             - a build-in primitive Python type: ``int``, ``str``, ``bool``, ``float``
-            - an instance of :class:`Enum`, e.g. ``Enum('one', 'two', 'three')``
+            - an instance of :class:`MEnum`, e.g. ``MEnum('one', 'two', 'three')``
             - a section to define references to other sections as quantity values
             - a custom meta-info :class:`DataType`, see :ref:`metainfo-custom-types`
             - a numpy `dtype`, e.g. ``np.dtype('float32')``
diff --git a/nomad/metainfo/optimade.py b/nomad/metainfo/optimade.py
index 1e71d23669..ef571ef0b9 100644
--- a/nomad/metainfo/optimade.py
+++ b/nomad/metainfo/optimade.py
@@ -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 MSection, Section, Quantity, SubSection, Enum, units
+from nomad.metainfo import MSection, Section, Quantity, SubSection, MEnum, units
 
 
 def optimade_links(section: str):
@@ -42,7 +42,7 @@ class Species(MSection):
         ''')
 
     chemical_symbols = Quantity(
-        type=Enum(chemical_symbols + ['x', 'vacancy']), shape=['1..*'],
+        type=MEnum(chemical_symbols + ['x', 'vacancy']), shape=['1..*'],
         a_optimade=Optimade(entry=True), description='''
             A list of strings of all chemical elements composing this species.
 
@@ -217,7 +217,7 @@ class OptimadeEntry(MSection):
     # TODO assemblies
 
     structure_features = Quantity(
-        type=Enum(['disorder', 'unknown_positions', 'assemblies']), shape=['1..*'],
+        type=MEnum(['disorder', 'unknown_positions', 'assemblies']), shape=['1..*'],
         links=optimade_links('h.6.2.15'),
         a_elastic=dict(type=Keyword),
         a_optimade=Optimade(query=True, entry=True), description='''
-- 
GitLab