GithubHelp home page GithubHelp logo

Comments (4)

simonw avatar simonw commented on May 29, 2024

This would need to have a separate registry object. Here's a potential design:

# Register three functions
registry = AsyncRegistry(func1, func2, func3)
# Add another function later on
registry.register(func4)
# Resolve the result:
result = await registry.resolve(func1, arg=x)

That .resolve() method could accept either a function that has already been registered OR a string function name.

from asyncinject.

simonw avatar simonw commented on May 29, 2024

Since functions are no longer being directly coupled to one particular registry (in their class) I could skip the bit where the method itself is rewritten by the decorator to work magically. That's actually potential usability improvement since it's less surprising.

from asyncinject.

simonw avatar simonw commented on May 29, 2024

Initial rough prototype:

from functools import wraps
import inspect
import graphlib
import asyncio

class AsyncRegistry:
    def __init__(self, *fns):
        self._registry = {}
        self._graph = None
        for fn in fns:
            self.register(fn)
    
    def register(self, fn):
        self._registry[fn.__name__] = fn
        # Clear _graph cache:
        self._graph = None
    
    @property
    def graph(self):
        if self._graph is None:
            self._graph = {
                key: {
                    p
                    for p in inspect.signature(fn).parameters.keys()
                    if not p.startswith("_")
                }
                for key, fn in self._registry.items()
            }
        return self._graph

    async def resolve(self, fn):
        try:
            name = fn.__name__
        except AttributeError:
            name = fn
        return (await self.resolve_multi([name]))[name]
    
    async def resolve_multi(self, names, results=None):
        if results is None:
            results = {}

        # Come up with an execution plan, just for these nodes
        ts = graphlib.TopologicalSorter()
        to_do = set(names)
        done = set()
        while to_do:
            item = to_do.pop()
            dependencies = self.graph.get(item) or set()
            ts.add(item, *dependencies)
            done.add(item)
            # Add any not-done dependencies to the queue
            to_do.update({k for k in dependencies if k not in done})

        ts.prepare()
        plan = []
        while ts.is_active():
            node_group = ts.get_ready()
            plan.append(node_group)
            ts.done(*node_group)

        #instance._log(
        #    "Resolving {} in {}>".format(names, repr(instance).split(" object at ")[0])
        #)

        for node_group in plan:
            awaitable_names = [name for name in node_group if name in self._registry]
            # instance._log("  Run {}".format(awaitable_names))
            awaitables = [
                self._registry[name](
                    **{k: v for k, v in results.items() if k in self.graph[name]},
                )
                for name in awaitable_names
            ]
            awaitable_results = await asyncio.gather(*awaitables)
            results.update(dict(zip(awaitable_names, awaitable_results)))

        print("results:", results)
        return results

    
def _make_fn(fn, registry):
    parameters = inspect.signature(fn).parameters

    @wraps(fn)
    async def inner(**kwargs):
        # Any parameters not provided by kwargs are resolved from registry
        to_resolve = [
            p
            for p in parameters
            # Not already provided
            if p not in kwargs
            # Doesn't have a default value
            and parameters[p].default is inspect._empty
        ]
        missing = [p for p in to_resolve if p not in registry]
        assert (
            not missing
        ), "The following DI parameters could not be found in the registry: {}".format(
            missing
        )

        results = {}
        results.update(kwargs)
        if to_resolve:
            resolved_parameters = await resolve(registry, to_resolve, results)
            results.update(resolved_parameters)
        return await method(
            self, **{k: v for k, v in results.items() if k in parameters}
        )

    return inner

from asyncinject.

simonw avatar simonw commented on May 29, 2024

Got this working in a branch. Updated documentation is here: https://github.com/simonw/asyncinject/blob/ed8fbbdef513ff8385f91cc345226439deab6515/README.md

from asyncinject.

Related Issues (14)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.