Relations¶
from wend import P, Param, T
root, name = Param("root"), Param("name")
data = P(root) / "data" / T(t"{name}.csv")
schema = data.sibling("schema.json")
report = data.cousin("../reports", T(t"{name}.md"))
>>> schema
PathExpr('{root}/data/schema.json')
>>> report
PathExpr('{root}/reports/{name}.md')
>>> moved = data.rebase(P(root) / "data", P("/archive") / "2026")
>>> moved
PathExpr('/archive/2026/{name}.csv')
>>> schema.rebase(data, moved)
PathExpr('/archive/2026/schema.json')
>>> report.rebase(data, moved)
PathExpr('/archive/reports/{name}.md')
schema and report are defined by data. Moving data to /archive/2026 and
rebasing them onto the moved path recomputes them from it: the sibling stays next to the
data file, and the cousin stays one directory up.
Derivations¶
Each expression records the public operation that built it and the arguments it was given: its derivation. The derivation is not part of the path. Equality, hashing, rendering and matching ignore it:
>>> schema == P(root) / "data" / "schema.json"
True
>>> schema.derived_from(data)
True
>>> (P(root) / "data" / "schema.json").derived_from(data)
False
derived_from reports whether an expression was built from
another one.
Base and relative part¶
A join records its left operand as its base and its right
operand as its base_rel. x / y, x.child(y) and
y.under(x) are all joins. Bases chain, one per join:
>>> config = P(root).child("config.yaml")
>>> config.base, config.base_rel
(PathExpr('{root}'), PathExpr('config.yaml'))
>>> data.base
PathExpr('{root}/data')
>>> data.base.base
PathExpr('{root}')
Siblings and cousins¶
sibling(n)isparent / n.cousin(p, *names)starts atparent. Each component of the relative pathpis either.., which takes the parent, or a name, which is joined. Then each ofnamesis joined.
Both are computed from the structure, so they need no bindings. A cousin with an absolute
p raises PathOperationError.
Rebasing¶
rebase takes one or two arguments:
rebase(new)replaces the base of a join:config.rebase(P("/etc/app"))is/etc/app/config.yaml.rebase(old, new)rebuilds the expression withnewin place of every expression in its derivation that equalsold, and applies each recorded operation again. The result equals building the expression from scratch withnewin place ofold.
>>> config.rebase(P("/etc/app"))
PathExpr('/etc/app/config.yaml')
>>> report.rebase(root, P("/srv"))
PathExpr('/srv/reports/{name}.md')
If old is not in the derivation, it must be a structural prefix of the expression, and
the result is new joined with the rest of the path.
relative_to gives that rest:
>>> data.relative_to(root)
PathExpr('data/{name}.csv')
One fragment, many bases¶
under places a relative expression below a base. One fragment
can be placed under several bases, and each result keeps the base it was given:
>>> cache = P("cache") / "models"
>>> [cache.under(home) for home in ("/home/a/.app", "/home/b/.app")]
[PathExpr('/home/a/.app/cache/models'), PathExpr('/home/b/.app/cache/models')]
>>> cache.under(root)
PathExpr('{root}/cache/models')
>>> cache.under(root).base
PathExpr('{root}')