Skip to content

Add public API regression guard for databricks.bundles.core - #6439

Open
Sankalp-Mittal wants to merge 2 commits into
mainfrom
sankalp-mittal/pydabs-public-api-guard
Open

Add public API regression guard for databricks.bundles.core#6439
Sankalp-Mittal wants to merge 2 commits into
mainfrom
sankalp-mittal/pydabs-public-api-guard

Conversation

@Sankalp-Mittal

@Sankalp-Mittal Sankalp-Mittal commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

A regression guard has been added to the public facing API's on PyDABs, this is done to test that auto generating the code does not change any public API's.

Why

The upcoming PyDABs codegen/wiring refactor (#6397) converts the hand-written databricks.bundles.core wiring — Resources, the *_mutator functions, the _ResourceType registry, __all__ — into generated code. Nothing today guards the typed public surface customers import and type-check against, so that refactor (or a future one) could silently drop a type hint, move a * keyword marker, rename a method, or change the export set.

This lands a golden snapshot of that surface on main first, freezing the known-good pre-refactor API. #6397 then merges main in and must reproduce the identical golden — that passing test is the proof the refactor preserved the public API. generate-check cannot do this: after the refactor regenerates the wiring it trivially passes ("checked-in == regenerated"); only a golden captured here, before the refactor, can show the new generated wiring equals the old hand-written surface.

What

An acceptance test at acceptance/bundle/python/public-api/ whose script runs a checked-in dump_public_api.py under uv, snapshotting databricks.bundles.core's __all__, every Resources.add_* signature and property, the *_mutator overloads, Variable/VariableOr*, the support types, and the _ResourceType.all() registry.

Notable design decisions

  • Types render by public short name (Variable[str], not databricks.bundles.core._variable.Variable). The refactor moves internal _-prefixed modules; a golden keyed on internal paths would false-fail even when the public API is unchanged. Public imports go through the core.__init__ re-exports, which is exactly what the golden pins.
  • Signatures are reconstructed from inspect.Signature so /, *, *args, **kwargs markers render explicitly and stably.
  • --python 3.11 is pinned so the golden is reproducible independent of the repo-wide UV_PYTHON minimum, and typing.get_overloads (3.11+) is available. Output verified identical on 3.11 and 3.12.
  • Runtime error strings are deliberately excluded — that's behavior, not the typed API.

This pull request and its description were written by Isaac.

@github-actions

Copy link
Copy Markdown
Contributor

Waiting for approval

Could not determine reviewers from git history.
Round-robin suggestion: @lennartkats-db

Eligible reviewers: @andrewnester, @anton-107, @denik, @janniklasrose, @lennartkats-db, @pietern, @shreyas-goenka

Suggestions based on git history. See OWNERS for ownership rules.

_bases() filtered out `object` but not underscore-prefixed private bases, so a
generated private base (e.g. _GeneratedResources introduced by the wiring
refactor) would surface in the golden as a false positive even though the public
contract is intact. Mirror _members()'s underscore filter. No-op for the current
golden on this branch, where Resources has no private base.

Co-authored-by: Isaac <no-reply@databricks.com>
# alongside this test and copied into the run dir).
# --python 3.11 is pinned deliberately: the snapshot must stay reproducible independent of the
# repo-wide UV_PYTHON minimum, and the dump uses typing.get_overloads (Python 3.11+).
uv run --python 3.11 -q $UV_ARGS python dump_public_api.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I feel like this should live in python/databricks_tests/ instead of acceptance/ ?
  2. why pin python version?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Sure that can be done, but I'm not sure what the advantage of that would be?
  2. to get the overloads present we need typing.get_overloads this requires python >= 3.11 and the default used is 3.10, pinning also ensures that the golden remains stable (although I'm not sure how it could change across versions still good to have this)

out.append("]")
out.append("")

for name in sorted(core.__all__):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have __all__? seems we are maintaining a list of exports of the module, when we can just use e.g. all = dir(core)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__all__ is defined in python/databricks/bundles/core/__init__.py and it is the list of stuff we want to publicly expose (it also acts as a guide for users on what they can use), on the other hand if we use dir(core) it just list everything that exists in the object, so it would also include submodules and other private attributes, so I don't think it makes sense to list them down

@janniklasrose janniklasrose Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it also acts as a guide for users on what they can use

__all__ should not be for users to consume - dunder variables are internal-only

it just list everything that exists in the object, so it would also include submodules and other private attributes

You can skip those from the snapshot, they aren't part of the stable public API in python by convention. Note:

>>> set(core.__all__) == set(x for x in dir(core) if not x.startswith('_'))
True

@Sankalp-Mittal Sankalp-Mittal Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__all__ should not be for users to consume - dunder variables are internal-only

Yes but __all__ does control what gets imported on from core import * so it makes sense to only keep these as the ones listed, if both the lists are same then, that's fine but I still don't know why we should prefer using dir(core)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants