From 67a8ab1a8ee36f10092dd9f79f966687b7137008 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 11 Apr 2026 12:39:46 +0530 Subject: [PATCH 1/5] fix(ontology): preserve user-facing schema fields in OWL generation\n\nFixes #446 --- semantica/ontology/owl_generator.py | 86 +++++++++++++------ tests/ontology/test_ontology_comprehensive.py | 30 +++++++ 2 files changed, 90 insertions(+), 26 deletions(-) diff --git a/semantica/ontology/owl_generator.py b/semantica/ontology/owl_generator.py index f9c3dea9..5f07b81b 100644 --- a/semantica/ontology/owl_generator.py +++ b/semantica/ontology/owl_generator.py @@ -156,6 +156,24 @@ class OWLGenerator: ) raise + @staticmethod + def _as_list(value: Any) -> List[Any]: + """Normalize scalar-or-list ontology values to a list.""" + if value is None: + return [] + if isinstance(value, list): + return value + return [value] + + @staticmethod + def _is_datatype_property(prop_type: Any) -> bool: + """Return True for supported datatype property aliases.""" + return str(prop_type or "").strip().lower() in { + "datatype", + "data", + "datatypeproperty", + } + def _generate_with_rdflib( self, ontology: Dict[str, Any], format: str = "turtle", **options ) -> Union[str, Graph]: @@ -189,19 +207,23 @@ class OWLGenerator: # Add classes classes = ontology.get("classes", []) for cls in classes: + class_name = cls.get("name") or cls.get("label") class_uri = URIRef( - cls.get("uri") or self.namespace_manager.generate_class_iri(cls["name"]) + cls.get("uri") + or self.namespace_manager.generate_class_iri(class_name) ) g.add((class_uri, RDF.type, OWL.Class)) - if cls.get("label"): - g.add((class_uri, RDFS.label, Literal(cls["label"]))) + class_label = cls.get("label") or cls.get("name") + if class_label: + g.add((class_uri, RDFS.label, Literal(class_label))) if cls.get("comment"): g.add((class_uri, RDFS.comment, Literal(cls["comment"]))) # Add subclass relationships - if cls.get("subClassOf"): - parent_uri = URIRef(cls["subClassOf"]) + subclass_of = cls.get("subClassOf") or cls.get("subclassOf") + if subclass_of: + parent_uri = URIRef(subclass_of) g.add((class_uri, RDFS.subClassOf, parent_uri)) # Add object properties @@ -210,15 +232,18 @@ class OWLGenerator: if prop.get("type") == "object": prop_uri = URIRef( prop.get("uri") - or self.namespace_manager.generate_property_iri(prop["name"]) + or self.namespace_manager.generate_property_iri( + prop.get("name") or prop.get("label") + ) ) g.add((prop_uri, RDF.type, OWL.ObjectProperty)) - if prop.get("label"): - g.add((prop_uri, RDFS.label, Literal(prop["label"]))) + prop_label = prop.get("label") or prop.get("name") + if prop_label: + g.add((prop_uri, RDFS.label, Literal(prop_label))) # Add domain - domains = prop.get("domain", []) + domains = self._as_list(prop.get("domain", [])) for domain in domains: domain_uri = URIRef( domain @@ -228,7 +253,7 @@ class OWLGenerator: g.add((prop_uri, RDFS.domain, domain_uri)) # Add range - ranges = prop.get("range", []) + ranges = self._as_list(prop.get("range", [])) for range_val in ranges: range_uri = URIRef( range_val @@ -237,18 +262,21 @@ class OWLGenerator: ) g.add((prop_uri, RDFS.range, range_uri)) - elif prop.get("type") == "data": + elif self._is_datatype_property(prop.get("type")): prop_uri = URIRef( prop.get("uri") - or self.namespace_manager.generate_property_iri(prop["name"]) + or self.namespace_manager.generate_property_iri( + prop.get("name") or prop.get("label") + ) ) g.add((prop_uri, RDF.type, OWL.DatatypeProperty)) - if prop.get("label"): - g.add((prop_uri, RDFS.label, Literal(prop["label"]))) + prop_label = prop.get("label") or prop.get("name") + if prop_label: + g.add((prop_uri, RDFS.label, Literal(prop_label))) # Add domain - domains = prop.get("domain", []) + domains = self._as_list(prop.get("domain", [])) for domain in domains: domain_uri = URIRef( domain @@ -303,16 +331,19 @@ class OWLGenerator: # Classes classes = ontology.get("classes", []) for cls in classes: + class_name = cls.get("name") or cls.get("label") class_uri = cls.get("uri") or self.namespace_manager.generate_class_iri( - cls["name"] + class_name ) lines.append(f"<{class_uri}> a owl:Class ;") - if cls.get("label"): - lines.append(f' rdfs:label "{cls["label"]}" ;') + class_label = cls.get("label") or cls.get("name") + if class_label: + lines.append(f' rdfs:label "{class_label}" ;') if cls.get("comment"): lines.append(f' rdfs:comment "{cls["comment"]}" ;') - if cls.get("subClassOf"): - parent_uri = cls["subClassOf"] + subclass_of = cls.get("subClassOf") or cls.get("subclassOf") + if subclass_of: + parent_uri = subclass_of lines.append(f" rdfs:subClassOf <{parent_uri}> .") else: lines[-1] = lines[-1].rstrip(" ;") + " ." @@ -322,7 +353,7 @@ class OWLGenerator: properties = ontology.get("properties", []) for prop in properties: prop_uri = prop.get("uri") or self.namespace_manager.generate_property_iri( - prop["name"] + prop.get("name") or prop.get("label") ) prop_type = ( "owl:ObjectProperty" @@ -330,11 +361,12 @@ class OWLGenerator: else "owl:DatatypeProperty" ) lines.append(f"<{prop_uri}> a {prop_type} ;") - if prop.get("label"): - lines.append(f' rdfs:label "{prop["label"]}" ;') + prop_label = prop.get("label") or prop.get("name") + if prop_label: + lines.append(f' rdfs:label "{prop_label}" ;') # Domain - domains = prop.get("domain", []) + domains = self._as_list(prop.get("domain", [])) for domain in domains: domain_uri = ( domain @@ -344,9 +376,11 @@ class OWLGenerator: lines.append(f" rdfs:domain <{domain_uri}> ;") # Range - ranges = prop.get("range", []) + ranges = self._as_list(prop.get("range", [])) for range_val in ranges: - if prop.get("type") == "data" and range_val.startswith("xsd:"): + if self._is_datatype_property(prop.get("type")) and range_val.startswith( + "xsd:" + ): lines.append( f" rdfs:range {range_val.replace('xsd:', 'xsd:')} ;" ) diff --git a/tests/ontology/test_ontology_comprehensive.py b/tests/ontology/test_ontology_comprehensive.py index 7c6302af..10776b6c 100644 --- a/tests/ontology/test_ontology_comprehensive.py +++ b/tests/ontology/test_ontology_comprehensive.py @@ -165,6 +165,36 @@ class TestOntologyComprehensive(unittest.TestCase): self.assertIn("Person", owl_output) self.assertIn("hasName", owl_output) + def test_owl_generator_user_facing_schema_compatibility(self): + try: + from semantica.ontology.owl_generator import OWLGenerator + except ImportError: + self.skipTest("OWLGenerator not importable") + + generator = OWLGenerator() + ontology = { + "name": "UserFacingOntology", + "uri": "http://example.org/ontology/", + "classes": [ + {"name": "Person", "subclassOf": "http://example.org/ontology/Agent"}, + ], + "properties": [ + { + "name": "birthDate", + "type": "datatype", + "domain": "Person", + "range": "xsd:date", + } + ], + } + + owl_output = generator.generate_owl(ontology, format="turtle") + + self.assertIn("owl:DatatypeProperty", owl_output) + self.assertIn("rdfs:subClassOf", owl_output) + self.assertIn("birthDate", owl_output) + self.assertIn("xsd:date", owl_output) + # --- OntologyValidator Tests --- # Removed as per request From d2c20d410c0a531bcccf6387ba16c4188d66f62b Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 11 Apr 2026 13:02:49 +0530 Subject: [PATCH 2/5] fix(ontology): address #446 follow-up review findings\n\n- prefer label over name for generated IRIs\n- fix datatype range list handling in rdflib path\n- align generated IRIs with ontology uri namespace\n- resolve local subclassOf names to class IRIs\n- expand regression coverage and update changelog --- CHANGELOG.md | 25 ++++ semantica/ontology/owl_generator.py | 114 ++++++++++-------- tests/ontology/test_ontology_comprehensive.py | 14 ++- 3 files changed, 101 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57b1b247..f160efc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- **OWLGenerator user-facing schema compatibility fixes** (Issue #446): + - Fixed OWL class/property IRI identifier fallback order to prefer `label` and then `name`. + - Fixed datatype property handling to accept scalar and list `range` values in rdflib path (including `xsd:*`, full IRIs, and local names), preventing list-based `.startswith()` crashes. + - Fixed generated class/property/domain/range IRIs to use the current ontology dict `uri` namespace for each generation call (instead of drifting to default namespace manager base URI when per-entity `uri` is omitted). + - Fixed `subClassOf` / `subclassOf` parent resolution so local class names are expanded to ontology IRIs consistently with domain/range behavior. + - Added/expanded regression coverage in `tests/ontology/test_ontology_comprehensive.py` (`test_owl_generator_user_facing_schema_compatibility`) for label-first fallback, lowercase `subclassOf`, datatype range lists, and ontology namespace consistency. + - **SKOS Vocabulary Module** (PR #319 by @KaifAhmad1): - **Namespace helpers** (`semantica/ontology/namespace_manager.py`): Added `get_skos_uri(local_name)` — returns the full `http://www.w3.org/2004/02/skos/core#` URI for any SKOS term. Added `build_concept_scheme_uri(name)` — slugifies a human-readable vocabulary name (spaces/special chars → hyphens, lower-cased) and anchors the result at the configured base URI as `/vocab/`. - **Triplet-store SKOS helpers** (`semantica/triplet_store/triplet_store.py`): Added `add_skos_concept(concept_uri, scheme_uri, pref_label, alt_labels, broader, narrower, related, definition, notation)` — assembles and stores all required SKOS triples (auto-declares the `skos:ConceptScheme`, asserts `rdf:type skos:Concept`, `skos:inScheme`, `skos:prefLabel`, and all optional predicates) via the existing `add_triplets()` API; no new storage paths introduced. Added `get_skos_concepts(scheme_uri=None)` — issues a SPARQL `SELECT` via `execute_query()` and collapses multi-valued `altLabel`/`broader`/`narrower`/`related` bindings into structured concept dicts; optional `scheme_uri` restricts results to one vocabulary. @@ -1080,6 +1087,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- **OWLGenerator user-facing schema compatibility fixes** (Issue #446): + - Fixed OWL class/property IRI identifier fallback order to prefer label and then +ame. + - Fixed datatype property handling to accept scalar and list +ange values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. + - Fixed generated class/property/domain/range IRIs to use the current ontology dict uri namespace for each generation call (instead of drifting to default namespace manager base URI when per-entity uri is omitted). + - Fixed subClassOf / subclassOf parent resolution so local class names are expanded to ontology IRIs consistently with domain/range behavior. + - Added/expanded regression coverage in ests/ontology/test_ontology_comprehensive.py ( est_owl_generator_user_facing_schema_compatibility) for label-first fallback, lowercase subclassOf, datatype range lists, and ontology namespace consistency. - Fixed: PolicyEngine latest version selection on ContextGraph; AgentContext fallback robustness and secure logging (PR #TBD by @KaifAhmad1) - Tests: Added ContextGraph fallback and AgentContext smoke tests; full suite passing @@ -1627,6 +1642,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- **OWLGenerator user-facing schema compatibility fixes** (Issue #446): + - Fixed OWL class/property IRI identifier fallback order to prefer label and then +ame. + - Fixed datatype property handling to accept scalar and list +ange values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. + - Fixed generated class/property/domain/range IRIs to use the current ontology dict uri namespace for each generation call (instead of drifting to default namespace manager base URI when per-entity uri is omitted). + - Fixed subClassOf / subclassOf parent resolution so local class names are expanded to ontology IRIs consistently with domain/range behavior. + - Added/expanded regression coverage in ests/ontology/test_ontology_comprehensive.py ( est_owl_generator_user_facing_schema_compatibility) for label-first fallback, lowercase subclassOf, datatype range lists, and ontology namespace consistency. - Fixed: PolicyEngine latest version selection on ContextGraph; AgentContext fallback robustness and secure logging (PR #TBD by @KaifAhmad1) - Tests: Added ContextGraph fallback and AgentContext smoke tests; full suite passing @@ -2166,3 +2189,5 @@ When breaking changes are introduced, migration guides will be provided in the r For detailed release notes, see [GitHub Releases](https://github.com/Hawksight-AI/semantica/releases). + + diff --git a/semantica/ontology/owl_generator.py b/semantica/ontology/owl_generator.py index 5f07b81b..9c988ce2 100644 --- a/semantica/ontology/owl_generator.py +++ b/semantica/ontology/owl_generator.py @@ -29,6 +29,7 @@ License: MIT from pathlib import Path from typing import Any, Dict, List, Optional, Union +from urllib.parse import urljoin from ..utils.exceptions import ProcessingError, ValidationError from ..utils.helpers import ensure_directory @@ -174,11 +175,45 @@ class OWLGenerator: "datatypeproperty", } + def _get_generation_namespace_manager(self, ontology: Dict[str, Any]) -> NamespaceManager: + """Build a namespace manager anchored to the ontology base URI for this call.""" + base_uri = ontology.get("uri") or self.namespace_manager.get_base_uri() + if isinstance(base_uri, str) and not base_uri.endswith(("/", "#")): + base_uri = base_uri + "/" + return NamespaceManager( + base_uri=base_uri, + version=self.namespace_manager.version, + use_speaking_iris=self.namespace_manager.use_speaking_iris, + ) + + @staticmethod + def _is_http_uri(value: Any) -> bool: + return isinstance(value, str) and value.startswith(("http://", "https://")) + + def _resolve_class_uri(self, value: Any, ns_manager: NamespaceManager) -> str: + if self._is_http_uri(value): + return value + return ns_manager.generate_class_iri(str(value)) + + def _resolve_property_identifier(self, prop: Dict[str, Any]) -> str: + return prop.get("label") or prop.get("name") + + def _resolve_class_identifier(self, cls: Dict[str, Any]) -> str: + return cls.get("label") or cls.get("name") + + def _resolve_datatype_range_uri(self, range_val: Any, ns_manager: NamespaceManager): + if isinstance(range_val, str) and range_val.startswith("xsd:"): + return XSD[range_val.replace("xsd:", "")] + if self._is_http_uri(range_val): + return URIRef(range_val) + return URIRef(ns_manager.generate_class_iri(str(range_val))) + def _generate_with_rdflib( self, ontology: Dict[str, Any], format: str = "turtle", **options ) -> Union[str, Graph]: """Generate OWL using rdflib.""" g = Graph() + gen_ns_manager = self._get_generation_namespace_manager(ontology) # Set up namespaces ns_manager = RDFNamespaceManager(g) @@ -191,6 +226,8 @@ class OWLGenerator: # Register ontology namespace base_uri = ontology.get("uri") or self.namespace_manager.get_base_uri() + if isinstance(base_uri, str) and not base_uri.endswith(("/", "#")): + base_uri = base_uri + "/" ont_ns = Namespace(base_uri) g.bind("", ont_ns) @@ -207,10 +244,10 @@ class OWLGenerator: # Add classes classes = ontology.get("classes", []) for cls in classes: - class_name = cls.get("name") or cls.get("label") + class_name = self._resolve_class_identifier(cls) class_uri = URIRef( cls.get("uri") - or self.namespace_manager.generate_class_iri(class_name) + or gen_ns_manager.generate_class_iri(class_name) ) g.add((class_uri, RDF.type, OWL.Class)) @@ -223,7 +260,7 @@ class OWLGenerator: # Add subclass relationships subclass_of = cls.get("subClassOf") or cls.get("subclassOf") if subclass_of: - parent_uri = URIRef(subclass_of) + parent_uri = URIRef(self._resolve_class_uri(subclass_of, gen_ns_manager)) g.add((class_uri, RDFS.subClassOf, parent_uri)) # Add object properties @@ -232,8 +269,8 @@ class OWLGenerator: if prop.get("type") == "object": prop_uri = URIRef( prop.get("uri") - or self.namespace_manager.generate_property_iri( - prop.get("name") or prop.get("label") + or gen_ns_manager.generate_property_iri( + self._resolve_property_identifier(prop) ) ) g.add((prop_uri, RDF.type, OWL.ObjectProperty)) @@ -245,28 +282,20 @@ class OWLGenerator: # Add domain domains = self._as_list(prop.get("domain", [])) for domain in domains: - domain_uri = URIRef( - domain - if domain.startswith("http") - else self.namespace_manager.generate_class_iri(domain) - ) + domain_uri = URIRef(self._resolve_class_uri(domain, gen_ns_manager)) g.add((prop_uri, RDFS.domain, domain_uri)) # Add range ranges = self._as_list(prop.get("range", [])) for range_val in ranges: - range_uri = URIRef( - range_val - if range_val.startswith("http") - else self.namespace_manager.generate_class_iri(range_val) - ) + range_uri = URIRef(self._resolve_class_uri(range_val, gen_ns_manager)) g.add((prop_uri, RDFS.range, range_uri)) elif self._is_datatype_property(prop.get("type")): prop_uri = URIRef( prop.get("uri") - or self.namespace_manager.generate_property_iri( - prop.get("name") or prop.get("label") + or gen_ns_manager.generate_property_iri( + self._resolve_property_identifier(prop) ) ) g.add((prop_uri, RDF.type, OWL.DatatypeProperty)) @@ -278,20 +307,14 @@ class OWLGenerator: # Add domain domains = self._as_list(prop.get("domain", [])) for domain in domains: - domain_uri = URIRef( - domain - if domain.startswith("http") - else self.namespace_manager.generate_class_iri(domain) - ) + domain_uri = URIRef(self._resolve_class_uri(domain, gen_ns_manager)) g.add((prop_uri, RDFS.domain, domain_uri)) # Add range - range_type = prop.get("range", "xsd:string") - if range_type.startswith("xsd:"): - range_uri = XSD[range_type.replace("xsd:", "")] - else: - range_uri = URIRef(range_type) - g.add((prop_uri, RDFS.range, range_uri)) + range_values = self._as_list(prop.get("range", "xsd:string")) + for range_val in range_values: + range_uri = self._resolve_datatype_range_uri(range_val, gen_ns_manager) + g.add((prop_uri, RDFS.range, range_uri)) # Serialize if format == "turtle": @@ -310,9 +333,12 @@ class OWLGenerator: ) -> str: """Generate OWL using basic string formatting (fallback).""" lines = [] + gen_ns_manager = self._get_generation_namespace_manager(ontology) # Namespace declarations base_uri = ontology.get("uri") or self.namespace_manager.get_base_uri() + if isinstance(base_uri, str) and not base_uri.endswith(("/", "#")): + base_uri = base_uri + "/" lines.append(f"@prefix : <{base_uri}> .") lines.append("@prefix rdf: .") lines.append("@prefix rdfs: .") @@ -331,8 +357,8 @@ class OWLGenerator: # Classes classes = ontology.get("classes", []) for cls in classes: - class_name = cls.get("name") or cls.get("label") - class_uri = cls.get("uri") or self.namespace_manager.generate_class_iri( + class_name = self._resolve_class_identifier(cls) + class_uri = cls.get("uri") or gen_ns_manager.generate_class_iri( class_name ) lines.append(f"<{class_uri}> a owl:Class ;") @@ -343,7 +369,7 @@ class OWLGenerator: lines.append(f' rdfs:comment "{cls["comment"]}" ;') subclass_of = cls.get("subClassOf") or cls.get("subclassOf") if subclass_of: - parent_uri = subclass_of + parent_uri = self._resolve_class_uri(subclass_of, gen_ns_manager) lines.append(f" rdfs:subClassOf <{parent_uri}> .") else: lines[-1] = lines[-1].rstrip(" ;") + " ." @@ -352,8 +378,8 @@ class OWLGenerator: # Properties properties = ontology.get("properties", []) for prop in properties: - prop_uri = prop.get("uri") or self.namespace_manager.generate_property_iri( - prop.get("name") or prop.get("label") + prop_uri = prop.get("uri") or gen_ns_manager.generate_property_iri( + self._resolve_property_identifier(prop) ) prop_type = ( "owl:ObjectProperty" @@ -368,28 +394,18 @@ class OWLGenerator: # Domain domains = self._as_list(prop.get("domain", [])) for domain in domains: - domain_uri = ( - domain - if domain.startswith("http") - else self.namespace_manager.generate_class_iri(domain) - ) + domain_uri = self._resolve_class_uri(domain, gen_ns_manager) lines.append(f" rdfs:domain <{domain_uri}> ;") # Range ranges = self._as_list(prop.get("range", [])) for range_val in ranges: - if self._is_datatype_property(prop.get("type")) and range_val.startswith( - "xsd:" - ): - lines.append( - f" rdfs:range {range_val.replace('xsd:', 'xsd:')} ;" - ) + if self._is_datatype_property(prop.get("type")) and isinstance( + range_val, str + ) and range_val.startswith("xsd:"): + lines.append(f" rdfs:range {range_val} ;") else: - range_uri = ( - range_val - if range_val.startswith("http") - else self.namespace_manager.generate_class_iri(range_val) - ) + range_uri = self._resolve_class_uri(range_val, gen_ns_manager) lines.append(f" rdfs:range <{range_uri}> ;") lines[-1] = lines[-1].rstrip(" ;") + " ." diff --git a/tests/ontology/test_ontology_comprehensive.py b/tests/ontology/test_ontology_comprehensive.py index 10776b6c..bde9ff9d 100644 --- a/tests/ontology/test_ontology_comprehensive.py +++ b/tests/ontology/test_ontology_comprehensive.py @@ -176,14 +176,18 @@ class TestOntologyComprehensive(unittest.TestCase): "name": "UserFacingOntology", "uri": "http://example.org/ontology/", "classes": [ - {"name": "Person", "subclassOf": "http://example.org/ontology/Agent"}, + # label should be preferred over name for generated class IRI + {"name": "Human", "label": "Person", "subclassOf": "Agent"}, ], "properties": [ { - "name": "birthDate", + # label should be preferred over name for generated property IRI + "name": "birthDateInternal", + "label": "birthDate", "type": "datatype", "domain": "Person", - "range": "xsd:date", + # datatype range may be list in user-facing schema + "range": ["xsd:date", "http://example.org/ontology/CustomDateType"], } ], } @@ -194,6 +198,10 @@ class TestOntologyComprehensive(unittest.TestCase): self.assertIn("rdfs:subClassOf", owl_output) self.assertIn("birthDate", owl_output) self.assertIn("xsd:date", owl_output) + self.assertIn("CustomDateType", owl_output) + self.assertIn(":Person", owl_output) + self.assertIn(":Agent", owl_output) + self.assertNotIn("https://semantica.dev/ontology/", owl_output) # --- OntologyValidator Tests --- # Removed as per request From 490d9c814b6de6bea12464e6c875c08e4d22493e Mon Sep 17 00:00:00 2001 From: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com> Date: Sat, 11 Apr 2026 13:11:10 +0530 Subject: [PATCH 3/5] Potential fix for pull request finding 'Unused import' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- semantica/ontology/owl_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/semantica/ontology/owl_generator.py b/semantica/ontology/owl_generator.py index 9c988ce2..85c1cff7 100644 --- a/semantica/ontology/owl_generator.py +++ b/semantica/ontology/owl_generator.py @@ -29,7 +29,6 @@ License: MIT from pathlib import Path from typing import Any, Dict, List, Optional, Union -from urllib.parse import urljoin from ..utils.exceptions import ProcessingError, ValidationError from ..utils.helpers import ensure_directory From 1ea5e5c012f50e46edd5428ceda7ba728c642cc6 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 11 Apr 2026 13:36:44 +0530 Subject: [PATCH 4/5] docs(changelog): resolve duplicate snapshot headers and clean unreleased formatting --- CHANGELOG.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f160efc8..b2d36c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1079,7 +1079,7 @@ When breaking changes are introduced, migration guides will be provided in the r For detailed release notes, see [GitHub Releases](https://github.com/Hawksight-AI/semantica/releases). -# Changelog +## Legacy Changelog Snapshot A (Preserved Merge Artifact) All notable changes to this project will be documented in this file. @@ -1088,10 +1088,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - **OWLGenerator user-facing schema compatibility fixes** (Issue #446): - - Fixed OWL class/property IRI identifier fallback order to prefer label and then -ame. - - Fixed datatype property handling to accept scalar and list -ange values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. + - Fixed OWL class/property IRI identifier fallback order to prefer label and then name. + - Fixed datatype property handling to accept scalar and list range values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. - Fixed generated class/property/domain/range IRIs to use the current ontology dict uri namespace for each generation call (instead of drifting to default namespace manager base URI when per-entity uri is omitted). - Fixed subClassOf / subclassOf parent resolution so local class names are expanded to ontology IRIs consistently with domain/range behavior. - Added/expanded regression coverage in ests/ontology/test_ontology_comprehensive.py ( est_owl_generator_user_facing_schema_compatibility) for label-first fallback, lowercase subclassOf, datatype range lists, and ontology namespace consistency. @@ -1634,7 +1632,7 @@ When breaking changes are introduced, migration guides will be provided in the r For detailed release notes, see [GitHub Releases](https://github.com/Hawksight-AI/semantica/releases). -# Changelog +## Legacy Changelog Snapshot B (Preserved Merge Artifact) All notable changes to this project will be documented in this file. @@ -1643,10 +1641,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - **OWLGenerator user-facing schema compatibility fixes** (Issue #446): - - Fixed OWL class/property IRI identifier fallback order to prefer label and then -ame. - - Fixed datatype property handling to accept scalar and list -ange values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. + - Fixed OWL class/property IRI identifier fallback order to prefer label and then name. + - Fixed datatype property handling to accept scalar and list range values in rdflib path (including xsd:*, full IRIs, and local names), preventing list-based .startswith() crashes. - Fixed generated class/property/domain/range IRIs to use the current ontology dict uri namespace for each generation call (instead of drifting to default namespace manager base URI when per-entity uri is omitted). - Fixed subClassOf / subclassOf parent resolution so local class names are expanded to ontology IRIs consistently with domain/range behavior. - Added/expanded regression coverage in ests/ontology/test_ontology_comprehensive.py ( est_owl_generator_user_facing_schema_compatibility) for label-first fallback, lowercase subclassOf, datatype range lists, and ontology namespace consistency. @@ -2191,3 +2187,4 @@ For detailed release notes, see [GitHub Releases](https://github.com/Hawksight-A + From 9366f07239acb562db43f4262ecb6241c64720c0 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 11 Apr 2026 13:51:12 +0530 Subject: [PATCH 5/5] test(ontology): assert ontology uri prefix is used for generated IRIs --- tests/ontology/test_ontology_comprehensive.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ontology/test_ontology_comprehensive.py b/tests/ontology/test_ontology_comprehensive.py index bde9ff9d..328425fa 100644 --- a/tests/ontology/test_ontology_comprehensive.py +++ b/tests/ontology/test_ontology_comprehensive.py @@ -195,6 +195,7 @@ class TestOntologyComprehensive(unittest.TestCase): owl_output = generator.generate_owl(ontology, format="turtle") self.assertIn("owl:DatatypeProperty", owl_output) + self.assertIn("@prefix : .", owl_output) self.assertIn("rdfs:subClassOf", owl_output) self.assertIn("birthDate", owl_output) self.assertIn("xsd:date", owl_output)