Open vuhi opened 3 years ago
Hey @vuhi ,
Yeah, that's a relevant problem. I think that I didn't hit it in the example cause I didn't use any models. I recall that django models can't be imported before django intialization is finished.
This definitely needs to have a solution.
I don't know if you familiar with ReactiveX
No, I'm not familiar with it. I'll take a look for sure.
Also I'll take a look on writing a custom app that could twist in django and dependency injector initialization properly.
Huge thanks for bringing this up!
Hi, @rmk135
I finally make it works. I am pretty new to python & Django. I am not sure how Django handle import models & stuff. Here is my work around:
I create an django app call ioc
. It contains a file to declare container class, in my case is: di_containers.py
.
> experiment (view app)
> __init__.py
> apps.py ( wiring dependencies here)
> urls.py
> views.py
> core
> db
> user.py (just a way to separate model class to each file)
> book.py
> models.py ( important, all models need to import back to this file)
> apps.py ( optionals, can be use to wire dependencies as old example )
> ...
> ioc
> __init__.py ( optionals, can put anything in here )
> apps.py ( optionals )
> di_containers.py ( important, declare container class & initialize it
ioc/di_containers.py
from path.to.import.model import User <-- test if we could import model
class DIContainer(containers.DeclarativeContainer):
config = providers.Configuration()
google_oauth = providers.Factory(GoogleOAuth)
facebook_oauth = providers.Factory(FaceBookOAuth)
auth_service = providers.Factory(AuthService)
token_service = providers.Factory(AccessToken)
user_service = providers.Singleton(User) # <-- test if we could use model
di_container: DIContainer = DIContainer()
di_container.config.from_dict({'CONFIG': settings.CONFIG})
...
I register the app at the end of INSTALLED_APPS
list. All of my model I put in a separate django app call core
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api_core.apps.core', <-- contains all models
'api_core.apps.experiment', <-- no model just view
...
'api_core.apps.ioc', <-- at the end
]
experiment/apps.py
def ready(self):
from . import views
from path.to.ioc.di_containers import di_container
# very important step to inject the dependencies in DI to each module
# wire will automatically inject dependencies to @inject
di_container.wire(modules=[views])
]
experiment/views.py
@api_view(['GET'])
@authentication_classes([JWTTokenAuthentication])
@permission_classes([IsAuthenticated])
@inject
def protected_ping(request: Request, user_service: User = Provide['user_service']):
print(user_service.__class__.service.all()[0]) -> success print to console
user = {'id': request.user.id, 'email': request.user.email}
return SuccessRes('Yeah!!. This route was protected behind jwt token!', {'user': user})
I don't understand how the wire process work. I do have a question on that.
What if I override a service
inside the container in one of the view some time after the wiring process (which happen once in apps.py
)
For example:
user_service
with different model like SuperUseruser_service
with the new model even though I only call di_container.wire(modules=[views])
once in experiment/apps.py
?Thank you,
Thanks, @vuhi.
I got here because I was having the same issue. Your fix worked for me. Is there anybody working on updating the docs?
In my latest experience there is no need to put all the models to an core app etc. The problematic part from the Django example seems to be this portion:
# githubnavigator/__init__.py
from .containers import Container # <--- here is the problematic part if containers.py imports/references models
from . import settings
container = Container()
container.config.from_dict(settings.__dict__)
Because the __init__.py
code is called early you cannot reference/import models inside githubnavigator/containers.py
. But if you move the code snippet from the __init__.py
file to web/apps.py
and githubnavigator/containers.py
then you have the chance to influence the point in time when the container and all its dependencies are created and then you can use Djangos def ready()
(Link) override to import everything (especiallly if it is related to models):
# web/apps.py
from django.apps import AppConfig
from githubnavigator import container
class WebConfig(AppConfig):
name = "web"
def ready(self):
from githubnavigator import container # <-- import the container here so further imports inside container.py like models can succeed
container.wire(modules=[".views"])
# githubnavigator/containers.py
from dependency_injector import containers, providers
from github import Github
from . import services
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
github_client = providers.Factory(
Github,
login_or_token=config.GITHUB_TOKEN,
timeout=config.GITHUB_REQUEST_TIMEOUT,
)
search_service = providers.Factory(
services.SearchService,
github_client=github_client,
)
container = Container() # <-- create the container here
container.config.from_dict(settings.__dict__)
Disclaimer: I am still learning Django and I am still bootstrapping my project so I can't tell something about long term issues with this approach but so far this was what made my project work again.
@rmk135 Can you confirm that and maybe change the Django example?
Hi,
Thank you so much for a wonderful project. I am using dependency injection in a Django side project of mine.
I notice a big problem with the way the container initialize. As you state in the example section for Django, we initiate the container in
__init__.py
at project level.https://python-dependency-injector.ets-labs.org/examples/django.html
However, if one of your provider (Ex: UserService needs User model) requires model to do some works, django will throw django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
I am not so sure how to solve it? I am using Singleton in my container (required). Not sure if it that the reason?
Update: I solve the problem with local import. But still I have to import locally multiple places in different function. The main root cause still because of initialization of the container in
__init__.py
at project level. Let say we move it to a custom app, which register at the end of INSTALLED_APP list, we still have a problem how to wire them to the other app. I don't know if you familiar with ReactiveX. I think this package can help solve the problem of wiring https://github.com/ReactiveX/RxPY. We could have an subscriber at each AppConfig indef ready: ...
. Then when we initiate the container, it will trigger a signal to tell them, ok it is safe for you guys to wire now ?