From ace1670dc1e74899ed36601640776c7a7272249c Mon Sep 17 00:00:00 2001 From: "databricks-ci-ghec-1[bot]" <184311507+databricks-ci-ghec-1[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 03:21:12 +0000 Subject: [PATCH] Release databricks-sdk-py --- .codegen/_last_sha | 2 +- CHANGELOG.md | 8 +++++++ databricks/sdk/runtime/__init__.py | 8 +++---- databricks/sdk/service/ml.py | 18 ++++++++++++++ databricks/sdk/version.py | 2 +- tests/test_runtime.py | 38 ++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/.codegen/_last_sha b/.codegen/_last_sha index ee0e1fa45..6f2f696f1 100644 --- a/.codegen/_last_sha +++ b/.codegen/_last_sha @@ -1 +1 @@ -c765e364f87ce050e4077db80b8ec800c9ec1ed9 \ No newline at end of file +231772f37bb5c01154bfc3d1401dc241ff1b7409 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 14470bcd7..11f5283f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Version changelog +## Release v0.143.0 (2026-09-26) + +### API Changes +* Add `job_id` and `pipeline_id` fields for `databricks.sdk.service.ml.MaterializedFeature`. + +### Bug Fixes +* Don't configure the root logger when importing `databricks.sdk.runtime`. Its import-time notebook-globals initialization logged through root-level `logging` helpers, which install a handler on the root logger when it has none. This also happened transitively through `WorkspaceClient` and `dbutils`, and made a later `logging.basicConfig()` a silent no-op. These messages now go through the SDK's `databricks.sdk` logger. + ## Release v0.142.0 (2026-09-25) ### API Changes diff --git a/databricks/sdk/runtime/__init__.py b/databricks/sdk/runtime/__init__.py index a57104311..921022b3d 100644 --- a/databricks/sdk/runtime/__init__.py +++ b/databricks/sdk/runtime/__init__.py @@ -138,12 +138,12 @@ def inner() -> Dict[str, str]: sqlContext: SQLContext = None # type: ignore table = sqlContext.table except Exception as e: - logging.debug(f"Failed to initialize globals 'sqlContext' and 'table', continuing. Cause: {e}") + logger.debug(f"Failed to initialize globals 'sqlContext' and 'table', continuing. Cause: {e}") try: from pyspark.sql.functions import udf # type: ignore # noqa: F401 except ImportError as e: - logging.debug(f"Failed to initialise udf global: {e}") + logger.debug(f"Failed to initialise udf global: {e}") try: from databricks.connect import DatabricksSession # type: ignore @@ -153,13 +153,13 @@ def inner() -> Dict[str, str]: except Exception as e: # We are ignoring all failures here because user might want to initialize # spark session themselves and we don't want to interfere with that - logging.debug(f"Failed to initialize globals 'spark' and 'sql', continuing. Cause: {e}") + logger.debug(f"Failed to initialize globals 'spark' and 'sql', continuing. Cause: {e}") try: # We expect this to fail locally since dbconnect does not support sparkcontext. This is just for typing sc = spark.sparkContext # type: ignore except Exception as e: - logging.debug(f"Failed to initialize global 'sc', continuing. Cause: {e}") + logger.debug(f"Failed to initialize global 'sc', continuing. Cause: {e}") def display(input=None, *args, **kwargs) -> None: # type: ignore """ diff --git a/databricks/sdk/service/ml.py b/databricks/sdk/service/ml.py index 2e1b3c5fb..a55c4982e 100644 --- a/databricks/sdk/service/ml.py +++ b/databricks/sdk/service/ml.py @@ -5069,6 +5069,10 @@ class MaterializedFeature: is_online: Optional[bool] = None """True if this is an online materialized feature. False if it is an offline materialized feature.""" + job_id: Optional[int] = None + """The ID of the job that materializes the feature. This is present for both batch and streaming + features.""" + last_materialization_time: Optional[str] = None """The timestamp when the pipeline last ran and updated the materialized feature values. If the pipeline has not run yet, this field will be null.""" @@ -5086,6 +5090,10 @@ class MaterializedFeature: online_store_config: Optional[OnlineStoreConfig] = None """Destination for writing feature values to an online Lakebase table.""" + pipeline_id: Optional[str] = None + """The ID of the pipeline that materializes this feature. This is only present for streaming + features.""" + pipeline_schedule_state: Optional[MaterializedFeaturePipelineScheduleState] = None """The schedule state of the materialization pipeline. Hidden from GraphQL: being deprecated, so not exposed to Catalog Explorer.""" @@ -5124,6 +5132,8 @@ def as_dict(self) -> dict: body["feature_name"] = self.feature_name if self.is_online is not None: body["is_online"] = self.is_online + if self.job_id is not None: + body["job_id"] = self.job_id if self.last_materialization_time is not None: body["last_materialization_time"] = self.last_materialization_time if self.latest_backfill_operation is not None: @@ -5134,6 +5144,8 @@ def as_dict(self) -> dict: body["offline_store_config"] = self.offline_store_config.as_dict() if self.online_store_config: body["online_store_config"] = self.online_store_config.as_dict() + if self.pipeline_id is not None: + body["pipeline_id"] = self.pipeline_id if self.pipeline_schedule_state is not None: body["pipeline_schedule_state"] = self.pipeline_schedule_state.value if self.streaming_mode: @@ -5159,6 +5171,8 @@ def as_shallow_dict(self) -> dict: body["feature_name"] = self.feature_name if self.is_online is not None: body["is_online"] = self.is_online + if self.job_id is not None: + body["job_id"] = self.job_id if self.last_materialization_time is not None: body["last_materialization_time"] = self.last_materialization_time if self.latest_backfill_operation is not None: @@ -5169,6 +5183,8 @@ def as_shallow_dict(self) -> dict: body["offline_store_config"] = self.offline_store_config if self.online_store_config: body["online_store_config"] = self.online_store_config + if self.pipeline_id is not None: + body["pipeline_id"] = self.pipeline_id if self.pipeline_schedule_state is not None: body["pipeline_schedule_state"] = self.pipeline_schedule_state if self.streaming_mode: @@ -5190,11 +5206,13 @@ def from_dict(cls, d: Dict[str, Any]) -> MaterializedFeature: cron_schedule_trigger=_from_dict(d, "cron_schedule_trigger", CronSchedule), feature_name=d.get("feature_name", None), is_online=d.get("is_online", None), + job_id=_int64(d, "job_id"), last_materialization_time=d.get("last_materialization_time", None), latest_backfill_operation=d.get("latest_backfill_operation", None), materialized_feature_id=d.get("materialized_feature_id", None), offline_store_config=_from_dict(d, "offline_store_config", OfflineStoreConfig), online_store_config=_from_dict(d, "online_store_config", OnlineStoreConfig), + pipeline_id=d.get("pipeline_id", None), pipeline_schedule_state=_enum(d, "pipeline_schedule_state", MaterializedFeaturePipelineScheduleState), streaming_mode=_from_dict(d, "streaming_mode", StreamingMode), table_name=d.get("table_name", None), diff --git a/databricks/sdk/version.py b/databricks/sdk/version.py index 193f98ee8..5a6bb17fb 100644 --- a/databricks/sdk/version.py +++ b/databricks/sdk/version.py @@ -1 +1 @@ -__version__ = "0.142.0" +__version__ = "0.143.0" diff --git a/tests/test_runtime.py b/tests/test_runtime.py index b509f27b8..9e1dca843 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -1,6 +1,8 @@ """Tests for the import-time behavior of ``databricks.sdk.runtime``.""" +import subprocess import sys +import textwrap import types import pytest @@ -60,3 +62,39 @@ def test_workspace_client_constructs_on_spark_connect(spark_connect_runtime, con ws = WorkspaceClient(config=config) assert ws is not None + + +def test_import_does_not_configure_root_logger(): + """Importing the module must not install a handler on the root logger. + + The module logs from its import-time global-init blocks. Routing those through the + root ``logging.()`` functions calls ``logging.basicConfig()`` while the root + logger has no handler, which installs one -- so a downstream ``logging.basicConfig()`` + silently becomes a no-op and the importer's logs vanish. Run in a subprocess with a + clean root logger, since this process has already imported the module (and configured + logging), and the module is import-cached. + """ + check = textwrap.dedent( + """ + import logging + import sys + + # databricks-connect is installed here; force its import in the OSS fallback to fail + # fast so the block logs via the ImportError path instead of trying to open a session. + sys.modules["databricks.connect"] = None + + assert not logging.getLogger().handlers, "precondition: root logger starts clean" + try: + import databricks.sdk.runtime # noqa: F401 + except Exception: + # The fallback ends by building RemoteDbUtils()/Config, which can fail or block on + # host resolution without credentials. That runs *after* the import-time global-init + # logging this test guards, so tolerate it -- we only assert the root logger was + # left untouched by that earlier logging. + pass + handlers = logging.getLogger().handlers + assert not handlers, f"import configured the root logger: {handlers!r}" + """ + ) + result = subprocess.run([sys.executable, "-c", check], capture_output=True, text=True) + assert result.returncode == 0, f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"