Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Python-side type checks for the `owlapy.class_expression.restriction` constructors #272 missed: `OWLObjectSomeValuesFrom`/`OWLObjectAllValuesFrom` (property/filler), `OWLObjectHasSelf` (property), `OWLObjectHasValue` (property/individual), `OWLDataCardinalityRestriction` (`OWLDataMinCardinality`/`Max`/`Exact`, cardinality/property -- its `OWLObjectCardinalityRestriction` sibling already had this from #272), `OWLDataSomeValuesFrom`/`OWLDataAllValuesFrom` (property), `OWLDataHasValue` (property/value), `OWLObjectOneOf`/`OWLDataOneOf` (element type, now raising `TypeError` instead of a bare, `-O`-strippable `assert`), `OWLDatatypeRestriction` (datatype/facet restrictions), and `OWLFacetRestriction` (facet). `owlapy.owl_data_ranges`'s `OWLNaryDataRange`/`OWLDataComplementOf` were deliberately left unchanged -- they're reused by `owlapy.utils.nnf.NNF` to wrap data-side class expressions (e.g. `OWLDataSomeValuesFrom`) during negation, not just genuine `OWLDataRange` instances, so a strict `OWLDataRange` check there would break NNF (#271)
- Python-side type checks across OWL construct constructors (`owlapy.owl_axiom`, `owlapy.class_expression`, `owlapy.owl_property`, `owlapy.owl_individual`, `owlapy.owl_datatype`, `owlapy.owl_literal`, `owlapy.namespaces`): constructing an OWL axiom/class expression/entity with an argument of the wrong type (e.g. an `OWLSubClassOfAxiom` given object properties instead of class expressions) now raises a clear `TypeError`/`ValueError` immediately, instead of succeeding silently and only failing later -- as an opaque Java exception -- once the construct is used with a JVM-backed ontology (#271, #272)
- Additional regression tests for the `owlapy.owl_axiom` type checks above, covering `OWLSubClassOfAxiom`, `OWLClassAssertionAxiom`, `OWLEquivalentClassesAxiom`, `OWLObjectPropertyAssertionAxiom`, `OWLObjectPropertyDomainAxiom`, `OWLFunctionalObjectPropertyAxiom`, and `OWLDeclarationAxiom` (#271)

Expand Down
94 changes: 84 additions & 10 deletions owlapy/class_expression/restriction.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class OWLQuantifiedObjectRestriction(OWLQuantifiedRestriction[OWLClassExpression
_filler: OWLClassExpression

def __init__(self, filler: OWLClassExpression):
if not isinstance(filler, OWLClassExpression):
raise TypeError(
f"Expected 'filler' to be an instance of OWLClassExpression, got {type(filler).__name__} instead ({filler!r})."
)
self._filler = filler

def get_filler(self) -> OWLClassExpression:
Expand Down Expand Up @@ -286,6 +290,10 @@ def __init__(self, property: OWLObjectPropertyExpression, filler: OWLClassExpres
An OWLObjectSomeValuesFrom restriction along the specified property with the specified filler.
"""
super().__init__(filler)
if not isinstance(property, OWLObjectPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLObjectPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
self._property = property

def __repr__(self):
Expand Down Expand Up @@ -313,6 +321,10 @@ class expression CE, and it contains all those individuals that are connected by
type_index: Final = 3006
def __init__(self, property: OWLObjectPropertyExpression, filler: OWLClassExpression):
super().__init__(filler)
if not isinstance(property, OWLObjectPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLObjectPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
self._property = property

def __repr__(self):
Expand Down Expand Up @@ -351,6 +363,10 @@ def __init__(self, property: OWLObjectPropertyExpression):
Returns:
A ObjectHasSelf class expression on the specified property.
"""
if not isinstance(property, OWLObjectPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLObjectPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
self._property = property

def get_property(self) -> OWLObjectPropertyExpression:
Expand Down Expand Up @@ -390,6 +406,14 @@ def __init__(self, property: OWLObjectPropertyExpression, individual: OWLIndivid
Returns:
A HasValue restriction with specified property and value
"""
if not isinstance(property, OWLObjectPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLObjectPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
if not isinstance(individual, OWLIndividual):
raise TypeError(
f"Expected 'individual' to be an instance of OWLIndividual, got {type(individual).__name__} instead ({individual!r})."
)
super().__init__(individual)
self._property = property

Expand Down Expand Up @@ -426,13 +450,15 @@ class OWLObjectOneOf(OWLAnonymousClassExpression, HasOperands[OWLIndividual]):
type_index: Final = 3004

def __init__(self, values: OWLIndividual | Iterable[OWLIndividual]):
#assert isinstance(values, OWLIndividual) | isinstance(values, set)
# f"The input of OWLObjectOneOf must be either an OWLIndividual or a set of OWLIndividual. Currently, {type(values)}!"
if isinstance(values, OWLIndividual):
self._values = values,
else:
for _ in values:
assert isinstance(_, OWLIndividual)
values = tuple(values)
for i, v in enumerate(values):
if not isinstance(v, OWLIndividual):
raise TypeError(
f"Expected all values to be instances of OWLIndividual, got {type(v).__name__} instead ({v!r}) at index {i}."
)
self._values = frozenset(values)

def individuals(self) -> Iterable[OWLIndividual]:
Expand Down Expand Up @@ -496,7 +522,10 @@ class OWLQuantifiedDataRestriction(OWLQuantifiedRestriction[OWLDataRange],
_filler: OWLDataRange

def __init__(self, filler: OWLDataRange):
assert isinstance(filler, OWLDataRange), "filler must be an OWLDataRange"
if not isinstance(filler, OWLDataRange):
raise TypeError(
f"Expected 'filler' to be an instance of OWLDataRange, got {type(filler).__name__} instead ({filler!r})."
)
self._filler = filler
# @TODO:CD: define it as @property

Expand All @@ -515,7 +544,18 @@ class OWLDataCardinalityRestriction(OWLCardinalityRestriction[OWLDataRange],

@abstractmethod
def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler: OWLDataRange):
assert isinstance(filler, OWLDataRange), "filler must be an OWLDataRange"
if not isinstance(cardinality, int) or cardinality < 0:
raise ValueError(
f"Expected 'cardinality' to be a non-negative integer, got {cardinality!r} instead."
)
if not isinstance(property, OWLDataPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLDataPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
if not isinstance(filler, OWLDataRange):
raise TypeError(
f"Expected 'filler' to be an instance of OWLDataRange, got {type(filler).__name__} instead ({filler!r})."
)
super().__init__(cardinality, filler)
self._property = property

Expand Down Expand Up @@ -641,6 +681,10 @@ def __init__(self, property: OWLDataPropertyExpression, filler: OWLDataRange):
An OWLDataSomeValuesFrom restriction along the specified property with the specified filler.
"""
super().__init__(filler)
if not isinstance(property, OWLDataPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLDataPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
self._property = property

def __repr__(self):
Expand Down Expand Up @@ -685,6 +729,10 @@ def __init__(self, property: OWLDataPropertyExpression, filler: OWLDataRange):
An OWLDataAllValuesFrom restriction along the specified property with the specified filler.
"""
super().__init__(filler)
if not isinstance(property, OWLDataPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLDataPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
self._property = property

def __repr__(self):
Expand Down Expand Up @@ -730,6 +778,14 @@ def __init__(self, property: OWLDataPropertyExpression, value: OWLLiteral):
Returns:
An OWLDataHasValue restriction along the specified property with the specified literal.
"""
if not isinstance(property, OWLDataPropertyExpression):
raise TypeError(
f"Expected 'property' to be an instance of OWLDataPropertyExpression, got {type(property).__name__} instead ({property!r})."
)
if not isinstance(value, OWLLiteral):
raise TypeError(
f"Expected 'value' to be an instance of OWLLiteral, got {type(value).__name__} instead ({value!r})."
)
super().__init__(value)
self._property = property

Expand Down Expand Up @@ -769,9 +825,13 @@ def __init__(self, values: Union[OWLLiteral, Iterable[OWLLiteral]]):
if isinstance(values, OWLLiteral):
self._values = values,
else:
for _ in values:
assert isinstance(_, OWLLiteral)
self._values = tuple(values)
values = tuple(values)
for i, v in enumerate(values):
if not isinstance(v, OWLLiteral):
raise TypeError(
f"Expected all values to be instances of OWLLiteral, got {type(v).__name__} instead ({v!r}) at index {i}."
)
self._values = values

def __repr__(self):
return f'OWLDataOneOf({self._values})'
Expand Down Expand Up @@ -815,10 +875,20 @@ class OWLDatatypeRestriction(OWLDataRange):

def __init__(self, type_: OWLDatatype, facet_restrictions: Union['OWLFacetRestriction',
Iterable['OWLFacetRestriction']]):
if not isinstance(type_, OWLDatatype):
raise TypeError(
f"Expected 'type_' to be an instance of OWLDatatype, got {type(type_).__name__} instead ({type_!r})."
)
self._type = type_
if isinstance(facet_restrictions, OWLFacetRestriction):
facet_restrictions = facet_restrictions,
self._facet_restrictions = tuple(facet_restrictions)
facet_restrictions = tuple(facet_restrictions)
for i, fr in enumerate(facet_restrictions):
if not isinstance(fr, OWLFacetRestriction):
raise TypeError(
f"Expected all facet_restrictions to be instances of OWLFacetRestriction, got {type(fr).__name__} instead ({fr!r}) at index {i}."
)
self._facet_restrictions = facet_restrictions

def get_datatype(self) -> OWLDatatype:
return self._type
Expand Down Expand Up @@ -851,6 +921,10 @@ class OWLFacetRestriction(OWLObject):
_literal: 'OWLLiteral'

def __init__(self, facet: OWLFacet, literal: Literals):
if not isinstance(facet, OWLFacet):
raise TypeError(
f"Expected 'facet' to be an instance of OWLFacet, got {type(facet).__name__} instead ({facet!r})."
)
self._facet = facet
if isinstance(literal, OWLLiteral):
self._literal = literal
Expand Down
84 changes: 83 additions & 1 deletion tests/test_restriction_extra_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
couple of default marker methods (is_data_restriction/is_object_restriction) and
convenience methods (as_intersection_of_min_max). Pure Python, no JVM involved.
"""
import pytest

from owlapy.class_expression import (
OWLDataAllValuesFrom,
OWLDataExactCardinality,
Expand All @@ -15,6 +17,7 @@
OWLDataSomeValuesFrom,
OWLDatatypeRestriction,
OWLFacetRestriction,
OWLObjectAllValuesFrom,
OWLObjectExactCardinality,
OWLObjectHasSelf,
OWLObjectHasValue,
Expand All @@ -23,6 +26,7 @@
OWLObjectMinCardinality,
OWLObjectOneOf,
OWLObjectSomeValuesFrom,
OWLThing,
)
from owlapy.iri import IRI
from owlapy.owl_individual import OWLNamedIndividual
Expand Down Expand Up @@ -169,4 +173,82 @@ def test_facet_restriction_eq_false_for_different_type():
assert a == b
assert a != c
assert (a == "not a facet restriction") is False
assert hash(a) == hash(b)


# ---------------------------------------------------------------------------
# Python-side type checks (owlapy#271): restriction constructors given an
# argument of the wrong OWL construct type must fail immediately with a clear
# TypeError/ValueError, not silently succeed and only blow up once the JVM
# gets involved. These close gaps #272 left in restriction.py -- it fixed
# OWLObjectCardinalityRestriction but missed several parallel constructs.
# ---------------------------------------------------------------------------

def test_object_some_values_from_rejects_data_property():
with pytest.raises(TypeError):
OWLObjectSomeValuesFrom(data_prop("age"), OWLThing)


def test_object_some_values_from_rejects_non_class_expression_filler():
with pytest.raises(TypeError):
OWLObjectSomeValuesFrom(obj_prop("p"), ind("alice"))


def test_object_all_values_from_rejects_non_class_expression_filler():
with pytest.raises(TypeError):
OWLObjectAllValuesFrom(obj_prop("p"), ind("alice"))


def test_object_has_self_rejects_data_property():
with pytest.raises(TypeError):
OWLObjectHasSelf(data_prop("age"))


def test_object_has_value_rejects_swapped_arguments():
with pytest.raises(TypeError):
OWLObjectHasValue(ind("alice"), obj_prop("p"))


def test_data_some_values_from_rejects_object_property():
with pytest.raises(TypeError):
OWLDataSomeValuesFrom(obj_prop("p"), IntegerOWLDatatype)


def test_data_all_values_from_rejects_object_property():
with pytest.raises(TypeError):
OWLDataAllValuesFrom(obj_prop("p"), IntegerOWLDatatype)


def test_data_has_value_rejects_non_literal_value():
with pytest.raises(TypeError):
OWLDataHasValue(data_prop("age"), ind("alice"))


def test_data_min_cardinality_rejects_object_property():
with pytest.raises(TypeError):
OWLDataMinCardinality(1, obj_prop("p"), IntegerOWLDatatype)


def test_data_min_cardinality_rejects_negative_cardinality():
with pytest.raises(ValueError):
OWLDataMinCardinality(-1, data_prop("age"), IntegerOWLDatatype)


def test_object_one_of_rejects_non_individual():
with pytest.raises(TypeError):
OWLObjectOneOf([ind("alice"), obj_prop("p")])


def test_data_one_of_rejects_non_literal():
with pytest.raises(TypeError):
OWLDataOneOf([OWLLiteral(1), ind("alice")])


def test_datatype_restriction_rejects_non_datatype():
facet = OWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, OWLLiteral(0))
with pytest.raises(TypeError):
OWLDatatypeRestriction(ind("alice"), facet)


def test_facet_restriction_rejects_non_facet():
with pytest.raises(TypeError):
OWLFacetRestriction("not a facet", OWLLiteral(0))
Loading