DEV Community

Muminur Rahman
Muminur Rahman

Posted on

Python Type Hinting : Eliminating ImportError Due to Circular Imports

We all face sometimes ImportError due to having a circular import that is occurred only for type hinting. There is a simple way to handle this kind of problem.
Let’s take we have two files like following:

#book_manager.py
from django.db import models

class BookManager(models.Manager):
    def create_new_version_of_book(self, old_book_object, version):
        return self.create(name=old_book_object.name, version=version)

Enter fullscreen mode Exit fullscreen mode

The another file:

from django.db import models

from library.models import BookManager

class Book(models.Model):
    name = models.CharField(max_length=200)
    version = models.CharField(max_length=50)

    objects = BookManager()

Enter fullscreen mode Exit fullscreen mode

These two files will work fine as we did not added any type hinting. Only the BookManager is imported in book_model.py file. But if we add type hint to the create_new_version_of_book method from BookManager, then it will be as follows:

from django.db import models

from library.models import Book


class BookManager(models.Manager):

    def create_new_version_of_book(self, old_book_object: Book, version: str) -> Book:
        return self.create(name=old_book_object.name, version=version)

Enter fullscreen mode Exit fullscreen mode

Here now we will get an ImportError due to circular import and we will get something like the below message when we want to run the project/files:

ImportError: cannot import name 'BookManager' from partially initialized module 'library.models' (most likely due to a circular import)
Enter fullscreen mode Exit fullscreen mode

The solution is using typing.TYPE_CHECKING constant as below:

from __future__ import annotations

from typing import TYPE_CHECKING

from django.db import models

if TYPE_CHECKING:
    from library.models import Book


class BookManager(models.Manager):

    def create_new_version_of_book(self, old_book_object: Book, version: str) -> Book:
        return self.create(name=old_book_object.name, version=version)

Enter fullscreen mode Exit fullscreen mode

Line 1: We have imported annotations. __future__.annotations is not default in Python now; but it will become the default in Python 3.11. For details, you have a look into PEP 563. If you don’t import it, you can use type hints as string. In our case, it will be old_book_object: ‘Book’.

Line 3: We have imported typing.TYPE_CHECKING special constant. The value of the constant is always False, but set to True by any type checkers, such as Mypy. We have used the this constant to make our import conditional.

Line 7–8: We have imported the model with a condition, so it will only be imported when the TYPE_CHECKING variable is True.

Hope, this will help. :)

Discussion (0)