Skip to content

Commit

Permalink
inject services into services
Browse files Browse the repository at this point in the history
  • Loading branch information
CameronSima committed Aug 28, 2024
1 parent 4810ce3 commit e826314
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 12 deletions.
25 changes: 21 additions & 4 deletions src/ziplineio/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ast import Not
import inspect
from typing import Any, Callable, List, Tuple, Type


Expand Down Expand Up @@ -38,19 +39,36 @@ def delete(self, path: str) -> Callable[[Handler], Callable]:
return self._router.delete(path)

def get_handler(self, method: str, path: str) -> Tuple[Handler, dict]:
app_level_deps = self._injector._injected_services["app"]
app_level_deps = self._injector.get_injected_services("app")
handler, params = self._router.get_handler(method, path)

if handler is None:
return None, {}

# TODO: Inject deps when added to the router, not here
# inject app-level dependencies into the handler
# inject app-level dependencies into the handler if the
# handler expects them in its signature
sig = inspect.signature(handler)

filtered_kwargs_names = [
name
for name, param in sig.parameters.items()
if param.default == inspect.Parameter.empty
]

for name, service in app_level_deps.items():
handler = inject(service, name)(handler)
if name in filtered_kwargs_names:
handler = inject(service, name)(handler)

return handler, params

def inject(
self, service_class: Type, name: str = None
) -> Callable[[Callable], Callable]:
if isinstance(service_class, list):
for service in service_class:
self._injector.add_injected_service(service, name, "app")
return None
return self._injector.add_injected_service(service_class, name, "app")

def middleware(self, middlewares: List[Callable]) -> None:
Expand All @@ -68,7 +86,6 @@ async def uvicorn_handler(scope: dict, receive: Any, send: Any) -> None:

if handler is None:
# try running through middlewares

req, ctx, res = await run_middleware_stack(
self._router._router_level_middelwares, req, **{}
)
Expand Down
40 changes: 32 additions & 8 deletions src/ziplineio/dependency_injector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any, Callable
from ziplineio.request import Request
from ziplineio.service import Service, is_service_class


# Injected services is a dictionary that stores the services that are injected.
Expand Down Expand Up @@ -50,15 +51,38 @@ def add_injected_service(

services = self._injected_services[scope]

if service_name in services:
instance = services[service_name]
elif isinstance(service_class, type):
instance = service_class()
else:
instance = service_class
# Check if the service class is a subclass of `Service`
if is_service_class(service_class):
services_in_scope = self.get_injected_services(scope)

# Prepare dependencies for the current service instance
service_kwargs = {
name: service
for name, service in services_in_scope.items()
if isinstance(service, Service)
}

# Create a new instance of the service
instance = service_class(**service_kwargs)
services[service_name] = instance

services[service_name] = instance
return instance, service_name
# Inject this service instance into all other services within the scope
for _name, service in services_in_scope.items():
if isinstance(service, Service):
setattr(service, service_name, instance)

return instance, service_name
else:
# Handle services that are not subclasses of `Service`
if service_name in services:
instance = services[service_name]
elif isinstance(service_class, type):
instance = service_class()
else:
instance = service_class

services[service_name] = instance
return instance, service_name

def get_injected_services(self, scope: str = "func") -> dict[str, Any]:
if scope not in self._injected_services:
Expand Down
6 changes: 6 additions & 0 deletions src/ziplineio/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Service:
pass


def is_service_class(service_class):
return isinstance(service_class, type) and issubclass(service_class, Service)
30 changes: 30 additions & 0 deletions test/test_dependency_injection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from operator import call
from os import name
import unittest

from ziplineio.app import App
from ziplineio.router import Router
from ziplineio.dependency_injector import DependencyInjector, inject
import ziplineio.dependency_injector
from ziplineio.service import Service
from ziplineio.utils import call_handler


class MockService:
Expand Down Expand Up @@ -122,3 +126,29 @@ async def test_handler(req, service: Service):
handler, params = router.get_handler("GET", "/")
response = await handler({})
self.assertEqual(response["message"], "Service")

async def test_inject_service_into_service(self):
# Define a service to inject
class Service1(Service):
def __init__(self, **kwargs):
self.name = "service1"
self.value = "Service 1"

# Define a service that depends on Service1
class Service2(Service):
def __init__(self, service1: Service1):
self.name = "service2"
self.value = service1.value

self.ziplineio.inject([Service1, Service2])

@self.ziplineio.get("/")
async def test_handler(req, service1: Service1):
return {"message": service1.value}

# Call the handler
handler, params = self.ziplineio.get_handler("GET", "/")
print(self.ziplineio._injector._injected_services)
response = await call_handler(handler, {})
print(response)
self.assertEqual(response["message"], "Service 1")

0 comments on commit e826314

Please sign in to comment.