Problem:

Mypy Error: The mypy error encountered is as follows:

error:Import cycle from Django settings module prevents type inference for 'LANGUAGES' [misc]

I'm encountering challenges with the usage of gettext_lazy in Django 4 settings while utilizing django-stubs. The recent update in Django 4 brought changes to the typing of gettext_lazy, where the old return type was str, and the new type is _StrPromise. The issue arises from the fact that _StrPromise is defined in "django-stubs/utils/functional.pyi," and within this file, there is also an import of django.db.model which imports settings. This creates a circular import.

current module-version:

typing

mypy = "1.7"
django-stubs = "4.2.7"

Django dependencies

Django = "4.2.10"

Seeking advice on a cleaner and more sustainable solution to the circular import issue and mypy error in the context of Django 4 settings with the updated gettext_lazy typing. Any insights or alternative approaches would be greatly appreciated.

possible Solutions:

Several solutions have been considered, but none of them are ideal:

  1. Disable Mypy Typing for Settings: Disabling Mypy typing for settings is a workaround, but it might compromise the benefits of static typing.

  2. Remove gettext_lazy from Settings: Another option is to remove gettext_lazy from settings. However, this contradicts the current recommendations in Django 4 and 5 documentation, which still advocate for the use of gettext_lazy in the LANGUAGE setting.


2

There are 2 best solutions below

0
On

Also just encountered this. This is explained in the django-stubs readme.

If you encounter this error in your own code, you can either cast the Promise to str (causing the translation to be evaluated), or use the StrPromise or StrOrPromise types from django-stubs-ext in type hints.

To get the type hints, you need to install the stubs extension:

pip install django-stubs-ext

Then you can import the hints:

from django_stubs_ext import StrPromise


my_lazy_str: StrPromise = gettext_lazy("foobar")
1
On

Dealing with circular imports and Mypy typing issues in Django settings, especially with the updated typing of gettext_lazy in Django 4, can indeed be challenging. Let's explore a few alternative approaches to address this problem:

Separate Configuration Module: Instead of defining settings directly in the settings.py file, create a separate module, say settings_config.py, where you define your settings. This module should not import anything from Django models or other modules that might lead to circular imports. Then, in your settings.py, import the settings from settings_config.py. This separation can help break the circular dependency.

Example:

# settings_config.py
from django.utils.translation import gettext_lazy as _

LANGUAGE_CODE = 'en-us'
LANGUAGES = [
    ('en', _('English')),
    ('fr', _('French')),
]

# settings.py
from .settings_config import *

Use Strings Instead of Constants: Instead of directly referencing gettext_lazy in the settings, use strings that will be resolved at runtime. While this might not offer the benefits of static typing, it can help avoid circular imports. Example:

# settings.py
LANGUAGES = [
    ('en', 'English'),
    ('fr', 'French'),
]

Custom Typing Stub: You can create a custom typing stub for gettext_lazy or _StrPromise if the existing stubs are causing circular imports. This approach involves defining the necessary types in a separate file without importing problematic modules. Example:

# custom_gettext_lazy.pyi
from typing import Any

class _StrPromise:
    def __init__(self, message: str):
        pass

    def __str__(self) -> str:
        ...

def gettext_lazy(message: str) -> _StrPromise: ...

Then you can use gettext_lazy in your settings with proper typing, and hopefully, it avoids the circular import issue.

These approaches should help mitigate the circular import issue while still allowing you to use gettext_lazy in your Django settings. Choose the one that fits best with your project structure and preferences.