Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. #466

Open
vuhi opened this issue Jun 15, 2021 · 4 comments
Open

django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. #466

vuhi opened this issue Jun 15, 2021 · 4 comments
Assignees
Labels

Comments

@vuhi
Copy link

vuhi commented Jun 15, 2021

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.

from .di_containers import DIContainer
from . import settings

di_container = DIContainer() -> create dependency before other app load
...

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?

class DIContainer(containers.DeclarativeContainer):
    user_service = providers.Singleton(UserService)
from api_core.apps.user.models import User <- error cause by this import


@inject
class UserService:
    def __init__(self):
        self.test = 'test'

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 in def 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 ?

@rmk135 rmk135 self-assigned this Jun 17, 2021
@rmk135
Copy link
Member

rmk135 commented Jun 17, 2021

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!

@vuhi
Copy link
Author

vuhi commented Jun 25, 2021

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})

PS:

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:

  • I override user_service with different model like SuperUser
  • Will I get the new user_service with the new model even though I only call di_container.wire(modules=[views]) once in experiment/apps.py ?

Thank you,

@renanivo
Copy link

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?

@stevenengland
Copy link

stevenengland commented Apr 10, 2023

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants