11from __future__ import annotations
22
3+ import copy
34from dataclasses import dataclass
45from dataclasses import field as dataclass_field
56from types import UnionType
910 Generic ,
1011 Self ,
1112 TypeVar ,
12- cast ,
1313 dataclass_transform ,
1414 get_type_hints ,
15+ overload ,
1516)
1617
1718from statica .config import StaticaConfig , default_config
@@ -76,6 +77,7 @@ class User(Statica):
7677
7778 # User-facing dataclass fields
7879
80+ default : T | Any | None = None
7981 min_length : int | None = None
8082 max_length : int | None = None
8183 min_value : float | None = None
@@ -117,6 +119,14 @@ def get_statica_subclass(self, sub_types: tuple[type, ...]) -> type[Statica] | N
117119 pass
118120 return None
119121
122+ def get_default_safe (self ) -> Any :
123+ """
124+ Get the default value of the field, safely handling mutable defaults.
125+ """
126+ if isinstance (self .default , (list , dict , set )):
127+ return copy .copy (self .default )
128+ return self .default
129+
120130 def __get__ (self , instance : object | None , owner : Any ) -> Any :
121131 """
122132 Get the value of the field from the instance.
@@ -200,8 +210,36 @@ def get_field_descriptors(cls: type[Statica]) -> list[FieldDescriptor]:
200210#### MARK: Type-safe field function
201211
202212
213+ @overload
214+ def Field (
215+ * ,
216+ default : T , # If default is used, the return type is T
217+ min_length : int | None = None ,
218+ max_length : int | None = None ,
219+ min_value : float | None = None ,
220+ max_value : float | None = None ,
221+ strip_whitespace : bool | None = None ,
222+ cast_to : Callable [..., T ] | None = None ,
223+ alias : str | None = None ,
224+ ) -> T : ...
225+
226+
227+ @overload
228+ def Field (
229+ * , # No default provided, return type is Any
230+ min_length : int | None = None ,
231+ max_length : int | None = None ,
232+ min_value : float | None = None ,
233+ max_value : float | None = None ,
234+ strip_whitespace : bool | None = None ,
235+ cast_to : Callable [..., T ] | None = None ,
236+ alias : str | None = None ,
237+ ) -> Any : ...
238+
239+
203240def Field ( # noqa: N802
204241 * ,
242+ default : T | Any | None = None ,
205243 min_length : int | None = None ,
206244 max_length : int | None = None ,
207245 min_value : float | None = None ,
@@ -211,11 +249,14 @@ def Field( # noqa: N802
211249 alias : str | None = None ,
212250) -> Any :
213251 """
214- Type-safe field function that returns the correct type for type checkers
215- but creates a Field descriptor at runtime.
216- """
252+ Type-safe field function that provides proper type checking for default values
253+ while creating a FieldDescriptor at runtime.
217254
218- fd = FieldDescriptor (
255+ When a default value is provided, the return type matches the default's type.
256+ This prevents type mismatches like: active: bool = Field(default="yes")
257+ """
258+ return FieldDescriptor (
259+ default = default ,
219260 min_length = min_length ,
220261 max_length = max_length ,
221262 min_value = min_value ,
@@ -225,11 +266,6 @@ def Field( # noqa: N802
225266 alias = alias ,
226267 )
227268
228- if TYPE_CHECKING :
229- return cast ("Any" , fd )
230-
231- return fd # type: ignore[unreachable]
232-
233269
234270########################################################################################
235271#### MARK: Internal metaclass
@@ -265,7 +301,14 @@ def __new__(
265301
266302 def statica_init (self : Statica , ** kwargs : Any ) -> None :
267303 for field_name in annotations :
268- setattr (self , field_name , kwargs .get (field_name ))
304+ field_descriptor = namespace .get (field_name )
305+ assert isinstance (field_descriptor , FieldDescriptor )
306+
307+ # Use default value if key is missing and default is available
308+ if field_name not in kwargs and field_descriptor .default is not None :
309+ setattr (self , field_name , field_descriptor .get_default_safe ())
310+ else :
311+ setattr (self , field_name , kwargs .get (field_name ))
269312
270313 namespace ["__init__" ] = statica_init
271314
@@ -280,8 +323,8 @@ def statica_init(self: Statica, **kwargs: Any) -> None:
280323 continue
281324
282325 # Case 3: name: str (no assignment) or name: Field[str] (no assignment)
283- # Create a default Field descriptor
284- namespace [attr_annotated ] = FieldDescriptor ()
326+ # Create a Field descriptor with the default if it exists
327+ namespace [attr_annotated ] = FieldDescriptor (default = namespace . get ( attr_annotated ) )
285328
286329 return super ().__new__ (cls , name , bases , namespace )
287330
@@ -293,20 +336,16 @@ def statica_init(self: Statica, **kwargs: Any) -> None:
293336class Statica (metaclass = StaticaMeta ):
294337 @classmethod
295338 def from_map (cls , mapping : Mapping [str , Any ]) -> Self :
296- # Fields might have aliases, so we need to map them correctly.
297- # Here we map the chosen alias to the original field name.
298- # If no alias is provided, we use the field name itself.
299- # Priority: parsing alias > general alias > field name
300- mapping_key_to_field_keys = {}
339+ # Fields might have aliases, so we need to map them correctly
301340
341+ kwargs = {}
302342 for field_descriptor in get_field_descriptors (cls ):
303- # Use alias for parsing if it exists
304- alias = field_descriptor .alias or field_descriptor .name
305- mapping_key_to_field_keys [alias ] = field_descriptor .name
343+ expected_field_name = field_descriptor .alias or field_descriptor .name
306344
307- parsed_mapping = {mapping_key_to_field_keys [k ]: v for k , v in mapping .items ()}
345+ if expected_field_name in mapping :
346+ kwargs [field_descriptor .name ] = mapping [expected_field_name ]
308347
309- return cls (** parsed_mapping ) # Init function will validate fields
348+ return cls (** kwargs ) # Init function will validate fields and set defaults
310349
311350 def to_dict (self , * , with_aliases : bool = True ) -> dict [str , Any ]:
312351 """
0 commit comments