Skip to main content

Invertible directory structure transformations with schema-driven path remapping

Project description

pathmorph

Invertible directory structure transformations.

pathmorph lets you remap a directory layout to a different schema and restore the original at any time — with zero external state beyond a small JSON manifest embedded in the packed directory.

It is designed to be:

  • Composable — fits naturally into shell pipelines and Makefiles
  • Reproducible — the manifest is a ground-truth record of every file move
  • Format-agnostic — schemas can be written in YAML, TOML, or JSON
  • Dependency-lightconfuk, omegaconf, rich, typer; nothing heavier

Motivation

Pipeline tools tend to emit outputs in machine-friendly directory structures: short IDs, flat hierarchies, terse filenames. Collaborators often expect something different: descriptive names, nested grouping, human-readable paths.

pathmorph lets you have both. Define the mapping as a schema, pack when you need the human layout, unpack when you need the original back.


Installation

pip install pathmorph

# Optional: faster hashing for large directories
pip install pathmorph[xxhash]

Quickstart

1. Write a schema (my_schema.yaml):

schema:
  name: human_v1
  description: "Friendly layout for collaborators"
  fallback: passthrough   # or: omit/cram

  rules:
    - pattern: "runs/(?P<exp>[^/]+)/(?P<variant>[^/]+)/scores\\.tsv"
      target:  "experiments/{exp}/candidates/{variant}/developability_scores.tsv"

    - pattern: "runs/(?P<exp>[^/]+)/(?P<variant>[^/]+)/(?P<rest>.+)"
      target:  "experiments/{exp}/candidates/{variant}/{rest}"

Each rule is a Python re full-match pattern with named capture groups. The target is a format string that references those groups via {name}.

Rules are evaluated in order — the first match wins.

fallback controls what happens when no rule matches:

  • passthrough — file is copied as-is (default)
  • omit — file is skipped entirely
  • cram – file is crammed into the subdirectory path specified in the crampath variable in the schema

2. Preview the mapping (dry-run, no filesystem changes):

pathmorph diff ./pipeline_output -s my_schema.yaml

3. Pack into the human layout:

pathmorph pack ./pipeline_output -d ./for_collaborators -s my_schema.yaml

This copies files (non-destructive by default) and writes ./for_collaborators/.pathmorph_manifest.json.

4. Restore the original layout:

pathmorph unpack ./for_collaborators ./restored_original

5. Verify integrity:

pathmorph verify ./for_collaborators

Command reference

pack

pathmorph pack SRCS -d/--dst DST -s/--schema SCHEMA [OPTIONS]

Options:
  --move                   Move files instead of copying
  --handle-existing        abort | skip | overwrite
                           (prompts interactively if omitted)
  --hash ALGO              sha256 (default), sha1, md5, blake2b,
                           xxh64, xxh128, xxh3_64, xxh3_128

unpack

pathmorph unpack PACKED_DIR DST [OPTIONS]

Options:
  --move                   Move files instead of copying
  --handle-existing        abort | skip | overwrite

diff

pathmorph diff SRCS -s/--schema SCHEMA [OPTIONS]

Options:
  --show-passthrough / --hide-passthrough   (default: show)

verify

pathmorph verify PACKED_DIR

Exit code 0 on success, 1 if any file fails.

Schema formats

pathmorph accepts any format supported by confuk: .yaml, .yml, .toml, .json.

See examples/ for the same schema in YAML and TOML.


Manifest

A packed directory is self-describing. The manifest at .pathmorph_manifest.json records:

{
  "version": 1,
  "schema_name": "human_v1",
  "schema_description": "...",
  "packed_at": "2026-04-29T...",
  "algorithm": "sha256",
  "entries": [
    {
      "original": "runs/exp_001/A/scores.tsv",
      "packed":   "experiments/exp_001/candidates/A/developability_scores.tsv",
      "hash":     "abc123...",
      "algorithm": "sha256"
    }
  ]
}

unpack uses the entry table directly — it does not re-evaluate schema rules. This means the manifest is a durable, schema-version-independent record: even if you later change the schema, existing packed directories remain unpackable.


Using as a library

from pathlib import Path
from pathmorph import pack, unpack, verify, diff
from pathmorph.schemas import Schema
from pathmorph.collision import CollisionStrategy

schema = Schema.from_file(Path("my_schema.yaml"))

# Dry-run
records = diff(Path("./pipeline_output"), schema)

# Pack
result = pack(
    Path("./pipeline_output"),
    Path("./for_collaborators"),
    schema=schema,
    collision=CollisionStrategy.SKIP,
    hash_algorithm="sha256",
)

# Verify
verify_result = verify(Path("./for_collaborators"))
assert verify_result.passed

# Restore
unpack(Path("./for_collaborators"), Path("./restored"))

Development

git clone https://github.com/yourusername/pathmorph
cd pathmorph
pip install -e ".[dev]"
pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pathmorph-0.1.0.tar.gz (67.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pathmorph-0.1.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file pathmorph-0.1.0.tar.gz.

File metadata

  • Download URL: pathmorph-0.1.0.tar.gz
  • Upload date:
  • Size: 67.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.04","id":"plucky","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pathmorph-0.1.0.tar.gz
Algorithm Hash digest
SHA256 76e436b2b245d5a85c55a765be59b7dd20494f203a0a45ba31372c71ddb392eb
MD5 ff5109bbee70cbbb505e756047fe3b71
BLAKE2b-256 c0906d9f1781c54212387b06e42198f4c3e0d800e99806dbf808b335908d4fb7

See more details on using hashes here.

File details

Details for the file pathmorph-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pathmorph-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.04","id":"plucky","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pathmorph-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e8031abfa0d87238a743d8f2a3e7e247082e259a55275e8bc5794f189b74afb0
MD5 46a1306e762279d7f6ced51290f8891a
BLAKE2b-256 6e4e3b1a886b67f32cb5b2a35af65545423f56d53033381e8d6935a12cb94e3f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page