Django - circular import module and submodule - python

I am reading many doc, blog, and SE questions about this topic but I cant get it right. I have a circular import between my models.py and tms.py in a same module named maps
models.py
from maps.tms import tileMapConfig
class LayerMapOptions(models.Model):
layer = models.ForeignKey(Shapefile)
basqui_map = models.ForeignKey(BasquiMap)
position = models.IntegerField(max_length=100, blank=True, null=True)
visible = models.BooleanField(default=False)
styles = models.ManyToManyField(LayerStyle, null=True, blank=True)
def save(self, *args, **kwargs):
super(LayerMapOptions, self).save(*args, **kwargs)
self.basqui_map.date_updated = datetime.now()
self.basqui_map.save()
tileMapConfig(self.basqui_map.pk)
tms.py:
from maps.models import LayerMapOptions
result = LayerMapOptions.objects.filter(basqui_map__pk=map_id)
def tileMapConfig(map_id):
...
This result in the following error:
from maps.models import LayerMapOptions ImportError: cannot import name LayerMapOptions
What would be the way to import those submodules without conflict?

Simply put your import into the function that uses it:
def tileMapConfig(map_id):
from maps.models import LayerMapOptions
...
Or
def save(self, *args, **kwargs):
from maps.tms import tileMapConfig
...
Modules are only imported once, so each time it re-enters the function, it will not reimport the whole module, it will only fetch it from the already imported modules, so there is (almost) no performance issue.

One way is to use the full namespace.
import maps.tms
maps.tmstileMapConfig(self.basqui_map.pk)
The other issue is that you have some global code running during the import:
result = LayerMapOptions.objects.filter(basqui_map__pk=map_id)
When importing a module, the code in the module gets executed, so if you can move this line into a function or method, that will delay the execution of that line until after both modules have fully imported.
Here's a better explanation:
http://effbot.org/zone/import-confusion.htm

Related

How to avoid import errors in django within same app

I have a django app where I want MyModel instances to be saved using an Enum choice, which I process in the save() method, such as:
# app/models.py
from django.db import models
from app.utils import translate_choice
from enum import Enum
class MyEnum(Enum):
CHOICE_A = 'Choice A'
CHOICE_B = 'Choice B'
class MyModel(models.Model):
...
choice = models.CharField(
max_length=10,
choices=[(tag.name, tag.value) for tag in MyEnum],
)
...
def save(self, *args, **kwargs):
self.choice = translate_choice(self.choice)
super().save(*args, **kwargs)
Whereas on the app/utils.py I have:
from app.models import MyEnum
def translate_choice(value):
...
return MyEnum.CHOICE_A # For example
I keep getting ImportError: cannot import name 'MyEnum' errors on the app/utils.py file when running the application. Is this due to a python circular import error, or am I missing something else? When moving the translate_choice() method to app/models.py it stops happening, but I would like to use this same method in other modules and it is kind of weird having a transforming feature within the models when used in another app.
Thank you in advance
It's probably due to the circular import, as you've guessed yourself. You can try putting the import statement not at the top of the file, but inside the function that uses the imported object:
def translate_choice(value):
from app.models import MyEnum
...
return MyEnum.CHOICE_A # For example
This is, admittedly, not the most elegant solution. See also the answers to this post, where you can find other approaches.

Unable to use objects from a module in sys.modules

Because of circular imports a want to load a module this way:
try:
from ..foo import serializers as foo_serializers
except ImportError:
import sys
foo_serializers = sys.modules['app.foo.serializers']
When I use the loaded module in a class definition like this, it says app.foo.serializers has no attribute FooSerializer although it definitely has:
class SomeSerializer(ModelSerializer):
foo_field = foo_serializers.FooSerializer()
But when I use foo_serializers.FooSerializer in a class function it works. What does it mean? Are modules in sys.modules fully loaded in the time when the class definition is loaded? What could be a problem here?

Does python support to import package

I am using Python 2.7, and have following code strucure
model
__init__.py
order.py
cart.py
That is, I define a package named model, and in this package, I define a module order, and I define a class in order.py
class MyOrder(object):
def __init__(self, name):
self.name = name
def getname(self):
return self.name
In the cart.py, the code is:
import model
x = model.order.MyOrder("Book")
print x.getname()
When I run it, it complains that AttributeError: 'module' object has no attribute 'order',
But the following is correct:
import model.order
x = model.order.MyOrder("Book")
print x.getname()
It looks that I can't import package (like import model) ?
If you want to have model automatically import order so it's available, you should do that in __init__.py. Simply put the following inside model/__init__.py:
from . import order
After that, you should be able to access model.order with just import model.

Why is the import statement importing a function outside of the class that is being imported?

I was under the understanding that using the syntax:
from foo import y
will import the class y from foo.py.
If this is true how come when I use the following:
models.py
from django.db import models
from .utils import codeGenerator, createShortcode
class KirrUrlManager(models.Manager):
def all(self, *args, **kwargs):
qs_main = super(KirrUrlManager, self).all(*args, **kwargs)
qs = qs_main.filter(active=True)
return qs
def refreshShortCodes(self):
qs = KirrUrl.objects.filter(id__gte=1) #id_gte is primary key
newCodes = 0
for q in qs:
q.shortcode = createShortcode(q)
q.save()
newCodes +=1
return "new codes made: {i} ".format(i=newCodes)
class KirrUrl(models.Model):
url = models.CharField(max_length=220, unique=True)
shortcode = models.CharField(max_length=50, unique=True, blank = True)
timestamp = models.DateTimeField(auto_now=True,)
timestamp = models.DateTimeField(auto_now_add=True,)
active = models.BooleanField(default=True,)
objects = KirrUrlManager()
def save(self, *args, **kwargs):
if self.shortcode is None or self.shortcode =="":
self.shortcode = createShortcode(self)
super(KirrUrl, self).save(*args, **kwargs)
def __str__(self):
return str(self.url)
foo.py
from django.core.management.base import BaseCommand, CommandError
from shortener.models import KirrUrl
class Command(BaseCommand):
help = "refreshes all shortcodes"
def handle(self, *args, **options):
return KirrUrl.objects.refreshShortCodes()
I am unsure why I am able to call the method "refreshShortCodes()" in foo.py. I am only using the import statement "from shortener.models import KirrUrl". Shouldnt this import statement only let me import the KirrUrl class? refreshShortCodes() is not part of the KirrUrl class, however it is the models.py file that is being imported.
Shouldnt this import statement only let me import the KirrUrl class?
Yes, and that's exactly what the import statement is doing. objects.refreshShortCodes() is part of the KirrUrl class. So you have access to the class name, as well as its attributes.
When Python imports a class, it imports the entire class object. That means all of variables and methods defined in the classes namespace can be reached. So Since you created an instance of KirrUrlManager() inside of the KirrUrl class, you can access the refreshShortCodes() method by first getting the KirrUrlManager() instance:
KirrUrl.objects
And then getting the refreshShortCodes() method from the KirrUrlManager() instance:
KirrUrl.objects.refreshShortCodes()
You only need to import the class to have access to its attributes; you can't import an object encapsulated in a class.
Therefore, after importing the class KirrUrl, the model manager objects is accessible via the class, and the method refreshShortCodes is equally accessible via the model manager instance which is composed in the class KirrUrl.
This is one of the ways objects that are not reachable by the import mechanism are accessed; dot referencing.

Python class method throws AttributeError

I'm having issues getting a class method to run in Flask.
In models/User.py:
from mongoengine import *
class User(Document):
first_name = StringField()
last_name = StringField()
...
def __init__(self, arg1, arg2, ...):
self.first_name = arg1
self.last_name = arg2
...
#classmethod
def create(self, arg1, arg2, ...):
#do some things like salting and hashing passwords...
user = self(arg1, arg2, ...)
user.save()
return user
In the main application python file:
from models import User
...
def func():
...
#Throws "AttributeError: type object 'User' has no attribute 'create'"
user = User.create(arg1, arg2, ...)
Shouldn't I be able to call create on the User class without instantiating a User object? I'm using Python 2.7.2, and I also tried the non-decorator syntax of using create = classmethod(create), but that didn't work. Thanks in advance!
EDIT: I found one issue: that the models folder did not contain an __init__.py file, so it wasn't a module, so from models import User was not actually importing the file I wanted it to. It did not give me an error from before because I used to have a models.py module in the same directory as the application python script, but after deleting it I never deleted the corresponding .pyc file. Now, I'm getting the error AttributeError: 'module' object has no attribute 'create' instead of what I had before, but I'm certain it is importing the correct file now.
EDIT2: Solved. I then changed the import to from models.User import User and It's hitting the method now.
The issue was twofold:
The User.py file was in the models/ folder, meaning that my import was actually looking for the User class in the models.py file, which no longer existed but still was being imported without error because the models.pyc file was still around
The import was incorrect for importing within a directory. it should have been from models.User import User, so long as the models/ folder is a module, so all I needed to do then was touch models/__init__.py.
>>> class foo(object):
... def __init__(self):
... pass
... #classmethod
... def classmethod(cls):
... return 0
...
>>> a = foo()
>>> a.classmethod()
0
>>>

Categories

Resources