Skip to content
11 changes: 6 additions & 5 deletions pyheritage/cidoc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
__all__ = ('CRMEntityBase', 'PropertyMixin', 'entity_register', )


ENTITY_REGISTRY: dict[str, type] = {}
ENTITY_REGISTRY: dict[str, dict[str, type]] = {}


class PropertyMixin(ABC, BaseModel):
Expand Down Expand Up @@ -54,18 +54,19 @@ class CRMEntityBase(Identity):
# ******************************************************************************************************************* #


def entity_register(label: str) -> callable:
def entity_register(label: str, version: str = "7.0") -> callable:
"""Entity decorator;

Adds entity class to entity registry;
Adds CRM code and CRM label to entity class attributes;
Adds entity class to versioned entity registry;
Adds CRM code, CRM label and CRM version to entity class attributes;

"""
def wrapper(cls: CRMEntityBase) -> CRMEntityBase:
code = label.split(" ")[0]
cls.crm_code = code
cls.crm_label = label
ENTITY_REGISTRY[code] = cls.__class__
cls.crm_version = version
ENTITY_REGISTRY.setdefault(code, {})[version] = cls

return cls

Expand Down
39 changes: 39 additions & 0 deletions pyheritage/cidoc/versions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-

"""CIDOC-CRM versioning package;

Provides version resolution for CIDOC CRM core and extensions;
Entry point: ``get_version_module(core_version)`` returns the versioned entities/properties module tree
for a given CRM core version;

Usage::

from pyheritage.cidoc.versions import get_version_module
mod = get_version_module("7.1.3")
mod.entities.E1CRMEntity
mod.properties.P1IsIdentifiedBy

The package also exports ``VersionMatrixResolver`` for resolving compatible extension versions
for a given core version;

"""


from pyheritage.cidoc.versions._registry import (
get_version_module,
VersionNotImplemented,
)
from pyheritage.cidoc.versions._resolver import (
ExtensionNotAvailable,
VersionMatrixResolver,
VersionNotSupported,
)


__all__ = (
'ExtensionNotAvailable',
'VersionMatrixResolver',
'VersionNotImplemented',
'VersionNotSupported',
'get_version_module',
)
57 changes: 57 additions & 0 deletions pyheritage/cidoc/versions/_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-

"""Version registry — maps CRM core versions to their implementation modules;

Only known versions with a corresponding 'versions/v{xx}/' package are considered "implemented". All other
known versions raise 'VersionNotImplemented';

"""

import types

from pyheritage.cidoc.versions._resolver import VersionMatrixResolver


__all__ = ('VersionNotImplemented', 'get_version_module', )


_IMPLEMENTED_VERSIONS: frozenset[str] = frozenset({"7.0"})

_resolver = VersionMatrixResolver()


class VersionNotImplemented(LookupError):
"""CRM core version is recognised by the matrix but not yet implemented;

Raised when a CRM core version (e.g. 7.1.2) is present in the compatibility matrix but there is
no 'versions/v{xx}/' package with concrete entity/property classes yet;

"""


# ******************************************************************************************************************* #


def get_version_module(core_version: str) -> types.ModuleType:
"""Return the version module for *core_version*;

Raises:
VersionNotSupported if *core_version* is not in the compatibility matrix;
VersionNotImplemented if *core_version* is known but not yet implemented;

"""
if not _resolver.is_known(core_version):
from pyheritage.cidoc.versions._resolver import VersionNotSupported
raise VersionNotSupported(
f"CRM version {core_version!r} is not supported"
)

if core_version not in _IMPLEMENTED_VERSIONS:
formatted = ", ".join(sorted(_IMPLEMENTED_VERSIONS))
raise VersionNotImplemented(
f"CRM {core_version} is not implemented yet "
f"(available: {formatted})"
)

from pyheritage.cidoc.versions import v70 # noqa: F811 — late import
return v70
103 changes: 103 additions & 0 deletions pyheritage/cidoc/versions/_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-

"""VersionMatrixResolver — compatibility matrix between CRM core versions and extensions;

"""


from typing import ClassVar


__all__ = ('ExtensionNotAvailable', 'VersionMatrixResolver', 'VersionNotSupported', )


class VersionNotSupported(LookupError):
"""Requested CRM core version is not supported;

Raised when a CRM core version string is not present in the compatibility matrix;

"""


# ******************************************************************************************************************* #


class ExtensionNotAvailable(LookupError):
"""Extension is not available for the requested CRM core version;

Raised when the compatibility matrix maps the extension to ``None``
for the requested core version (e.g. CRMgeo on CRM 7.0);

"""


# ******************************************************************************************************************* #


class VersionMatrixResolver:
"""Matrix of compatible core-version → extension-version mappings;

Maps each supported CRM core version to the compatible versions of each extension (archaeo, dig, sci, inf, geo);
A value of None means the extension is not available for that core version;

"""

_matrix: ClassVar[dict[str, dict[str, str | None]]] = {
"7.0": {"archaeo": None, "dig": None, "sci": None, "inf": None, "geo": None},
"7.1.1": {"archaeo": None, "dig": None, "sci": None, "inf": None, "geo": None},
"7.1.2": {"archaeo": "2.1.1", "dig": None, "sci": "2.0", "inf": None,
"geo": None, "tex": "2.0", "frbroo": "2.4"},
"7.1.3": {"archaeo": None, "dig": "4.0", "sci": None, "inf": "1.0",
"geo": "1.2+2024", "lrmoo": "1.0", "act": "0.2",
"ba": "1.4", "presso": "1.3"},
}

# ------------------------- #

def resolve(self, core_version: str, ext_name: str) -> str:
"""Return the extension version for *ext_name* at *core_version*;

Raises
VersionNotSupported if *core_version* is not in the matrix;
ExtensionNotAvailable if the extension is explicitly None;

"""
try:
ext_ver = self._matrix[core_version][ext_name]
except KeyError:
raise VersionNotSupported(
f"CRM version {core_version!r} is not supported"
) from None
if ext_ver is None:
raise ExtensionNotAvailable(
f"Extension {ext_name!r} is not available for CRM {core_version}"
)
return ext_ver

# ------------------------- #

def is_known(self, core_version: str) -> bool:
"""Return True if 'core_version' is present in the compatibility matrix;

"""
return core_version in self._matrix

# ------------------------- #

def compatible_extensions(self, core_version: str) -> dict[str, str]:
"""Return all available extensions for 'core_version' with their versions;

Raises
VersionNotSupported if 'core_version' is not in the matrix;

"""
try:
return {
k: v
for k, v in self._matrix[core_version].items()
if v is not None
}
except KeyError:
raise VersionNotSupported(
f"CRM version {core_version!r} is not supported"
) from None
25 changes: 25 additions & 0 deletions pyheritage/cidoc/versions/v70/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-

"""CIDOC-CRM v7.0 version package;

Re-exports core CIDOC-CRM 7.0 entities and properties;

"""


from pyheritage.cidoc.versions.v70 import _entities, _properties


__all__ = (
'entities',
'properties',
)

# Module-level access to subpackages;
entities = _entities
properties = _properties


# Namespace for model_rebuild — inherited by child versions;
# Contains all v7.0 entity classes + builtins (List, Optional);
__namespace__: dict[str, object] = dict(_entities.__namespace__)
119 changes: 119 additions & 0 deletions pyheritage/cidoc/versions/v70/_entities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-

"""CIDOC-CRM v7.0 entity models;

Re-exports all entity classes from core (CIDOC-CRM 7.0 base);

"""


from typing import List, Optional

from pyheritage.cidoc.core import entities as _core_entities
from pyheritage.cidoc.core.entities import ( # noqa: F401, F811, E402 — re-export
CoercedNumber,
CoercedSpace,
CoercedString,
CoercedTime,
E1CRMEntity,
E2TemporalEntity,
E3ConditionState,
E4Period,
E5Event,
E6Destruction,
E7Activity,
E8Acquisition,
E9Move,
E10TransferOfCustody,
E11Modification,
E12Production,
E13AttributeAssignment,
E14ConditionAssessment,
E15IdentifierAssignment,
E16Measurement,
E17TypeAssignment,
E18PhysicalThing,
E19PhysicalObject,
E20BiologicalObject,
E21Person,
E22HumanMadeObject,
E24PhysicalHumanMadeObject,
E25HumanMadeFeature,
E26PhysicalFeature,
E27Site,
E28ConceptualObject,
E29DesignOrProcedure,
E30Right,
E31Document,
E32AuthorityDocument,
E33LinguisticObject,
E34Inscription,
E35Title,
E36VisualItem,
E37Mark,
E39Actor,
E41Appellation,
E42Identifier,
E52TimeSpan,
E53Place,
E54Dimension,
E55Type,
E56Language,
E57Material,
E58MeasurementUnit,
E59PrimitiveValue,
E60Number,
E61TimePrimitive,
E62String,
E63BeginningOfExistence,
E64EndOfExistence,
E65Creation,
E66Formation,
E67Birth,
E68Dissolution,
E69Death,
E70Thing,
E71HumanMadeThing,
E72LegalObject,
E73InformationObject,
E74Group,
E77PersistentItem,
E78CuratedHolding,
E79PartAddition,
E80PartRemoval,
E81Transformation,
E83TypeCreation,
E85Joining,
E86Leaving,
E87CurationActivity,
E89PropositionalObject,
E90SymbolicObject,
E92SpaceTimeVolume,
E93Presence,
E94SpacePrimitive,
E95SpaceTimePrimitive,
E96Purchase,
E97MonetaryAmount,
E98Currency,
E99ProductType,
)


__all__ = _core_entities.__all__ # type: ignore[has-type]


__builtin_namespace__: dict[str, object] = {
'List': List,
'Optional': Optional,
}


__namespace__: dict[str, object] = {}

for name, obj in _core_entities.__namespace__.items():
__namespace__[name] = obj

__namespace__.update(__builtin_namespace__)

# Clean up namespace helpers;
del name, obj
Loading
Loading