The class Hero has a reference to the class Team internally.
But the class Team also has a reference to the class Hero.
So, if those two classes were in separate files and you tried to import the classes in each other's file directly, it would result in a circular import. π
And Python will not be able to handle it and will throw an error. π¨
But we actually want to mean that circular reference, because in our code, we would be able to do crazy things like:
And we also have an empty __init__.py file to make this project a "Python package" (a collection of Python modules). This way we can use relative imports in the app.py file/module, like:
We can use these relative imports because, for example, in the file app.py (the app module) Python knows that it is part of our Python package because it is in the same directory as the file __init__.py. And all the Python files on the same directory are part of the same Python package too.
Then you could put the code creating the engine and the function to create all the tables (if you are not using migrations) in another file database.py:
Remember that Order Matters when calling SQLModel.metadata.create_all()?
The point of that section in the docs is that you have to import the module that has the models before calling SQLModel.metadata.create_all().
We are doing that here, we import the models in app.py and after that we create the database and tables, so we are fine and everything works correctly. π
Because now this is a larger project with a Python package and not a single Python file, we cannot call it just passing a single file name as we did before with:
$ pythonapp.py
Now we have to tell Python that we want it to execute a module that is part of a package:
$ python-mproject.app
The -m is to tell Python to call a module. And the next thing we pass is a string with project.app, that is the same format we would use in an import:
importproject.app
Then Python will execute that module inside of that package, and because Python is executing it directly, the same trick with the main block that we have in app.py will still work:
if__name__=='__main__':main()
So, the output would be:
fast βpython -m project.app Created hero: id=1 secret_name='Dive Wilson' team_id=1 name='Deadpond' age=None Hero's team: name='Z-Force' headquarters='Sister Margaret's Bar' id=1
Let's say that for some reason you hate the idea of having all the database models together in a single file, and you really want to have separate files a hero_model.py file and a team_model.py file.
You can also do it. π There's a couple of things to keep in mind. π€
Warning
This is a bit more advanced.
If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. π€
But these type annotations we want to declare are not needed at runtime.
In fact, remember that we used list["Hero"], with a "Hero" in a string?
For Python, at runtime, that is just a string.
So, if we could add the type annotations we need using the string versions, Python wouldn't have a problem.
But if we just put strings in the type annotations, without importing anything, the editor wouldn't know what we mean, and wouldn't be able to help us with autocompletion and inline errors.
So, if there was a way to "import" some things that act as "imported" only while editing the code but not at runtime, that would solve it... And it exists! Exactly that. π
For the simplest cases (for most of the cases) you can just keep all the models in a single file, and structure the rest of the application (including setting up the engine) in as many files as you want.
And for the complex cases that really need separating all the models in different files, you can use the TYPE_CHECKING to make it all work and still have the best developer experience with the best editor support. β¨