proboscis / pinjected

MIT License
10 stars 0 forks source link

Resource Cleanup Feature #20

Closed proboscis closed 2 months ago

proboscis commented 4 months ago

We need to be able to register deconstructors.

proboscis commented 4 months ago

We can have "__del__" suffix to specify that the binding is a destructor. And then finally we can inspect the values in graph and gather all __del__ instances and await it.

proboscis commented 4 months ago

Another option would be to make design hold bindings with destructors. This is less hacky

proboscis commented 4 months ago

Adding a destructor to IBind is possible. But, what do you do with the Injected?

proboscis commented 4 months ago

We need to accumulate the destructors and call them appropriately at the end.

proboscis commented 4 months ago

This changes the semantics of provider function to return a destructor as well.

proboscis commented 4 months ago
class IResource(Generic[T], ABC):
    value: T

    async def close(self):
        pass

class IBind(Generic[T], ABC):
    """
    hold a provider and metadata
    """

    @abc.abstractmethod
    async def provide(self, cxt: ProvideContext, deps: Dict[IBindKey, Any]) -> IResource[T]:
        pass
proboscis commented 4 months ago

I realized that an IBind would return an arbitrary functor object rather than IResource[T]. It can be Future[T] or anything. Our IBind is async, this means it's already holding Awaitable as the functor. If we add IResource, then this IBind becomes

IBind[IResource[Awaitable[T]]]

This means we can define the IBind to be

class IBind(Generic[M,T],ABC):
    monad:Monad[M]
    def provide(...)->M[T]
proboscis commented 4 months ago

This way, we can add arbitrary monadic context to the resource without modifying IBind class. While IBind itself is a Monad (called Reader Monad), what we are doing is actually building the tree of Monad: IBind[Awaitable[IResource[T]]]

proboscis commented 4 months ago

Actually, IBind cannot be a monad since dependencies cannot be known until runtime.

proboscis commented 4 months ago

Now, how do I eval the tree of Awaitable[Resource]?

proboscis commented 4 months ago

Just do

res = await eval(target)
... do anything with the res.value
await res.close
proboscis commented 4 months ago

Now, how should an Injected hold a destructor?

proboscis commented 4 months ago

I think I should separate the implementation of injected

proboscis commented 4 months ago

So,, adding resource cleaner to IBind and Injected involves too much work.

Currently, there are several kind of resources.

  1. Resources that are created by an injected function, created multiple times during session.
  2. Resources that are created only once in a session, such as logger.

We aim to support cleaning the resources for case 2. Such services are often defined in global design definition. The question now is, how to register the destructor to a IBindKey.

proboscis commented 4 months ago

Examples:

# case1
design = providers(
resource = get_resource
resource__del__ = destruct_resource # async def resource__del__(dep,/,tgt)
) 

#case 2:
design = providers(
resource = closable(
    constructor=get_resource,
    destructor=destruct_resource
)
)

#case3:
design = providers(
    resource = get_resource
) + destructors(
    resource = destruct_resource
)
proboscis commented 4 months ago
  1. is cryptic and hard to remember.
  2. is easier to manage but hard to remember
  3. is easy to remember
proboscis commented 4 months ago

lets go from 3 to 2.