Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/data-designer-config/src/data_designer/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from pythonjsonlogger.json import JsonFormatter

_logging_configured = False


@dataclass
class LoggerConfig:
Expand Down Expand Up @@ -115,6 +117,7 @@ def working() -> str:


def configure_logging(config: LoggingConfig | None = None) -> None:
global _logging_configured
config = config or LoggingConfig.default()

root_logger = logging.getLogger()
Expand All @@ -137,6 +140,18 @@ def configure_logging(config: LoggingConfig | None = None) -> None:
for name in config.to_silence:
quiet_noisy_logger(name)

_logging_configured = True


def is_logging_configured() -> bool:
"""Return whether ``configure_logging()`` has run in this process.

Only tracks Data Designer's own ``configure_logging()``. Logging configured
through stdlib APIs (e.g. ``logging.basicConfig()`` or manually attached
root handlers) is not detected.
"""
return _logging_configured


def quiet_noisy_logger(name: str) -> None:
logger = logging.getLogger(name)
Expand Down
12 changes: 12 additions & 0 deletions packages/data-designer-config/tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

import pytest

import data_designer.logging as logging_mod
from data_designer.logging import (
LoggerConfig,
LoggingConfig,
OutputConfig,
RandomEmoji,
configure_logging,
is_logging_configured,
quiet_noisy_logger,
)

Expand Down Expand Up @@ -105,6 +107,16 @@ def test_configure_logging_basic(stub_default_logging_config):
assert ndd_logger.level == logging.INFO


def test_configure_logging_marks_logging_configured(stub_default_logging_config, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(logging_mod, "_logging_configured", False)

assert is_logging_configured() is False

configure_logging(stub_default_logging_config)

assert is_logging_configured() is True


def test_configure_logging_with_file():
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".log") as tmp_file:
tmp_path = Path(tmp_file.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
DataDesignerProfilingError,
)
from data_designer.interface.results import DatasetCreationResults
from data_designer.logging import LOG_INDENT, RandomEmoji, configure_logging
from data_designer.logging import LOG_INDENT, RandomEmoji, configure_logging, is_logging_configured
from data_designer.plugins.plugin import PluginType
from data_designer.plugins.registry import PluginRegistry

Expand All @@ -88,12 +88,13 @@
_interface_runtime_initialized = False


def _initialize_interface_runtime() -> None:
def _initialize_interface_runtime(*, auto_configure_logging: bool = True) -> None:
"""Run one-time runtime initialization for the interface package."""
global _interface_runtime_initialized
if _interface_runtime_initialized:
return
configure_logging()
if auto_configure_logging and not is_logging_configured():
configure_logging()
resolve_seed_default_model_settings()
_interface_runtime_initialized = True

Expand Down Expand Up @@ -141,6 +142,12 @@ class DataDesigner(DataDesignerInterface[DatasetCreationResults]):
mcp_providers: Optional list of MCP provider configurations to enable tool-calling for
LLM generation columns. Supports both MCPProvider (remote SSE or Streamable HTTP) and
LocalStdioMCPProvider (local subprocess).
auto_configure_logging: Whether to apply Data Designer's default logging
configuration during construction. Set to False when embedding Data
Designer in an application that manages its own logging — Data Designer
will then leave existing root handlers and log levels untouched.
Automatic configuration is also skipped when
`data_designer.logging.configure_logging()` was already called.
"""

def __init__(
Expand All @@ -153,8 +160,9 @@ def __init__(
managed_assets_path: Path | str | None = None,
person_reader: PersonReader | None = None,
mcp_providers: list[MCPProviderT] | None = None,
auto_configure_logging: bool = True,
):
_initialize_interface_runtime()
_initialize_interface_runtime(auto_configure_logging=auto_configure_logging)
self._secret_resolver = secret_resolver or DEFAULT_SECRET_RESOLVER
self._artifact_path = Path(artifact_path) if artifact_path is not None else Path.cwd() / "artifacts"
self._run_config = RunConfig()
Expand Down
90 changes: 90 additions & 0 deletions packages/data-designer/tests/interface/test_data_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import data_designer.interface.data_designer as dd_mod
import data_designer.lazy_heavy_imports as lazy
import data_designer.logging as dd_logging
from data_designer.config.column_configs import (
ExpressionColumnConfig,
LLMTextColumnConfig,
Expand Down Expand Up @@ -1517,6 +1518,7 @@ def test_validate_raises_error_when_seed_collides(
def test_initialize_interface_runtime_runs_once(monkeypatch: pytest.MonkeyPatch) -> None:
"""_initialize_interface_runtime only runs initialization once."""
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", False)

with (
patch("data_designer.interface.data_designer.configure_logging") as mock_logging,
Expand All @@ -1528,6 +1530,94 @@ def test_initialize_interface_runtime_runs_once(monkeypatch: pytest.MonkeyPatch)
mock_resolve.assert_called_once()


def test_initialize_interface_runtime_respects_preconfigured_logging(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", True)

with (
patch("data_designer.interface.data_designer.configure_logging") as mock_logging,
patch("data_designer.interface.data_designer.resolve_seed_default_model_settings") as mock_resolve,
):
dd_mod._initialize_interface_runtime()

mock_logging.assert_not_called()
mock_resolve.assert_called_once()


def test_initialize_interface_runtime_skips_logging_when_auto_configure_disabled(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", False)

with (
patch("data_designer.interface.data_designer.configure_logging") as mock_logging,
patch("data_designer.interface.data_designer.resolve_seed_default_model_settings") as mock_resolve,
):
dd_mod._initialize_interface_runtime(auto_configure_logging=False)

mock_logging.assert_not_called()
mock_resolve.assert_called_once()


def test_init_auto_configures_logging_by_default(
monkeypatch: pytest.MonkeyPatch,
stub_artifact_path: Path,
stub_model_providers: list[ModelProvider],
) -> None:
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", False)

with patch("data_designer.interface.data_designer.configure_logging") as mock_logging:
DataDesigner(artifact_path=stub_artifact_path, model_providers=stub_model_providers)

mock_logging.assert_called_once()


def test_init_auto_configure_logging_false_preserves_root_handlers(
monkeypatch: pytest.MonkeyPatch,
stub_artifact_path: Path,
stub_model_providers: list[ModelProvider],
) -> None:
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", False)
root_logger = logging.getLogger()
sentinel_handler = logging.NullHandler()
root_logger.addHandler(sentinel_handler)

try:
DataDesigner(
artifact_path=stub_artifact_path,
model_providers=stub_model_providers,
auto_configure_logging=False,
)

assert sentinel_handler in root_logger.handlers
assert dd_logging.is_logging_configured() is False
finally:
root_logger.removeHandler(sentinel_handler)


def test_init_preserves_preconfigured_logging(
monkeypatch: pytest.MonkeyPatch,
stub_artifact_path: Path,
stub_model_providers: list[ModelProvider],
) -> None:
monkeypatch.setattr(dd_mod, "_interface_runtime_initialized", False)
monkeypatch.setattr(dd_logging, "_logging_configured", False)
data_designer_logger = logging.getLogger("data_designer")
previous_level = data_designer_logger.level

try:
dd_logging.configure_logging(dd_logging.LoggingConfig.debug())

DataDesigner(artifact_path=stub_artifact_path, model_providers=stub_model_providers)

assert data_designer_logger.level == logging.DEBUG
finally:
data_designer_logger.setLevel(previous_level)


def test_create_dataset_e2e_with_directory_seed_source(
stub_artifact_path: Path,
stub_model_providers: list[ModelProvider],
Expand Down
Loading