Search code examples
pythondependency-injectiondependenciesfastapi

How can I implement Fastapi like Depends() without using any package or using raw python code?


I want to implement my own Dependency Injection like Fastapi Depends() do actually without using external package or framework. What will be the approach? Code example will be helpful for me. Thanks in advance.

from typing import Callable, Optional, Any

class Depends:
    def __init__(self, dependencies= Optional[Callable[..., Any]]):
        self.dependencies = dependencies
        
        
def get_db():
    pass

    
def get_token():
    pass

def get_current_user(db= Depends(get_db),  token= Depends(get_token)):
    pass

Solution

  • A starting point could be something like this, where we create a decorator that allows us to preempt any calls to the function and resolve any dependencies.

    from typing import Dict, Callable, Any
    from functools import wraps
    import inspect
    
    
    # Our decorator which inspects the function and resolves any
    # dependencies when called
    def resolve_dependencies(func):
        # based on https://stackoverflow.com/a/69170441/
        f_sig = inspect.signature(func)
    
        @wraps(func)
        def resolve_nice_to_have(*args, **kwargs):
            bound = f_sig.bind(*args, **kwargs)
            bound.apply_defaults()
    
            for k, arg in bound.arguments.items():
                if type(arg) == ItWouldBeNice:
                    bound.arguments[k] = arg()
    
            return func(*bound.args, **bound.kwargs)
    
        return resolve_nice_to_have
    
    
    # Our actual dependency wrapper, with a simple cache to avoid
    # invocating an already resolved dependency.
    # Slightly friendlier named than actually depending on something.
    class ItWouldBeNice:
        cache: Dict[Callable, Any] = {}
    
        def __init__(self, dependency: Callable):
            self.dependency = dependency
    
        def __call__(self) -> Any:
            if self.dependency in ItWouldBeNice.cache:
                return ItWouldBeNice.cache[self.dependency]
    
            result = self.dependency()
            ItWouldBeNice.cache[self.dependency] = result
            return result
    

    Example of usage:

    from iwant import ItWouldBeNice, resolve_dependencies
    
    
    def late_eval():
        print("late was called")
        return "static string"
    
    
    @resolve_dependencies
    def i_want_it(s: str = ItWouldBeNice(late_eval)):
        print(s)
    
    
    @resolve_dependencies
    def i_want_it_again(s: str = ItWouldBeNice(late_eval)):
        print(s)
    
    
    i_want_it()
    i_want_it_again()
    

    This doesn't support hierarchical dependencies etc., but should at least illustrate a concept you could apply to do something similar.