8
8
from __future__ import annotations
9
9
10
10
import dataclasses
11
+ import functools
11
12
import typing as t
12
13
13
14
from typing_extensions import dataclass_transform
@@ -38,10 +39,15 @@ def frozen_dataclass(cls: type[_T]) -> type[_T]:
38
39
# A. Convert to a dataclass with frozen=False
39
40
cls = dataclasses .dataclass (cls )
40
41
42
+ # B. Explicitly annotate and initialize the `_frozen` attribute for static analysis
43
+ cls .__annotations__ ["_frozen" ] = bool
44
+ setattr (cls , "_frozen" , False )
45
+
41
46
# Save the original __init__ to use in our hooks
42
47
original_init = cls .__init__
43
48
44
49
# C. Create a new __init__ that will call the original and then set _frozen flag
50
+ @functools .wraps (original_init )
45
51
def __init__ (self : t .Any , * args : t .Any , ** kwargs : t .Any ) -> None :
46
52
# Call the original __init__
47
53
original_init (self , * args , ** kwargs )
@@ -52,6 +58,7 @@ def __init__(self: t.Any, *args: t.Any, **kwargs: t.Any) -> None:
52
58
def __setattr__ (self : t .Any , name : str , value : t .Any ) -> None :
53
59
# If _frozen is set and we're trying to set a field, block it
54
60
if getattr (self , "_frozen" , False ) and not name .startswith ("_" ):
61
+ # Allow mutation of private (_-prefixed) attributes after initialization
55
62
error_msg = f"{ cls .__name__ } is immutable: cannot modify field '{ name } '"
56
63
raise AttributeError (error_msg )
57
64
@@ -68,10 +75,9 @@ def __delattr__(self: t.Any, name: str) -> None:
68
75
# Allow the deletion
69
76
object .__delattr__ (self , name )
70
77
71
- # F. Inject into the class
72
- # Add type ignore directives to silence mypy "Cannot assign to a method" errors
73
- cls .__init__ = __init__ # type: ignore[method-assign]
74
- cls .__setattr__ = __setattr__ # type: ignore[method-assign]
75
- cls .__delattr__ = __delattr__ # type: ignore[method-assign]
78
+ # F. Inject into the class using setattr to avoid explicit mypy type ignores
79
+ setattr (cls , "__init__" , __init__ )
80
+ setattr (cls , "__setattr__" , __setattr__ )
81
+ setattr (cls , "__delattr__" , __delattr__ )
76
82
77
83
return cls
0 commit comments