Example: Flatten (UNNEST-like) over iterable attributes#
Flatten lets you turn an iterable-of-iterables into a flat sequence of items while keeping the original parent binding, similar to SQL UNNEST. It is handy when a selected variable has an attribute that is a list and you want one row per element of that list.
In this example, we place a single “cabinet” view into world.views
. The cabinet has a drawers
list. Using
views.drawers.flatten()
yields one row per drawer while preserving the views
binding to the cabinet.
from dataclasses import dataclass, field
from typing import List
from entity_query_language import a, set_of, symbolic_mode, let, flatten
from entity_query_language.predicate import symbol
# Minimal dataset for the example
@symbol
@dataclass
class Body:
name: str
@symbol
@dataclass
class Handle(Body):
...
@symbol
@dataclass
class Container(Body):
...
@symbol
@dataclass
class View:
world: object = field(default=None, repr=False, kw_only=True)
@symbol
@dataclass
class Drawer(View):
handle: Handle
container: Container
@symbol
@dataclass
class World:
bodies: List[Body] = field(default_factory=list)
views: List[View] = field(default_factory=list)
# Build a small world
world = World()
container1 = Container(name="Container1")
container3 = Container(name="Container3")
handle1 = Handle(name="Handle1")
handle3 = Handle(name="Handle3")
world.bodies.extend([container1, container3, handle1, handle3])
# Two drawers
drawer1 = Drawer(handle=handle1, container=container1)
drawer2 = Drawer(handle=handle3, container=container3)
# A simple view-like class with an iterable attribute
class CabinetLike(View):
def __init__(self, drawers):
super().__init__()
self.drawers = list(drawers)
cabinet = CabinetLike([drawer1, drawer2])
world.views = [cabinet]
with symbolic_mode():
views = let(type_=View, domain=world.views)
drawers = flatten(views.drawers) # <-- UNNEST-like flatten
query = a(set_of([views, drawers]))
rows = list(query.evaluate())
# rows is a list of UnificationDict, each containing both the parent view and one flattened drawer
assert len(rows) == 2
assert {r[drawers].handle.name for r in rows} == {"Handle1", "Handle3"}
assert all(r[views] is cabinet for r in rows)
Notes:
.flatten()
works on any expression that yields an iterable (e.g., an attribute likeviews.drawers
).Each solution produced by
flatten
retains the original bindings (here,views
), so you can use them in further constraints or selections.