Skip to content
Become a
Sponsor or follow @15r10nk on
or
for updates

# @register_format()

## General

`register_format()` allows you to add support for your custom file formats, such as the following `Array` type.

my_array.py

    from dataclasses import dataclass
    from typing import List

    @dataclass
    class Array:
        numbers: List[int]

conftest.py

    from pathlib import Path
    from inline_snapshot import register_format, TextDiff, Format

    from my_array import Array

    @register_format
    class ArrayFormat(TextDiff, Format[Array]):
        suffix = ".arr"

        @staticmethod
        def is_format_for(data: object):
            return isinstance(data, Array)

        @staticmethod
        def encode(value: Array, path: Path):
            with path.open("w", encoding="utf-8", newline="\n") as f:
                f.write("\n".join(map(str, value.numbers)))

        @staticmethod
        def decode(path: Path) -> Array:
            with path.open("r", encoding="utf-8", newline="\n") as f:
                return Array(list(map(int, f.read().splitlines())))

You can then use `external()` to save this type into an external file.

    from my_array import Array
    from inline_snapshot import external

    def test_array():
        assert Array([1, 2, 3]) == external()

inline-snapshot will check if the type matches by using `is_format_for()` and create a file with the given suffix.

## Report

inline-snapshot needs to know how changes in your external files should be displayed. `TextDiff` and `BinaryDiff` can be used as mixin classes (see the `Array` example above) to provide generic representations for text or binary formats, but you can also define custom functions for your files.

- `rich_diff()` is used every time the external snapshot value is changed and should show the difference between the original and new versions in a human-readable form.
- `rich_show()` is used every time an external snapshot is created and should show a human-readable form of the initial value.

conftest.py

    from pathlib import Path
    from inline_snapshot import register_format, Format, external
    from number_set import NumberSet

    @register_format
    class NumberSetFormat(Format[NumberSet]):
        suffix = ".numberset"

        def rich_diff(self, original: Path, new: Path):
            original_numbers = set(self.decode(original).numbers)
            new_numbers = set(self.decode(new).numbers)

            return (
                f"new numbers: [green]{new_numbers-original_numbers}[/]\n"
                f"removed numbers: [red]{original_numbers-new_numbers}[/]"
            )

        def rich_show(self, path: Path):
            return " ".join(f"[blue]{n}[/]" for n in self.decode(path).numbers)

        def is_format_for(self, data: object):
            return isinstance(data, NumberSet)

        def encode(self, value: NumberSet, path: Path):
            with path.open("w", encoding="utf-8", newline="\n") as f:
                f.write("\n".join(sorted(map(str, value.numbers))))

        def decode(self, path: Path) -> NumberSet:
            with path.open("r", encoding="utf-8", newline="\n") as f:
                return NumberSet(set(map(int, f.read().splitlines())))

number_set.py

    from dataclasses import dataclass
    from typing import Set

    @dataclass
    class NumberSet:
        numbers: Set[int]

The custom format is then used every time a `NumberSet` is compared with an empty external.

    from number_set import NumberSet
    from inline_snapshot import external

    def test():
        assert NumberSet([1, 2, 5]) == external()
        assert NumberSet([1, 2, 8]) == external()

## Reference

### `` `Format`

Base class for the Format Protocol.

#### `` `priority = 0` `class-attribute` `instance-attribute`

Determines the correct format when multiple format handlers can handle a given value (`is_format_for`).

priority is `0` by default and can be set to a smaller number for generic formats that work with many data types (e.g., .json), where `is_format_for()` also returns `True` for `str` and `bytes`. This allows you to use `assert "text" == external()` without explicitly providing a `".txt"` suffix to distinguish it from `".json"`.

A higher priority can be used for more specific formats, such as bytes with a `b"\x89PNG"` prefix that should be stored as .png files.

#### `` `suffix` `instance-attribute`

The suffix associated with this format handler.

Every format implementation must define a suffix. This suffix is used when the external file is written and is required to find the correct format handler when the file is read again.

#### `` `decode(path)`

Reads the value from the specified path.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`path` | `Path` | The path to a temporary file where the value is stored. | required

Returns:

Type | Description
--- | ---
`T` | The value of the external object.

#### `` `encode(value, path)`

Converts the value and stores it in the specified path.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`value` | `T` | The value to be stored. | required
`path` | `Path` | The path to a temporary file where the value should be stored. | required

#### `` `is_format_for(value)`

Determines if this format handler can handle the given value.

This function is used to find the correct format handler when no suffix is provided.

    assert value == external()

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`value` | `object` | The value to be formatted. | required

Returns:

Type | Description
--- | ---
`bool` | `True` if the value is handled by this format implementation.

#### `` `rich_diff(original, new)`

Displays a diff between the original and new files.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`original` | `Path` | The path to the original external file. | required
`new` | `Path` | The path to the new external file. | required

Returns:

Type | Description
--- | ---
`RenderableType` | A rich renderable object representing the diff. This can be a
`RenderableType` | textual diff or another type of representation.

#### `` `rich_show(path)`

Displays a representation of a newly created external object.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`path` | `Path` | The path to the external file. | required

Returns:

Type | Description
--- | ---
`RenderableType` | A rich renderable object representing the new object. The
`RenderableType` | representation should be concise.

### `` `register_format(format=None, *, replace_handler=False)`

Registers a new format handler for the suffix `format.suffix`.

This function can also be used as a decorator:

    @register_format
    class MyFormat: ...

which is equivalent to:

    register_format(MyFormat())

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`format` | `type[Format[FormatT]] | Format[FormatT] | None` | The format handler class or instance. | `None`
`replace_handler` | `bool` | If `True`, replaces an existing handler for the same suffix. | `False`

Raises:

Type | Description
--- | ---
`UsageError` | If a handler for the same suffix already exists and `replace_handler` is not set to `True`.

### `` `register_format_alias(suffix, format)`

Registers an alias for a given format suffix.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`suffix` | `str` | The suffix to register the alias for. | required
`format` | `str` | The suffix of the format that should be used instead. | required

Notes

The alias suffix is used to find the correct format handler.