Skip to content

Circular import: importing data_designer.engine.models.errors (or .registry) first raises ImportError #990

Closed

Description

@3mei

Priority Level

Medium (Annoying but has workaround)

Describe the bug

In a fresh interpreter, import data_designer.engine.models.errors fails with:

ImportError: cannot import name 'FormattedLLMErrorMessage' from partially initialized module 'data_designer.engine.models.errors' (most likely due to a circular import)

import data_designer.engine.models.registry fails the same way, because it imports errors first. Importing almost any other Data Designer module first (data_designer.interface, data_designer.engine.models, ...models.facade, ...models.factory, ...models.clients) loads the modules in an order that avoids the cycle. So whether the import works depends on what the process happened to import earlier.

data_designer.engine.models.errors defines the model exception classes (ModelRateLimitError, ModelAuthenticationError, ModelContextWindowExceededError, and so on), and they aren't re-exported anywhere else. Code that catches them, such as a plugin, a script or a test module, crashes on import when this is its first Data Designer import. In test suites that shows up as failures that depend on collection order.

Steps/Code to reproduce bug

python -c "import data_designer.engine.models.errors"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import data_designer.engine.models.errors
  File ".../data_designer/engine/models/errors.py", line 14, in <module>
    from data_designer.engine.models.clients.errors import ProviderError, ProviderErrorKind, SyncClientUnavailableError
  File ".../data_designer/engine/models/clients/__init__.py", line 14, in <module>
    from data_designer.engine.models.clients.factory import create_model_client
  File ".../data_designer/engine/models/clients/factory.py", line 15, in <module>
    from data_designer.engine.models.errors import FormattedLLMErrorMessage
ImportError: cannot import name 'FormattedLLMErrorMessage' from partially initialized module 'data_designer.engine.models.errors' (most likely due to a circular import) (.../data_designer/engine/models/errors.py)

python -c "import data_designer.engine.models.registry" fails with the same traceback. Importing another module first works around it:

python -c "import data_designer.engine.models.factory; import data_designer.engine.models.errors"

Reproduced on data-designer 0.9.3 and on main at f9aff6e (built from source).

Expected behavior

Every module imports successfully regardless of which Data Designer module the process imports first.

Agent Diagnostic / Prior Investigation

The cycle (at f9aff6e):

  1. engine/models/errors.py L14 imports engine.models.clients.errors, which first executes the clients package __init__.
  2. engine/models/clients/__init__.py L14 eagerly imports create_model_client from clients.factory.
  3. engine/models/clients/factory.py L15 imports FormattedLLMErrorMessage from engine.models.errors, which is still on its line 14, so the class (defined at L155) doesn't exist yet. factory.py uses it in one place (L100).

registry.py imports errors at L11, so it enters the same cycle.

#781 fixed a regression caused by this same cycle. Its first revision made create_model_client a lazy export in clients/__init__.py, which broke the cycle. The merged version instead removed the health-check script's direct import of engine.models.errors and reverted the lazy export. The cycle itself is still present, so the error returns whenever errors or registry is the first module imported. The fresh-process test in tests/test_lazy_imports.py covers the health-check script's entry point, not this import order.

Additional context

Possible fixes, not mutually exclusive:

  1. Smallest: in clients/factory.py, import FormattedLLMErrorMessage inside the function that uses it, with a short comment explaining why the import is deferred.
  2. Remove the edge: move FormattedLLMErrorMessage into a module that doesn't import clients (for example next to GenerationTruncationReason in engine/models/utils.py), and re-export it from errors.py for compatibility.
  3. Public import path: if engine.* is meant to stay internal, re-export the model exception classes from a public module (such as data_designer.interface), so downstream code never has to import engine.models.errors to catch them. That would fit the boundary direction taken in fix: break health check import cycle #781.

Regression coverage: a parametrized fresh-process test (the _run_python_snippet helper in tests/test_lazy_imports.py already does this) that imports each module under data_designer.engine.models as the very first import.

Happy to contribute a PR for whichever direction is preferred.

Checklist

  • I reproduced this issue or provided a minimal example
  • I searched the docs/issues myself, or had my agent do so
  • If I used an agent, I included its diagnostics above

Activity

  1. added
    bugSomething isn't working
    on Oct 6, 2026
  2. nabinchha commented on Oct 14, 2026

    @nabinchha
    Contributor

    Confirmed the circular import in fresh Python processes for both data_designer.engine.models.errors and data_designer.engine.models.registry.

    To keep imports at module level, the proposed fix is to move FormattedLLMErrorMessage into a client-independent module, such as engine/models/utils.py, and import it from there in both clients/factory.py and models/errors.py. Keep the formatter re-exported from models/errors.py so existing import paths continue to work, with its fields and formatting unchanged. This removes the factory → errors back edge without introducing an inline import or changing the package-level factory export.

    Please add fresh-subprocess regression tests for direct imports of models.errors and models.registry, and verify that both formatter import paths reference the same class, the package-level create_model_client export remains available, and unsupported-provider errors retain their current type and formatting. Keep the existing health-check import regression as well.

    One small clarification to the workaround description: importing the bare data_designer.interface or data_designer.engine.models package does not avoid the failure in our checks. Importing data_designer.interface.data_designer, models.factory, or models.clients first does.

  3. added theissue type on Oct 14, 2026
  4. added
    triagedIssue reviewed and approved by a maintainer
    and removed
    bugSomething isn't working
    on Oct 14, 2026
  5. Thanatos9404 commented on Oct 15, 2026

    @Thanatos9404
    Contributor

    @3mei are you planning to open a PR for this? If not, I'd be glad to take it, following the approach @nabinchha outlined above: move FormattedLLMErrorMessage into engine/models/utils.py, import it from there in clients/factory.py and models/errors.py, and add fresh-process import tests for both modules.

  6. nabinchha commented on Oct 15, 2026

    @nabinchha
    Contributor

    You can go for it @Thanatos9404, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

triagedIssue reviewed and approved by a maintainer

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions