@@ -282,21 +282,48 @@ def __call__(self) -> T:
282282 return self ._instance
283283
284284
285- class _AttributeInjection (Generic [T ]):
285+ # NOTE(pyctrl): we MUST inherit `_AttributeInjection` from `property`
286+ # 0. (personal opinion, based on a bunch of cases including this one)
287+ # dataclasses are mess
288+ # 1. dataclasses treat all non-`property` descriptors by the very specific logic
289+ # https://docs.python.org/3/library/dataclasses.html#descriptor-typed-fields
290+ # 2. and treat `property` descriptors in a special way — like we used to know:
291+ # ```
292+ # @dataclass
293+ # class MyDataclass:
294+ # @property
295+ # def my_prop(self) -> int:
296+ # return 42
297+ # MyDataclass.my_prop # gives '<property at 0x73055337f150>' on class
298+ # MyDataclass().my_prop # and on instance will show you '42'
299+ # ```
300+ # it behaves the same in the case of alternative notation:
301+ # ```
302+ # @dataclass
303+ # class MyDataclass2:
304+ # my_prop = property(fget=lambda _: 42)
305+ # MyDataclass2.my_prop # gives '<property at 0x73055337ec00>' on class
306+ # MyDataclass2().my_prop # and on instance will show you '42'
307+ # ```
308+ # which is more relevant to the `inject.attr` case
309+ # 3. but the behavior around `property`-ies has an exception
310+ # - you can't annotate `property` attribute when using the second notation
311+ # (this one `my_prop: int = property(fget=lambda _: 42)` will fail)
312+ # - so the type hinting the very matters
313+ # - in this case dataclasses don't treat class member as property
314+ # (even if it's inherited from `property` or used directly)
315+ # - dataclasses behave greedy when discover their attributes
316+ # and class member annotations are "must have" markers
317+ # 4. so for `inject.attr`s case we should follow 2 rules:
318+ # - `attr` implementation is inherited from `property`
319+ # - `attr` class member is not annotated
320+ class _AttributeInjection (property ):
286321 def __init__ (self , cls : Type [T ] | Hashable ) -> None :
287322 self ._cls = cls
288-
289- @overload
290- def __get__ (self , obj : None , owner : Any ) -> Self : ...
291-
292- @overload
293- def __get__ (self , obj : Hashable , owner : Any ) -> Injectable : ...
294-
295- def __get__ (self , obj , owner ):
296- if obj is None :
297- return self
298-
299- return instance (self ._cls )
323+ super ().__init__ (
324+ fget = lambda _ : instance (self ._cls ),
325+ doc = "Return an attribute injection" ,
326+ )
300327
301328
302329class _ParameterInjection (Generic [T ]):
0 commit comments