Skip to content

Commit 8f1473a

Browse files
committed
feat: instanced of subclasses of Immutable are now considered equal if they have the same attributes, in the same order, with the same values
refactor: the return value of `make_immutable` now passes `issubclass(cls, Immutable)` check and has `__eq__` of `Immutable` test: add tests
1 parent f177c09 commit 8f1473a

File tree

8 files changed

+443
-10
lines changed

8 files changed

+443
-10
lines changed

.github/workflows/integration_delivery.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,57 @@ jobs:
9393
- name: Lint
9494
run: poetry run poe lint
9595

96+
test:
97+
name: Test
98+
needs:
99+
- dependencies
100+
runs-on: ubuntu-latest
101+
environment:
102+
name: test
103+
url: https://app.codecov.io/gh/${{ github.repository }}/
104+
steps:
105+
- uses: actions/checkout@v4
106+
name: Checkout
107+
108+
- uses: actions/setup-python@v5
109+
name: Setup Python
110+
with:
111+
python-version: ${{ env.PYTHON_VERSION }}
112+
architecture: x64
113+
114+
- name: Load Cached Poetry
115+
uses: actions/cache/restore@v4
116+
id: cached-poetry
117+
with:
118+
path: |
119+
~/.cache
120+
~/.local
121+
key: poetry-${{ hashFiles('poetry.lock') }}
122+
123+
- name: Run Tests
124+
run: poetry run poe test
125+
126+
- name: Collect Store Snapshots
127+
uses: actions/upload-artifact@v4
128+
if: always()
129+
with:
130+
name: snapshots
131+
path: tests/**/results/**/*.jsonc
132+
133+
- name: Collect HTML Coverage Report
134+
uses: actions/upload-artifact@v4
135+
with:
136+
name: coverage-report
137+
path: htmlcov
138+
139+
- name: Upload Coverage to Codecov
140+
uses: codecov/codecov-action@v4
141+
with:
142+
file: ./coverage.xml
143+
flags: integration
144+
fail_ci_if_error: true
145+
token: ${{ secrets.CODECOV_TOKEN }}
146+
96147
build:
97148
name: Build
98149
needs:
@@ -177,6 +228,7 @@ jobs:
177228
needs:
178229
- type-check
179230
- lint
231+
- test
180232
- build
181233
runs-on: ubuntu-latest
182234
environment:
@@ -207,6 +259,7 @@ jobs:
207259
needs:
208260
- type-check
209261
- lint
262+
- test
210263
- build
211264
- pypi-publish
212265
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## Version 1.1.0
4+
5+
- feat: instanced of subclasses of `Immutable` are now considered equal if they
6+
have the same attributes, in the same order, with the same values
7+
- refactor: the return value of `make_immutable` now passes `issubclass(cls, Immutable)`
8+
check and has `__eq__` of `Immutable`
9+
- test: add tests
10+
311
## Version 1.0.6
412

513
- feat: add `make_immutable` to be on par with `make_dataclass`

immutable/main.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import sys
55
from dataclasses import dataclass, make_dataclass
6-
from typing import Any, Iterable, Mapping, TypeGuard, TypeVar
6+
from typing import Any, Iterable, TypeGuard, TypeVar
77

88
from typing_extensions import dataclass_transform
99

@@ -13,19 +13,34 @@
1313
@dataclass_transform(kw_only_default=True, frozen_default=True)
1414
def immutable(cls: type[_T]) -> type[_T]:
1515
if sys.version_info < (3, 10):
16-
return dataclass(frozen=True)(cls)
17-
return dataclass(frozen=True, kw_only=True)(cls)
16+
return dataclass(frozen=True)(cls) # pragma: no cover
17+
return dataclass(frozen=True, kw_only=True, eq=False, unsafe_hash=True)(cls)
1818

1919

2020
@dataclass_transform(kw_only_default=True, frozen_default=True)
21-
@immutable
2221
class Immutable:
2322
def __init_subclass__(
2423
cls: type[Immutable],
25-
**kwargs: Mapping[str, Any],
24+
*,
25+
_immutable_applied: bool = False,
26+
**kwargs: object,
2627
) -> None:
2728
super().__init_subclass__(**kwargs)
28-
immutable(cls)
29+
if not _immutable_applied:
30+
immutable(cls)
31+
32+
def __eq__(self: Immutable, other: object) -> bool:
33+
if not isinstance(other, Immutable):
34+
return NotImplemented
35+
return tuple(self.__dict__.values()) == tuple(other.__dict__.values())
36+
37+
38+
class _Immutable(Immutable):
39+
def __init_subclass__(
40+
cls: type[_Immutable],
41+
**kwargs: object,
42+
) -> None:
43+
super().__init_subclass__(_immutable_applied=True, **kwargs)
2944

3045

3146
def is_immutable(obj: object) -> TypeGuard[Immutable]:
@@ -41,4 +56,10 @@ def make_immutable(
4156
cls_name: str,
4257
fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]],
4358
) -> type:
44-
return make_dataclass(cls_name, fields, frozen=True, kw_only=True)
59+
return make_dataclass(
60+
cls_name,
61+
fields,
62+
frozen=True,
63+
kw_only=True,
64+
bases=(_Immutable,),
65+
)

poetry.lock

Lines changed: 184 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)