Skip to main content

Securely copy files to multiple destinations using source and destination verification.

Project description

o/COPY

CI PyPI version GitHub license Ruff codecov

o/COPY copies a directory tree to one or more destinations at once.

  • Hashing. Each file gets an xxh64 checksum during the copy (the value recorded in MHL output). Verification is on by default: o/COPY re-reads the source and destinations and confirms all xxh64 values match. Disable that with --dont-verify or verify=False.

  • ASC MHL (default on). Each destination gets an ASC Media Hash List (ASC MHL) history: the ascmhl folder, chain file, and XML generation manifests that document checksums together with file metadata, following the layout defined in the spec and read/written by the mhllib / ascmhl reference implementation. o/COPY supplies the xxh64 from the copy step so sealing does not hash file contents again. For flat *.mhl files in the original Media Hash List format instead, use --legacy-mhl or legacy_mhl=True. --no-mhl / mhl=False skips writing MHL output.

  • Skip-existing (default on). A destination file is fast-skipped only when its size and modification time match the source (within a small tolerance) and o/COPY already trusts an xxh64 for that path. Trusted digests are resolved in this order: .ocopy-checkpoint, an ASC MHL history in an ascmhl folder, then a legacy flat *.mhl. If metadata matches but no trusted hash exists while integrity is required, o/COPY re-reads and verifies so ASC MHL records are never written empty. A destination that exists but disagrees raises unless --overwrite / overwrite=True.

  • Integrity off. If both --no-mhl and --dont-verify are set (or mhl=False and verify=False in code), only size/mtime are used for skip-existing; hashes are not checked.

  • Resume. While a run is in progress, each destination tree keeps a .ocopy-checkpoint sidecar. When the run finishes without error, those files are removed (including when MHL output is disabled). If you interrupt the CLI, it exits with code 3, leaves checkpoints in place, and does not append a new ASC MHL generation or other MHL output. Run ocopy again to continue and finish.

Installation / Update

Recommended: uv

uv is an extremely fast Python package and project manager, written in Rust. If you do not have it yet: with Homebrew, run brew install uv; for the official standalone installer and everything else, see Installing uv.

uv tool install --python 3.14 ocopy

--python 3.14 pins the interpreter to the latest stable Python; uv will download a managed one automatically if you don't have it. Without it, uv picks the lowest version that satisfies o/COPY's requires-python (currently 3.11), which is fine but ages faster.

Update an existing install:

uv tool upgrade ocopy

With pipx

If you prefer the pip ecosystem, pipx does the same job as uv tool install: it puts each CLI app in its own environment and exposes the ocopy command on your PATH. Make sure python3.14 is on your PATH first (Homebrew: brew install python@3.14).

pipx install --python python3.14 ocopy
pipx upgrade ocopy

Usage

CLI

cli

After install the command is ocopy. Pass a source directory and one or more destination directories (each path must already exist and be a writable folder):

ocopy /path/to/source /path/to/dest1 /path/to/dest2

Run ocopy --help for the full flag list. The introduction above describes skip-existing, verification, ASC MHL histories vs. legacy flat MHL, and checkpoints.

During a long run the CLI tries to keep the system from going to idle sleep; that is best-effort and may not work in headless setups, and o/COPY will warn and continue copying.

Machine-readable output

--machine-readable makes ocopy emit a JSON Lines (JSONL) event stream on stdout (one JSON object per line) instead of a human progress bar. Use it from scripts and CI to track progress, capture warnings, and read a structured final result. See docs/machine-readable.md for the full event reference and schema version.

Python

import tempfile
from pathlib import Path
from time import sleep

from ocopy.verified_copy import CopyJob


def simple_example():
    # For the sake of this example we will create temporary directory.
    # You will not be doing this in your code.
    with tempfile.TemporaryDirectory() as tmp:
        tmp = Path(tmp)

        # Define source and destination directories
        source = tmp / "source"
        destinations = [tmp / "destination_1", tmp / "destination_2", tmp / "destination_3"]

        # Create some test content
        source.mkdir(parents=True, exist_ok=True)
        (source / "testfile").write_text("Some test content")

        # ``CopyJob`` starts work as soon as it is constructed.
        job = CopyJob(source, destinations, overwrite=True, verify=True)
        while job.finished is not True:
            sleep(0.1)

        # Print errors
        for error in job.errors:
            print(f"Failed to copy {error.source.name}:\n{error.error_message}")

        # Show the start of the latest ASC MHL generation (XML hash list)
        gen = next((destinations[0] / source.name / "ascmhl").glob("*.mhl"))
        print(gen.read_text()[:800])


if __name__ == "__main__":
    simple_example()

Development

This project uses uv for dependency management, Ruff for linting and formatting, and ty for type checking.

# Create the virtual environment and install runtime + dev dependencies
uv sync

# Run tests
uv run pytest

# Lint, format, and type check
uv run ruff check .
uv run ruff format .
uv run ty check

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

ocopy-0.9.1.tar.gz (125.8 kB view details)

Uploaded Source

Built Distribution

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

ocopy-0.9.1-py3-none-any.whl (35.2 kB view details)

Uploaded Python 3

File details

Details for the file ocopy-0.9.1.tar.gz.

File metadata

  • Download URL: ocopy-0.9.1.tar.gz
  • Upload date:
  • Size: 125.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ocopy-0.9.1.tar.gz
Algorithm Hash digest
SHA256 3799d47a02128f003dee46320dd387c3c88f4e67ed490e3e973e1f9bdf317289
MD5 b40185738c3abc949adfc169b3e11b8c
BLAKE2b-256 5217a6e119da4e631314b284bf3b9e5c2ceb23ffd5b754c2de5ffad240428e34

See more details on using hashes here.

Provenance

The following attestation bundles were made for ocopy-0.9.1.tar.gz:

Publisher: release.yml on ottomatic-io/ocopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ocopy-0.9.1-py3-none-any.whl.

File metadata

  • Download URL: ocopy-0.9.1-py3-none-any.whl
  • Upload date:
  • Size: 35.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ocopy-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2c6af6cf5b9039f38fc91ef7efeae5f0fb379151bd653946b673247ee02eecb8
MD5 907e5a82a3cf647adff63f8bf867cc18
BLAKE2b-256 827e5935b03989ad40c69ff25668b51df99f4b8067e0102c7ee16b4627facb60

See more details on using hashes here.

Provenance

The following attestation bundles were made for ocopy-0.9.1-py3-none-any.whl:

Publisher: release.yml on ottomatic-io/ocopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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