Avoiding circular imports

How Python imports work

When Python imports a file, it evaluates it from top to bottom.

With ForeignKey columns we sometimes have to reference tables lower down in the file (which haven’t been evaluated yet).

The solutions are:

Import Table definitions as early as possible

In the entrypoint to your app, at the top of the file, it’s recommended to import your tables.

# main.py
from my_app.tables import Manager, Band

This ensures that the tables are imported, and setup correctly.

Keep table files focused

You should try and keep your tables.py files pretty focused (i.e. just contain your Table definitions).

If you have lots of logic alongside your Table definitions, it might cause your LazyTableReference references to evaluate too soon (causing circular import errors). An example of this is with create_pydantic_model:

# tables.py

from piccolo.columns import ForeignKey, Varchar
from piccolo.table import Table
from piccolo.utils.pydantic import create_pydantic_model


class Band(Table):
    name = Varchar()
    # This automatically gets converted into a LazyTableReference, because a
    # string is passed in:
    manager = ForeignKey("Manager")


# This is not recommended, as it will cause the LazyTableReference to be
# evaluated before Manager has imported.
# Instead, move this to a separate file, or below Manager.
BandModel = create_pydantic_model(Band)


class Manager(Table):
    name = Varchar()

Simplify your schema if possible

Even with LazyTableReference, you may run into some problems if your schema is really complicated.

An example is when you have two tables, and they have foreign keys to each other.

class Band(Table):
    name = Varchar()
    manager = ForeignKey("Manager")


class Manager(Table):
    name = Varchar()
    favourite_band = ForeignKey(Band)

Piccolo should be able to create these tables, and query them. However, some Piccolo tooling may struggle - for example when loading fixtures.

A joining table can help in these situations:

class Band(Table):
    name = Varchar()
    manager = ForeignKey("Manager")


class Manager(Table):
    name = Varchar()


class ManagerFavouriteBand(Table):
    manager = ForeignKey(Manager, unique=True)
    band = ForeignKey(Band)