Skip to content

wend.conditions

Conditions: what a path must be, and may be used for, when it is realised.

An expression can declare conditions about the filesystem (Exists, Absent), about the kind of thing it names (File, Dir), about how it will be used (Readable, Writable, Creatable, Listable) and about where it may lead (Under(base))::

src = (P(root) / "raw" / T(t"{name}.csv")).requires(File, Exists, Readable)
dst = (P(root) / "out" / T(t"{name}.parquet")).requires(Absent, Creatable)
dst = dst.ensures(File, Exists)

PathExpr.render stays pure: the only condition it enforces is the lexical part of Under, which needs no filesystem. Everything else is checked by an explicit step, PathExpr.check (preconditions) and PathExpr.confirm (postconditions), or by a Step that groups the paths of one pipeline step.

Checks are point in time. A path that exists when it is checked may be gone when it is opened, and a symlink may be swapped between the two: checks describe and catch mistakes, they are not a sandbox and do not prevent races. See docs/SEMANTICS.md §10.

Absent module-attribute

Absent: Final[Condition[Literal["absent"], Literal["post_absent"]]] = Condition(
    "Absent"
)

Nothing occupies the path's name, not even a broken symlink.

Condition

Condition(name: str)

Bases: Generic[Pre, Post]

A property of a path, checked when the path is realised. For a type checker it adds its atom Pre to what an expression requires, and Post to what it ensures (the same atom, for every condition).

name instance-attribute

name = name

__call__

__call__(
    path: Any, /, *, optional: bool = False, many: bool = False, open: bool = False
) -> Entry

File(path, ...) and Dir(path, ...) declare a schema entry (see wend.schema): a relative path that names a file or directory, with its cardinality. Other conditions are not callable.

Creatable module-attribute

Creatable: Final[Condition[Literal["create"], Literal["post_create"]]] = Condition(
    "Creatable"
)

The path exists, or could be created now: no broken symlink occupies its name or a name above it, and its nearest existing ancestor is a directory the process may write to and search.

Declarations

Bases: NamedTuple

What an expression declares (docs/SEMANTICS.md §10.1): the atoms it requires before it is used, the atoms it ensures after it is produced, and the bases it is declared Under.

A declaration is an obligation, not a fact about the filesystem. Only containment holds of every path the expression renders (render checks it); every other atom holds when a check finds it, at that moment.

requires instance-attribute

requires: frozenset[str]

ensures instance-attribute

ensures: frozenset[str]

under instance-attribute

under: tuple[PathExpr, ...]

verbs property

verbs: frozenset[str]

The effects declared (what Step.effects reports): each permission in either phase, create for exists ensured but not required, and delete for absent ensured but not required.

__and__

__and__(other: object) -> Declarations

What both declare: the atoms of each phase in both, and the bases both are declared under.

Dir module-attribute

Dir: Final[Condition[Literal['dir'], Literal['post_dir']]] = Condition('Dir')

The path names a directory: if it exists it is one.

Effect

Bases: NamedTuple

What a step may do to one of its paths, as declared.

name instance-attribute

name: str

expr instance-attribute

expr: PathExpr

verbs instance-attribute

verbs: frozenset[str]

Exists module-attribute

Exists: Final[Condition[Literal["exists"], Literal["post_exists"]]] = Condition(
    "Exists"
)

Something exists at the path (symlinks are followed).

File module-attribute

File: Final[FileCondition] = FileCondition('File')

The path names a regular file: if it exists it is one, and nothing can be joined below it. Add Exists to require that it exists.

Listable module-attribute

Listable: Final[Condition[Literal["list"], Literal["post_list"]]] = Condition(
    "Listable"
)

The path is an existing directory the process may list.

Readable module-attribute

Readable: Final[Condition[Literal["read"], Literal["post_read"]]] = Condition(
    "Readable"
)

The path exists and the process may read it.

Step

Step(paths: Mapping[str, Any] | None = None, /, **named: Any)

The paths one step of a pipeline uses, with their conditions.

A single declaration of what must hold before the step runs (each path's requires) and after it finishes (each path's ensures)::

convert = Step(src=src, dst=dst)
with convert(root="/data", name="a") as p:
    write(transform(read(p["src"])), p["dst"])

Entering checks every precondition and yields the rendered paths; leaving without an exception checks every postcondition. The bindings are shared: each path takes the parameters it uses.

paths instance-attribute

paths: dict[str, PathExpr] = {k: _coerce(v, at_head=True) for k, v in {**paths or {}, **named}.items()}

param_names property

param_names: frozenset[str]

pattern property

pattern: str

check

check(mapping: Any = None, /, *, fs: Any = None, **kw: Any) -> dict[str, Path]

Render every path and check its preconditions, in declaration order, on fs (a wend.fs filesystem; the local disk by default).

confirm

confirm(mapping: Any = None, /, *, fs: Any = None, **kw: Any) -> dict[str, Path]

Render every path and check its postconditions, in declaration order, on fs.

__call__

__call__(
    mapping: Any = None, /, *, fs: Any = None, **kw: Any
) -> Iterator[dict[str, Path]]

effects

effects() -> list[Effect]

The filesystem effects each path declares: read, write, create, list (capabilities), plus create for a path that must exist afterwards but need not before, and delete for one that must be absent afterwards but need not be before. Paths declaring none are listed with no verbs. This reports declarations, not behaviour.

Under dataclass

Under(base: L)
Under(base: str | PathLike[str] | Param | PathExpr[Any, Any, Any])

Bases: Condition['Under[B] | Under[str]', 'Under[B] | Under[str]'], Generic[B]

The path stays under base: lexically when it is rendered, and after resolving symlinks when it is checked.

base is rendered with the same bindings as the path, so its parameters must be among the path's. Lexically, the path must start with the base's components and have no .. after them.

For a type checker, a literal string base gives the atom its identity (Under[Literal["/srv/static"]]); every base, literal or not, also declares Under[str], "under its own base".

base instance-attribute

base: PathExpr

name property

name: str

Writable module-attribute

Writable: Final[Condition[Literal["write"], Literal["post_write"]]] = Condition(
    "Writable"
)

The process may write the path: it exists and is writable, or it is Creatable.