Skip to content

Snapshot Names

Every snapshot is stored under a name built from the test that owns it. You rarely need to read these names: ditto.lock records each snapshot's exact test and key, and ditto list shows them. This page explains the names for when you do, such as when reviewing a diff or writing a backend.

The parts of a name

A name has five parts: the test module, the test, the snapshot key, a short hash, and the recorder. With the project root as pytest's rootdir, a test test_create in tests/api/test_users.py that calls snapshot(value, key="response") with the json recorder is stored as:

tests/api/.ditto/tests.api.test_users.test_create@response~90e755f5c20755bd.json
                 └────────┬─────────┘ └────┬────┘ └──┬───┘ └──────┬───────┘ └┬─┘
                     test module         test       key         hash     recorder
Part What it is
test module The test file's path relative to pytest's rootdir, without its extension.
test The test's name within the module, from its pytest node ID. A test in a class includes the class (TestUsers.test_create), and a parametrized test includes its ID (test_create[admin]).
key The key passed to snapshot().
hash The first 16 hex characters of a SHA-256 of the test's exact node ID, the key and the recorder.
recorder The name of the recorder that wrote the snapshot, such as json or pandas.parquet.

Local files and remote storage

A file:// target, including the default .ditto directory, stores each snapshot as one file. The module path's slashes become dots, so every file sits directly in the directory, as above.

Any other target stores one object per snapshot under the URI's path, and the module path keeps its slashes. The same test, with target="s3://my-bucket/snapshots/", is stored as:

s3://my-bucket/snapshots/tests/api/test_users/test_create@response~90e755f5c20755bd.json
                         └────────┬─────────┘ └────┬────┘ └──┬───┘ └──────┬───────┘ └┬─┘
                             test module         test       key         hash     recorder

Safe characters and length

The test and key are there to be read, not decoded, so they're made safe for every file system: characters other than ASCII letters, digits and . _ - [ ] = , + become _. The test is shortened to 80 characters and the key to 40.

A local file name must also fit in 255 bytes. If the module path is long, the test and key are shortened further to make room; a module path too long to leave room for them is an error. A remote name isn't a file name, so it has no such limit.

Why the hash

Replacing characters and ignoring case can give two snapshots the same readable part. The hash, taken from the exact node ID rather than the readable part, keeps them apart:

Test File
test_at[12:00] tests.test_api.test_at[12_00]@body~6617c5399a6ba420.json
test_at[12_00] tests.test_api.test_at[12_00]@body~105d78e147145d58.json
test_at[A] tests.test_api.test_at[A]@body~46cccc8d4a59c1ef.json
test_at[a] tests.test_api.test_at[a]@body~a99f8460327efdf4.json

[A] and [a] would otherwise be the same file name on Windows and macOS, whose file systems ignore case.

Because the name depends on the node ID, renaming or moving a test, changing a parametrize ID, changing a key, or switching recorder all give the snapshot a new name. The old snapshot stays where it was until you prune it.

Writes and temporary files

A local snapshot is written to a temporary file next to it, then renamed into place, so a write that fails partway through (a full disk, an interrupted run) leaves the previous snapshot intact. An overwritten snapshot keeps its permissions. A process killed mid-write can leave the temporary file behind: .ditto-tmp-, then 32 hex characters, then .tmp. ditto ignores it, and it's safe to delete.