Skip to content

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) is parent / n.
  • cousin(p, *names) starts at parent. Each component of the relative path p is either .., which takes the parent, or a name, which is joined. Then each of names is 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 with new in place of every expression in its derivation that equals old, and applies each recorded operation again. The result equals building the expression from scratch with new in place of old.
>>> 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}')

Reference