Skip to content

Make Closing marker working with providers that contains a resource as argument #633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kiriharu opened this issue Nov 2, 2022 · 13 comments
Assignees
Labels

Comments

@kiriharu
Copy link

kiriharu commented Nov 2, 2022

Hi!

Is it possible to make the Closing marker work with all types providers that contain a resource inside? For example, in the following code, the Closing marker does not work:

# handlers
@inject
async def create_project(
    request: web.Request,
    project_repository: ProjectRepository = Closing[Provide[Container.project_repository]],
) -> web.Response:
    ....
# di
class Container(containers.DeclarativeContainer):
    db_session = providers.Resource(get_session, database=db)
    project_repository = providers.Factory(
        ProjectRepository,
        session=db_session
    )

At this moment it will work if we add a separate resource argument to handler:

@inject
async def create_project(
    request: web.Request,
    project_repository: Session = Closing[Provide[Container.db_session]],
    project_repository: ProjectRepository = Closing[Provide[Container.project_repository]],
) -> web.Response:
    ....
@kiriharu
Copy link
Author

kiriharu commented Nov 2, 2022

I think following issues have same problems: #475, #455

@kiriharu kiriharu changed the title Make Closing marker working with resources in any providers Make Closing marker working with providers that contains a resource as argument Nov 2, 2022
@StummeJ
Copy link
Contributor

StummeJ commented Nov 15, 2022

I'd like to see this as well. More in general it looks like resources used as a dependency to another provider are not encapsulated with the Closing. I've found that if you include the initial resource in your method you're decorating with @Inject then it does work, but that seems like an unreasonable public interface

@StummeJ
Copy link
Contributor

StummeJ commented Nov 15, 2022

@kiriharu until it's merged, it can be monkey patched with the following code

from typing import Any, Callable
from dependency_injector import providers, wiring
from dependency_injector.wiring import ProvidersMap, _patched_registry, Provide, Provider

# This backports/monkeypatches the dependency injector to allow `Closing` on dependent resources.
# It is extremely important that it is imported BEFORE dependency_injector is used. This can be
# removed once https://github.com/ets-labs/python-dependency-injector/pull/636 is merged and released.

def _locate_dependent_closing_args(provider: providers.Provider) -> dict[str, providers.Provider]:
    if not hasattr(provider, "args"):
        return {}

    closing_deps = {}
    for arg in provider.args:
        if not isinstance(arg, providers.Provider) or not hasattr(arg, "args"):
            continue

        if not arg.args and isinstance(arg, providers.Resource):
            return {str(id(arg)): arg}
        else:
            closing_deps += _locate_dependent_closing_args(arg)
    return closing_deps


def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
    patched_callable = _patched_registry.get_callable(fn)
    if patched_callable is None:
        return

    for injection, marker in patched_callable.reference_injections.items():
        provider = providers_map.resolve_provider(marker.provider, marker.modifier)

        if provider is None:
            continue

        if isinstance(marker, Provide):
            patched_callable.add_injection(injection, provider)
        elif isinstance(marker, Provider):
            if isinstance(provider, providers.Delegate):
                patched_callable.add_injection(injection, provider)
            else:
                patched_callable.add_injection(injection, provider.provider)

        if injection in patched_callable.reference_closing:
            patched_callable.add_closing(injection, provider)
            deps = _locate_dependent_closing_args(provider)
            for key, dep in deps.items():
                patched_callable.add_closing(key, dep)

wiring._bind_injections = _bind_injections

@rmk135 rmk135 self-assigned this Dec 19, 2022
@rmk135 rmk135 added the feature label Dec 19, 2022
@rmk135
Copy link
Member

rmk135 commented Dec 19, 2022

Amazing feature! Thanks to everybody involved and special thanks to @StummeJ for the PR!

@rmk135
Copy link
Member

rmk135 commented Dec 19, 2022

Published on PyPI in 4.41.0.

@federinik
Copy link
Contributor

Hi @StummeJ @rmk135,

I would really like to use this new feature in my project, but I inject everything as kwargs and they seemd to be ignored, so I made a small change to the new feature to cover this case too, would you mind reviewing it? (Sorry for the double PR, I used a wrong GitHub account and had to remake everything from the beginning).

There's just a little problem: I wasn't able to run tests on my changes (despite I wrote them) and I didn't find any documentation on how to run them, could you lend me a hand on this?

Thank you

@federinik
Copy link
Contributor

Hi @StummeJ @rmk135,

any news on my previous message?

@Stoom
Copy link

Stoom commented Jan 9, 2023

@federinik I think I just used the pytest command. In any regards, I use VSCode with the python test runner in there too.

It also looks like there's a tox.ini file with pytest. Check out Tox

@federinik
Copy link
Contributor

federinik commented Jan 10, 2023

Hi @Stoom,

I can confirm I've been able to run tests via tox (while I was having some issues with raw pytest), thank you for the hint!

All tests passed btw

@federinik
Copy link
Contributor

Hi @rmk135,

any news? I really, REALLY need this in my project! 😄

Thx

@rmk135
Copy link
Member

rmk135 commented Jan 18, 2023

Hey @federinik,

Does the PR that you provided above contain complete implementation of what you need?

@federinik
Copy link
Contributor

Hi @rmk135,

yes, I should be fine with the PR content, as you can see it was just a small detail, while the core of the feature was kinda complete.

Thank you

@federinik
Copy link
Contributor

Hi @rmk135,
it's been a month since I opened the PR, it's not even this big or complicated (tbh, the current version is broken, being that += is not a valid operator between dictionaries, so it even fixes a bug). Is there any actual, real problem with my changes? Is there something that you don't like or that bothers you in any way?

If yes, please tell me what to update the PR.

If no, please either merge it or tell me you won't and what's the reason.

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants