Summary
When a third-party plugin's config module imports from data_designer.config, a circular import race condition causes plugin discovery to fail with a misleading error message, even though the plugin is correctly implemented.
Problem
During plugin discovery, the Plugin._load() method uses importlib.import_module() to load the config class. However, if the plugin's config module is currently being imported (i.e., partially initialized), getattr(module, class_name) fails because the class definition hasn't been executed yet.
Error Message
🛑 Failed to load plugin from entry point 'my-plugin': Could not find class 'MyPluginConfig' in module 'my_plugin.config'
This is confusing because the class does exist in the file — it's just not available yet due to import timing.
Reproduction
1. Create a plugin with this structure:
my-plugin/
├── pyproject.toml
└── src/
└── my_plugin/
├── __init__.py
├── config.py
├── generator.py
└── plugin.py
2. Plugin config (config.py):
from data_designer.config.column_configs import SingleColumnConfig
from typing import Literal
class MyPluginConfig(SingleColumnConfig):
column_type: Literal["my-plugin"] = "my-plugin"
# ... other fields
3. Plugin entry point (plugin.py):
from data_designer.plugins import Plugin, PluginType
plugin = Plugin(
impl_qualified_name="my_plugin.generator.MyPluginGenerator",
config_qualified_name="my_plugin.config.MyPluginConfig",
plugin_type=PluginType.COLUMN_GENERATOR,
)
4. User script:
from my_plugin.config import MyPluginConfig # This triggers the bug!
from data_designer.interface import DataDesigner
# ...
Import chain that causes the issue:
- User imports
my_plugin.config
config.py imports from data_designer.config.column_configs import SingleColumnConfig
- This triggers
data_designer.config initialization
- Which triggers plugin discovery via
PluginManager
- Plugin discovery tries to validate
my_plugin.config.MyPluginConfig
_load() calls importlib.import_module("my_plugin.config")
- But that module is already being imported (step 1), so
sys.modules["my_plugin.config"] exists but is incomplete
getattr(module, "MyPluginConfig") fails → PluginLoadError
Proposed Solution
Modify Plugin._load() to handle partially-initialized modules by attempting a reload:
@staticmethod
def _load(fully_qualified_object: str) -> type:
module_name, object_name = _get_module_and_object_names(fully_qualified_object)
module = importlib.import_module(module_name)
# Handle case where module is partially initialized (circular import during plugin discovery).
# If the class isn't available yet, try reloading the module.
if not hasattr(module, object_name):
# Module may be partially loaded due to circular imports during plugin discovery.
# Try to reload it to complete initialization.
try:
module = importlib.reload(module)
except Exception:
pass # If reload fails, fall through to the error below
try:
return getattr(module, object_name)
except AttributeError:
raise PluginLoadError(f"Could not find class {object_name!r} in module {module_name!r}")
Why This Fix Is Safe
| Scenario |
Before |
After |
| Normal load (class exists) |
✅ Works |
✅ Works (no change) |
| Class doesn't exist |
❌ PluginLoadError |
❌ Same error |
| Circular import (class not yet defined) |
❌ PluginLoadError |
✅ Reload succeeds |
- The reload only triggers when
hasattr() returns False — meaning it would have failed anyway
- Same exception type and message for actual missing classes
- No API changes
Workarounds (current)
Users can avoid this by:
- Using
uv run python instead of system Python (ensures correct venv)
- Importing
DataDesigner before importing plugin config classes
- Using lazy imports in the plugin's
__init__.py
However, these are unintuitive and the error message doesn't guide users toward them.
Environment
- Data Designer version: latest (main branch)
- Python: 3.11+
- OS: Windows/Linux/macOS
Summary
When a third-party plugin's config module imports from
data_designer.config, a circular import race condition causes plugin discovery to fail with a misleading error message, even though the plugin is correctly implemented.Problem
During plugin discovery, the
Plugin._load()method usesimportlib.import_module()to load the config class. However, if the plugin's config module is currently being imported (i.e., partially initialized),getattr(module, class_name)fails because the class definition hasn't been executed yet.Error Message
This is confusing because the class does exist in the file — it's just not available yet due to import timing.
Reproduction
1. Create a plugin with this structure:
2. Plugin config (
config.py):3. Plugin entry point (
plugin.py):4. User script:
Import chain that causes the issue:
my_plugin.configconfig.pyimportsfrom data_designer.config.column_configs import SingleColumnConfigdata_designer.configinitializationPluginManagermy_plugin.config.MyPluginConfig_load()callsimportlib.import_module("my_plugin.config")sys.modules["my_plugin.config"]exists but is incompletegetattr(module, "MyPluginConfig")fails →PluginLoadErrorProposed Solution
Modify
Plugin._load()to handle partially-initialized modules by attempting a reload:Why This Fix Is Safe
PluginLoadErrorPluginLoadErrorhasattr()returnsFalse— meaning it would have failed anywayWorkarounds (current)
Users can avoid this by:
uv run pythoninstead of system Python (ensures correct venv)DataDesignerbefore importing plugin config classes__init__.pyHowever, these are unintuitive and the error message doesn't guide users toward them.
Environment