diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b133ec..daf22cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,14 +20,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12.0-beta.3"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12.0-beta.4"] runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 - if: ${{ matrix.python-version != '3.12.0-beta.3' }} # remove when codeql is fixed on 3.12 + if: ${{ matrix.python-version != '3.12.0-beta.4' }} # remove when codeql is fixed on 3.12 with: languages: python - name: Install Poetry @@ -45,7 +45,7 @@ jobs: run: make test - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 - if: ${{ matrix.python-version != '3.12.0-beta.3' }} # remove when codeql is fixed on 3.12 + if: ${{ matrix.python-version != '3.12.0-beta.4' }} # remove when codeql is fixed on 3.12 - name: Codecov uses: codecov/codecov-action@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index ed83f78..9ee6659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Version 3.0.0 + +- `copy()` (**breaking change**): remove the option to pass keyword arguments (which were present as key/value pairs in the copy). Now the method doesn't take any arguments (it behaves the same as a normal `dict`). +- Python versions: drop Python 3.7 support +- Typing: fixes + - Make the key covariant. Thanks to [@spacether](https://github.com/spacether) for the [PR #244](https://github.com/corenting/immutabledict/pull/244) + - Fix key/value typing missing for ImmutableOrderedDict + # Version 2.2.5 - Fix hard-coded class reference in fromkeys() resulting in always using `dict` for `fromkeys()` (instead of OrderedDict in ImmutableOrderedDict for example). Thanks to [@cthoyt](https://github.com/cthoyt) for the [PR #234](https://github.com/corenting/immutabledict/pull/234) diff --git a/Makefile b/Makefile index 7f6d2c0..a62eb3a 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ format: style: $(PYTHON) black --check immutabledict tests $(PYTHON) ruff immutabledict tests - $(PYTHON) mypy -- immutabledict + $(PYTHON) mypy -- immutabledict tests .PHONY: test test: diff --git a/README.md b/README.md index dbef543..67b7290 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,13 @@ This library is a pure Python, MIT-licensed alternative to the new LGPL-3.0 lice It implements the complete mapping interface and can be used as a drop-in replacement for dictionaries where immutability is desired. The immutabledict constructor mimics dict, and all of the expected interfaces (iter, len, repr, hash, getitem) are provided. Note that an immutabledict does not guarantee the immutability of its values, so the utility of hash method is restricted by usage. -The only difference is that the copy() method of immutable takes variable keyword arguments, which will be present as key/value pairs in the new, immutable copy. - ## Installation -Available as `immutabledict` on : -- [pypi](https://pypi.org/project/immutabledict/) -- [conda-forge](https://anaconda.org/conda-forge/immutabledict) (community-maintained, not an official release) -- alpine as [py3-immutabledict](https://pkgs.alpinelinux.org/packages?name=py3-immutabledict) (community-maintained, not an official release) +Official release in [on pypy](https://pypi.org/project/immutabledict/) as `immutabledict`. + +**Community-maintained** releases are available: +- On [conda-forge](https://anaconda.org/conda-forge/immutabledict) as `immutabledict` +- On [various package repositories](https://repology.org/project/python:immutabledict/versions) ## Example @@ -30,7 +29,8 @@ print(my_item["a"]) # Print "value" ## Differences with the old original frozendict package -- Dropped support of EOL Python versions (version 1.0.0 supports Python 3.5, versions <= 2.2.1 supports Python 3.6) +- Dropped support of EOL Python versions (older versions of the library may support older Python versions) - Fixed `collections.Mapping` deprecation warning - Typing - [PEP 584 union operators](https://www.python.org/dev/peps/pep-0584/) +- Keep the same signature for `copy()` as `dict` (starting with immutabledict 3.0.0), don't accept extra keyword arguments. diff --git a/immutabledict/__init__.py b/immutabledict/__init__.py index a86f3fd..26988d1 100644 --- a/immutabledict/__init__.py +++ b/immutabledict/__init__.py @@ -3,7 +3,7 @@ from collections import OrderedDict from typing import Any, Dict, Iterable, Iterator, Mapping, Optional, Type, TypeVar -__version__ = "2.2.5" +__version__ = "3.0.0" _K = TypeVar("_K") _V = TypeVar("_V", covariant=True) @@ -22,7 +22,7 @@ class immutabledict(Mapping[_K, _V]): @classmethod def fromkeys( cls, seq: Iterable[_K], value: Optional[_V] = None - ) -> "immutabledict[_K, _V]": + ) -> immutabledict[_K, _V]: return cls(cls.dict_cls.fromkeys(seq, value)) def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -45,7 +45,7 @@ def __len__(self) -> int: return len(self._dict) def __repr__(self) -> str: - return "%s(%r)" % (self.__class__.__name__, self._dict) + return "{}({!r})".format(self.__class__.__name__, self._dict) def __hash__(self) -> int: if self._hash is None: @@ -74,12 +74,9 @@ def __ior__(self, other: Any) -> immutabledict[_K, _V]: raise TypeError(f"'{self.__class__.__name__}' object is not mutable") -class ImmutableOrderedDict(immutabledict): +class ImmutableOrderedDict(immutabledict[_K, _V]): """ An immutabledict subclass that maintains key order. - - Not necessary anymore as for Python >= 3.7 order is guaranteed with the normal - immutabledict class, but kept for compatibility purpose. """ dict_cls = OrderedDict diff --git a/poetry.lock b/poetry.lock index 688544b..e7b4c24 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,36 +2,33 @@ [[package]] name = "black" -version = "23.3.0" +version = "23.7.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" -files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, ] [package.dependencies] @@ -41,7 +38,6 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -74,18 +70,17 @@ files = [ [[package]] name = "click" -version = "8.1.3" +version = "8.1.6" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -175,13 +170,13 @@ toml = ["tomli"] [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] @@ -228,26 +223,6 @@ files = [ docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -297,7 +272,6 @@ files = [ [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=4.1.0" [package.extras] @@ -361,18 +335,15 @@ testing = ["funcsigs", "pytest"] [[package]] name = "platformdirs" -version = "3.8.0" +version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, - {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} - [package.extras] docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] @@ -388,9 +359,6 @@ files = [ {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -411,13 +379,13 @@ plugins = ["importlib-metadata"] [[package]] name = "pyproject-api" -version = "1.5.2" +version = "1.5.3" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.7" files = [ - {file = "pyproject_api-1.5.2-py3-none-any.whl", hash = "sha256:9cffcbfb64190f207444d7579d315f3278f2c04ba46d685fad93197b5326d348"}, - {file = "pyproject_api-1.5.2.tar.gz", hash = "sha256:999f58fa3c92b23ebd31a6bad5d1f87d456744d75e05391be7f5c729015d3d91"}, + {file = "pyproject_api-1.5.3-py3-none-any.whl", hash = "sha256:14cf09828670c7b08842249c1f28c8ee6581b872e893f81b62d5465bec41502f"}, + {file = "pyproject_api-1.5.3.tar.gz", hash = "sha256:ffb5b2d7cad43f5b2688ab490de7c4d3f6f15e0b819cb588c4b771567c9729eb"}, ] [package.dependencies] @@ -462,7 +430,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -542,52 +509,17 @@ cachetools = ">=5.3.1" chardet = ">=5.1" colorama = ">=0.4.6" filelock = ">=3.12.2" -importlib-metadata = {version = ">=6.7", markers = "python_version < \"3.8\""} packaging = ">=23.1" platformdirs = ">=3.8" pluggy = ">=1.2" pyproject-api = ">=1.5.2" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} virtualenv = ">=20.23.1" [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.3,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"] -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] - [[package]] name = "typing-extensions" version = "4.7.1" @@ -601,19 +533,18 @@ files = [ [[package]] name = "virtualenv" -version = "20.23.1" +version = "20.24.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, - {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"}, + {file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"}, ] [package.dependencies] distlib = ">=0.3.6,<1" filelock = ">=3.12,<4" -importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} platformdirs = ">=3.5.1,<4" [package.extras] @@ -630,22 +561,7 @@ files = [ {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, ] -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "949af5df3fec19d092a9872722509156c76f991ccee40e48bd9180401232656b" +python-versions = "^3.8" +content-hash = "0ca9424a0fb02bbe6fbebac73386c3d705d3b5f6a2d5364412c28896032a5194" diff --git a/pyproject.toml b/pyproject.toml index fc50cd6..9d05d43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "immutabledict" -version = "2.2.5" +version = "3.0.0" description = "Immutable wrapper around dictionaries (a fork of frozendict)" authors = ["Corentin Garcia "] license = "MIT" @@ -20,7 +20,7 @@ include = [ "Bug Tracker" = "https://github.com/corenting/immutabledict/issues" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" [tool.poetry.dev-dependencies] black = "*" diff --git a/tests/test_immutabledict.py b/tests/test_immutabledict.py index e72ae40..948a8d4 100644 --- a/tests/test_immutabledict.py +++ b/tests/test_immutabledict.py @@ -1,85 +1,115 @@ +from typing import Any, Dict, Union import pytest from immutabledict import ImmutableOrderedDict, immutabledict class TestImmutableDict: - def test_covariance(self): - assert immutabledict.__parameters__[0].__covariant__ is False - assert immutabledict.__parameters__[1].__covariant__ is True + def test_covariance(self) -> None: + """ + Not a real unit test, but test covariance + as mypy runs on the tests. + """ - def test_cannot_assign_value(self): + class Base: + pass + + class One(Base): + pass + + class Two(Base): + pass + + # Value test + my_dict: immutabledict[str, Union[Base, One]] = immutabledict() + second_dict: immutabledict[str, Two] = immutabledict({"t": Two()}) + my_dict = second_dict + assert my_dict == second_dict + + def test_cannot_assign_value(self) -> None: with pytest.raises(AttributeError): - immutabledict().setitem("key", "value") + immutabledict().setitem("key", "value") # type: ignore - def test_from_keys(self): - keys = ("a", "b", "c") - immutable_dict = immutabledict.fromkeys(keys) + def test_from_keys(self) -> None: + keys = ["a", "b", "c"] + immutable_dict: immutabledict[str, Any] = immutabledict.fromkeys(keys) assert "a" in immutable_dict assert "b" in immutable_dict assert "c" in immutable_dict - def test_init_and_compare(self): + def test_init_and_compare(self) -> None: normal_dict = {"a": "value", "b": "other_value"} - immutable_dict = immutabledict(normal_dict) + immutable_dict: immutabledict[str, str] = immutabledict(normal_dict) assert immutable_dict == normal_dict - def test_get_existing(self): - immutable_dict = immutabledict({"a": "value"}) + def test_get_existing(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict({"a": "value"}) assert immutable_dict["a"] == "value" - def test_get_not_existing(self): - immutable_dict = immutabledict({"a": "value"}) + def test_get_not_existing(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict({"a": "value"}) with pytest.raises(KeyError): immutable_dict["b"] - def test_contains(self): - immutable_dict = immutabledict({"a": "value"}) + def test_contains(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict({"a": "value"}) assert "a" in immutable_dict - def test_contains_not_existing(self): - immutable_dict = immutabledict({"a": "value"}) + def test_contains_not_existing(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict({"a": "value"}) assert "b" not in immutable_dict - def test_copy(self): - original = immutabledict({"a": "value"}) + def test_copy(self) -> None: + original: immutabledict[str, str] = immutabledict({"a": "value"}) copy = original.copy() assert original == copy assert id(original) != id(copy) - def test_iter(self): - immutable_dict = immutabledict({"a": "value", "b": "other_value"}) - itered_keys = {x for x in immutable_dict} + def test_iter(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) + itered_keys = set(immutable_dict) assert immutable_dict.keys() == itered_keys - def test_len(self): - immutable_dict = immutabledict({"a": "value", "b": "other_value"}) + def test_len(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) assert len(immutable_dict) == 2 - def test_len_empty(self): - immutable_dict = immutabledict({}) + def test_len_empty(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict({}) assert len(immutable_dict) == 0 - def test_repr(self): - immutable_dict = immutabledict({"a": "value", "b": "other_value"}) + def test_repr(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) repr_ret = repr(immutable_dict) assert repr_ret.startswith("immutabledict") assert repr_ret.endswith(")") - def test_repr_should_eval(self): - immutable_dict = immutabledict({"a": "value", "b": "other_value"}) + def test_repr_should_eval(self) -> None: + immutable_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) eval_ret = eval(repr(immutable_dict)) assert immutable_dict == eval_ret - def test_hash(self): - first_dict = immutabledict({"a": "value", "b": "other_value"}) - second_dict = immutabledict({"a": "value", "b": "other_value"}) + def test_hash(self) -> None: + first_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) + second_dict: immutabledict[str, str] = immutabledict( + {"a": "value", "b": "other_value"} + ) assert hash(first_dict) == hash(second_dict) - def test_union_operator_merge(self): - first_dict = immutabledict({"a": "a", "b": "b"}) - second_dict = immutabledict({"a": "A", "c": "c"}) + def test_union_operator_merge(self) -> None: + first_dict: immutabledict[str, str] = immutabledict({"a": "a", "b": "b"}) + second_dict: immutabledict[str, str] = immutabledict({"a": "A", "c": "c"}) merged_dict = first_dict | second_dict assert isinstance(merged_dict, immutabledict) assert merged_dict == { @@ -90,9 +120,9 @@ def test_union_operator_merge(self): assert first_dict == {"a": "a", "b": "b"} assert second_dict == {"a": "A", "c": "c"} - def test_union_operator_merge_with_dict(self): - first_dict = dict({"a": "a", "b": "b"}) - second_dict = immutabledict({"a": "A", "c": "c"}) + def test_union_operator_merge_with_dict_first(self) -> None: + first_dict: Dict[str, str] = dict({"a": "a", "b": "b"}) + second_dict: immutabledict[str, str] = immutabledict({"a": "A", "c": "c"}) merged_dict = first_dict | second_dict assert isinstance(merged_dict, dict) assert merged_dict == { @@ -103,8 +133,9 @@ def test_union_operator_merge_with_dict(self): assert first_dict == {"a": "a", "b": "b"} assert second_dict == {"a": "A", "c": "c"} - first_dict = immutabledict({"a": "a", "b": "b"}) - second_dict = dict({"a": "A", "c": "c"}) + def test_union_operator_merge_with_dict_second(self) -> None: + first_dict: immutabledict[str, str] = immutabledict({"a": "a", "b": "b"}) + second_dict: Dict[str, str] = dict({"a": "A", "c": "c"}) merged_dict = first_dict | second_dict assert isinstance(merged_dict, immutabledict) assert merged_dict == { @@ -115,8 +146,8 @@ def test_union_operator_merge_with_dict(self): assert first_dict == {"a": "a", "b": "b"} assert second_dict == {"a": "A", "c": "c"} - def test_union_operator_merge_fail(self): - first_dict = immutabledict({"a": "a", "b": "b"}) + def test_union_operator_merge_fail(self) -> None: + first_dict: immutabledict[str, str] = immutabledict({"a": "a", "b": "b"}) with pytest.raises(TypeError): first_dict | 0 @@ -124,16 +155,16 @@ def test_union_operator_merge_fail(self): with pytest.raises(TypeError): 0 | first_dict - def test_union_operator_update(self): - first_dict = immutabledict({"a": "a", "b": "b"}) - second_dict = immutabledict({"a": "A", "c": "c"}) + def test_union_operator_update(self) -> None: + first_dict: immutabledict[str, str] = immutabledict({"a": "a", "b": "b"}) + second_dict: immutabledict[str, str] = immutabledict({"a": "A", "c": "c"}) with pytest.raises(TypeError): first_dict |= second_dict - def test_union_operator_update_with_dict(self): - first_dict = dict({"a": "a", "b": "b"}) - second_dict = immutabledict({"a": "A", "c": "c"}) + def test_union_operator_update_with_dict_first(self) -> None: + first_dict: Dict[str, str] = dict({"a": "a", "b": "b"}) + second_dict: immutabledict[str, str] = immutabledict({"a": "A", "c": "c"}) first_dict |= second_dict assert isinstance(first_dict, dict) @@ -144,8 +175,9 @@ def test_union_operator_update_with_dict(self): } assert second_dict == {"a": "A", "c": "c"} - first_dict = immutabledict({"a": "a", "b": "b"}) - second_dict = dict({"a": "A", "c": "c"}) + def test_union_operator_update_with_dict_second(self) -> None: + first_dict: immutabledict[str, str] = immutabledict({"a": "a", "b": "b"}) + second_dict: Dict[str, str] = dict({"a": "A", "c": "c"}) with pytest.raises(TypeError): first_dict |= second_dict @@ -155,15 +187,15 @@ def test_union_operator_update_with_dict(self): class TestImmutableOrderedDict: - def test_ordered(self): - ordered = ImmutableOrderedDict( + def test_ordered(self) -> None: + ordered: ImmutableOrderedDict[str, str] = ImmutableOrderedDict( { "a": "1", "b": "2", "c": "3", } ) - itered_keys = [x for x in ordered] + itered_keys = list(ordered) assert itered_keys[0] == "a" assert itered_keys[1] == "b" assert itered_keys[2] == "c"