emmocheck¶
A module for testing an ontology against conventions defined for EMMO.
A YAML file can be provided with additional test configurations.
Example configuration file:
test_unit_dimensions:
exceptions:
- myunits.MyUnitCategory1
- myunits.MyUnitCategory2
skip:
- name_of_test_to_skip
enable:
- name_of_test_to_enable
TestEMMOConventions
¶
Base class for testing an ontology against EMMO conventions.
Source code in emmopy/emmocheck.py
class TestEMMOConventions(unittest.TestCase):
"""Base class for testing an ontology against EMMO conventions."""
config = {} # configurations
def get_config(self, string, default=None):
"""Returns the configuration specified by `string`.
If configuration is not found in the configuration file, `default` is
returned.
Sub-configurations can be accessed by separating the components with
dots, like "test_namespace.exceptions".
"""
result = self.config
try:
for token in string.split("."):
result = result[token]
except KeyError:
return default
return result
get_config(self, string, default=None)
¶
Returns the configuration specified by string
.
If configuration is not found in the configuration file, default
is
returned.
Sub-configurations can be accessed by separating the components with dots, like "test_namespace.exceptions".
Source code in emmopy/emmocheck.py
def get_config(self, string, default=None):
"""Returns the configuration specified by `string`.
If configuration is not found in the configuration file, `default` is
returned.
Sub-configurations can be accessed by separating the components with
dots, like "test_namespace.exceptions".
"""
result = self.config
try:
for token in string.split("."):
result = result[token]
except KeyError:
return default
return result
TestFunctionalEMMOConventions
¶
Test functional EMMO conventions.
Source code in emmopy/emmocheck.py
class TestFunctionalEMMOConventions(TestEMMOConventions):
"""Test functional EMMO conventions."""
def test_description(self):
"""Check that all entities have a description.
A description is either an emmo:elucidation, an
emmo:definition or an emmo:conceptualisation.
Exceptions include entities from standard w3c vocabularies.
"""
exceptions = set()
exceptions.update(self.get_config("test_description.exceptions", ()))
props = self.onto.world._props # pylint: disable=protected-access
if (
"EMMO_967080e5_2f42_4eb2_a3a9_c58143e835f9" not in props
or "EMMO_31252f35_c767_4b97_a877_1235076c3e13" not in props
or "EMMO_70fe84ff_99b6_4206_a9fc_9a8931836d84" not in props
):
self.fail(
"ontology has no description (emmo:elucidation, "
"emmo:definition or emmo:conceptualisation)"
)
for entity in self.onto.classes(self.check_imported):
# Skip concepts from exceptions and common w3c vocabularies
vocabs = "owl.", "0.1.", "bibo.", "core.", "terms.", "vann."
r = repr(entity)
if r in exceptions or any(r.startswith(v) for v in vocabs):
continue
label = str(get_label(entity))
with self.subTest(entity=entity, label=label):
self.assertTrue(
hasattr(entity, "elucidation"),
msg=f"{label} has no emmo:elucidation",
)
self.assertTrue(
hasattr(entity, "definition"),
msg=f"{label} has no emmo:definition",
)
self.assertTrue(
hasattr(entity, "conceptualisation"),
msg=f"{label} has no emmo:conceptualisation",
)
self.assertTrue(
len(entity.elucidation)
+ len(entity.definition)
+ len(entity.conceptualisation)
>= 1,
msg="missing description (emmo:elucidation, "
f"emmo:deinition and/or emmo:conceptualidation): {label}",
)
self.assertTrue(
len(
[
s
for s in entity.elucidation
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:elucidation for {label}",
)
self.assertTrue(
len(
[
s
for s in entity.definition
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:definition for {label}",
)
self.assertTrue(
len(
[
s
for s in entity.conceptualisation
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:conceptualisation for {label}",
)
def test_unit_dimension(self):
"""Check that all measurement units have a physical dimension.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"metrology.MultipleUnit",
"metrology.SubMultipleUnit",
"metrology.OffSystemUnit",
"metrology.PrefixedUnit",
"metrology.NonPrefixedUnit",
"metrology.SpecialUnit",
"metrology.DerivedUnit",
"metrology.BaseUnit",
"metrology.UnitSymbol",
"siunits.SICoherentDerivedUnit",
"siunits.SINonCoherentDerivedUnit",
"siunits.SISpecialUnit",
"siunits.SICoherentUnit",
"siunits.SIPrefixedUnit",
"siunits.SIBaseUnit",
"siunits.SIUnitSymbol",
"siunits.SIUnit",
"emmo.MultipleUnit",
"emmo.SubMultipleUnit",
"emmo.OffSystemUnit",
"emmo.PrefixedUnit",
"emmo.NonPrefixedUnit",
"emmo.SpecialUnit",
"emmo.DerivedUnit",
"emmo.BaseUnit",
"emmo.UnitSymbol",
"emmo.SIAccepted",
"emmo.SICoherentDerivedUnit",
"emmo.SINonCoherentDerivedUnit",
"emmo.SISpecialUnit",
"emmo.SICoherentUnit",
"emmo.SIPrefixedUnit",
"emmo.SIBaseUnit",
"emmo.SIUnitSymbol",
"emmo.SIUnit",
)
)
if not hasattr(self.onto, "MeasurementUnit"):
return
exceptions.update(self.get_config("test_unit_dimension.exceptions", ()))
regex = re.compile(r"^(emmo|metrology).hasDimensionString.value\(.*\)$")
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.MeasurementUnit.descendants():
if not self.check_imported and cls not in classes:
continue
# Assume that actual units are not subclassed
if not list(cls.subclasses()) and repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
self.assertTrue(
any(
regex.match(repr(r))
for r in cls.get_indirect_is_a()
),
msg=cls,
)
def test_quantity_dimension_beta3(self):
"""Check that all quantities have a physicalDimension annotation.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"properties.ModelledQuantitativeProperty",
"properties.MeasuredQuantitativeProperty",
"properties.ConventionalQuantitativeProperty",
"metrology.QuantitativeProperty",
"metrology.Quantity",
"metrology.OrdinalQuantity",
"metrology.BaseQuantity",
"metrology.PhysicalConstant",
"metrology.PhysicalQuantity",
"metrology.ExactConstant",
"metrology.MeasuredConstant",
"metrology.DerivedQuantity",
"isq.ISQBaseQuantity",
"isq.InternationalSystemOfQuantity",
"isq.ISQDerivedQuantity",
"isq.SIExactConstant",
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.Quantity",
"emmo.OrdinalQuantity",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.AtomicAndNuclear",
"emmo.Defined",
"emmo.Electromagnetic",
"emmo.FrequentlyUsed",
"emmo.PhysicoChemical",
"emmo.ChemicalCompositionQuantity",
"emmo.Universal",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_quantity_dimension.exceptions", ())
)
regex = re.compile(
"^T([+-][1-9]|0) L([+-][1-9]|0) M([+-][1-9]|0) I([+-][1-9]|0) "
"(H|Θ)([+-][1-9]|0) N([+-][1-9]|0) J([+-][1-9]|0)$"
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
anno = cls.get_annotations()
self.assertIn("physicalDimension", anno, msg=cls)
physdim = anno["physicalDimension"].first()
self.assertRegex(physdim, regex, msg=cls)
def test_quantity_dimension(self):
"""Check that all quantities have a physicalDimension.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
# pylint: disable=invalid-name
exceptions = set(
(
"properties.ModelledQuantitativeProperty",
"properties.MeasuredQuantitativeProperty",
"properties.ConventionalQuantitativeProperty",
"metrology.QuantitativeProperty",
"metrology.Quantity",
"metrology.OrdinalQuantity",
"metrology.BaseQuantity",
"metrology.PhysicalConstant",
"metrology.PhysicalQuantity",
"metrology.ExactConstant",
"metrology.MeasuredConstant",
"metrology.DerivedQuantity",
"isq.ISQBaseQuantity",
"isq.InternationalSystemOfQuantity",
"isq.ISQDerivedQuantity",
"isq.SIExactConstant",
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.Quantity",
"emmo.OrdinalQuantity",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.ISO80000Categorised",
"emmo.AtomicAndNuclear",
"emmo.Defined",
"emmo.Electromagnetic",
"emmo.FrequentlyUsed",
"emmo.ChemicalCompositionQuantity",
"emmo.EquilibriumConstant", # physical dimension may change
"emmo.Solubility",
"emmo.Universal",
"emmo.Intensive",
"emmo.Extensive",
"emmo.Concentration",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_quantity_dimension.exceptions", ())
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if issubclass(cls, self.onto.ISO80000Categorised):
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
for r in cls.get_indirect_is_a():
if isinstance(r, owlready2.Restriction) and repr(
r
).startswith("emmo.hasMeasurementUnit.some"):
self.assertTrue(
issubclass(
r.value,
(
self.onto.DimensionalUnit,
self.onto.DimensionlessUnit,
),
)
)
break
else:
self.assertTrue(
issubclass(cls, self.onto.ISQDimensionlessQuantity)
)
def test_dimensional_unit(self):
"""Check correct syntax of dimension string of dimensional units."""
# This test requires that the ontology has imported SIDimensionalUnit
if "SIDimensionalUnit" not in self.onto:
self.skipTest("SIDimensionalUnit is not imported")
# pylint: disable=invalid-name
regex = re.compile(
"^T([+-][1-9][0-9]*|0) L([+-][1-9]|0) M([+-][1-9]|0) "
"I([+-][1-9]|0) (H|Θ)([+-][1-9]|0) N([+-][1-9]|0) "
"J([+-][1-9]|0)$"
)
for cls in self.onto.SIDimensionalUnit.__subclasses__():
with self.subTest(cls=cls, label=get_label(cls)):
self.assertEqual(len(cls.equivalent_to), 1)
r = cls.equivalent_to[0]
self.assertIsInstance(r, owlready2.Restriction)
self.assertRegex(r.value, regex)
def test_physical_quantity_dimension(self):
"""Check that all physical quantities have `hasPhysicalDimension`.
Note: this test will fail before isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.AtomicAndNuclearPhysicsQuantity",
"emmo.ThermodynamicalQuantity",
"emmo.LightAndRadiationQuantity",
"emmo.SpaceAndTimeQuantity",
"emmo.AcousticQuantity",
"emmo.PhysioChememicalQuantity",
"emmo.ElectromagneticQuantity",
"emmo.MechanicalQuantity",
"emmo.CondensedMatterPhysicsQuantity",
"emmo.ChemicalCompositionQuantity",
"emmo.Extensive",
"emmo.Intensive",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_physical_quantity_dimension.exceptions", ())
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
try:
class_props = cls.INDIRECT_get_class_properties()
except AttributeError:
# The INDIRECT_get_class_properties() method
# does not support inverse properties. Build
# class_props manually...
class_props = set()
for _ in cls.mro():
if hasattr(_, "is_a"):
class_props.update(
[
restriction.property
for restriction in _.is_a
if isinstance(
restriction, owlready2.Restriction
)
]
)
self.assertIn(
self.onto.hasPhysicalDimension, class_props, msg=cls
)
def test_namespace(self):
"""Check that all IRIs are namespaced after their (sub)ontology.
Configurations:
exceptions - full name of entities to ignore.
"""
exceptions = set(
(
"owl.qualifiedCardinality",
"owl.minQualifiedCardinality",
"terms.creator",
"terms.contributor",
"terms.publisher",
"terms.title",
"terms.license",
"terms.abstract",
"core.prefLabel",
"core.altLabel",
"core.hiddenLabel",
"mereotopology.Item",
"manufacturing.EngineeredMaterial",
)
)
exceptions.update(self.get_config("test_namespace.exceptions", ()))
def checker(onto, ignore_namespace):
if list(
filter(onto.base_iri.strip("#").endswith, self.ignore_namespace)
):
print(f"Skipping namespace: {onto.base_iri}")
return
entities = itertools.chain(
onto.classes(),
onto.object_properties(),
onto.data_properties(),
onto.individuals(),
onto.annotation_properties(),
)
for entity in entities:
if entity not in visited and repr(entity) not in exceptions:
visited.add(entity)
with self.subTest(
iri=entity.iri,
base_iri=onto.base_iri,
entity=repr(entity),
):
self.assertTrue(
entity.iri.endswith(entity.name),
msg=(
"the final part of entity IRIs must be their "
"name"
),
)
self.assertEqual(
entity.iri,
onto.base_iri + entity.name,
msg=(
f"IRI {entity.iri!r} does not correspond to "
f"module namespace: {onto.base_iri!r}"
),
)
if self.check_imported:
for imp_onto in onto.imported_ontologies:
if imp_onto not in visited_onto:
visited_onto.add(imp_onto)
checker(imp_onto, ignore_namespace)
visited = set()
visited_onto = set()
checker(self.onto, self.ignore_namespace)
test_description(self)
¶
Check that all entities have a description.
A description is either an emmo:elucidation, an emmo:definition or an emmo:conceptualisation.
Exceptions include entities from standard w3c vocabularies.
Source code in emmopy/emmocheck.py
def test_description(self):
"""Check that all entities have a description.
A description is either an emmo:elucidation, an
emmo:definition or an emmo:conceptualisation.
Exceptions include entities from standard w3c vocabularies.
"""
exceptions = set()
exceptions.update(self.get_config("test_description.exceptions", ()))
props = self.onto.world._props # pylint: disable=protected-access
if (
"EMMO_967080e5_2f42_4eb2_a3a9_c58143e835f9" not in props
or "EMMO_31252f35_c767_4b97_a877_1235076c3e13" not in props
or "EMMO_70fe84ff_99b6_4206_a9fc_9a8931836d84" not in props
):
self.fail(
"ontology has no description (emmo:elucidation, "
"emmo:definition or emmo:conceptualisation)"
)
for entity in self.onto.classes(self.check_imported):
# Skip concepts from exceptions and common w3c vocabularies
vocabs = "owl.", "0.1.", "bibo.", "core.", "terms.", "vann."
r = repr(entity)
if r in exceptions or any(r.startswith(v) for v in vocabs):
continue
label = str(get_label(entity))
with self.subTest(entity=entity, label=label):
self.assertTrue(
hasattr(entity, "elucidation"),
msg=f"{label} has no emmo:elucidation",
)
self.assertTrue(
hasattr(entity, "definition"),
msg=f"{label} has no emmo:definition",
)
self.assertTrue(
hasattr(entity, "conceptualisation"),
msg=f"{label} has no emmo:conceptualisation",
)
self.assertTrue(
len(entity.elucidation)
+ len(entity.definition)
+ len(entity.conceptualisation)
>= 1,
msg="missing description (emmo:elucidation, "
f"emmo:deinition and/or emmo:conceptualidation): {label}",
)
self.assertTrue(
len(
[
s
for s in entity.elucidation
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:elucidation for {label}",
)
self.assertTrue(
len(
[
s
for s in entity.definition
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:definition for {label}",
)
self.assertTrue(
len(
[
s
for s in entity.conceptualisation
if not hasattr(s, "lang") or s.lang == "en"
]
)
< 2,
msg=f"more than one emmo:conceptualisation for {label}",
)
test_dimensional_unit(self)
¶
Check correct syntax of dimension string of dimensional units.
Source code in emmopy/emmocheck.py
def test_dimensional_unit(self):
"""Check correct syntax of dimension string of dimensional units."""
# This test requires that the ontology has imported SIDimensionalUnit
if "SIDimensionalUnit" not in self.onto:
self.skipTest("SIDimensionalUnit is not imported")
# pylint: disable=invalid-name
regex = re.compile(
"^T([+-][1-9][0-9]*|0) L([+-][1-9]|0) M([+-][1-9]|0) "
"I([+-][1-9]|0) (H|Θ)([+-][1-9]|0) N([+-][1-9]|0) "
"J([+-][1-9]|0)$"
)
for cls in self.onto.SIDimensionalUnit.__subclasses__():
with self.subTest(cls=cls, label=get_label(cls)):
self.assertEqual(len(cls.equivalent_to), 1)
r = cls.equivalent_to[0]
self.assertIsInstance(r, owlready2.Restriction)
self.assertRegex(r.value, regex)
test_namespace(self)
¶
Check that all IRIs are namespaced after their (sub)ontology.
Configurations
exceptions - full name of entities to ignore.
Source code in emmopy/emmocheck.py
def test_namespace(self):
"""Check that all IRIs are namespaced after their (sub)ontology.
Configurations:
exceptions - full name of entities to ignore.
"""
exceptions = set(
(
"owl.qualifiedCardinality",
"owl.minQualifiedCardinality",
"terms.creator",
"terms.contributor",
"terms.publisher",
"terms.title",
"terms.license",
"terms.abstract",
"core.prefLabel",
"core.altLabel",
"core.hiddenLabel",
"mereotopology.Item",
"manufacturing.EngineeredMaterial",
)
)
exceptions.update(self.get_config("test_namespace.exceptions", ()))
def checker(onto, ignore_namespace):
if list(
filter(onto.base_iri.strip("#").endswith, self.ignore_namespace)
):
print(f"Skipping namespace: {onto.base_iri}")
return
entities = itertools.chain(
onto.classes(),
onto.object_properties(),
onto.data_properties(),
onto.individuals(),
onto.annotation_properties(),
)
for entity in entities:
if entity not in visited and repr(entity) not in exceptions:
visited.add(entity)
with self.subTest(
iri=entity.iri,
base_iri=onto.base_iri,
entity=repr(entity),
):
self.assertTrue(
entity.iri.endswith(entity.name),
msg=(
"the final part of entity IRIs must be their "
"name"
),
)
self.assertEqual(
entity.iri,
onto.base_iri + entity.name,
msg=(
f"IRI {entity.iri!r} does not correspond to "
f"module namespace: {onto.base_iri!r}"
),
)
if self.check_imported:
for imp_onto in onto.imported_ontologies:
if imp_onto not in visited_onto:
visited_onto.add(imp_onto)
checker(imp_onto, ignore_namespace)
visited = set()
visited_onto = set()
checker(self.onto, self.ignore_namespace)
test_physical_quantity_dimension(self)
¶
Check that all physical quantities have hasPhysicalDimension
.
Note: this test will fail before isq is moved to emmo/domain.
Configurations
exceptions - full class names of classes to ignore.
Source code in emmopy/emmocheck.py
def test_physical_quantity_dimension(self):
"""Check that all physical quantities have `hasPhysicalDimension`.
Note: this test will fail before isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.AtomicAndNuclearPhysicsQuantity",
"emmo.ThermodynamicalQuantity",
"emmo.LightAndRadiationQuantity",
"emmo.SpaceAndTimeQuantity",
"emmo.AcousticQuantity",
"emmo.PhysioChememicalQuantity",
"emmo.ElectromagneticQuantity",
"emmo.MechanicalQuantity",
"emmo.CondensedMatterPhysicsQuantity",
"emmo.ChemicalCompositionQuantity",
"emmo.Extensive",
"emmo.Intensive",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_physical_quantity_dimension.exceptions", ())
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
try:
class_props = cls.INDIRECT_get_class_properties()
except AttributeError:
# The INDIRECT_get_class_properties() method
# does not support inverse properties. Build
# class_props manually...
class_props = set()
for _ in cls.mro():
if hasattr(_, "is_a"):
class_props.update(
[
restriction.property
for restriction in _.is_a
if isinstance(
restriction, owlready2.Restriction
)
]
)
self.assertIn(
self.onto.hasPhysicalDimension, class_props, msg=cls
)
test_quantity_dimension(self)
¶
Check that all quantities have a physicalDimension.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations
exceptions - full class names of classes to ignore.
Source code in emmopy/emmocheck.py
def test_quantity_dimension(self):
"""Check that all quantities have a physicalDimension.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
# pylint: disable=invalid-name
exceptions = set(
(
"properties.ModelledQuantitativeProperty",
"properties.MeasuredQuantitativeProperty",
"properties.ConventionalQuantitativeProperty",
"metrology.QuantitativeProperty",
"metrology.Quantity",
"metrology.OrdinalQuantity",
"metrology.BaseQuantity",
"metrology.PhysicalConstant",
"metrology.PhysicalQuantity",
"metrology.ExactConstant",
"metrology.MeasuredConstant",
"metrology.DerivedQuantity",
"isq.ISQBaseQuantity",
"isq.InternationalSystemOfQuantity",
"isq.ISQDerivedQuantity",
"isq.SIExactConstant",
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.Quantity",
"emmo.OrdinalQuantity",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.ISO80000Categorised",
"emmo.AtomicAndNuclear",
"emmo.Defined",
"emmo.Electromagnetic",
"emmo.FrequentlyUsed",
"emmo.ChemicalCompositionQuantity",
"emmo.EquilibriumConstant", # physical dimension may change
"emmo.Solubility",
"emmo.Universal",
"emmo.Intensive",
"emmo.Extensive",
"emmo.Concentration",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_quantity_dimension.exceptions", ())
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if issubclass(cls, self.onto.ISO80000Categorised):
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
for r in cls.get_indirect_is_a():
if isinstance(r, owlready2.Restriction) and repr(
r
).startswith("emmo.hasMeasurementUnit.some"):
self.assertTrue(
issubclass(
r.value,
(
self.onto.DimensionalUnit,
self.onto.DimensionlessUnit,
),
)
)
break
else:
self.assertTrue(
issubclass(cls, self.onto.ISQDimensionlessQuantity)
)
test_quantity_dimension_beta3(self)
¶
Check that all quantities have a physicalDimension annotation.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations
exceptions - full class names of classes to ignore.
Source code in emmopy/emmocheck.py
def test_quantity_dimension_beta3(self):
"""Check that all quantities have a physicalDimension annotation.
Note: this test will be deprecated when isq is moved to emmo/domain.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"properties.ModelledQuantitativeProperty",
"properties.MeasuredQuantitativeProperty",
"properties.ConventionalQuantitativeProperty",
"metrology.QuantitativeProperty",
"metrology.Quantity",
"metrology.OrdinalQuantity",
"metrology.BaseQuantity",
"metrology.PhysicalConstant",
"metrology.PhysicalQuantity",
"metrology.ExactConstant",
"metrology.MeasuredConstant",
"metrology.DerivedQuantity",
"isq.ISQBaseQuantity",
"isq.InternationalSystemOfQuantity",
"isq.ISQDerivedQuantity",
"isq.SIExactConstant",
"emmo.ModelledQuantitativeProperty",
"emmo.MeasuredQuantitativeProperty",
"emmo.ConventionalQuantitativeProperty",
"emmo.QuantitativeProperty",
"emmo.Quantity",
"emmo.OrdinalQuantity",
"emmo.BaseQuantity",
"emmo.PhysicalConstant",
"emmo.PhysicalQuantity",
"emmo.ExactConstant",
"emmo.MeasuredConstant",
"emmo.DerivedQuantity",
"emmo.ISQBaseQuantity",
"emmo.InternationalSystemOfQuantity",
"emmo.ISQDerivedQuantity",
"emmo.SIExactConstant",
"emmo.NonSIUnits",
"emmo.StandardizedPhysicalQuantity",
"emmo.CategorizedPhysicalQuantity",
"emmo.AtomicAndNuclear",
"emmo.Defined",
"emmo.Electromagnetic",
"emmo.FrequentlyUsed",
"emmo.PhysicoChemical",
"emmo.ChemicalCompositionQuantity",
"emmo.Universal",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
return
exceptions.update(
self.get_config("test_quantity_dimension.exceptions", ())
)
regex = re.compile(
"^T([+-][1-9]|0) L([+-][1-9]|0) M([+-][1-9]|0) I([+-][1-9]|0) "
"(H|Θ)([+-][1-9]|0) N([+-][1-9]|0) J([+-][1-9]|0)$"
)
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.PhysicalQuantity.descendants():
if not self.check_imported and cls not in classes:
continue
if repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
anno = cls.get_annotations()
self.assertIn("physicalDimension", anno, msg=cls)
physdim = anno["physicalDimension"].first()
self.assertRegex(physdim, regex, msg=cls)
test_unit_dimension(self)
¶
Check that all measurement units have a physical dimension.
Configurations
exceptions - full class names of classes to ignore.
Source code in emmopy/emmocheck.py
def test_unit_dimension(self):
"""Check that all measurement units have a physical dimension.
Configurations:
exceptions - full class names of classes to ignore.
"""
exceptions = set(
(
"metrology.MultipleUnit",
"metrology.SubMultipleUnit",
"metrology.OffSystemUnit",
"metrology.PrefixedUnit",
"metrology.NonPrefixedUnit",
"metrology.SpecialUnit",
"metrology.DerivedUnit",
"metrology.BaseUnit",
"metrology.UnitSymbol",
"siunits.SICoherentDerivedUnit",
"siunits.SINonCoherentDerivedUnit",
"siunits.SISpecialUnit",
"siunits.SICoherentUnit",
"siunits.SIPrefixedUnit",
"siunits.SIBaseUnit",
"siunits.SIUnitSymbol",
"siunits.SIUnit",
"emmo.MultipleUnit",
"emmo.SubMultipleUnit",
"emmo.OffSystemUnit",
"emmo.PrefixedUnit",
"emmo.NonPrefixedUnit",
"emmo.SpecialUnit",
"emmo.DerivedUnit",
"emmo.BaseUnit",
"emmo.UnitSymbol",
"emmo.SIAccepted",
"emmo.SICoherentDerivedUnit",
"emmo.SINonCoherentDerivedUnit",
"emmo.SISpecialUnit",
"emmo.SICoherentUnit",
"emmo.SIPrefixedUnit",
"emmo.SIBaseUnit",
"emmo.SIUnitSymbol",
"emmo.SIUnit",
)
)
if not hasattr(self.onto, "MeasurementUnit"):
return
exceptions.update(self.get_config("test_unit_dimension.exceptions", ()))
regex = re.compile(r"^(emmo|metrology).hasDimensionString.value\(.*\)$")
classes = set(self.onto.classes(self.check_imported))
for cls in self.onto.MeasurementUnit.descendants():
if not self.check_imported and cls not in classes:
continue
# Assume that actual units are not subclassed
if not list(cls.subclasses()) and repr(cls) not in exceptions:
with self.subTest(cls=cls, label=get_label(cls)):
self.assertTrue(
any(
regex.match(repr(r))
for r in cls.get_indirect_is_a()
),
msg=cls,
)
TestSyntacticEMMOConventions
¶
Test syntactic EMMO conventions.
Source code in emmopy/emmocheck.py
class TestSyntacticEMMOConventions(TestEMMOConventions):
"""Test syntactic EMMO conventions."""
def test_number_of_labels(self):
"""Check that all entities have one and only one prefLabel.
Use "altLabel" for synonyms.
The only allowed exception is entities who's representation
starts with "owl.".
"""
exceptions = set(
(
"0.1.homepage", # foaf:homepage
"0.1.logo",
"0.1.page",
"0.1.name",
"bibo:doi",
"core.altLabel",
"core.hiddenLabel",
"core.prefLabel",
"terms.abstract",
"terms.alternative",
"terms:bibliographicCitation",
"terms.contributor",
"terms.created",
"terms.creator",
"terms.hasFormat",
"terms.identifier",
"terms.issued",
"terms.license",
"terms.modified",
"terms.publisher",
"terms.source",
"terms.title",
"vann:preferredNamespacePrefix",
"vann:preferredNamespaceUri",
)
)
exceptions.update(
self.get_config("test_number_of_labels.exceptions", ())
)
if (
"prefLabel"
in self.onto.world._props # pylint: disable=protected-access
):
for entity in self.onto.classes(self.check_imported):
if repr(entity) not in exceptions:
with self.subTest(
entity=entity,
label=get_label(entity),
prefLabels=entity.prefLabel,
):
if not repr(entity).startswith("owl."):
self.assertTrue(hasattr(entity, "prefLabel"))
self.assertEqual(1, len(entity.prefLabel))
else:
self.fail("ontology has no prefLabel")
def test_class_label(self):
"""Check that class labels are CamelCase and valid identifiers.
For CamelCase, we are currently only checking that the labels
start with upper case.
"""
exceptions = set(
(
"0-manifold", # not needed in 1.0.0-beta
"1-manifold",
"2-manifold",
"3-manifold",
"C++",
"3DPrinting",
)
)
exceptions.update(self.get_config("test_class_label.exceptions", ()))
for cls in self.onto.classes(self.check_imported):
for label in cls.label + getattr(cls, "prefLabel", []):
if str(label) not in exceptions:
with self.subTest(entity=cls, label=label):
self.assertTrue(label.isidentifier())
self.assertTrue(label[0].isupper())
def test_object_property_label(self):
"""Check that object property labels are lowerCamelCase.
Allowed exceptions: "EMMORelation"
If they start with "has" or "is" they should be followed by a
upper case letter.
If they start with "is" they should also end with "Of".
"""
exceptions = set(("EMMORelation",))
exceptions.update(
self.get_config("test_object_property_label.exceptions", ())
)
for obj_prop in self.onto.object_properties():
if repr(obj_prop) not in exceptions:
for label in obj_prop.label:
with self.subTest(entity=obj_prop, label=label):
self.assertTrue(
label[0].islower(), "label start with lowercase"
)
if label.startswith("has"):
self.assertTrue(
label[3].isupper(),
'what follows "has" must be "uppercase"',
)
if label.startswith("is"):
self.assertTrue(
label[2].isupper(),
'what follows "is" must be "uppercase"',
)
self.assertTrue(
label.endswith(("Of", "With")),
'should end with "Of" or "With"',
)
test_class_label(self)
¶
Check that class labels are CamelCase and valid identifiers.
For CamelCase, we are currently only checking that the labels start with upper case.
Source code in emmopy/emmocheck.py
def test_class_label(self):
"""Check that class labels are CamelCase and valid identifiers.
For CamelCase, we are currently only checking that the labels
start with upper case.
"""
exceptions = set(
(
"0-manifold", # not needed in 1.0.0-beta
"1-manifold",
"2-manifold",
"3-manifold",
"C++",
"3DPrinting",
)
)
exceptions.update(self.get_config("test_class_label.exceptions", ()))
for cls in self.onto.classes(self.check_imported):
for label in cls.label + getattr(cls, "prefLabel", []):
if str(label) not in exceptions:
with self.subTest(entity=cls, label=label):
self.assertTrue(label.isidentifier())
self.assertTrue(label[0].isupper())
test_number_of_labels(self)
¶
Check that all entities have one and only one prefLabel.
Use "altLabel" for synonyms.
The only allowed exception is entities who's representation starts with "owl.".
Source code in emmopy/emmocheck.py
def test_number_of_labels(self):
"""Check that all entities have one and only one prefLabel.
Use "altLabel" for synonyms.
The only allowed exception is entities who's representation
starts with "owl.".
"""
exceptions = set(
(
"0.1.homepage", # foaf:homepage
"0.1.logo",
"0.1.page",
"0.1.name",
"bibo:doi",
"core.altLabel",
"core.hiddenLabel",
"core.prefLabel",
"terms.abstract",
"terms.alternative",
"terms:bibliographicCitation",
"terms.contributor",
"terms.created",
"terms.creator",
"terms.hasFormat",
"terms.identifier",
"terms.issued",
"terms.license",
"terms.modified",
"terms.publisher",
"terms.source",
"terms.title",
"vann:preferredNamespacePrefix",
"vann:preferredNamespaceUri",
)
)
exceptions.update(
self.get_config("test_number_of_labels.exceptions", ())
)
if (
"prefLabel"
in self.onto.world._props # pylint: disable=protected-access
):
for entity in self.onto.classes(self.check_imported):
if repr(entity) not in exceptions:
with self.subTest(
entity=entity,
label=get_label(entity),
prefLabels=entity.prefLabel,
):
if not repr(entity).startswith("owl."):
self.assertTrue(hasattr(entity, "prefLabel"))
self.assertEqual(1, len(entity.prefLabel))
else:
self.fail("ontology has no prefLabel")
test_object_property_label(self)
¶
Check that object property labels are lowerCamelCase.
Allowed exceptions: "EMMORelation"
If they start with "has" or "is" they should be followed by a upper case letter.
If they start with "is" they should also end with "Of".
Source code in emmopy/emmocheck.py
def test_object_property_label(self):
"""Check that object property labels are lowerCamelCase.
Allowed exceptions: "EMMORelation"
If they start with "has" or "is" they should be followed by a
upper case letter.
If they start with "is" they should also end with "Of".
"""
exceptions = set(("EMMORelation",))
exceptions.update(
self.get_config("test_object_property_label.exceptions", ())
)
for obj_prop in self.onto.object_properties():
if repr(obj_prop) not in exceptions:
for label in obj_prop.label:
with self.subTest(entity=obj_prop, label=label):
self.assertTrue(
label[0].islower(), "label start with lowercase"
)
if label.startswith("has"):
self.assertTrue(
label[3].isupper(),
'what follows "has" must be "uppercase"',
)
if label.startswith("is"):
self.assertTrue(
label[2].isupper(),
'what follows "is" must be "uppercase"',
)
self.assertTrue(
label.endswith(("Of", "With")),
'should end with "Of" or "With"',
)
main(argv=None)
¶
Run all checks on ontology iri
.
Default is 'http://emmo.info/emmo'.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
argv |
list |
List of arguments, similar to |
None |
Source code in emmopy/emmocheck.py
def main(
argv: list = None,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Run all checks on ontology `iri`.
Default is 'http://emmo.info/emmo'.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("iri", help="File name or URI to the ontology to test.")
parser.add_argument(
"--database",
"-d",
metavar="FILENAME",
default=":memory:",
help=(
"Load ontology from Owlready2 sqlite3 database. The `iri` argument"
" should in this case be the IRI of the ontology you want to "
"check."
),
)
parser.add_argument(
"--local",
"-l",
action="store_true",
help=(
"Load imported ontologies locally. Their paths are specified in "
"Protègè catalog files or via the --path option. The IRI should "
"be a file name."
),
)
parser.add_argument(
"--catalog-file",
default="catalog-v001.xml",
help=(
"Name of Protègè catalog file in the same folder as the ontology. "
"This option is used together with --local and defaults to "
'"catalog-v001.xml".'
),
)
parser.add_argument(
"--path",
action="append",
default=[],
help=(
"Paths where imported ontologies can be found. May be provided as "
"a comma-separated string and/or with multiple --path options."
),
)
parser.add_argument(
"--check-imported",
"-i",
action="store_true",
help="Whether to check imported ontologies.",
)
parser.add_argument(
"--verbose", "-v", action="store_true", help="Verbosity level."
)
parser.add_argument(
"--configfile",
"-c",
help="A yaml file with additional test configurations.",
)
parser.add_argument(
"--skip",
"-s",
action="append",
default=[],
help=(
"Shell pattern matching tests to skip. This option may be "
"provided multiple times."
),
)
parser.add_argument(
"--enable",
"-e",
action="append",
default=[],
help=(
"Shell pattern matching tests to enable that have been skipped by "
"default or in the config file. This option may be provided "
"multiple times."
),
)
parser.add_argument( # deprecated, replaced by --no-catalog
"--url-from-catalog",
"-u",
default=None,
action="store_true",
help="Get url from catalog file",
)
parser.add_argument(
"--no-catalog",
action="store_false",
dest="url_from_catalog",
default=None,
help="Whether to not read catalog file even if it exists.",
)
parser.add_argument(
"--ignore-namespace",
"-n",
action="append",
default=[],
help="Namespace to be ignored. Can be given multiple times",
)
# Options to pass forward to unittest
parser.add_argument(
"--buffer",
"-b",
dest="unittest",
action="append_const",
const="-b",
help=(
"The standard output and standard error streams are buffered "
"during the test run. Output during a passing test is discarded. "
"Output is echoed normally on test fail or error and is added to "
"the failure messages."
),
)
parser.add_argument(
"--catch",
dest="unittest",
action="append_const",
const="-c",
help=(
"Control-C during the test run waits for the current test to end "
"and then reports all the results so far. A second control-C "
"raises the normal KeyboardInterrupt exception"
),
)
parser.add_argument(
"--failfast",
"-f",
dest="unittest",
action="append_const",
const="-f",
help="Stop the test run on the first error or failure.",
)
try:
args = parser.parse_args(args=argv)
sys.argv[1:] = args.unittest if args.unittest else []
if args.verbose:
sys.argv.append("-v")
except SystemExit as exc:
sys.exit(exc.code) # Exit without traceback on invalid arguments
# Append to onto_path
for paths in args.path:
for path in paths.split(","):
if path not in onto_path:
onto_path.append(path)
# Load ontology
world = World(filename=args.database)
if args.database != ":memory:" and args.iri not in world.ontologies:
parser.error(
"The IRI argument should be one of the ontologies in "
"the database:\n " + "\n ".join(world.ontologies.keys())
)
onto = world.get_ontology(args.iri)
onto.load(
only_local=args.local,
url_from_catalog=args.url_from_catalog,
catalog_file=args.catalog_file,
)
# Store settings TestEMMOConventions
TestEMMOConventions.onto = onto
TestEMMOConventions.check_imported = args.check_imported
TestEMMOConventions.ignore_namespace = args.ignore_namespace
# Configure tests
verbosity = 2 if args.verbose else 1
if args.configfile:
import yaml # pylint: disable=import-outside-toplevel
with open(args.configfile, "rt") as handle:
TestEMMOConventions.config.update(
yaml.load(handle, Loader=yaml.SafeLoader)
)
# Run all subclasses of TestEMMOConventions as test suites
status = 0
for cls in TestEMMOConventions.__subclasses__():
# pylint: disable=cell-var-from-loop,undefined-loop-variable
suite = unittest.TestLoader().loadTestsFromTestCase(cls)
# Mark tests to be skipped
for test in suite:
name = test.id().split(".")[-1]
skipped = set( # skipped by default
[
"test_namespace",
"test_physical_quantity_dimension_annotation",
"test_quantity_dimension_beta3",
"test_physical_quantity_dimension",
]
)
msg = {name: "skipped by default" for name in skipped}
# enable/skip tests from config file
for pattern in test.get_config("enable", ()):
if fnmatch.fnmatchcase(name, pattern):
skipped.remove(name)
for pattern in test.get_config("skip", ()):
if fnmatch.fnmatchcase(name, pattern):
skipped.add(name)
msg[name] = "skipped from config file"
# enable/skip from command line
for pattern in args.enable:
if fnmatch.fnmatchcase(name, pattern):
skipped.remove(name)
for pattern in args.skip:
if fnmatch.fnmatchcase(name, pattern):
skipped.add(name)
msg[name] = "skipped from command line"
if name in skipped:
setattr(test, "setUp", lambda: test.skipTest(msg.get(name, "")))
runner = TextTestRunner(verbosity=verbosity)
runner.resultclass.checkmode = True
result = runner.run(suite)
if result.failures:
status = 1
return status