Search code examples
pythonflask-sqlalchemyflask-restful

Python Import Dependency


I know this question has been asked many times, but even after I tried many SO suggestions, I still cannot get it right. So, posting here for your help.

I'm building a simple web server with Flask-Restful and Flask-SqlAlchemy. Basically, I think I'm getting an error because of a circular import. My Widget class is dependent on Visualization class and vice versa...

The error message:

Traceback (most recent call last):
  File "server.py", line 1, in <module>
    from app.resources.dashboard import Dashboards, Dashboard
  File "app/__init__.py", line 14, in <module>
    from models import *
  File "app/models/__init__.py", line 1, in <module>
    import visualization.Visualization as VisualizationModel
  File "app/models/visualization.py", line 3, in <module>
    from app.models import WidgetModel
ImportError: cannot import name WidgetModel

Directory structure:

├── app
│   ├── app/__init__.py
│   ├── app/models
│   │   ├── app/models/__init__.py
│   │   ├── app/models/dashboard.py
│   │   ├── app/models/visualization.py
│   │   ├── app/models/widget.py
│   └── app/resources
│       ├── app/resources/__init__.py
│       ├── app/resources/dashboard.py
│       ├── app/resources/visualization.py
│       ├── app/resources/widget.py
├── server.py

app/models/__init__.py:

from visualization import Visualization as VisualizationModel
from widget import Widget as WidgetModel
from dashboard import Dashboard as DashboardModel

app/models/visualization.py

from sqlalchemy import types
from app import db
from app.models import WidgetModel


class Visualization(db.Model):
    __tablename__ = 'visualizations'
    ...
    widget = db.relationship(WidgetModel, cascade="all, delete-orphan", backref="visualizations")

app/models/widget.py

from app import db
from app.models import VisualizationModel


class Widget(db.Model):
    __tablename__ = 'widgets'
    ...
    visualization = db.relationship(VisualizationModel, backref="widgets")

I tried changing my import to from app import models, and then use models.WidgetModel / models.VisualizationModel. However, still getting an ImportError.

Error message:

Traceback (most recent call last):
  File "server.py", line 1, in <module>
    from app.resources.dashboard import Dashboards, Dashboard
  File "app/__init__.py", line 14, in <module>
    from models import *
  File "app/models/__init__.py", line 1, in <module>
    from visualization import Visualization as VisualizationModel
  File "app/models/visualization.py", line 3, in <module>
    from app import models
ImportError: cannot import name models

I'm very new to Python. I would be grateful if you can help me out. Thanks for you help in advance!


Update

The intention of defining bi-directional relationship is that I want to attach the Visualization object in the fields of Widget object upon a return of GET request on a widget record.

In the app/resources/widget.py I have:

...
from flask_restful import fields, Resource, marshal_with, abort
from app.models import WidgetModel
import visualization as visualizationResource


widget_fields = {
    'id': fields.String,
    ...
    'dashboard_id': fields.String,
    'visualization_id': fields.String,
    'visualization': fields.Nested(visualizationResource.visualization_fields)
}

class Widgets(Resource):

    @marshal_with(widget_fields)
    def get(self):
        return WidgetModel.query.all()

I also want to have the cascade delete feature because if a widget cannot exist without a visualization.


Solution

  • Change your import to look like this:

    from app import models
    

    And then use models.WidgetModel / models.VisualizationModel instead.

    The problem is that you're creating a circular import dependency where both files require the other file to already have been processed before they can be processed. By moving to importing the entire models namespace rather than trying to import a specific model name at import time, you avoid making the import dependent on a fully processed file. That way, both files can be fully processed before either tries to invoke an object created by the other.

    It may still not work in this case however because you're trying to immediately use the import in part of the class definition, which is evaluated at processing time.


    It looks like you're trying to define bi-directional relationships - the backref parameter is intended to do this automatically without you having to specify the relationship on both models. (backref tells sqlalchemy what field to add to the other model to point back to the original models that link to it). So you may not need to be doing both of these imports in the first place.

    For instance, the fact that Visualization.widget is defined with backref="visualizations" means that Widget.visualizations will already exist - you don't need to explicitly create it.

    If what you specifically need is many:many relationships, then chances are what you actually want to do is define an association table for the many-to-many relationship.