Python recursive import issue - python

I have a Python/Flask app that uses MongoEngine for the database. I have defined my models, and everything was working until the newest models were added. I believe the problem occurs because both models reference each other and it's causing a recursive import loop. I'm not sure what to do to solve it though. This is going to be a large project with lots of models referencing each other. This particular instance is because users are in practices, and practices have users, so it's a many to many relationship.
User Model
from utilities.common import utc_now_ts as now
from mongoengine import *
from models.practice import Practice
class User(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
practices = ListField(ReferenceField(Practice))
And the practice model
from utilities.common import utc_now_ts as now
from mongoengine import *
from models import user
class Practice(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
users = ListField(ReferenceField(user.User))
admins = ListField(ReferenceField(user.User))
The error I get is ImportError: cannot import name 'Practice'
I have two other models that are running into the same issue. The models worked fine until I added in the imports to the other model.

I am by no means an expert on MongoEngine, but it looks like you can reference a model by string as opposed to by class. In that case you can change your Practice model to look like this.
from utilities.common import utc_now_ts as now
from mongoengine import *
class Practice(Document):
name = StringField()
created = IntField(db_field="cr", default=now)
users = ListField(ReferenceField('User'))
admins = ListField(ReferenceField('User'))
I hope this helps.

The short answer is that you can't have circular references. The compiler doesn't trust that you will properly "bottom out" on resolving references, and it's not going to iterate through the graph to find out.
One way to fix this is to use a master class that imports the various classes; your worker classes then import their needs from the master class.

Related

Django cyclic imports and typechecking with mypy

I'm trying to create a simple custom manager with one of my Django models. It causes a cyclic import since I'm trying to import the manager from models.py and the model from managers.py. However, because my manager is creating the model and adding some extra attributes, the type hint for the method is the model instance. I'm having trouble with fixing that type hint since it's not yet imported.
# models.py
from .managers import PublishedBundleManager
class PublishedBundle(models.Model):
data = JSONField()
md5_checksum = models.CharField(max_length=128)
objects = PublishedBundleManager()
The manager has a method to help me create the model instance, but as a convenience, calculates a checksum to fill in during creation. To fix the cyclic imports, I made use of typing.TYPE_CHECKING
# managers.py
import typing as t
from django.apps import apps
from django.db import models
if t.TYPE_CHECKING:
PublishedBundle = apps.get_model(app_label="the_hugh", model_name="PublishedBundle")
class PublishedBundleManager(models.Manager): # Error 1
def create_publish(self, data: t.Dict, description: str) -> PublishedBundle: # Error 2
PublishedBundle = apps.get_model(app_label="my_app", model_name="PublishedBundle")
json_data = json.dumps(data, sort_keys=True)
md5_checksum = hashlib.md5(json_data.encode("utf-8")).hexdigest()
return PublishedBundle.objects.create(data=data, md5_checksum=md5_checksum)
However, I'm getting 2 errors.
Missing type parameters for generic type "Manager" [type-arg]mypy(error)
name 'PublishedBundle' is not defined
I'm fairly new with typed python and never faced this issue before. I understand that 2 is happening because PublishedBundle hasn't yet been defined, but I can't define it because it causes a cyclic import. Can anyone help me out please?
One simple way to work around this is to have the manager and the model in the same file. They are intricately related anyway.

Visibility of class definition

This may be a simple Python question, however after searching for "imported name not defined" and similar criteria, I did not find an answer to this.
I have a models.py, as prescribed by django, with class definitions for all models.
I then have a loadtestdata.py which is simply a programmatic way to populate the database with some initial entries.
Within loadtestdata.py, I import all the models using "from myproject.models import *".
This seems to work as 'global code' within loadtestdata.py is able to see all the class names imported from models.py. It can populate the database just fine.
However if I define a function inside loadtestdata.py, suddenly that function is unable to see any of the imported class names.
For example, this function would not see ImportedModelName:
def AddSomeEntries(list1):
for value in list1:
new_model = ImportedModelName()
# set some stuff on the model
new_model.save()
AddSomeEntries([1,2,3,4,5])
However, I could write this and it sees ImportedModelName just fine
new_model = ImportedModelName()
# set some stuff on the model
new_model.save()
Why does the former example not see the imported class, but the latter example can?
EDIT:
I cut down the loadtestdata.py to its bare minimum and this is it
from django.contrib.auth.models import User
User.objects.filter(email='someemail#here.com').delete()
User.objects.create_superuser('someuser', 'someuser#here.com', 'somepassword')
from django.db import models
from myproject.models import *
from datetime import datetime
def AddTestModel(list_data):
for entry in list_data:
test_model = TestModel()
test_model.name = entry
test_model.save()
AddTestModel([1,2,3])
In this file it fails because NameError: name 'TestModel' is not defined.
However if I replace the function AddTestModel with this:
for entry in [1,2,3]:
test_model = TestModel()
test_model.name = entry
test_model.save()
It works just fine. Note: the above, aside from changing the database login particulars, is now exactly what is in my loadtestdata.py.
The models.py begins with this (it has many other model definitions which I can't strip out because django will complain, but they are all simple definitions which certainly cannot clash with a 'TestModel' name):
from django.db import models
class TestModel(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
In working through explaining this post, I had a random idea which (after some google searching) resulted in this:
When I execute the script I am doing so with this:
python manage.py shell < loadtestdata.py
I don't know how that is functionally different to this:
python manage.py shell
>>> exec(open('loadtestdata.py').read())
But the latter works (being that functions I define in loadtestdata.py are able to see the imported class names) whereas the former produces the 'name is not defined' error.

Importing from views to models in Django

I have something like this in one of my apps's models.py :
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
I want to import a class from another app's views.py as below:
from anotherapp.views import MyClass
The problem is in the first lines of anotherapp.views file, I imported the Account class. So when I want to import MyClass into my models.py file, this error raises :
ImportError: cannot import name Account
That is circular import error you are encountering. While it is bad practice to import from views to models in Django, if you still want to, you can follow methods in this question to resolve it.
Here are few alternative ways that can be considered as good practice:
You can consider importing other low-level modules exist within anotherapp instead of MyClass (not depending on views.py)
You can use Django's signals to catch anotherapp's events project wide and act according to.
You can create a third file, say utils.py in anotherapp, move MyClass there and let anotherapp/views.py and your app's models.py import from anotherapp.utils

Django: Refactoring models into sub-modules

After the models.py in one app grew quite large, I've tried to move some of the classes into subpackages.
The old structure was something like this:
# File: assets/models.py
class Asset(...):
# lots of irrelevant code
# File: widgets/models.py
from assets.models import Asset
class Video(...):
asset = models.ForeignKey(Asset)
This worked without a problem, so I'm not going into further details about the structure.
What I've tried to do now is move the Asset class into a submodule. The structure is now as follows:
# File: assets/models/__init__.py (of course I deleted the old models.py)
from .assets import Asset
# File: assets/models/assets.py
class Asset(...):
# lots of irrelevant code
# File: widgets/models.py
from assets.models.assets import Asset
class Video(...):
asset = models.ForeignKey(Asset)
Somehow this doesn't work, and I can't figure out what actually causes the trouble. The error I'm getting is this:
widgets.video: 'asset' has a relation with model , which has either not been installed or
is abstract
It appears that Django can't reliably detect which app a model belongs to if it's in a nested submodule (ie. not directly inside APPNAME.models).
This is a known problem and can be solved by adding the following lines (in this case to the Asset class), thus defining explicitly which app a model belongs to:
class Asset(models.Model):
...
class Meta:
app_label = 'assets'
...
References:
https://groups.google.com/forum/#!topic/django-users/MmaiKvbDlDc
https://code.djangoproject.com/ticket/14007
You should import from models as before:
from assets.models import Asset
This allows you to always import from models but organise the models separately within the models directory. It also means, conceptually, that Asset is still in models as your ForeignKey refers to a assets.models.Asset object, not assets.models.assets.Asset.

Crossed import in django

On example, i have 2 apps: alpha and beta
in alpha/models.py import of model from beta.models
and in beta/models.py import of model from alpha.models
manage.py validate says that ImportError: cannot import name ModelName
how to solve this problem?
I have had this issue in the past there are two models that refer to one another, i.e. using a ForeignKey field. There is a simple way to deal with it, per the Django documentation:
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself:
So in your beta/models.py model, you would have this:
class BetaModel(models.Model):
alpha = models.ForeignKey('alpha.AlphaModel')
...
At this point, importing from alpha.models is not necessary.

Categories