feat: replace Owlready2 with RDFLib (#981)

<!-- .github/pull_request_template.md -->

## Description
Replaces Owlready2 with RDFLib

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.

---------

Co-authored-by: Igor Ilic <igorilic03@gmail.com>
This commit is contained in:
hajdul88 2025-06-17 14:49:53 +02:00 committed by GitHub
parent 456f3b58c0
commit acdcb0e8d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 283 additions and 168 deletions

View file

@ -35,7 +35,7 @@ jobs:
# Remove Cognee wheel that came from PyPI # Remove Cognee wheel that came from PyPI
uv pip uninstall cognee uv pip uninstall cognee
# Install of the freshly-checked-out Cognee branch # Install of the freshly-checked-out Cognee branch
uv pip install --no-deps --force-reinstall -e ../ uv pip install --force-reinstall -e ../
- name: Run MCP test - name: Run MCP test
env: env:

View file

@ -7,7 +7,6 @@ from cognee.modules.engine.utils import (
generate_node_id, generate_node_id,
generate_node_name, generate_node_name,
) )
from owlready2 import Thing, ThingClass
from cognee.shared.data_models import KnowledgeGraph from cognee.shared.data_models import KnowledgeGraph
from cognee.modules.ontology.rdf_xml.OntologyResolver import OntologyResolver from cognee.modules.ontology.rdf_xml.OntologyResolver import OntologyResolver
@ -76,7 +75,7 @@ def expand_with_nodes_and_edges(
ont_node_id = generate_node_id(ontology_node_to_store.name) ont_node_id = generate_node_id(ontology_node_to_store.name)
ont_node_name = generate_node_name(ontology_node_to_store.name) ont_node_name = generate_node_name(ontology_node_to_store.name)
if isinstance(ontology_node_to_store, ThingClass): if ontology_node_to_store.category == "classes":
ont_node_key = f"{ont_node_id}_type" ont_node_key = f"{ont_node_id}_type"
if (ont_node_key not in added_nodes_map) and ( if (ont_node_key not in added_nodes_map) and (
ont_node_key not in added_ontology_nodes_map ont_node_key not in added_ontology_nodes_map
@ -88,7 +87,7 @@ def expand_with_nodes_and_edges(
ontology_valid=True, ontology_valid=True,
) )
elif isinstance(ontology_node_to_store, Thing): elif ontology_node_to_store.category == "individuals":
ont_node_key = f"{ont_node_id}_entity" ont_node_key = f"{ont_node_id}_entity"
if (ont_node_key not in added_nodes_map) and ( if (ont_node_key not in added_nodes_map) and (
ont_node_key not in added_ontology_nodes_map ont_node_key not in added_ontology_nodes_map
@ -157,7 +156,7 @@ def expand_with_nodes_and_edges(
ont_node_id = generate_node_id(ontology_node_to_store.name) ont_node_id = generate_node_id(ontology_node_to_store.name)
ont_node_name = generate_node_name(ontology_node_to_store.name) ont_node_name = generate_node_name(ontology_node_to_store.name)
if isinstance(ontology_node_to_store, ThingClass): if ontology_node_to_store.category == "classes":
ont_node_key = f"{ont_node_id}_type" ont_node_key = f"{ont_node_id}_type"
if (ont_node_key not in added_nodes_map) and ( if (ont_node_key not in added_nodes_map) and (
ont_node_key not in added_ontology_nodes_map ont_node_key not in added_ontology_nodes_map
@ -169,7 +168,7 @@ def expand_with_nodes_and_edges(
ontology_valid=True, ontology_valid=True,
) )
elif isinstance(ontology_node_to_store, Thing): elif ontology_node_to_store.category == "individuals":
ont_node_key = f"{ont_node_id}_entity" ont_node_key = f"{ont_node_id}_entity"
if (ont_node_key not in added_nodes_map) and ( if (ont_node_key not in added_nodes_map) and (
ont_node_key not in added_ontology_nodes_map ont_node_key not in added_ontology_nodes_map

View file

@ -3,7 +3,7 @@ import difflib
from cognee.shared.logging_utils import get_logger from cognee.shared.logging_utils import get_logger
from collections import deque from collections import deque
from typing import List, Tuple, Dict, Optional, Any from typing import List, Tuple, Dict, Optional, Any
from owlready2 import get_ontology, ClassConstruct, Ontology, Thing from rdflib import Graph, URIRef, RDF, RDFS, OWL
from cognee.modules.ontology.exceptions import ( from cognee.modules.ontology.exceptions import (
OntologyInitializationError, OntologyInitializationError,
@ -14,46 +14,85 @@ from cognee.modules.ontology.exceptions import (
logger = get_logger("OntologyAdapter") logger = get_logger("OntologyAdapter")
class AttachedOntologyNode:
"""Lightweight wrapper to be able to parse any ontology solution and generalize cognee interface."""
def __init__(self, uri: URIRef, category: str):
self.uri = uri
self.name = self._extract_name(uri)
self.category = category
@staticmethod
def _extract_name(uri: URIRef) -> str:
uri_str = str(uri)
if "#" in uri_str:
return uri_str.split("#")[-1]
return uri_str.rstrip("/").split("/")[-1]
def __repr__(self):
return f"AttachedOntologyNode(name={self.name}, category={self.category})"
class OntologyResolver: class OntologyResolver:
def __init__( def __init__(self, ontology_file: Optional[str] = None):
self,
ontology_file: Optional[str] = None,
fallback_url: str = "http://example.org/empty_ontology",
):
self.ontology_file = ontology_file self.ontology_file = ontology_file
try: try:
if ontology_file and os.path.exists(ontology_file): if ontology_file and os.path.exists(ontology_file):
self.ontology: Ontology = get_ontology(ontology_file).load() self.graph = Graph()
self.graph.parse(ontology_file)
logger.info("Ontology loaded successfully from file: %s", ontology_file) logger.info("Ontology loaded successfully from file: %s", ontology_file)
else: else:
logger.info( logger.info(
"Ontology file '%s' not found. Using fallback ontology at %s", "Ontology file '%s' not found. No owl ontology will be attached to the graph.",
ontology_file, ontology_file,
fallback_url,
) )
self.ontology = get_ontology(fallback_url) self.graph = None
self.build_lookup() self.build_lookup()
except Exception as e: except Exception as e:
logger.error("Failed to load ontology", exc_info=e) logger.error("Failed to load ontology", exc_info=e)
raise OntologyInitializationError() from e raise OntologyInitializationError() from e
def _uri_to_key(self, uri: URIRef) -> str:
uri_str = str(uri)
if "#" in uri_str:
name = uri_str.split("#")[-1]
else:
name = uri_str.rstrip("/").split("/")[-1]
return name.lower().replace(" ", "_").strip()
def build_lookup(self): def build_lookup(self):
try: try:
self.lookup: Dict[str, Dict[str, Thing]] = { classes: Dict[str, URIRef] = {}
"classes": { individuals: Dict[str, URIRef] = {}
cls.name.lower().replace(" ", "_").strip(): cls
for cls in self.ontology.classes() if not self.graph:
}, self.lookup: Dict[str, Dict[str, URIRef]] = {
"individuals": { "classes": classes,
ind.name.lower().replace(" ", "_").strip(): ind "individuals": individuals,
for ind in self.ontology.individuals() }
},
return None
for cls in self.graph.subjects(RDF.type, OWL.Class):
key = self._uri_to_key(cls)
classes[key] = cls
for subj, _, obj in self.graph.triples((None, RDF.type, None)):
if obj in classes.values():
key = self._uri_to_key(subj)
individuals[key] = subj
self.lookup = {
"classes": classes,
"individuals": individuals,
} }
logger.info( logger.info(
"Lookup built: %d classes, %d individuals", "Lookup built: %d classes, %d individuals",
len(self.lookup["classes"]), len(classes),
len(self.lookup["individuals"]), len(individuals),
) )
return None
except Exception as e: except Exception as e:
logger.error("Failed to build lookup dictionary: %s", str(e)) logger.error("Failed to build lookup dictionary: %s", str(e))
raise RuntimeError("Lookup build failed") from e raise RuntimeError("Lookup build failed") from e
@ -77,64 +116,90 @@ class OntologyResolver:
logger.error("Error in find_closest_match: %s", str(e)) logger.error("Error in find_closest_match: %s", str(e))
raise FindClosestMatchError() from e raise FindClosestMatchError() from e
def _get_category(self, uri: URIRef) -> str:
if uri in self.lookup.get("classes", {}).values():
return "classes"
if uri in self.lookup.get("individuals", {}).values():
return "individuals"
return "unknown"
def get_subgraph( def get_subgraph(
self, node_name: str, node_type: str = "individuals" self, node_name: str, node_type: str = "individuals", directed: bool = True
) -> Tuple[List[Any], List[Tuple[str, str, str]], Optional[Any]]: ) -> Tuple[List[Any], List[Tuple[str, str, str]], Optional[Any]]:
nodes_set = set() nodes_set = set()
edges: List[Tuple[str, str, str]] = [] edges: List[Tuple[str, str, str]] = []
visited_nodes = set() visited = set()
queue = deque() queue = deque()
try: try:
closest_match = self.find_closest_match(name=node_name, category=node_type) closest_match = self.find_closest_match(name=node_name, category=node_type)
if not closest_match: if not closest_match:
logger.info("No close match found for '%s' in category '%s'", node_name, node_type) logger.info("No close match found for '%s' in category '%s'", node_name, node_type)
return list(nodes_set), edges, None return [], [], None
node = self.lookup[node_type].get(closest_match) node = self.lookup[node_type].get(closest_match)
if node is None: if node is None:
logger.info("Node '%s' not found in lookup.", closest_match) logger.info("Node '%s' not found in lookup.", closest_match)
return list(nodes_set), edges, None return [], [], None
logger.info("%s match was found for found for '%s' node", node.name, node_name) logger.info("%s match was found for found for '%s' node", node, node_name)
queue.append(node) queue.append(node)
visited_nodes.add(node) visited.add(node)
nodes_set.add(node) nodes_set.add(node)
obj_props = set(self.graph.subjects(RDF.type, OWL.ObjectProperty))
while queue: while queue:
current_node = queue.popleft() current = queue.popleft()
current_label = self._uri_to_key(current)
if hasattr(current_node, "is_a"): if node_type == "individuals":
for parent in current_node.is_a: for parent in self.graph.objects(current, RDF.type):
if isinstance(parent, ClassConstruct): parent_label = self._uri_to_key(parent)
if hasattr(parent, "value") and hasattr(parent.value, "name"): edges.append((current_label, "is_a", parent_label))
parent = parent.value if parent not in visited:
else: visited.add(parent)
continue
edges.append((current_node.name, "is_a", parent.name))
nodes_set.add(parent)
if parent not in visited_nodes:
visited_nodes.add(parent)
queue.append(parent) queue.append(parent)
nodes_set.add(parent)
for prop in self.ontology.object_properties(): for parent in self.graph.objects(current, RDFS.subClassOf):
for target in prop[current_node]: parent_label = self._uri_to_key(parent)
edges.append((current_node.name, prop.name, target.name)) edges.append((current_label, "is_a", parent_label))
nodes_set.add(target) if parent not in visited:
if target not in visited_nodes: visited.add(parent)
visited_nodes.add(target) queue.append(parent)
nodes_set.add(parent)
for prop in obj_props:
prop_label = self._uri_to_key(prop)
for target in self.graph.objects(current, prop):
target_label = self._uri_to_key(target)
edges.append((current_label, prop_label, target_label))
if target not in visited:
visited.add(target)
queue.append(target) queue.append(target)
nodes_set.add(target)
for source in prop.range: if not directed:
if current_node in prop[source]: for source in self.graph.subjects(prop, current):
edges.append((source.name, prop.name, current_node.name)) source_label = self._uri_to_key(source)
nodes_set.add(source) edges.append((source_label, prop_label, current_label))
if source not in visited_nodes: if source not in visited:
visited_nodes.add(source) visited.add(source)
queue.append(source) queue.append(source)
nodes_set.add(source)
return list(nodes_set), edges, node rdf_nodes = [
AttachedOntologyNode(uri=uri, category=self._get_category(uri))
for uri in list(nodes_set)
]
rdf_root = (
AttachedOntologyNode(uri=node, category=self._get_category(node))
if node is not None
else None
)
return rdf_nodes, edges, rdf_root
except Exception as e: except Exception as e:
logger.error("Error in get_subgraph: %s", str(e)) logger.error("Error in get_subgraph: %s", str(e))
raise GetSubgraphError() from e raise GetSubgraphError() from e

View file

@ -1,161 +1,174 @@
import pytest import pytest
from owlready2 import get_ontology, Thing from rdflib import Graph, Namespace, RDF, OWL, RDFS
from cognee.modules.ontology.rdf_xml.OntologyResolver import OntologyResolver from cognee.modules.ontology.rdf_xml.OntologyResolver import OntologyResolver, AttachedOntologyNode
def test_ontology_adapter_initialization_success(): def test_ontology_adapter_initialization_success():
"""Test successful initialization of OntologyAdapter.""" """Test successful initialization of OntologyAdapter."""
ontology = get_ontology("http://example.org/test_ontology")
adapter = OntologyResolver() adapter = OntologyResolver()
adapter.ontology = ontology
adapter.build_lookup() adapter.build_lookup()
assert adapter.ontology is not None
assert isinstance(adapter.lookup, dict) assert isinstance(adapter.lookup, dict)
def test_ontology_adapter_initialization_file_not_found(): def test_ontology_adapter_initialization_file_not_found():
"""Test OntologyAdapter initialization with nonexistent file.""" """Test OntologyAdapter initialization with nonexistent file."""
adapter = OntologyResolver(ontology_file="nonexistent.owl") adapter = OntologyResolver(ontology_file="nonexistent.owl")
assert adapter.ontology.base_iri == "http://example.org/empty_ontology#" assert adapter.graph is None
def test_build_lookup(): def test_build_lookup():
"""Test the lookup dictionary is correctly built.""" """Test the lookup dictionary is correctly built."""
ontology = get_ontology("http://example.org/test_ontology") ns = Namespace("http://example.org/test#")
g = Graph()
with ontology: g.add((ns.Car, RDF.type, OWL.Class))
class Car(Thing): g.add((ns.Audi, RDF.type, ns.Car))
pass
Car("Audi") resolver = OntologyResolver()
resolver.graph = g
resolver.build_lookup()
adapter = OntologyResolver() lookup = resolver.lookup
adapter.ontology = ontology assert isinstance(lookup, dict)
adapter.build_lookup()
assert isinstance(adapter.lookup, dict) assert "car" in lookup["classes"]
assert "car" in adapter.lookup["classes"] assert lookup["classes"]["car"] == ns.Car
assert "audi" in adapter.lookup["individuals"]
assert "audi" in lookup["individuals"]
assert lookup["individuals"]["audi"] == ns.Audi
def test_find_closest_match_exact(): def test_find_closest_match_exact():
"""Test finding exact match in lookup.""" """Test finding exact match in lookup."""
ontology = get_ontology("http://example.org/test_ontology")
with ontology: ns = Namespace("http://example.org/test#")
g = Graph()
class Car(Thing): g.add((ns.Car, RDF.type, OWL.Class))
pass g.add((ns.Audi, RDF.type, ns.Car))
Car("Audi") resolver = OntologyResolver()
resolver.graph = g
adapter = OntologyResolver() resolver.build_lookup()
adapter.ontology = ontology
adapter.build_lookup()
result = adapter.find_closest_match("Audi", "individuals")
result = resolver.find_closest_match("Audi", "individuals")
assert result is not None assert result is not None
assert result == "audi" assert result == "audi"
def test_find_closest_match_fuzzy(): def test_find_closest_match_fuzzy():
"""Test fuzzy matching for lookup.""" """Test fuzzy matching for lookup using the RDFlib adapter."""
ontology = get_ontology("http://example.org/test_ontology")
with ontology: ns = Namespace("http://example.org/test#")
class Car(Thing): g = Graph()
pass
Car("Audi") g.add((ns.Car, RDF.type, OWL.Class))
Car("BMW")
adapter = OntologyResolver() g.add((ns.Audi, RDF.type, ns.Car))
adapter.ontology = ontology g.add((ns.BMW, RDF.type, ns.Car))
adapter.build_lookup()
result = adapter.find_closest_match("Audii", "individuals") resolver = OntologyResolver()
resolver.graph = g
resolver.build_lookup()
result = resolver.find_closest_match("Audii", "individuals")
assert result == "audi" assert result == "audi"
def test_find_closest_match_no_match(): def test_find_closest_match_no_match():
"""Test no match found in lookup.""" """Test no match found in lookup."""
ontology = get_ontology("http://example.org/test_ontology") """Test that find_closest_match returns None when there is no match."""
ns = Namespace("http://example.org/test#")
adapter = OntologyResolver() g = Graph()
adapter.ontology = ontology
adapter.build_lookup()
result = adapter.find_closest_match("Nonexistent", "individuals") g.add((ns.Car, RDF.type, OWL.Class))
g.add((ns.Audi, RDF.type, ns.Car))
g.add((ns.BMW, RDF.type, ns.Car))
resolver = OntologyResolver()
resolver.graph = g
resolver.build_lookup()
result = resolver.find_closest_match("Nonexistent", "individuals")
assert result is None assert result is None
def test_get_subgraph_no_match(): def test_get_subgraph_no_match_rdflib():
"""Test get_subgraph with no matching node.""" """Test get_subgraph returns empty results for a non-existent node."""
ontology = get_ontology("http://example.org/test_ontology") g = Graph()
adapter = OntologyResolver() resolver = OntologyResolver()
adapter.ontology = ontology resolver.graph = g
adapter.build_lookup() resolver.build_lookup()
nodes, relationships, start_node = adapter.get_subgraph("Nonexistent", "individuals") nodes, relationships, start_node = resolver.get_subgraph("Nonexistent", "individuals")
assert nodes == [] assert nodes == []
assert relationships == [] assert relationships == []
assert start_node is None assert start_node is None
def test_get_subgraph_success(): def test_get_subgraph_success_rdflib():
"""Test successful retrieval of subgraph.""" """Test successful retrieval of subgraph using the RDFlib adapter."""
ontology = get_ontology("http://example.org/test_ontology")
with ontology: ns = Namespace("http://example.org/test#")
g = Graph()
class Company(Thing): g.add((ns.Company, RDF.type, OWL.Class))
pass g.add((ns.Vehicle, RDF.type, OWL.Class))
g.add((ns.Car, RDF.type, OWL.Class))
class Vehicle(Thing): g.add((ns.Vehicle, RDFS.subClassOf, OWL.Thing))
pass g.add((ns.Car, RDFS.subClassOf, ns.Vehicle))
class Car(Vehicle): g.add((ns.Audi, RDF.type, ns.Car))
pass g.add((ns.Porsche, RDF.type, ns.Car))
g.add((ns.VW, RDF.type, ns.Company))
audi = Car("Audi") owns = ns.owns
porsche = Car("Porsche") g.add((owns, RDF.type, OWL.ObjectProperty))
vw = Company("VW") g.add((ns.VW, owns, ns.Audi))
g.add((ns.VW, owns, ns.Porsche))
vw.owns = [audi, porsche] resolver = OntologyResolver()
resolver.graph = g
resolver.build_lookup()
adapter = OntologyResolver() nodes, relationships, start_node = resolver.get_subgraph("Audi", "individuals")
adapter.ontology = ontology
adapter.build_lookup()
nodes, relationships, start_node = adapter.get_subgraph("Audi", "individuals") uris = {n.uri for n in nodes}
assert ns.Audi in uris
assert ns.Car in uris
assert ns.Vehicle in uris
assert OWL.Thing in uris
assert audi in nodes rels = set(relationships)
assert Car in nodes assert ("audi", "is_a", "car") in rels
assert Vehicle in nodes assert ("car", "is_a", "vehicle") in rels
assert Thing in nodes assert ("vehicle", "is_a", "thing") in rels
assert ("Audi", "is_a", "Car") in relationships
assert ("Car", "is_a", "Vehicle") in relationships assert isinstance(start_node, AttachedOntologyNode)
assert ("Vehicle", "is_a", "Thing") in relationships assert start_node.uri == ns.Audi
def test_refresh_lookup(): def test_refresh_lookup_rdflib():
"""Test refreshing lookup rebuilds the dictionary.""" """Test that refresh_lookup rebuilds the lookup dict into a new object."""
ontology = get_ontology("http://example.org/test_ontology") g = Graph()
adapter = OntologyResolver() resolver = OntologyResolver()
adapter.ontology = ontology resolver.graph = g
adapter.build_lookup() resolver.build_lookup()
original_lookup = adapter.lookup.copy() original_lookup = resolver.lookup
adapter.refresh_lookup()
assert adapter.lookup is not original_lookup resolver.refresh_lookup()
assert resolver.lookup is not original_lookup

52
poetry.lock generated
View file

@ -3650,6 +3650,19 @@ files = [
[package.dependencies] [package.dependencies]
pygments = "*" pygments = "*"
[[package]]
name = "isodate"
version = "0.7.2"
description = "An ISO 8601 date/time/duration parser and formatter"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version < \"3.11\""
files = [
{file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"},
{file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"},
]
[[package]] [[package]]
name = "isoduration" name = "isoduration"
version = "20.11.0" version = "20.11.0"
@ -6829,20 +6842,6 @@ files = [
{file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"},
] ]
[[package]]
name = "owlready2"
version = "0.47"
description = "A package for ontology-oriented programming in Python: load OWL 2.0 ontologies as Python objects, modify them, save them, and perform reasoning via HermiT. Includes an optimized RDF quadstore."
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "owlready2-0.47.tar.gz", hash = "sha256:af7e1d2205c0b5886d2e34397ab8c10ca29ff68c3dc3702d43393966ac7f6eb0"},
]
[package.extras]
test = ["flask", "gevent", "rdflib"]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.2" version = "24.2"
@ -9100,6 +9099,29 @@ files = [
[package.extras] [package.extras]
all = ["numpy"] all = ["numpy"]
[[package]]
name = "rdflib"
version = "7.1.4"
description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
optional = false
python-versions = "<4.0.0,>=3.8.1"
groups = ["main"]
files = [
{file = "rdflib-7.1.4-py3-none-any.whl", hash = "sha256:72f4adb1990fa5241abd22ddaf36d7cafa5d91d9ff2ba13f3086d339b213d997"},
{file = "rdflib-7.1.4.tar.gz", hash = "sha256:fed46e24f26a788e2ab8e445f7077f00edcf95abb73bcef4b86cefa8b62dd174"},
]
[package.dependencies]
isodate = {version = ">=0.7.2,<1.0.0", markers = "python_version < \"3.11\""}
pyparsing = ">=2.1.0,<4"
[package.extras]
berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"]
html = ["html5rdf (>=1.2,<2)"]
lxml = ["lxml (>=4.3,<6.0)"]
networkx = ["networkx (>=2,<4)"]
orjson = ["orjson (>=3.9.14,<4)"]
[[package]] [[package]]
name = "readme-renderer" name = "readme-renderer"
version = "44.0" version = "44.0"
@ -12037,4 +12059,4 @@ weaviate = ["weaviate-client"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10,<=3.13" python-versions = ">=3.10,<=3.13"
content-hash = "4d5f5cfe7072a53e4d9d38e5503a9839b555add9087b8947e5eecf0f80b9cbbb" content-hash = "caff6dd76472193be899359bd8f74842050b0bd296239349035f2f77a656a809"

View file

@ -36,7 +36,7 @@ dependencies = [
"filetype>=1.2.0", "filetype>=1.2.0",
"aiohttp>=3.11.14", "aiohttp>=3.11.14",
"aiofiles>=23.2.1", "aiofiles>=23.2.1",
"owlready2>=0.47,<0.48", "rdflib>=7.1.4,<7.2.0",
"graphistry>=0.33.5,<0.34", "graphistry>=0.33.5,<0.34",
"pypdf>=4.1.0,<6.0.0", "pypdf>=4.1.0,<6.0.0",
"jinja2>=3.1.3,<4", "jinja2>=3.1.3,<4",

32
uv.lock generated
View file

@ -892,7 +892,6 @@ dependencies = [
{ name = "numpy", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "numpy", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
{ name = "onnxruntime" }, { name = "onnxruntime" },
{ name = "openai" }, { name = "openai" },
{ name = "owlready2" },
{ name = "pandas" }, { name = "pandas" },
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "pydantic" }, { name = "pydantic" },
@ -901,6 +900,7 @@ dependencies = [
{ name = "pypdf" }, { name = "pypdf" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "rdflib" },
{ name = "scikit-learn" }, { name = "scikit-learn" },
{ name = "sentry-sdk", extra = ["fastapi"] }, { name = "sentry-sdk", extra = ["fastapi"] },
{ name = "sqlalchemy" }, { name = "sqlalchemy" },
@ -1078,7 +1078,6 @@ requires-dist = [
{ name = "numpy", specifier = ">=1.26.4,<=2.1" }, { name = "numpy", specifier = ">=1.26.4,<=2.1" },
{ name = "onnxruntime", specifier = "<=1.21.1" }, { name = "onnxruntime", specifier = "<=1.21.1" },
{ name = "openai", specifier = ">=1.59.4,<2" }, { name = "openai", specifier = ">=1.59.4,<2" },
{ name = "owlready2", specifier = ">=0.47,<0.48" },
{ name = "pandas", specifier = ">=2.2.2" }, { name = "pandas", specifier = ">=2.2.2" },
{ name = "pgvector", marker = "extra == 'postgres'", specifier = ">=0.3.5,<0.4" }, { name = "pgvector", marker = "extra == 'postgres'", specifier = ">=0.3.5,<0.4" },
{ name = "pgvector", marker = "extra == 'postgres-binary'", specifier = ">=0.3.5,<0.4" }, { name = "pgvector", marker = "extra == 'postgres-binary'", specifier = ">=0.3.5,<0.4" },
@ -1103,6 +1102,7 @@ requires-dist = [
{ name = "python-multipart", specifier = "==0.0.20" }, { name = "python-multipart", specifier = "==0.0.20" },
{ name = "qasync", marker = "extra == 'gui'", specifier = ">=0.27.1,<0.28" }, { name = "qasync", marker = "extra == 'gui'", specifier = ">=0.27.1,<0.28" },
{ name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1.14.2,<2" }, { name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1.14.2,<2" },
{ name = "rdflib", specifier = ">=7.1.4,<7.2.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.2,<1.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.2,<1.0.0" },
{ name = "s3fs", extras = ["boto3"], marker = "extra == 'aws'", specifier = "==2025.3.2" }, { name = "s3fs", extras = ["boto3"], marker = "extra == 'aws'", specifier = "==2025.3.2" },
{ name = "scikit-learn", specifier = ">=1.6.1,<2" }, { name = "scikit-learn", specifier = ">=1.6.1,<2" },
@ -2784,6 +2784,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 },
] ]
[[package]]
name = "isodate"
version = "0.7.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 },
]
[[package]] [[package]]
name = "isoduration" name = "isoduration"
version = "20.11.0" version = "20.11.0"
@ -5075,12 +5084,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 },
] ]
[[package]]
name = "owlready2"
version = "0.47"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6a/8b/6f0534ff7285e8d97e36a3838f88ffc8deb7cd0bc7b34f9f97d8bfdaae90/owlready2-0.47.tar.gz", hash = "sha256:af7e1d2205c0b5886d2e34397ab8c10ca29ff68c3dc3702d43393966ac7f6eb0", size = 27271190 }
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.2" version = "24.2"
@ -6635,6 +6638,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/c5/c243b05a15a27b946180db0d1e4c999bef3f4221505dff9748f1f6c917be/rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", size = 1553782 }, { url = "https://files.pythonhosted.org/packages/c1/c5/c243b05a15a27b946180db0d1e4c999bef3f4221505dff9748f1f6c917be/rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", size = 1553782 },
] ]
[[package]]
name = "rdflib"
version = "7.1.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "isodate", marker = "python_full_version < '3.11'" },
{ name = "pyparsing" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/7e/cb2d74466bd8495051ebe2d241b1cb1d4acf9740d481126aef19ef2697f5/rdflib-7.1.4.tar.gz", hash = "sha256:fed46e24f26a788e2ab8e445f7077f00edcf95abb73bcef4b86cefa8b62dd174", size = 4692745 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/31/e9b6f04288dcd3fa60cb3179260d6dad81b92aef3063d679ac7d80a827ea/rdflib-7.1.4-py3-none-any.whl", hash = "sha256:72f4adb1990fa5241abd22ddaf36d7cafa5d91d9ff2ba13f3086d339b213d997", size = 565051 },
]
[[package]] [[package]]
name = "readme-renderer" name = "readme-renderer"
version = "44.0" version = "44.0"