PathPattern
PathPattern(*args: Ref | Concept | Chain | PathPattern | Relationship, model=None)A reusable specification of a pattern that can match paths through a knowledge graph.
Construct a PathPattern with Model.path
(or its standalone convenience form path), optionally refine it with
PathPattern.where or repeat it with PathPattern.repeat,
then call PathPattern.all_paths to enumerate every matching path.
Positional arguments are classified by type:
- Edges — a
Chainor aRelationship. - Nodes — a
Conceptor aRef. - Nested sub-paths — a
PathPattern.
Path pattern arguments are joined via path-join semantics: the last field of an
argument unifies with the first slot of the next argument. For example, if A,
B, and C are nodes and r1 and r2 are edges, then
m.path(A, r1, B)implies thatA == r1[0]andr1[-1] == B,m.path(A, B, C)implies thatA == B == C, andm.path(r1, r2)implies thatr1[-1] == r2[0].
All node identifiers before the first edge and all node identifiers after the
final edge of a path pattern act as the explicit src and dst endpoints
of the path, respectively. Only these node identifiers may be referenced outside
the path pattern, e.g.,
m.where(m.path(src, Person.follows.repeat(1, 3), dst).all_paths()).select(src, dst).
All other node identifiers are referred to as interior nodes and are not yet accessible
outside the path pattern.
All occurrences of the same Concept within a
single m.path(...) refer to the same variable, just like elsewhere in PyRel. Reusing a Concept or Ref
at more than one node position is rejected with an error. For example,
m.path(Person.follows, Person.works_with) is invalid because the concept Person
appears in two node positions, and x = Person.ref(); m.path(x.follows, x.works_with)
is invalid because the reference x appears in two node positions. If the intent
is to keep node positions independent, use distinct Refs:
x, y = Person.ref(), Person.ref(); m.path(x.follows, y.works_with).
Important: The following two repeat forms have different semantics:
m.path(C.r.repeat(N))— repeats the edgeronly. Note that only the first node must be of typeC; intermediate nodes are not constrained to typeC.m.path(C.r).repeat(N)— repeats the patternC.r: every intermediate node must be of typeC.
Parameters
(*argsRef|Concept|Chain|PathPattern|Relationship, default:()) - One or more arguments defining the path pattern to be matched. Each argument may be a node (ConceptorRef), an edge (ChainorRelationship), or a nested sub-path (PathPattern).
(modelModel, default:None) - TheModelthis path pattern belongs to. When omitted, the active model is resolved automatically, which errors if multiple models are defined in this Python process.
Examples
Person = m.Concept("Person")source, destination = Person.ref(), Person.ref()m.path(Person.relationship)m.path(source.relationship)m.path(source.relationship, destination)m.path(source.relationship.repeat(1, 10))m.path(source.relationship.repeat(1, 10), destination)m.path(Person.relationship).repeat(3)m.path(Person.follows, path(Employee.follows).repeat(min=3, max=5))Notes
Most users should not instantiate this class directly. Prefer
Model.path (or its standalone
convenience form path).
Methods
.where()
PathPattern.where(*args: Expression | Match | Not) -> PathPatternAttach filters and constraints to this path pattern.
We currently support only local filters, which are those that reference a single
node or the endpoints of a single edge. They are routed onto that node or hop and
are applied during traversal, pruning the search space. Non-local filters (also
called cross-hop filters) are those that are not local to a single node or a
single edge’s endpoints, and are currently unsupported except for those that
reference only the path’s endpoints (src and dst). That special case is
allowed, but the filter is applied as a post-filter after all matching paths
are enumerated and does not prune the traversal.
Filters defined here (called attached filters) differ from those placed in an
enclosing Model.where (called enclosing
filters) in two primary ways:
- because only the path’s endpoints unify with the enclosing scope, attached filters can reference interior nodes, whereas enclosing filters cannot.
- attached filters are applied during traversal, which more efficiently prunes the traversal space, whereas enclosing filters are applied after enumeration.
Parameters:
(*argsExpression|Match|Not, default:()) - Zero or more of the following constraints on a single node, a single edge’s endpoints, or the pattern’s endpoints only: a booleanExpression, aMatch(a | b), or aNot(not_(...)).
Returns:
PathPattern- This pattern (self), to allow chaining.
Examples:
# Constrain the source node.m.path(x, Person.follows.repeat(1, 3)).where(x.name == "Alice")# Constrain an interior node's concept.m.path(x.follows, y.follows).where(Criminal(y))# Constrain the endpoints of a single edge.m.path(x.follows, y.follows).where(x.age < y.age)# A src/dst cross-hop filter (applied after enumeration).m.path(src.follows.repeat(1, 10), dst).where(src.age < dst.age)
# PathInternalError: a cross-hop filter that references an interior node ``z``.m.path(src.follows, y.follows, z.follows, dst).where(src.age < z.age).repeat()
PathPattern.repeat( min_or_exact: int | None = None, max: int | None = None, *, min: int | None = None) -> PathPatternMatch the specified number of repetitions of this pattern.
Repeats the whole path pattern: For example, given a concept Person with
property follows, m.path(Person.follows).repeat(3) repeats the pattern
Person.follows three times with independent Person instances, effectively
m.path(Person.ref().follows, Person.ref().follows, Person.ref().follows).
Note that the whole pattern is repeated, including its start: For example, in
m.path(Criminal.follows).repeat(2), the start Criminal repeats together
with the edge follows, effectively m.path(Criminal.ref().follows, Criminal.ref().follows)
— so every intermediate node must be of type Criminal. See
Chain.repeat for how this differs from a repeated
chain.
Important: Note that no node identifier may be used both inside and outside a
repeated pattern, due to ambiguity of which repetition the identifier refers to.
For example, the following are disallowed due to Criminal appearing both inside
and outside the repeated pattern:
>>> m.where(
… p := m.path(Criminal.follows).repeat(2,5).all_paths(),
… Criminal.age > 18
… ).select(p, p.nodes[“index”], Person(p.nodes).name)
>>> m.where( … p := m.path(Criminal.follows).repeat(2,5).all_paths(), … ).select(Criminal)
>>> repeated_pattern = m.path(Criminal.follows).repeat(2,5) >>> m.where( … p := m.path(repeated_pattern).where(Criminal.age > 18).all_paths() … ).select(p, p.nodes[“index”], Person(p.nodes).name)
Referencing a node identifier in the where attached to the repeated pattern is allowed and refers to every repetition (so this example matches chains of Criminals who are all above age 18): >>> m.where( … p := m.path(Criminal.follows).where(Criminal.age > 18).repeat(2,5) … ).select(p, p.nodes[“index”], Person(p.nodes).name)
You may reference the source endpoint of a repeated pattern’s first repetition or the destination endpoint of its last repetition by introducing a node identifier before or after the repeated pattern, respectively: >>> src, dst = Criminal.ref(), Criminal.ref() >>> m.where( … p := m.path(src, m.path(Criminal.follows).repeat(2,5), dst).all_paths() … ).select(src, dst)
An explicit finite upper bound is required; unbounded repeats are not currently supported.
Parameters:
(min_or_exactint, default:None) - When it is the only argument, the exact number of repetitions. Otherwise the lower bound (paired withmax).
(maxint, default:None) - The inclusive upper bound on the number of repetitions.
(minint, default:None) - Keyword-only alternative to the positional lower bound. Cannot be combined with a positionalmin_or_exact.
Returns:
PathPattern- This pattern (self), to allow chaining.
Raises:
ValueError- If no argument is given; if a bound is negative; ifmin > max; if only one ofmin/maxis supplied; or if both a positional min and themin=keyword are given.
Examples:
pattern.repeat(3) # exactly 3 times -> (3, 3)pattern.repeat(1, 20) # between 1 and 20 -> (1, 20)pattern.repeat(min=1, max=20) # keyword form -> (1, 20)pattern.repeat(max=20) # ValueError: an explicit min is requiredpattern.repeat(min=1) # ValueError: an explicit max is required.all_paths()
PathPattern.all_paths() -> DerivedColumnEnumerate every path matching this pattern.
Returns one PathTraversal per path matching the specified pattern. Enumeration
uses walk semantics: nodes and edges may repeat, so cycles in the graph can
result in arbitrarily many traversals.
Returns:
DerivedColumn- A column ofPathTraversalvalues, one per matching path. Use it as a relation insideModel.where/Model.select, and read the per-path details (length,nodes,relationships,relationship_fields) via thePathTraversalproperties.
Examples:
p = m.path(Person.follows.repeat(1, 3)).where(Person.name == "Alice").all_paths()m.where(p).select( p, p.length, p.nodes["index"], Person(p.nodes).name.alias("node")).inspect()# p length index node# 0 6w6baylbPzQBFBq7X+77Ug 1 0 Alice# 1 6w6baylbPzQBFBq7X+77Ug 1 1 Bob# 2 IBUHvCZTYTKnBRjIey8suw 2 0 Alice# 3 IBUHvCZTYTKnBRjIey8suw 2 1 Bob# 4 IBUHvCZTYTKnBRjIey8suw 2 2 DavidEach distinct p value identifies one path; rows sharing a p describe the
nodes visited along it.
Notes:
Only the path’s src and dst unify with the enclosing scope. Filters on
interior nodes must be attached with PathPattern.where on the pattern;
a filter referencing interior nodes placed in an enclosing
Model.where cannot be routed into the path and
raises.
Performance: Enumerating all paths is worst-case exponential in the size of the
graph and thus may be EXTREMELY EXPENSIVE! Try to make your path pattern as
selective as possible, and run on smaller test data when building your model and
query for path count first. Confirm that your graph is acyclic via Graph.is_acyclic.
Unless you need variable repeats or enumeration of nodes and edges along the path,
consider using regular PyRel to express your query instead of .all_paths()