Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c63854b
[EQL] match automatically determines which comparator type to use, wh…
AbdelrhmanBassiouny Nov 19, 2025
dc6103c
[EQL] set_of can use match instances as its selectables.
AbdelrhmanBassiouny Nov 20, 2025
9df3ea4
[EQLMatch] match does not require kwargs.
AbdelrhmanBassiouny Nov 20, 2025
191df0a
[EQLMatch] selectables.
AbdelrhmanBassiouny Nov 20, 2025
4de351f
[EQLMatch] match can return a set_of.
AbdelrhmanBassiouny Nov 20, 2025
d0581d1
[EQLMatch] match can return a set_of.
AbdelrhmanBassiouny Nov 20, 2025
cac9883
[EQLMatch] in progress selecting literals.
AbdelrhmanBassiouny Nov 20, 2025
d0ec235
[EQL] select seems to work.
AbdelrhmanBassiouny Nov 21, 2025
7e5af65
[EQLMatch] cleaning.
AbdelrhmanBassiouny Nov 24, 2025
0a9a16c
[EQLMatch] only return entity() with the parent match variable select…
AbdelrhmanBassiouny Nov 24, 2025
d430882
[EQLMatch] cleaning.
AbdelrhmanBassiouny Nov 24, 2025
16503d5
[EQLMatch] cleaning.
AbdelrhmanBassiouny Nov 24, 2025
bb00cf5
[EQLMatch] doc.
AbdelrhmanBassiouny Nov 24, 2025
3f59ad7
[EQLMatch] fix type hints of match return
AbdelrhmanBassiouny Nov 24, 2025
ad9fe80
[EQLMatch] test match.
AbdelrhmanBassiouny Nov 24, 2025
bd59c08
[EQLMatch] test match any
AbdelrhmanBassiouny Nov 24, 2025
ae2ebec
[EQLMatch] cleaning and docs.
AbdelrhmanBassiouny Nov 24, 2025
7bcd501
[EQLMatch] more tests, cleaning.
AbdelrhmanBassiouny Nov 25, 2025
993b245
[EQLMatch] back to match_any, and select_any.
AbdelrhmanBassiouny Nov 25, 2025
0426954
Merge remote-tracking branch 'code_iai/main' into better_match
AbdelrhmanBassiouny Nov 25, 2025
67a7522
[EQLMatch] restructuring, created match.py, and quantify_entity.py.
AbdelrhmanBassiouny Nov 25, 2025
c321957
[EQLMatch] update docs.
AbdelrhmanBassiouny Nov 25, 2025
db9f2e5
[EQLMatch] better way of finding if variable values are iterable.
AbdelrhmanBassiouny Nov 25, 2025
2a3ee59
[EQLMatch] more efficient exists and comparator.
AbdelrhmanBassiouny Nov 25, 2025
c613396
[EQLMatch] doc update
AbdelrhmanBassiouny Nov 25, 2025
95e1955
[EQLMatch] doc update
AbdelrhmanBassiouny Nov 25, 2025
7ea4804
[EQL] updated match logic, need to fix existential and universal cond…
AbdelrhmanBassiouny Nov 26, 2025
d627aaa
[EQL] universal match doesn't work.
AbdelrhmanBassiouny Nov 27, 2025
6157830
[EQL] Symbol doc update.
AbdelrhmanBassiouny Nov 27, 2025
b261b8e
[EQL] fixed match all.
AbdelrhmanBassiouny Nov 28, 2025
cead85e
[EQL] fixed match notebook.
AbdelrhmanBassiouny Nov 28, 2025
e948639
[EQL] fixed match notebook.
AbdelrhmanBassiouny Nov 28, 2025
84f8a87
[EQL] fix method doc.
AbdelrhmanBassiouny Nov 28, 2025
8c5365f
[EQL] review changes.
AbdelrhmanBassiouny Nov 28, 2025
7686f7f
[EQL] review changes.
AbdelrhmanBassiouny Nov 28, 2025
f0c159d
[EQL] review changes.
AbdelrhmanBassiouny Nov 28, 2025
e5f6838
[EQL] Created AttributeAssignment class.
AbdelrhmanBassiouny Nov 28, 2025
11358b3
[EQL] Class doc.
AbdelrhmanBassiouny Nov 28, 2025
1fda110
[EQLMatch] fix type hints.
AbdelrhmanBassiouny Nov 29, 2025
6d9582e
[EQLMatch] doc fix.
AbdelrhmanBassiouny Nov 29, 2025
5387298
Merge remote-tracking branch 'code_iai/main' into better_match
AbdelrhmanBassiouny Nov 29, 2025
144ff40
[EQLMatch] fix selection.
AbdelrhmanBassiouny Nov 29, 2025
51981cd
Merge remote-tracking branch 'code_iai/main' into better_match
AbdelrhmanBassiouny Nov 29, 2025
3a3e138
[EQLMatch] us normal in.
AbdelrhmanBassiouny Nov 29, 2025
a6f2415
Merge remote-tracking branch 'code_iai/main' into better_match
AbdelrhmanBassiouny Nov 29, 2025
2f72c1d
[EQLMatch] compare variables using hash to avoid symbolic comparison.
AbdelrhmanBassiouny Nov 29, 2025
591faa0
[EQLMatch] compare variables using hash to avoid symbolic comparison.
AbdelrhmanBassiouny Nov 29, 2025
b5b9418
[EQLFeatures] review changes.
AbdelrhmanBassiouny Nov 30, 2025
32d029a
[EQLMatch] removed unused method.
AbdelrhmanBassiouny Dec 1, 2025
d620f52
[EQLMatch] review changes.
AbdelrhmanBassiouny Dec 1, 2025
bf2e0c9
[EQLMatch] review changes.
AbdelrhmanBassiouny Dec 1, 2025
d497b41
[EQLMatch] fix None instances problem.
AbdelrhmanBassiouny Dec 1, 2025
c63d037
removed is_universal_match and is_existential_match
LucaKro Dec 1, 2025
5be0136
Merge pull request #2 from LucaKro/better_match
AbdelrhmanBassiouny Dec 2, 2025
31ea32d
Merge remote-tracking branch 'code_iai/main' into better_match
AbdelrhmanBassiouny Dec 2, 2025
a78c6d4
[EQLMatch] fix and cleaning
AbdelrhmanBassiouny Dec 2, 2025
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
3 changes: 2 additions & 1 deletion examples/eql/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ from dataclasses import dataclass

from typing_extensions import List

from krrood.entity_query_language.entity import entity, an, let, contains, Symbol
from krrood.entity_query_language.entity import entity, let, contains, Symbol
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
3 changes: 2 additions & 1 deletion examples/eql/comparators.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ from dataclasses import dataclass
from typing_extensions import List

from krrood.entity_query_language.entity import (
entity, an, let, Symbol,
entity, let, Symbol,
in_, contains, not_, and_, or_,
)
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
3 changes: 1 addition & 2 deletions examples/eql/domain_mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ from typing_extensions import List, Dict
from krrood.entity_query_language.entity import (
entity,
set_of,
an,
let,
flatten,
Symbol,
)

from krrood.entity_query_language.quantify_entity import an

@dataclass
class Body(Symbol):
Expand Down
3 changes: 2 additions & 1 deletion examples/eql/eql_for_sql_experts.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ from dataclasses import dataclass, field

from typing_extensions import List

from krrood.entity_query_language.entity import let, Symbol, entity, an, and_, in_, contains, set_of
from krrood.entity_query_language.entity import let, Symbol, entity, and_, in_, contains, set_of
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
3 changes: 2 additions & 1 deletion examples/eql/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ from dataclasses import dataclass

from typing_extensions import List

from krrood.entity_query_language.entity import entity, an, let, contains, Symbol
from krrood.entity_query_language.entity import entity, let, contains, Symbol
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
3 changes: 2 additions & 1 deletion examples/eql/logical_operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ from dataclasses import dataclass

from typing_extensions import List

from krrood.entity_query_language.entity import entity, an, or_, Symbol, let, not_, and_
from krrood.entity_query_language.entity import entity, or_, Symbol, let, not_, and_
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
149 changes: 147 additions & 2 deletions examples/eql/match.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ The following example shows how nested patterns translate
into an equivalent manual query built with `entity(...)` and predicates.

```{code-cell} ipython3
from krrood.entity_query_language.symbol_graph import SymbolGraph
from dataclasses import dataclass
from typing_extensions import List

from krrood.entity_query_language.entity import (
let, entity, the,
match, entity_matching, Symbol,
let, entity, Symbol,
)
from krrood.entity_query_language.quantify_entity import the, an
from krrood.entity_query_language.match import (
match,
entity_matching,
)
from krrood.entity_query_language.predicate import HasType

Expand Down Expand Up @@ -61,7 +66,19 @@ class FixedConnection(Connection):
@dataclass
class World:
connections: List[Connection]

@dataclass
class Drawer(Symbol):
handle: Handle
container: Container


@dataclass
class Cabinet(Symbol):
container: Container
drawers: List[Drawer]

SymbolGraph()

# Build a small world with a few connections
c1 = Container("Container1")
Expand Down Expand Up @@ -123,3 +140,131 @@ Notes:
- Use `entity_matching` for the outer pattern when a domain is involved; inner attributes use `match`.
- Nested `match(...)` can be composed arbitrarily deep following your object graph.
- `entity_matching` is a syntactic sugar over the explicit `entity` + predicates form, so both are interchangeable.

## Selecting inner objects with `select()`

Use `select(Type)` when you want the matched inner objects to appear in the result. The evaluation then
returns a mapping from the selected variables to the concrete objects (a unification dictionary).

```{code-cell} ipython3
from krrood.entity_query_language.match import select

container, handle = select(Container), select(Handle)
fixed_connection_query = the(
entity_matching(FixedConnection, world.connections)(
parent=container(name="Container1"),
child=handle(name="Handle1"),
)
)

answers = fixed_connection_query.evaluate()
print(answers[container].name, answers[handle].name)
```

## Existential matches in collections with `match_any()`

When matching a container-like attribute (for example, a list), use `match_any(pattern)` to express that
at least one element of the collection should satisfy the given pattern.

Below we add two simple view classes and build a small scene of drawers and a cabinet.

```{code-cell} ipython3
from krrood.entity_query_language.match import match_any

# Build a simple set of views
drawer1 = Drawer(handle=h1, container=c1)
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
views = [drawer1, drawer2, cabinet1, cabinet2]

# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))

found_cabinets = list(cabinet_query.evaluate())
assert len(found_cabinets) == 2
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
```

## Selecting elements from collections with `select_any()`

If you want to retrieve a specific element from a collection attribute while matching, use `select_any(Type)`.
It behaves like `match_any(Type)` but also selects the matched element so you can access it in the result.

```{code-cell} ipython3
from krrood.entity_query_language.match import select_any

selected_drawers = select_any([drawer1, drawer2])
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=selected_drawers))

ans = list(cabinet_query.evaluate())
assert len(ans) == 2
print(ans)
```

## Selecting inner objects with `select()`

Use `select(Type)` when you want the matched inner objects to appear in the result. The evaluation then
returns a mapping from the selected variables to the concrete objects (a unification dictionary).

```{code-cell} ipython3
from krrood.entity_query_language.match import select

container, handle = select(Container), select(Handle)
fixed_connection_query = the(
entity_matching(FixedConnection, world.connections)(
parent=container(name="Container1"),
child=handle(name="Handle1"),
)
)

answers = fixed_connection_query.evaluate()
print(answers[container].name, answers[handle].name)
```

## Existential matches in collections with `match_any()`

When having multiple possible matches, and you care only if at least the attribute matches one possibility, use
`match_any(IterableOfPossibleValues)` to express that
at least one element of the collection should satisfy the given pattern.

Below we add two simple view classes and build a small scene of drawers and a cabinet.

```{code-cell} ipython3
from krrood.entity_query_language.match import match_any

# Build a simple set of views
drawer1 = Drawer(handle=h1, container=c1)
drawer2 = Drawer(handle=Handle("OtherHandle"), container=other_c)
cabinet1 = Cabinet(container=c1, drawers=[drawer1, drawer2])
cabinet2 = Cabinet(container=other_c, drawers=[drawer2])
views = [drawer1, drawer2, cabinet1, cabinet2]

# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet_query = an(entity_matching(Cabinet, views)(drawers=match_any([drawer1, drawer2])))

found_cabinets = list(cabinet_query.evaluate())
assert len(found_cabinets) == 2
print(found_cabinets[0].container.name, found_cabinets[0].drawers[0].handle.name)
print(found_cabinets[1].container.name, found_cabinets[1].drawers[0].handle.name)
```

## Selecting elements from collections with `select_any()`

If you want to retrieve a specific element from a collection attribute while matching, use `select_any(Type)`.
It behaves like `match_any(Type)` but also selects the matched element so you can access it in the result.

```{code-cell} ipython3
from krrood.entity_query_language.match import select_any, entity_selection

selected_drawers = select_any([drawer1, drawer2])
# Query: find the cabinet that has any drawer from the set {drawer1, drawer2}
cabinet = entity_selection(Cabinet, views)
cabinet_query = an(cabinet(drawers=selected_drawers))

ans = list(cabinet_query.evaluate())
assert len(ans) == 2
print(ans)
```
3 changes: 2 additions & 1 deletion examples/eql/predicate_and_symbolic_function.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ Lets first define our model and some sample data.
from dataclasses import dataclass
from typing_extensions import List

from krrood.entity_query_language.entity import entity, let, an, Symbol
from krrood.entity_query_language.entity import entity, let, Symbol
from krrood.entity_query_language.predicate import Predicate, symbolic_function
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
5 changes: 3 additions & 2 deletions examples/eql/result_quantifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ from dataclasses import dataclass

from typing_extensions import List

from krrood.entity_query_language.entity import entity, let, the, Symbol, an
from krrood.entity_query_language.entity import entity, let, Symbol
from krrood.entity_query_language.quantify_entity import an, the
from krrood.entity_query_language.result_quantification_constraint import AtLeast, AtMost, Exactly, Range
from krrood.entity_query_language.failures import MultipleSolutionFound, LessThanExpectedNumberOfSolutions, GreaterThanExpectedNumberOfSolutions

Expand Down Expand Up @@ -81,7 +82,7 @@ Below we reuse the same `World` and `Body` setup from above.
The world contains exactly two bodies, so all the following examples will evaluate successfully.

```{code-cell} ipython3
# Require at least two results
# Require at least one result
query = an(
entity(body := let(Body, domain=world.bodies)),
quantification=AtLeast(1),
Expand Down
5 changes: 3 additions & 2 deletions examples/eql/writing_queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ This approach ensures that your class definitions remain pure and decoupled from
outside the explicit symbolic context. Consequently, your classes can focus exclusively on their domain logic,
leading to better adherence to the [Single Responsibility Principle](https://realpython.com/solid-principles-python/#single-responsibility-principle-srp).

Here is a query that does work due to the missing `let` statement:
Here is a query example that finds all bodies in a world whose name starts with "B":

```{code-cell} ipython3
from dataclasses import dataclass

from typing_extensions import List

from krrood.entity_query_language.entity import entity, an, let, Symbol
from krrood.entity_query_language.entity import entity, let, Symbol
from krrood.entity_query_language.quantify_entity import an


@dataclass
Expand Down
3 changes: 2 additions & 1 deletion examples/eql/writing_rule_trees.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Lets define our domain model and build a small world. We will then build a rule
instances to the world.

```{code-cell} ipython3
from krrood.entity_query_language.entity import entity, an, let, and_, Symbol, inference
from krrood.entity_query_language.entity import entity, let, and_, Symbol, inference
from krrood.entity_query_language.quantify_entity import an

from krrood.entity_query_language.rule import refinement, alternative
from krrood.entity_query_language.conclusion import Add
Expand Down
Empty file modified scripts/test_documentation.sh
100644 → 100755
Empty file.
8 changes: 7 additions & 1 deletion src/krrood/class_diagrams/class_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def get_wrapped_class(self, clazz: Type) -> Optional[WrappedClass]:
except KeyError:
raise ClassIsUnMappedInClassDiagram(clazz)

def add_node(self, clazz: WrappedClass):
def add_node(self, clazz: Union[Type, WrappedClass]):
"""
Adds a new node to the dependency graph for the specified wrapped class.

Expand All @@ -481,6 +481,12 @@ class to the wrapped class.

:param clazz: The wrapped class object to be added to the dependency graph.
"""
try:
clazz = self.get_wrapped_class(clazz)
except ClassIsUnMappedInClassDiagram:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very weird way of checking if the class already belings to the class diagram

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how else would it be?

clazz = WrappedClass(clazz)
if clazz.index is not None:
return
clazz.index = self._dependency_graph.add_node(clazz)
clazz._class_diagram = self
self._cls_wrapped_cls_map[clazz.clazz] = clazz
Expand Down
Loading