Upgrading to 2.0¶
pytest-ditto 2.0 stores snapshots under new names and in strict JSON by default, so it finds none of the snapshots 1.x recorded. A plain run records them all again from your code's current output, and passes. Follow the workflow below so that first green run doesn't hide a regression.
Upgrade workflow¶
- Upgrade pytest-ditto, and any recorder plugins you use, to 2.0.
- Make the code changes that apply to you from
What changed: marks and recorders, a
ditto_backendfixture,DittoTestCase, credentials in target URIs. -
Run the complete suite to record every snapshot under its new name:
Tests that snapshot values outside strict JSON fail. Change what they snapshot, or select a suitable recorder.
-
Review every new snapshot in your diff. Each was recorded from current output, not converted from or compared with the 1.x baseline.
-
Run the complete suite again, then record the lock:
-
Delete the 1.x snapshot files.
ditto lintlists them as malformed names. Delete them by hand:ditto prunedoesn't remove them, because their names don't start with the test module, so prune never treats them as belonging to your suite. - Commit the new snapshots, the deletions and
ditto.locktogether. Then set up CI to runditto verify; see Running in CI.
What changed¶
Strict JSON is the default; pickle is a plugin¶
The unmarked default changes from pickle to strict JSON, and pickle moves out
of the core distribution. Core ignores existing .pkl snapshots: it doesn't
inspect, load, compare, convert or migrate them. A read-only ditto verify run
reports their JSON replacements as missing until they're recorded.
If you need pickle, install pytest-ditto-pickle and select its recorder
explicitly with @ditto.pickle. Loading pickle data can execute arbitrary
code, so only load trusted snapshots. Core provides no pickle warning, guard,
migration command or convenience extra.
pytest-ditto-pickle names its snapshot files after the recorder, so they end
in .pickle, not .pkl. It doesn't find 1.x .pkl files either: re-record
them with the pickle recorder and review the result, as in the workflow above.
Snapshot names¶
Snapshots are stored under new names, so none recorded by 1.x are found:
- A local snapshot is one file directly in
.ditto/, namedmodule.test@key~hash.recorder, with no per-module subdirectories. Remote snapshots use the same name with the module path's slashes kept. - A test in a class includes the class name (
TestClass.test_methodrather thantest_method). - The name ends with the recorder's registered name, such as
.json,.yamlor.pandas.parquet.
See Snapshot Names.
snapshot() returns the value as read back¶
snapshot() now returns the value as the recorder reads it back on every run.
Before, the run that recorded or updated a snapshot, or verified a missing one,
returned the value passed in. A value the recorder doesn't store exactly used
to pass on that run and fail on the next one; it now fails straight away. For
example, the YAML recorder reads a tuple back as a list, so
assert (1, 2) == snapshot((1, 2), key="pair") under @ditto.yaml now fails
when first recorded. Snapshot a list instead, or use a recorder that keeps the
distinction. A recorder whose loads can't read the bytes its dumps produced
now raises before anything is written.
The pytest plugin is named ditto¶
Version 2.0 registers the pytest plugin under the name ditto instead of
recording, which collided with the pytest-recording plugin. To disable
pytest-ditto for a run, use -p no:ditto in place of -p no:recording.
DittoTestCase is removed¶
Unittest-style classes collected by pytest should use pytest fixtures and
marks. Direct Snapshot construction is the lower-level alternative when
fixture injection is unsuitable.
A directly constructed Snapshot takes a single mode in place of the
update and readonly flags: Snapshot(..., mode=SnapshotMode.UPDATE)
replaces update=True, and mode=SnapshotMode.VERIFY replaces
readonly=True. The default, SnapshotMode.RECORD, matches the old defaults.
Import SnapshotMode from ditto. A Snapshot given a recorder also needs
recorder_name=, the name the recorder is registered under, which names its
snapshot files: Snapshot(..., recorder=recorders.get("yaml"),
recorder_name="yaml"). Passing one without the other raises TypeError. Omit
both for strict JSON.
Recorders serialise to bytes¶
A Recorder is Recorder(dumps=..., loads=...), where dumps turns a value
into the snapshot file's bytes and loads turns them back. The path-based
Recorder(save=..., load=...) is gone. A plugin still built on it fails to
load with a message saying so. Rebuild it on the library's in-memory functions,
or wrap its file functions with ditto.recorders.recorder_from_files. See
Writing a Recorder.
The ditto_backend fixture is replaced by registered backends¶
A ditto_backend fixture now raises an error. Register a URI scheme for your
backend and select it with a target instead:
-
Register a URI scheme under
ditto_backends: -
Move runtime auth and connection keyword arguments into
ditto_storage_options: -
Select the backend with
target=orditto_target:
See Writing a Backend.
Credentials can't go in target URIs¶
Version 2.0 refuses target URIs that contain a password or a secret query
parameter, such as redis://alice:secret@host or an Azure SAS URL's sig=,
because target URIs are recorded in ditto.lock. Move those credentials into
the ditto_storage_options fixture, or, for a target profile, into the
profile's storage_options (a profile ignores ditto_storage_options). See
Credentials and connection settings.
Verify, lock and prune refuse pytest-xdist¶
Under pytest-xdist distribution (-n N, or --dist with --tx),
--ditto-verify, --ditto-lock, --ditto-prune and --ditto-prune-dry-run
are now a usage error (exit code 4), raised before any test runs. Before,
verify passed without checking anything, prune only warned, and --ditto-lock
failed only after the whole suite had run under -n. Run these single-process,
after a parallel test run if you like. See
Running tests in parallel.
Prune leaves shared targets alone, and fails when it can't finish¶
ditto prune no longer deletes from a target that other checkouts might
share, such as a remote URI or a file:// path outside the project (after
following symlinks), unless you pass --shared (pytest --ditto-prune-shared).
Another branch's or project's snapshots on such a target look like orphans, so
pass it only when nothing else writes to the target. See
Sharing a target.
ditto prune (--ditto-prune) and ditto prune --check
(--ditto-prune-dry-run) now fail the run when they can't read a target, and
ditto prune fails when it can't delete a snapshot. Before, both only warned
and exited 0, so a CI prune step passed with the orphans still there.
Line endings are "\n" everywhere¶
YAML and pytest-ditto-pandas CSV snapshots are now written with "\n" line
endings on every platform, as JSON already was. On Windows, snapshots those
recorders wrote before used "\r\n". They still load; the next
--ditto-update rewrites them with "\n", a one-time line-ending diff. To stop
Git converting them on checkout, see
Commit your snapshots and the lock.
Upgrading from a 2.0 pre-release¶
- 2.0.0b1 named snapshots without the hash (
module.test@key.recorder), so they aren't found.ditto lintreports them as malformed names. Re-record them as in the upgrade workflow and delete the old files. pytest-ditto-pickle2.0.0b1 wrote.pklfiles, also without the hash. Re-record them with the current plugin, which writes.pickle, in the same way.