Skip to main content

Drop-in read-only dictionary with 100% typing and runtime compatibility

Project description

ReadonlyDict

Release Python Downloads DOI Tests

Drop-in read-only dictionary with 100% typing and runtime compatibility

Overview: Why ReadonlyDict?

This package is built strictly on the following formula: ReadonlyDict = (built-in dictionary features) - (in-place features) + (read-only features).

  • 100% compatibility and zero custom API: Our goal is to achieve flawless compatibility with Python's built-in dictionary in both static type checking (e.g., mypy, Pyright) and runtime behavior. We simply removed in-place methods (e.g., pop(), update()). We do not introduce any custom methods.
  • True immutable semantics: The only additions are those strictly required for a read-only data structure: it is fully hashable (only if all values are hashable), and shallow copies (i.e., self.copy(), copy.copy(self)) simply return itself to save memory.
  • When to use this package: If you want extended read-only features, existing packages like frozendict, immutabledict, or immutables are better choices. However, if your priority is pure compatibility and perfect static type inference, ReadonlyDict should be the optimal choice.

Installation

pip install readonlydict

Basic Usage

It works exactly like a built-in dictionary, but raises an error if you try to modify it.

from readonlydict import ReadonlyDict


# Initialization works just like the built-in dictionary:
>>> ro = ReadonlyDict(a=0, b=1)
>>> ro
ReadonlyDict({'a': 0, 'b': 1})


# It is fully hashable (can be used as a dictionary key or in a set):
>>> hash(ro)
-5925576189957013898
>>> {ro, ro}
{ReadonlyDict({'a': 0, 'b': 1})}


# Mutation is strictly prohibited (static type checkers will also warn you):
>>> ro["c"] = 2
TypeError: 'ReadonlyDict' object does not support item assignment
>>> ro.update(c=2)
AttributeError: 'ReadonlyDict' object has no attribute 'update'

Advanced Usage: Subclassing with Type Hints

If you want to create your own custom read-only dictionary by subclassing ReadonlyDict, you can maintain static type inference by utilizing TYPE_CHECKING and @overload. Here is the best-practice template for subclassing:

# standard library
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, TypeVar, overload

# dependencies
from readonlydict import Items, ReadonlyDict

# type variables
K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")


class CustomDict(ReadonlyDict[K, V]):
    # Modify the return types to guarantee type inference:
    if TYPE_CHECKING:

        @overload
        def __new__(cls, **kwargs: V) -> "CustomDict[str, V]": ...
        @overload
        def __new__(cls, iterable: Items[K, V], /, **kwargs: V2) -> "CustomDict[K | str, V | V2]": ...
        @overload
        def __new__(cls, mapping: Mapping[K, V], /, **kwargs: V2) -> "CustomDict[K | str, V | V2]": ...
        def __new__(cls, *args: Any, **kwargs: Any) -> Any: ... # type: ignore

        @overload
        @classmethod
        def fromkeys(cls, iterable: Iterable[K2], /) -> "CustomDict[K2, None]": ...
        @overload
        @classmethod
        def fromkeys(cls, iterable: Iterable[K2], value: V2, /) -> "CustomDict[K2, V2]": ...
        @classmethod
        def fromkeys(cls, *args: Any, **kwargs: Any) -> Any: ...

        def __or__(self, other: Mapping[K2, V2], /) -> "CustomDict[K | K2, V | V2]": ...

    # Then add your custom properties or methods:
    @property
    def first(self) -> tuple[K, V]:
        return next(iter(self.items()))

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

readonlydict-2.0.0.tar.gz (75.8 kB view details)

Uploaded Source

Built Distribution

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

readonlydict-2.0.0-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file readonlydict-2.0.0.tar.gz.

File metadata

  • Download URL: readonlydict-2.0.0.tar.gz
  • Upload date:
  • Size: 75.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for readonlydict-2.0.0.tar.gz
Algorithm Hash digest
SHA256 aa7493e8ae6db3893629af3bf1ee91497c77de799b42e9af9de4a350878b80a2
MD5 39c56a65779e6a7478d0c84ac2dfa322
BLAKE2b-256 af40ff39e0d75c72ab3b2d8a875db5417c925d224f4ce868dd5d4fe7552d9560

See more details on using hashes here.

File details

Details for the file readonlydict-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: readonlydict-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for readonlydict-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 01297648d3373f9d3238aa92a4cd6732641e5978d21301a91d51f8aa77b9e209
MD5 8dd898842a64414ae76d5273318db6ad
BLAKE2b-256 9451018dde3bb24d31b2ee0de13accf3e44021b2d562f486a800b2a3853ab7d0

See more details on using hashes here.

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