import logging
from .attribute import OMAttributeMetadata, OMAttribute, ObjectOMAttribute
from oldman.exception import OMAlreadyDeclaredDatatypeError, OMPropertyDefTypeError, OMAttributeDefError
from oldman.exception import OMAlreadyGeneratedAttributeError, OMInternalError, OMPropertyDefError
from oldman.common import DATATYPE_PROPERTY, OBJECT_PROPERTY
[docs]class OMProperty(object):
"""An :class:`~oldman.property.OMProperty` object represents the support of a RDF property by a RDFS class.
TODO: check this documentation after the removal of the resource_manager.
It gathers some :class:`~oldman.attribute.OMAttribute` objects (usually one).
An :class:`~oldman.property.OMProperty` object is in charge of generating its
:class:`~oldman.attribute.OMAttribute` objects according to the metadata that
has been extracted from the schema and JSON-LD context.
A property can be reversed: the :class:`~oldman.resource.Resource` object
to which the :class:`~oldman.attribute.OMAttribute` objects will be (indirectly)
attached is then the object of this property, not its subject (?o ?p ?s).
Consequently, two :class:`~oldman.property.OMProperty` objects can refer
to the same RDF property when one is reversed while the second is not.
:param property_iri: IRI of the RDF property.
:param supporter_class_iri: IRI of the RDFS class that supports the property.
:param is_required: If `True` instances of the supporter class must assign a value
to this property for being valid. Defaults to `False`.
:param read_only: If `True`, the value of the property cannot be modified by a regular end-user.
Defaults to `False`.
:param write_only: If `True`, the value of the property cannot be read by a regular end-user.
Defaults to `False`.
:param reversed: If `True`, the property is reversed. Defaults to `False`.
:param cardinality: Defaults to `None`. Not yet supported.
:param property_type: String. In OWL, a property is either a DatatypeProperty or an ObjectProperty.
Defaults to `None` (unknown).
:param domains: Set of class IRIs that are declared as the RDFS domain of the property. Defaults to `set()`.
:param ranges: Set of class IRIs that are declared as the RDFS range of the property. Defaults to `set()`.
:param link_class_iri: TODO: describe.
"""
def __init__(self, property_iri, supporter_class_iri, is_required=False, read_only=False,
write_only=False, reversed=False, cardinality=None, property_type=None,
domains=None, ranges=None, link_class_iri=None):
self._logger = logging.getLogger(__name__)
self._iri = property_iri
self._supporter_class_iri = supporter_class_iri
self._is_required = is_required
if cardinality:
raise NotImplementedError(u"Property cardinality is not yet supported")
# 1, 42, "*", "+"
self._cardinality = cardinality
self._type = property_type
self._ranges = set()
self._domains = set()
if read_only and write_only:
raise OMPropertyDefError(u"Property %s cannot be read-only and write-only" % property_iri)
self._read_only = read_only
self._write_only = write_only
self._reversed = reversed
# Temporary list, before creating attributes
self._tmp_attr_mds = []
self._om_attributes = None
# When the property is a hydra:Link
self._link_class_iri = link_class_iri
if domains is not None:
for domain in domains:
self.add_domain(domain)
if ranges is not None:
for range in ranges:
self.add_range(range)
@property
[docs] def link_class_iri(self):
"""TODO: describe """
return self._link_class_iri
@property
[docs] def iri(self):
"""IRI of RDF property."""
return self._iri
@property
def type(self):
"""The property can be a owl:DatatypeProperty (`"datatype"`) or an owl:ObjectProperty
(`"object"`). Sometimes its type is unknown (`None`).
"""
return self._type
@type.setter
[docs] def type(self, property_type):
"""
:param property_type: String value that is in {"datatype", "object", None}
"""
if self._type is not None:
if self._type != property_type:
raise OMPropertyDefTypeError(u"Already declared as %s so cannot also be a %s "
% (self._type, property_type))
return
self._type = property_type
@property
[docs] def supporter_class_iri(self):
"""IRI of the RDFS class that supports the property."""
return self._supporter_class_iri
@property
[docs] def is_required(self):
"""`True` if the property is required."""
return self._is_required
@property
[docs] def is_read_only(self):
"""`True` if the property cannot be modified by regular end-users."""
return self._read_only
@property
[docs] def is_write_only(self):
"""`True` if the property cannot be accessed by regular end-users."""
return self._write_only
@property
[docs] def reversed(self):
"""`True` if the property is reversed (?o ?p ?s)."""
return self._reversed
[docs] def declare_is_required(self):
"""Makes the property be required. Is irreversible."""
self._is_required = True
@property
[docs] def ranges(self):
"""Set of class IRIs that are declared as the RDFS range of the property."""
return self._ranges
[docs] def add_range(self, p_range):
"""Declares a RDFS class as part of the range of the property.
:param p_range: IRI of RDFS class.
"""
# Detects XSD
if p_range.startswith(u"http://www.w3.org/2001/XMLSchema#"):
self.type = DATATYPE_PROPERTY
if (self.type == DATATYPE_PROPERTY) and (p_range not in self._ranges) and (len(self._ranges) >= 1):
raise OMAlreadyDeclaredDatatypeError(u"Property datatype can only be specified once")
self._ranges.add(p_range)
@property
[docs] def domains(self):
"""Set of class IRIs that are declared as the RDFS domain of the property."""
return self._domains
[docs] def add_domain(self, domain):
"""Declares a RDFS class as part of the domain of the property.
:param domain: IRI of RDFS class.
"""
# Detects XSD
if domain.startswith(u"http://www.w3.org/2001/XMLSchema#"):
#TODO: find a better error type
raise Exception(u"Domain cannot have a literal datatype")
self._domains.add(domain)
@property
[docs] def default_datatype(self):
"""IRI that is the default datatype of the property.
May be `None` (if not defined or if the property is an owl:ObjectProperty)
"""
if self._type == DATATYPE_PROPERTY and len(self._ranges) >= 1:
# Should be one
return list(self._ranges)[0]
return None
@property
[docs] def om_attributes(self):
"""Set of :class:`~oldman.attribute.OMAttribute` objects that depends on this property.
"""
if self._om_attributes is None:
raise OMInternalError(u"Please generate them before accessing this attribute")
return self._om_attributes
[docs] def generate_attributes(self, attr_format_selector):
"""Generates its :class:`~oldman.attribute.OMAttribute` objects.
Can be called only once.
When called a second time, raises an :class:`~oldman.exception.OMAlreadyGeneratedAttributeError` exception.
:param attr_format_selector: :class:`~oldman.parsing.schema.attribute.ValueFormatSelector` object.
"""
if self._om_attributes:
raise OMAlreadyGeneratedAttributeError()
self._om_attributes = set()
for md in self._tmp_attr_mds:
value_format = attr_format_selector.find_value_format(md)
attr_cls = ObjectOMAttribute if self._type == OBJECT_PROPERTY else OMAttribute
self._om_attributes.add(attr_cls(md, value_format))
# Clears mds
self._tmp_attr_mds = []