Skip to main content

Varsnap Python

Build Status Maintainability Code Coverage

Python Varsnap Client

Installation

Install from PyPI - pip install varsnap

Requirements

The client depends on four environment variables to be set:

  • VARSNAP - Should be either true or false. Varsnap will be disabled if the variable is anything other than true.
  • ENV - If set to development, the client will receive events from production. If set to production, the client will emit events.
  • VARSNAP_PRODUCER_TOKEN - Only clients with this token may emit production snapshots. Copied from https://www.varsnap.com/user/
  • VARSNAP_CONSUMER_TOKEN - Only clients with this token may consume production snapshots in development. Copied from https://www.varsnap.com/user/

Usage

Add the varsnap decorator in front of any function you'd like to make better:

from varsnap import varsnap


@varsnap
def example(args, **kwargs):
    return 'output'

Custom serialization

Varsnap serializes a function's inputs and outputs as JSON. Values that JSON can't represent exactly keep their type if they're one of: bytes, tuples, sets and frozensets, dicts with non-string keys, datetime / date / time / timedelta, Decimal, UUID, raised exceptions, or the Flask types below. Raised exceptions are compared by their type's name, their arguments, and any attributes they set; the exception's class is never imported or instantiated from a snapshot.

NaN and infinity, as floats or Decimals, are never snapshotted: they aren't valid JSON, and a NaN never compares equal to itself, so such a snapshot could never match.

Other values (such as arbitrary objects) are not snapshotted; varsnap logs a warning and skips that call. To snapshot a value of another type, give that type a pair of varsnap_serialize / varsnap_deserialize classmethods. Varsnap reads the function's type annotations and uses these classmethods for any annotated parameter or return value:

from varsnap import varsnap


class Money:
    def __init__(self, cents):
        self.cents = cents

    @classmethod
    def varsnap_serialize(cls, value):
        return str(value.cents)

    @classmethod
    def varsnap_deserialize(cls, data):
        return cls(int(data))


@varsnap
def add_tax(price: Money) -> Money:
    return Money(round(price.cents * 1.1))

If a function isn't annotated (or you want to override its annotations), pass the types explicitly to the decorator:

@varsnap(types={'price': Money}, returns=Money)
def add_tax(price):
    return Money(round(price.cents * 1.1))

An instance method's self uses these classmethods from the class the method is defined on, so methods of a class that provides them are snapshotted too. Calls on an instance of a subclass are skipped, since the snapshot couldn't restore the subclass.

The type is always taken from the decorated function, never from the serialized data, so stored snapshots can't redirect deserialization to a different type. Values whose type doesn't provide these classmethods use the default serialization.

Flask

A Flask Response returned from a handler (including inside a (response, status) tuple) is compared by its status code, mimetype, and body. Headers are ignored, since values like cookies and dates change between runs. Werkzeug MultiDicts such as request.args and request.form, Headers, and Markup keep their types; request.headers is restored as Headers. Flask is not a varsnap dependency.

A streamed response, or one returned by send_file, is not snapshotted: reading its body would consume it before the client received it.

Security of deserialization

Snapshots are fetched from the varsnap server and deserialized on your machine, so the wire format is treated as untrusted input. Varsnap serializes values in one of three formats, all safe to deserialize:

  • json: — plain JSON.
  • builtin: — JSON with a tag for each value's type, for the types listed under "Custom serialization". Each tag maps to fixed decoding code in varsnap, so a payload can't name a class to import or a constructor to call, and nesting depth is limited.
  • type: — produced by a type's varsnap_serialize. The class is taken from the decorated function's annotations, never from the payload.

Pickle is not supported, since unpickling executes arbitrary code embedded in the payload. Snapshots recorded by older clients in the pickle: format are skipped.

Testing

With the proper environment variables set, in a test file, add:

import unittest
from varsnap import test

class TestIntegration(unittest.TestCase):
    def test_varsnap(self):
        matches, logs = test()
        if matches is None:
            raise unittest.case.SkipTest('No Snaps found')
        self.assertTrue(matches, logs)

If you're testing a Flask application, set up a test request context when testing:

# app = Flask()
with app.test_request_context():
    matches, logs = test()

Troubleshooting

Decorators changing function names

Using decorators may change the name of functions. In order to not confuse varsnap, set the decorated function's __qualname__ and __signature__ to match the original function:

import inspect


def decorator(func):
    def decorated(*args, **kwargs):
        return func(*args, **kwargs)
    decorated.__qualname__ = func.__qualname__
    decorated.__signature__ = inspect.signature(func)
    return decorated

Publishing

pip install build twine
python -m build
twine upload dist/*

Metadata

Release files for varsnap 1.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for varsnap 1.8.0
File Size Uploaded
varsnap-1.8.0.tar.gz 22.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for varsnap 1.8.0
File Interpreter ABI Platform
varsnap-1.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 40.0 kB

Release files / varsnap-1.8.0.tar.gz

Download URL varsnap-1.8.0.tar.gz
Size 22.1 kB
Tags Source
SHA-256 checksum
How to use checksums
0554a9a971d5ec3d567a15b8e6bba09a20cff0928af427aaaec91b03394b1bb4
BLAKE2b-256 checksum
How to use checksums
644023b7fbc6a9a4b58e472e055f9e7e04f3ccf8a798b1a36a728dc91b58cb34
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / varsnap-1.8.0-py3-none-any.whl

Download URL varsnap-1.8.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a3a51e8570ca738293b2c17022d657a53022d148c7189abfeb643f62ec794594
BLAKE2b-256 checksum
How to use checksums
fbdf0bb2a91a583063a521c7cea2213667eb5569ab584ad5bdab7e882ba82e05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.8.0 This release

2 release files

1.7.0

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.9

2 release files

1.5.8

2 release files

1.5.7

2 release files

1.5.6

2 release files

1.5.5

2 release files

1.5.4

2 release files

1.5.3

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.7

2 release files

1.4.6

2 release files

1.4.5

2 release files

1.4.4

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.0

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

1 release file

0.6.1

2 release files

0.6.0

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page