Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

Commit c37e6bd

Browse files
[EQL] review changes.
1 parent a9bef12 commit c37e6bd

4 files changed

Lines changed: 120 additions & 92 deletions

File tree

src/krrood/class_diagrams/class_diagram.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
from __future__ import annotations
22

3-
import dataclasses
43
import logging
54
from abc import ABC
6-
from collections import defaultdict
75
from copy import copy
86
from dataclasses import dataclass
9-
from dataclasses import field, InitVar, fields
7+
from dataclasses import field, InitVar
108
from functools import cached_property, lru_cache
11-
from types import NoneType
12-
from typing_extensions import List, Optional, Dict, Iterable, Union, Any, Tuple, Set
139

1410
import rustworkx as rx
1511
from rustworkx_utils import RWXNode
16-
from typing_extensions import Type, TYPE_CHECKING
12+
from typing_extensions import List, Optional, Dict, Union, Tuple
13+
from typing_extensions import Type
1714

1815
from .attribute_introspector import (
1916
AttributeIntrospector,

src/krrood/class_diagrams/wrapped_field.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
if TYPE_CHECKING:
3131
from .class_diagram import WrappedClass
32-
from ..entity_query_language.property_descriptor import PropertyDescriptor
3332

3433

3534
@dataclass
@@ -65,11 +64,6 @@ class WrappedField:
6564
If the field is a relationship managed field, this is public name of the relationship that manages the field.
6665
"""
6766

68-
property_descriptor: Optional[PropertyDescriptor] = None
69-
"""
70-
The property descriptor instance that manages the field.
71-
"""
72-
7367
container_types: ClassVar[List[Type]] = [list, set, tuple, type, Sequence]
7468
"""
7569
A list of container types that are supported by the parser.
@@ -246,6 +240,7 @@ def search_class_in_globals(target_class_name: str) -> List[Type]:
246240
if inspect.isclass(value) and value.__name__ == target_class_name
247241
]
248242

243+
249244
def search_class_in_sys_modules(target_class_name: str) -> List[Type]:
250245
"""
251246
Searches for a class with the given name in all loaded modules (via sys.modules).

src/krrood/entity_query_language/hashed_data.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass, field
4+
45
from typing_extensions import (
56
Generic,
67
Optional,
@@ -9,14 +10,11 @@
910
Any,
1011
Callable,
1112
List,
12-
Union,
1313
)
14-
1514
from typing_extensions import TypeVar, ClassVar
1615

1716
from .utils import make_list, ALL
1817

19-
2018
T = TypeVar("T")
2119

2220

src/krrood/entity_query_language/predicate.py

Lines changed: 115 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
from .utils import is_iterable, make_list
3636
from ..class_diagrams.wrapped_field import WrappedField
3737

38-
if TYPE_CHECKING:
39-
from .property_descriptor import MonitoredSet
40-
4138
cls_args = {}
39+
"""
40+
Cache of class arguments.
41+
"""
4242

4343

4444
def symbolic_function(
@@ -175,10 +175,26 @@ def holds_direct(
175175

176176
@property
177177
def domain_value(self):
178+
"""
179+
Property that retrieves the domain value for the current instance.
180+
181+
This property should be implemented in a subclass where it returns
182+
the specific domain value. Attempting to access it directly from
183+
this base implementation will raise a NotImplementedError.
184+
185+
:raise: NotImplementedError: If the property is accessed and not
186+
implemented in a subclass.
187+
"""
178188
raise NotImplementedError
179189

180190
@property
181191
def range_value(self):
192+
"""
193+
Gets the range value. This is an abstract property and must be implemented
194+
by subclasses to return a value indicating the range.
195+
196+
:raises NotImplementedError: If not implemented in a subclass.
197+
"""
182198
raise NotImplementedError
183199

184200
@classmethod
@@ -209,41 +225,6 @@ def _neighbors(cls, value: Symbol, outgoing: bool = True) -> Iterable[Symbol]:
209225
)
210226
)
211227

212-
@classmethod
213-
def _get_super_property_descriptors(cls, value: Symbol) -> Iterable[MonitoredSet]:
214-
"""
215-
Find neighboring symbols connected by super edges.
216-
217-
This method identifies neighboring symbols that are connected
218-
through edge with predicate types that are superclasses of the current predicate.
219-
220-
:param value: (Symbol): The symbol for which neighboring symbols are
221-
evaluated through super predicate type edges.
222-
223-
:return: A list containing neighboring symbols connected by super type edges.
224-
"""
225-
wrapped_cls = SymbolGraph().type_graph.get_wrapped_class(type(value))
226-
if not wrapped_cls:
227-
return
228-
yield from (
229-
getattr(value, property_field.public_name)
230-
for property_field in SymbolGraph().type_graph.get_fields_of_superclass_property_descriptors(
231-
wrapped_cls, cls
232-
)
233-
)
234-
role_taker_property_fields = (
235-
SymbolGraph().type_graph.get_role_taker_superclass_properties(
236-
wrapped_cls, cls
237-
)
238-
)
239-
if not role_taker_property_fields:
240-
return
241-
for role_taker_field in role_taker_property_fields.fields:
242-
yield getattr(
243-
getattr(value, role_taker_property_fields.role_taker.public_name),
244-
role_taker_field.public_name,
245-
)
246-
247228
def __call__(
248229
self,
249230
domain_value: Optional[Symbol] = None,
@@ -258,33 +239,6 @@ def __call__(
258239
range_value = range_value or self.range_value
259240
return self.holds_direct(domain_value, range_value)
260241

261-
@classmethod
262-
def get_inverse(cls, obj) -> MonitoredSet:
263-
wrapped_cls = SymbolGraph().type_graph.get_wrapped_class(type(obj))
264-
inverse_field = (
265-
SymbolGraph().type_graph.get_the_field_of_property_descriptor_type(
266-
wrapped_cls, cls.inverse_of
267-
)
268-
)
269-
if not inverse_field:
270-
# try to get it from role taker if there is one.
271-
role_taker = SymbolGraph().get_role_takers_of_instance(obj)
272-
role_taker_wrapped_cls = SymbolGraph().type_graph.get_wrapped_class(
273-
type(role_taker)
274-
)
275-
if role_taker:
276-
inverse_field = (
277-
SymbolGraph().type_graph.get_the_field_of_property_descriptor_type(
278-
role_taker_wrapped_cls, cls.inverse_of
279-
)
280-
)
281-
return getattr(role_taker, inverse_field.public_name)
282-
if not inverse_field:
283-
raise ValueError(
284-
f"cannot find a field for the inverse {cls.inverse_of} defined for {wrapped_cls}"
285-
)
286-
return getattr(obj, inverse_field.public_name)
287-
288242
def add_relation(
289243
cls,
290244
domain_value: Symbol,
@@ -298,12 +252,6 @@ def add_relation(
298252
range_value = make_list(range_value)
299253
for rv in range_value:
300254
SymbolGraph().add_edge(cls.get_relation(domain_value, rv, inferred))
301-
for property_value in cls._get_super_property_descriptors(domain_value):
302-
property_value.add(rv, inferred=True)
303-
if cls.inverse_of:
304-
inverse = cls.get_inverse(rv)
305-
if domain_value not in inverse:
306-
cls.get_inverse(rv).add(domain_value, inferred=True)
307255
if cls.transitive:
308256
for nxt in cls._neighbors(rv):
309257
cls.add_relation(domain_value, nxt, inferred=True)
@@ -317,6 +265,18 @@ def get_relation(
317265
range_value: Symbol,
318266
inferred: bool = False,
319267
) -> PredicateRelation:
268+
"""
269+
Gets or creates a relation between two symbols, representing the domain and range
270+
values. The function ensures that the symbols are wrapped in instances, adding
271+
them to the symbol graph if they do not already exist. It then creates and returns
272+
a `PredicateRelation` object linking the domain and range symbols.
273+
274+
:param domain_value: The symbol representing the domain value.
275+
:param range_value: The symbol representing the range value.
276+
:param inferred: A boolean flag indicating whether the relationship is inferred. Defaults to False.
277+
278+
:return: A `PredicateRelation` instance that links the given domain and range values.
279+
"""
320280
wrapped_domain_instance = SymbolGraph().get_wrapped_instance(domain_value)
321281
if not wrapped_domain_instance:
322282
wrapped_domain_instance = WrappedInstance(domain_value)
@@ -335,8 +295,22 @@ def get_relation(
335295

336296
@dataclass(eq=False)
337297
class HasType(BinaryPredicate):
298+
"""
299+
Represents a predicate to check if a given variable is an instance of a specified type.
300+
301+
This class is used to evaluate whether the domain value belongs to a given type by leveraging
302+
Python's built-in `isinstance` functionality. It provides methods to retrieve the domain and
303+
range values and perform direct checks.
304+
"""
305+
338306
variable: Any
307+
"""
308+
The variable whose type is being checked.
309+
"""
339310
types_: Type
311+
"""
312+
The type or tuple of types against which the `variable` is validated.
313+
"""
340314

341315
@classmethod
342316
def holds_direct(cls, domain_value: Any, range_value: Type) -> bool:
@@ -353,12 +327,36 @@ def range_value(self):
353327

354328
@dataclass(eq=False)
355329
class HasTypes(HasType):
330+
"""
331+
Represents a specialized data structure holding multiple types.
332+
333+
This class is a data container designed to store and manage a tuple of
334+
types. It inherits from the `HasType` class and extends its functionality
335+
to handle multiple types efficiently. The primary goal of this class is to
336+
allow structured representation and access to a collection of type
337+
information with equality comparison explicitly disabled.
338+
"""
339+
356340
types_: Tuple[Type, ...]
341+
"""
342+
A tuple containing Type objects that are associated with this instance.
343+
"""
357344

358345

359346
def bind_first_argument_of_predicate_if_in_query_context(
360347
node: SymbolicExpression, predicate_type: Optional[PredicateType], *args
361348
):
349+
"""
350+
Binds the first argument of a predicate to a result quantifier's selected variable if
351+
in a query context and predicate type is specified.
352+
353+
:param node: The symbolic expression node to evaluate or use.
354+
:param predicate_type: The type of predicate, can be None.
355+
:param args: Additional arguments to bind with the predicate.
356+
357+
:return: A list of arguments where the first argument of the predicate is potentially
358+
replaced by the result quantifier's selected variable.
359+
"""
362360
if predicate_type and node and in_symbolic_mode(EQLMode.Query):
363361
if not isinstance(node, ResultQuantifier):
364362
result_quantifier = node._parent_._parent_
@@ -374,6 +372,18 @@ def update_query_child_expression_if_in_query_context(
374372
predicate_type: Optional[PredicateType],
375373
var: SymbolicExpression,
376374
):
375+
"""
376+
Updates the child expression of a given symbolic expression node in the context of a
377+
query mode. This function modifies the structure of the symbolic expression to inject
378+
a logical condition involving the provided variable, based on the specified predicate
379+
type and the current query mode.
380+
381+
:param node: The symbolic expression node whose child expression may be updated.
382+
:param predicate_type: The type of logical predicate guiding the behavior of this update.
383+
:param var: The symbolic expression to be integrated into the node's child expression if applicable.
384+
:raise: AssertionError: If any condition for in_symbolic_mode or other logical assertions fail during
385+
execution.
386+
"""
377387
if predicate_type and node and in_symbolic_mode(EQLMode.Query):
378388
if node._child_._child_:
379389
node._child_._child_ = AND(node._child_._child_, var)
@@ -414,11 +424,18 @@ def extract_selected_variable_and_expression(
414424
**kwargs,
415425
):
416426
"""
417-
:param symbolic_cls: The constructed class.
418-
:param domain: The domain source for the values of the variable by.
419-
:param predicate_type: The predicate type.
420-
:param kwargs: The keyword arguments to the class constructor.
421-
:return: The selected variable and expression.
427+
Extracts a variable and constructs its expression tree for the given symbolic class.
428+
429+
This function generates a variable of the specified `symbolic_cls` and uses the
430+
provided domain, predicate type, and additional arguments to create its expression
431+
tree. The domain can optionally be filtered when iterating through its elements
432+
if specified or retrieved from the cache keys associated with the symbolic class.
433+
434+
:param symbolic_cls: The symbolic class type to be used for variable creation.
435+
:param domain: Optional domain to provide constraints for the variable.
436+
:param predicate_type: Optional predicate type associated with the variable.
437+
:param kwargs: Additional properties to define and construct the variable.
438+
:return: A tuple containing the generated variable and its corresponding expression tree.
422439
"""
423440
cache_keys = get_cache_keys_for_class_(Variable._cache_, symbolic_cls)
424441
if not domain and cache_keys:
@@ -451,7 +468,14 @@ def extract_selected_variable_and_expression(
451468

452469
def update_cache(instance: Symbol):
453470
"""
454-
:param instance: The instance to update the cache with.
471+
Updates the cache with the given instance of a symbolic type. The function ensures
472+
proper handling of symbolic class cache, updates associated arguments, and adds
473+
relevant instances to the variable cache and symbol graph. This function operates
474+
based on the type and characteristics of the given instance.
475+
476+
:param instance: The symbolic instance to be cached, which can include types such as
477+
Symbol or BinaryPredicate, among others.
478+
:return: Returns the updated instance that has been added to the cache.
455479
"""
456480
symbolic_cls = type(instance)
457481
index = index_class_cache(symbolic_cls)
@@ -476,6 +500,18 @@ def update_cache(instance: Symbol):
476500

477501

478502
def update_cls_args(symbolic_cls: Type):
503+
"""
504+
Updates the global `cls_args` dictionary with the constructor arguments
505+
of the given symbolic class, if it is not already present. The keys in
506+
`cls_args` are symbolic class types, and the values are lists of
507+
constructor parameter names for those classes.
508+
509+
This function inspects the signature of the `__init__` method of the
510+
given symbolic class and stores the parameter names if the class
511+
is not already in the global `cls_args`.
512+
513+
:param symbolic_cls: A symbolic class type to be inspected.
514+
"""
479515
global cls_args
480516
if symbolic_cls not in cls_args:
481517
cls_args[symbolic_cls] = list(
@@ -486,5 +522,7 @@ def update_cls_args(symbolic_cls: Type):
486522
def index_class_cache(symbolic_cls: Type) -> bool:
487523
"""
488524
Determine whether the class cache should be indexed.
525+
526+
:param symbolic_cls: The symbolic class type.
489527
"""
490528
return issubclass(symbolic_cls, BinaryPredicate) and symbolic_cls.is_expensive

0 commit comments

Comments
 (0)