Skip to main content

Dependency injection library

Project description

# FunDI

Solution for problem no one had before

Fun stays for function(or for fun if you wish) and DI for Dependency Injection

This library provides fast(to write!) and convenient(to use!) Dependency Injection for functional programming on python.

Why?

This library was inspired by FastAPI's dependency injection. The reasons for its existence are simple:

  • A standalone dependency injection library. DI shouldn't be tied to a specific framework.
  • It simplifies code writing. Dependency injection reduces boilerplate and improves maintainability.
  • Lack of DI libraries for functional programming in Python. Or maybe I just didn't want to find one :3

How?

Main definitions

  • Scope - dependency injection scope (list of root values that will be set to parameters without dependency function)

    Example:

    from contextlib import ExitStack
    
    from fundi import inject, scan
    
    def dependant(value: str):
        print(value)
    
    
    with ExitStack() as stack:
      inject({"value": "Value that will be passed to dependency"}, scan(dependant), stack)
    
  • Dependant - Function that has dependencies (either function, or scope dependencies)

    Example:

    from contextlib import ExitStack
    
    from fundi import from_, inject, scan
    
    class Session:
        pass
    
    
    def string() -> str:
        return "string from dependency"
    
    
    def dependant(session: from_(Session), scope_value: str, dependency_value: str = from_(string)):
        pass
    
    
    with ExitStack() as stack:
        inject({"scope_value": "string from scope", "session": Session()}, scan(dependant), stack)
    
  • Dependency - Function that is used to resolve dependant's parameter value. Dependency can have its own dependencies.

    Example:

    from contextlib import ExitStack
    
    from fundi import from_, inject, scan
    
    class Session:
        pass
    
    
    def string() -> str:
        return "string from dependency"
    
    
    def dependency(session: from_(Session), scope_value: str, dependency_value: str = from_(string)):
        pass
    
    
    def dependant(value: None = from_(dependency)):
        pass
    
    
    with ExitStack() as stack:
        inject({"scope_value": "string from scope", "session": Session()}, scan(dependant), stack)
    

No more words, let's try!

Sync

from contextlib import ExitStack
from typing import Generator, Any

from fundi import from_, inject, scan

def require_database_session(database_url: str) -> Generator[str, Any]:
    print(f"Opened database session at {database_url = }")
    yield "database session"
    print("Closed database session")


def require_user(session: str = from_(require_database_session)) -> str:
    return "user"


def application(user: str = from_(require_user), session: str = from_(require_database_session)):
    print(f"Application started with {user = }")


with ExitStack() as stack:
    inject({"database_url": "postgresql://kuyugama:insecurepassword@localhost:5432/database"}, scan(application), stack)

Async

import asyncio
from contextlib import AsyncExitStack
from typing import AsyncGenerator, Any

from fundi import from_, ainject, scan

async def require_database_session(database_url: str) -> AsyncGenerator[str, Any]:
    print(f"Opened database session at {database_url = }")
    yield "database session"
    print("Closed database session")


async def require_user(session: str = from_(require_database_session)) -> str:
    return "user"


async def application(user: str = from_(require_user), session: str = from_(require_database_session)):
    print(f"Application started with {user = }")


async def main():
    async with AsyncExitStack() as stack:
        await ainject({"database_url": "postgresql://kuyugama:insecurepassword@localhost:5432/database"}, scan(application), stack)


asyncio.run(main())

Resolve scope dependencies by type

It's simple! Use from_ on type annotation

from contextlib import ExitStack

from fundi import from_, inject, scan

class Session:
    """Database session"""

def require_user(_: from_(Session)) -> str:
    return "user"


def application(session: from_(Session), user: str = from_(require_user)):
    print(f"Application started with {user = }")
    print(f"{session = }")


with ExitStack() as stack:
    inject({"db": Session()}, scan(application), stack)

Note: while resolving dependencies by type - parameter names doesn't really matter.

Overriding dependency results

As simple, as injecting!

import time
import random
from contextlib import ExitStack

from fundi import from_, inject, scan


def require_random_animal() -> str:
    time.sleep(3)  # simulate web request

    return random.choice(["cat", "dog", "chicken", "horse", "platypus", "cow"])


def application(animal: str = from_(require_random_animal)):
    print("Animal:", animal)


with ExitStack() as stack:
    inject({}, scan(application), stack, override={require_random_animal: "dog"})

Component explanation:

  • fundi.from_ - Helps define dependencies.

    Use cases:

    • Tell resolver to resolve parameter value by its type, not name (ex: parameter: from_(Session))

    • Define dependency (ex: parameter: type = from_(dependency)).

      In this case it can be replaced with fundi.scan.scan (ex: parameter: type = scan(dependency))

  • fundi.scan - Scans function for dependencies. Returns CallableInfo object that is used by all functions that resolve dependencies.

  • fundi.order - returns order in which dependencies will be resolved

  • fundi.tree - returns dependency resolving tree

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fundi-0.0.6.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fundi-0.0.6-py3-none-any.whl (32.6 kB view details)

Uploaded Python 3

File details

Details for the file fundi-0.0.6.tar.gz.

File metadata

  • Download URL: fundi-0.0.6.tar.gz
  • Upload date:
  • Size: 31.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.26

File hashes

Hashes for fundi-0.0.6.tar.gz
Algorithm Hash digest
SHA256 c8eff79a01c2a76a82b6ebe81c875aca128736f89f1a9541c84aac7fb85042aa
MD5 d0405952405553adedd28cf0b99eab84
BLAKE2b-256 bbbbebb2d005dd4fe5919be197175d7db6cecc96c3cdd3ff848be537729b10e8

See more details on using hashes here.

File details

Details for the file fundi-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: fundi-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 32.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.26

File hashes

Hashes for fundi-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 8680639053868a06f75b07fc76affc526e7936654ed7ffa140af435f6b1fce93
MD5 5c243ebbcc7f85e96b74a6ecc79f3b0f
BLAKE2b-256 d7032c11756411da0615551fc247ffefdc32ff70fb49d0468ad7f2c9692ede2f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page