Skip to content
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

Pass exception to generators #113

Closed
Tishka17 opened this issue Mar 27, 2024 · 3 comments
Closed

Pass exception to generators #113

Tishka17 opened this issue Mar 27, 2024 · 3 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@Tishka17
Copy link
Collaborator

When we finalize dependencies we have no information if there were exception during process handling.

We can use send or asend to pass it without breaking compatibility.

Also we will need to add an optional parameter to close method if Container object and modify it's __exit__

@Tishka17 Tishka17 added the enhancement New feature or request label Mar 27, 2024
@lubaskinc0de
Copy link
Contributor

lubaskinc0de commented Mar 27, 2024

i am try to work on this and will post my progress here.

@Tishka17 Tishka17 added this to the 1.1 milestone Mar 27, 2024
@lubaskinc0de
Copy link
Contributor

Problem Description:

We have a generator function that provides a dependency at the beginning of the request lifecycle and finalizes it at the end of the request lifecycle.

Inside the request lifecycle e.g:

with container() as request_container:
    ...

An exception may occur, the dependency is finalized anyway, but we also want to give it information about the exception that occurred by sending its object to the generator using the .send() and .asend() methods for asynchronous generators.

The code

provider = Provider()


class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __str__(self) -> str:
        return f"I'm {self.name}, my age is: {self.age}"


def get_person() -> Iterator[Person]:
    print("dependency initialization")

    x = yield Person("alex", 18)
    if isinstance(x, Exception):
        print("Some exception occurred:", x)

    print("dependency finalization")


provider.provide(get_person, scope=Scope.REQUEST)
container = make_container(provider)

with container() as request_container:
    person = request_container.get(Person)
    print("Hello!", person)
    raise ValueError("Some error in request scope!")

Expected output

dependency initialization
Hello! Im alex, my age is: 18
Some exception occurred: Some error in request scope!
dependency finalization

<Traceback here>

Current output

dependency initialization
Hello! Im alex, my age is: 18
dependency finalization

<Traceback here>

Correct me if I'm wrong.

@lubaskinc0de
Copy link
Contributor

Implementation of this functionality

Where do we need to make changes?

In the case of REQUEST scope, the finalization of generators is handled by the Container.close method, which in this case is called by the __exit__ method of the ContextWrapper class.

Accordingly, we need to change __exit__ and .close() in the synchronous and asynchronous versions of Container respectively.

What can we use to implement this functionality?

We can easily see if an exception has occurred in the context manager block, because __exit__ takes information about the exception that occurred

def __exit__(self, exc_type, exc_val, exc_tb):

Where exc_val is the instance of the exception class that occurred, and I guess that's what we need to pass to the generator function.

The finalization of the generators is handled by the .close() method of the container, it is called by __exit__() of the ContextWrapper, here is how the finalization itself happens:

    def close(self) -> None:
        errors = []
        for exit_generator in self._exits[:::-1]:
            try:
                if exit_generator.type is FactoryType.GENERATOR:
                    next(exit_generator.callable)

We could add an Optional parameter to the .close() method that defaults to None, the signature would look like this, and it wouldn't break backwards compatibility:

def close(self, exception: Optional[Exception] = None) -> None:

Next, we just check for the argument in the loop and call .send()

                if exit_generator.type is FactoryType.GENERATOR:
                    if not exception:
                        next(exit_generator.callable)
                    else:
                        exit_generator.callable.send(exception)

The same is for the asynchronous version of Container, respectively.

Conclusion

If you have no objections, I'll start to get down to implementation.

lubaskinc0de added a commit to lubaskinc0de/dishka that referenced this issue Mar 28, 2024
Tishka17 added a commit that referenced this issue Apr 8, 2024
Implement pass exception to generators #113
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants