{"id":47662368,"url":"https://github.com/astropenguin/readonlydict","last_synced_at":"2026-04-11T10:10:58.117Z","repository":{"id":346254645,"uuid":"1188002102","full_name":"astropenguin/readonlydict","owner":"astropenguin","description":"Drop-in read-only dictionary with 100% typing and runtime compatibility","archived":false,"fork":false,"pushed_at":"2026-04-11T08:10:40.000Z","size":2630,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T09:27:53.730Z","etag":null,"topics":["dictionary","hashable","immutable","mapping","python","read-only","typing"],"latest_commit_sha":null,"homepage":"https://astropenguin.github.io/readonlydict/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/astropenguin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T13:40:05.000Z","updated_at":"2026-04-11T08:07:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/astropenguin/readonlydict","commit_stats":null,"previous_names":["astropenguin/readonlydict"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/astropenguin/readonlydict","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astropenguin%2Freadonlydict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astropenguin%2Freadonlydict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astropenguin%2Freadonlydict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astropenguin%2Freadonlydict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/astropenguin","download_url":"https://codeload.github.com/astropenguin/readonlydict/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astropenguin%2Freadonlydict/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31676446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T08:18:19.405Z","status":"ssl_error","status_checked_at":"2026-04-11T08:17:08.892Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dictionary","hashable","immutable","mapping","python","read-only","typing"],"created_at":"2026-04-02T11:39:43.873Z","updated_at":"2026-04-11T10:10:58.111Z","avatar_url":"https://github.com/astropenguin.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ReadonlyDict\n\n[![Release](https://img.shields.io/pypi/v/readonlydict?label=Release\u0026color=cornflowerblue\u0026style=flat-square)](https://pypi.org/project/readonlydict/)\n[![Python](https://img.shields.io/pypi/pyversions/readonlydict?label=Python\u0026color=cornflowerblue\u0026style=flat-square)](https://pypi.org/project/readonlydict/)\n[![Downloads](https://img.shields.io/pypi/dm/readonlydict?label=Downloads\u0026color=cornflowerblue\u0026style=flat-square)](https://pepy.tech/project/readonlydict)\n[![DOI](https://img.shields.io/badge/DOI-10.5281/zenodo.19187089-cornflowerblue?style=flat-square)](https://doi.org/10.5281/zenodo.19187089)\n[![Tests](https://img.shields.io/github/actions/workflow/status/astropenguin/readonlydict/tests.yaml?label=Tests\u0026style=flat-square)](https://github.com/astropenguin/readonlydict/actions)\n\nDrop-in read-only dictionary with 100% typing and runtime compatibility\n\n## Overview: Why ReadonlyDict?\n\nThis package is built strictly on the following formula:\n``ReadonlyDict = (built-in dictionary features) - (in-place features) + (read-only features)``.\n\n- **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.\n- **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.\n- **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.\n\n## Installation\n\n```bash\npip install readonlydict\n```\n\n## Basic Usage\n\nIt works exactly like a built-in dictionary, but raises an error if you try to modify it.\n\n```python\nfrom readonlydict import ReadonlyDict\n\n\n# Initialization works just like the built-in dictionary:\n\u003e\u003e\u003e ro = ReadonlyDict(a=0, b=1)\n\u003e\u003e\u003e ro\nReadonlyDict({'a': 0, 'b': 1})\n\n\n# It is fully hashable (can be used as a dictionary key or in a set):\n\u003e\u003e\u003e hash(ro)\n-5925576189957013898\n\u003e\u003e\u003e {ro, ro}\n{ReadonlyDict({'a': 0, 'b': 1})}\n\n\n# Mutation is strictly prohibited (static type checkers will also warn you):\n\u003e\u003e\u003e ro[\"c\"] = 2\nTypeError: 'ReadonlyDict' object does not support item assignment\n\u003e\u003e\u003e ro.update(c=2)\nAttributeError: 'ReadonlyDict' object has no attribute 'update'\n```\n\n## Advanced Usage: Subclassing with Type Hints\n\nIf 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``.\nHere is the best-practice template for subclassing:\n\n```python\n# standard library\nfrom collections.abc import Iterable, Mapping\nfrom typing import TYPE_CHECKING, Any, TypeVar, overload\n\n# dependencies\nfrom readonlydict import ReadonlyDict, Tuples\n\n# type variables\nK = TypeVar(\"K\")\nV = TypeVar(\"V\")\nK2 = TypeVar(\"K2\")\nV2 = TypeVar(\"V2\")\n\n\nclass CustomDict(ReadonlyDict[K, V]):\n    # Modify the return types to guarantee type inference:\n    if TYPE_CHECKING:\n\n        @overload\n        def __new__(cls, **kwargs: V) -\u003e \"CustomDict[str, V]\": ...\n        @overload\n        def __new__(cls, mapping: Mapping[K, V], /, **kwargs: V2) -\u003e \"CustomDict[K | str, V | V2]\": ...\n        @overload\n        def __new__(cls, iterable: Tuples[K, V], /, **kwargs: V2) -\u003e \"CustomDict[K | str, V | V2]\": ...\n        def __new__(cls, *args: Any, **kwargs: Any) -\u003e Any: ... # type: ignore[misc]\n\n        @overload\n        @classmethod\n        def fromkeys(cls, iterable: Iterable[K2], /) -\u003e \"CustomDict[K2, None]\": ...\n        @overload\n        @classmethod\n        def fromkeys(cls, iterable: Iterable[K2], value: V2, /) -\u003e \"CustomDict[K2, V2]\": ...\n        @classmethod\n        def fromkeys(cls, *args: Any, **kwargs: Any) -\u003e Any: ...\n\n        def __or__(self, other: Mapping[K2, V2], /) -\u003e \"CustomDict[K | K2, V | V2]\": ...\n\n    # Then add your custom properties or methods:\n    @property\n    def first(self) -\u003e tuple[K, V]:\n        return next(iter(self.items()))\n```\n\n[frozendict]: https://github.com/Marco-Sulla/python-frozendict\n[immutabledict]: https://immutabledict.corenting.fr\n[immutables]: https://github.com/MagicStack/immutables\n[mypy]: https://www.mypy-lang.org\n[Pyright]: https://microsoft.github.io/pyright\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fastropenguin%2Freadonlydict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fastropenguin%2Freadonlydict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fastropenguin%2Freadonlydict/lists"}