diff --git a/tests/wandb_interface/test_project_creator.py b/tests/wandb_interface/test_project_creator.py index 27402627060e..43e73617cd15 100644 --- a/tests/wandb_interface/test_project_creator.py +++ b/tests/wandb_interface/test_project_creator.py @@ -5,6 +5,7 @@ import pytest from weave.compat.wandb import AuthenticationError, CommError +from weave.trace.settings import override_settings from weave.wandb_interface.project_creator import ( UnableToCreateProjectError, ensure_project_exists, @@ -138,6 +139,30 @@ def test_ensure_project_exists_authentication_error(mock_api_with_no_project): ) +def test_ensure_project_exists_autocreate_disabled_existing_project_ok( + mock_api_with_existing_project, +): + """With autocreate disabled, an existing project still resolves normally.""" + with override_settings(autocreate_project=False): + result = ensure_project_exists("test_entity", "test_project") + + assert result == {"project_name": "existing_project"} + mock_api_with_existing_project.upsert_project.assert_not_called() + + +def test_ensure_project_exists_autocreate_disabled_missing_project_errors( + mock_api_with_no_project, +): + """With autocreate disabled, a missing project raises without upserting.""" + with ( + override_settings(autocreate_project=False), + pytest.raises(UnableToCreateProjectError, match="autocreate_project is disabled"), + ): + ensure_project_exists("test_entity", "test_project") + + mock_api_with_no_project.upsert_project.assert_not_called() + + @pytest.mark.disable_logging_error_check def test_ensure_project_exists_comm_error(mock_api_with_no_project): """Test project creation with communication error.""" diff --git a/weave/trace/api.py b/weave/trace/api.py index 6c6fc7745668..06f5417156e5 100644 --- a/weave/trace/api.py +++ b/weave/trace/api.py @@ -108,6 +108,9 @@ def init( call data (start and end) into a single request instead of separate start/end requests. This reduces server load and improves performance, especially for short-lived ops. Default: `True` + - `autocreate_project` (bool): Allows `weave.init` to create the project on the + server if it does not already exist. When `False`, `weave.init` raises an + error instead of creating the project. Default: `True` autopatch_settings: (Deprecated) Configuration for autopatch integrations. Use explicit patching instead. postprocess_inputs: A function applied to the inputs of every op traced by this client. postprocess_output: A function applied to the output of every op traced by this client. diff --git a/weave/trace/settings.py b/weave/trace/settings.py index 8451c3acc121..0f8a46fd8f56 100644 --- a/weave/trace/settings.py +++ b/weave/trace/settings.py @@ -317,6 +317,16 @@ class UserSettings: Can be overridden with the environment variable `WEAVE_USE_OTEL_V2` """ + autocreate_project: bool = True + """Toggles automatic project creation on `weave.init()`. + + If True (default), `weave.init('entity/project')` upserts the project, + creating it on the server if it does not yet exist. If False, `weave.init` + raises `UnableToCreateProjectError` when the project does not exist; the + project must be created out-of-band (e.g. via the W&B UI). + Can be overridden with the environment variable `WEAVE_AUTOCREATE_PROJECT` + """ + class _SettingsOverrides(TypedDict, total=False): """Typed kwargs accepted by :func:`override_settings`. @@ -360,6 +370,7 @@ class _SettingsOverrides(TypedDict, total=False): enable_wal: bool disable_wal_sender: bool use_otel_v2: bool + autocreate_project: bool # Resolve string annotations once at import; used for env-var coercion. @@ -634,3 +645,10 @@ def should_disable_wal_sender() -> bool: def should_use_otel_v2() -> bool: """Returns whether OTel-capable integrations should use their OTel variant.""" return _env_or_default("use_otel_v2", _current_settings.get().use_otel_v2) + + +def should_autocreate_project() -> bool: + """Returns whether `weave.init` may create the project if it does not exist.""" + return _env_or_default( + "autocreate_project", _current_settings.get().autocreate_project + ) diff --git a/weave/wandb_interface/project_creator.py b/weave/wandb_interface/project_creator.py index 2c6d977f87bb..98ecd87f93c4 100644 --- a/weave/wandb_interface/project_creator.py +++ b/weave/wandb_interface/project_creator.py @@ -23,7 +23,11 @@ from weave.compat import wandb from weave.compat.wandb import wandb_logger -from weave.trace.settings import retry_max_attempts, retry_max_interval +from weave.trace.settings import ( + retry_max_attempts, + retry_max_interval, + should_autocreate_project, +) class UnableToCreateProjectError(Exception): ... @@ -120,6 +124,13 @@ def _ensure_project_exists(entity_name: str, project_name: str) -> dict[str, str if project_exception is not None: _raise_project_access_error(entity_name, project_name, project_exception) + if not should_autocreate_project(): + raise UnableToCreateProjectError( + f"Project `{entity_name}/{project_name}` does not exist and " + "autocreate_project is disabled. Create the project in the W&B UI " + "or set WEAVE_AUTOCREATE_PROJECT=true." + ) + # Try to create the project exception = None try: