ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.86k stars 303 forks source link

traverse visits providers in a random order #780

Open BEllis opened 8 months ago

BEllis commented 8 months ago

When container.init_resources() is called, it is expected that resources are loaded in the same order every time (and ideally in a way that can be controlled, but that is outside the scope of this bug).

What actually happens is the resources are initialized in a random order each time an application is started.

This is due to def traverse(...) using set() to iterate over the providers, this results in a random start-up order every time an app is restarted.

Proposed short-term solution is to init providers in the order they are defined in the container. Proposed long-term solution (separate ticket / feature request) is that providers can be given a "priority" or "order" to indicate precedence.

BEllis commented 8 months ago

I've been using this as a work around, I'll look at raising a PR when I get some time.

    def traverse(
        *target_providers: Provider[Any],
        types: Iterable[type] | None = None,
    ) -> Iterator[Provider[Any]]:
        """Return providers traversal generator."""
        visited = set()

        providers_ = [*target_providers]

        if types:
            types_tuple: tuple[type, ...] = tuple[type, ...](types)
        else:
            types_tuple = tuple[type, ...]()

        for provider in providers_:
            if provider in visited:
                continue

            visited.add(provider)

            for child in provider.related:
                if child in visited:
                    continue
                providers_.append(child)

            if types and not isinstance(provider, types_tuple):
                continue

            yield provider