Metadata-Version: 2.4
Name: readonlydict
Version: 1.0.0
Summary: Drop-in read-only dictionary with 100% typing and runtime compatibility
Project-URL: homepage, https://astropenguin.github.io/readonlydict
Project-URL: repository, https://github.com/astropenguin/readonlydict
Author-email: Akio Taniguchi <a-taniguchi@mail.kitami-it.ac.jp>
License: MIT License
        
        Copyright (c) 2026 Akio Taniguchi
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: dictionary,hashable,immutable,mapping,python,read-only,typing
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: <4.0,>=3.10
Requires-Dist: typing-extensions<5,>=4
Description-Content-Type: text/markdown

# ReadonlyDict

[![Release](https://img.shields.io/pypi/v/readonlydict?label=Release&color=cornflowerblue&style=flat-square)](https://pypi.org/project/readonlydict/)
[![Python](https://img.shields.io/pypi/pyversions/readonlydict?label=Python&color=cornflowerblue&style=flat-square)](https://pypi.org/project/readonlydict/)
[![Downloads](https://img.shields.io/pypi/dm/readonlydict?label=Downloads&color=cornflowerblue&style=flat-square)](https://pepy.tech/project/readonlydict)
[![DOI](https://img.shields.io/badge/DOI-10.5281/zenodo.19187089-cornflowerblue?style=flat-square)](https://doi.org/10.5281/zenodo.19187089)
[![Tests](https://img.shields.io/github/actions/workflow/status/astropenguin/readonlydict/tests.yaml?label=Tests&style=flat-square)](https://github.com/astropenguin/readonlydict/actions)

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) - (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

```bash
pip install readonlydict
```

## Basic Usage

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

```python
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:

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

# dependencies
from readonlydict import ReadonlyDict

# type variables
K = TypeVar("K")
K2 = TypeVar("K2")
V = TypeVar("V")
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, mapping: Mapping[K, V], /, **kwargs: V2) -> "CustomDict[K | str, V | V2]": ...
        @overload
        def __new__(cls, iterable: Iterable[tuple[K, V]], /, **kwargs: V2) -> "CustomDict[K | str, V | V2]": ...
        def __new__(cls, *args: Any, **kwargs: Any) -> Any: ... # type: ignore[misc]

        @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()))
```

[frozendict]: https://github.com/Marco-Sulla/python-frozendict
[immutabledict]: https://immutabledict.corenting.fr
[immutables]: https://github.com/MagicStack/immutables
[mypy]: https://www.mypy-lang.org
[Pyright]: https://microsoft.github.io/pyright
