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
flattenretains the original bindings (here,views), so you can use them in further constraints or selections.