ets-labs/python-dependency-injector

[BUG] wire() fails when module imports a mutable Pydantic dataclass with pydantic @classmethod @field_validator

Closed this issue · 5 comments

MCVE

  1. models.py (Defines the pydantic dataclass)
from pydantic import field_validator
from pydantic.dataclasses import dataclass

@dataclass(slots=True)
class Data:
    a: str

    @classmethod
    @field_validator("a")
    def check_module_id(cls, v):
        ...
  1. services.py (Defines the wired service module, importing the Pydantic model)
from dependency_injector.wiring import inject, Provide
from .container import Container
from .models import ProblematicModel # <--- Key: Pydantic model is directly imported

@inject
def my_example_service(
    dummy_config_value=Provide[Container.config.some_setting]
):
    ...
  1. main.py
from .container import Container
from . import services # Import the services module

if __name__ == "__main__":
    container = Container()
    container.wire(modules=["services"]  # <-- raise

Expected Behavior

Traceback (most recent call last):
  File "/Users/colas/Work/infra/case/case/main.py", line 5, in <module>
    containers.wire(
    ~~~~~~~~~~~~~~~^
    modules=["service"]
    ^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "src/dependency_injector/containers.pyx", line 315, in dependency_injector.containers.DynamicContainer.wire
  File "/Users/colas/Work/infra/case/.venv/lib/python3.13/site-packages/dependency_injector/wiring.py", line 454, in wire
    _patch_method(
    ~~~~~~~~~~~~~^
        cls, cls_member_name, cls_member, providers_map
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/colas/Work/infra/case/.venv/lib/python3.13/site-packages/dependency_injector/wiring.py", line 532, in _patch_method
    if not _is_patched(fn):
           ~~~~~~~~~~~^^^^
  File "/Users/colas/Work/infra/case/.venv/lib/python3.13/site-packages/dependency_injector/wiring.py", line 742, in _is_patched
    return _patched_registry.has_callable(fn)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^
  File "/Users/colas/Work/infra/case/.venv/lib/python3.13/site-packages/dependency_injector/wiring.py", line 130, in has_callable
    return fn in self._callables
           ^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'PydanticDescriptorProxy

Environment

  • python: 3.13
  • pydantic 2.11.4
  • pydantic-core 2.33.2
  • dependency-injector 4.46.0

I'm not sure if this is a bug, because the way you define field validator is not how Pydantic expects field validators to be defined.

So fix for your issue is either:

  1. Swap @classmethod and @field_validator.
  2. Remove @classmethod altogether.
  3. Use new Annotated-based way of defining field validators.

I'm not sure if this is a bug, because the way you define field validator is not how Pydantic expects field validators to be defined.

So fix for your issue is either:

  1. Swap @classmethod and @field_validator.
  2. Remove @classmethod altogether.
  3. Use new Annotated-based way of defining field validators.

Alright, that was my mistake. Before using DI, the code worked fine. Now, with @field_validator and @classmethod in the right order, everything is running smoothly.

Thanks for your help. This framework has been incredibly helpful for me.

Before using DI, the code worked fine.

I strongly suggest to verify if it did. If you put @classmethod at the top, Pydantic will ignore your field validator. Here's the code to verify:

Code
@dataclass(slots=True)
class Foo:
    a: str

    @classmethod
    @field_validator("a")
    def check_module_id(cls, v):
        raise ValueError("nope")

Foo(a="test")  # nothing happens

vs

@dataclass(slots=True)
class Foo:
    a: str

    @field_validator("a")
    @classmethod
    def check_module_id(cls, v):
        raise ValueError("nope")

Foo(a="test")  # raises ValidationError, as expected

在使用 DI 之前,代码运行良好。

我强烈建议您验证一下是否有效。如果您将其放在@classmethod顶部,Pydantic 将忽略您的字段验证器。以下是验证代码:

代码
@DataClass(slots=True)
class Foo:
a: str

@classmethod
@field_validator("a")
def check_module_id(cls, v):
    raise ValueError("nope")

Foo(a="test") # nothing happens
对比

@DataClass(slots=True)
class Foo:
a: str

@field_validator("a")
@classmethod
def check_module_id(cls, v):
    raise ValueError("nope")

Foo(a="test") # raises ValidationError, as expected

The bad news is it seems I've been using invalid validators all along🫠. Thanks for pointing that out.

it's great help!!!