Skip to content

Add orthogonal setup-local flags: --no-constraints and --no-dbconnect - #6464

Open
rugpanov wants to merge 6 commits into
mainfrom
setup-local/orthogonal-flags
Open

Add orthogonal setup-local flags: --no-constraints and --no-dbconnect#6464
rugpanov wants to merge 6 commits into
mainfrom
setup-local/orthogonal-flags

Conversation

@rugpanov

@rugpanov rugpanov commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Why

databricks environments setup-local had a single --constraints-only mode that
bundled "skip databricks-connect" together with the rest of the setup. Callers
need to control the setup axes independently — skip the remote pins, or skip the
databricks-connect dependency — so the flags that control them should be
orthogonal.

What

Two orthogonal, composable negative flags:

  • --no-constraints — skip writing the remote Python-version and dependency
    pins (requires-python and the [tool.uv] constraint block). Existing values
    are left untouched, and (when provisioning) the resolved Python is still
    installed — the flag only governs what is written.
  • --no-dbconnect — skip the databricks-connect dependency. Equivalent to
    the existing --constraints-only, which is left in place for now (either flag
    triggers the same behaviour).

Both axes are threaded explicitly through the merge as a MergeOptions
struct (SkipConstraints / SkipDBConnect) rather than inferred from the shape of
a value: the merge, fresh-project render, warning detector, and validate step all
gate on the flags, so they always see the real artifact values. This removes the
former signal values — a nil ConstraintDeps and an empty databricks-connect pin
are now plain data (treated identically to their non-empty forms), not a stand-in
for "leave this unmanaged". The zero-value MergeOptions manages every axis, so a
future axis is a new struct field rather than another positional parameter.

The setup-local telemetry event records a skip_constraints field so a
--no-constraints run is distinguishable from a plain one (mode only records
the databricks-connect axis). The matching field must be added to the server-side
lumberjack proto for the value to be captured.

Note on --no-constraints + provisioning

--no-constraints governs only what is written. A provisioning run still installs
and validates the resolved Python, so if a user's kept requires-python is
disjoint from the target, uv surfaces it as a normal E_PROVISION rather than the
command guessing an alternative.

Backward compatibility

Default runs (no new flags) produce byte-for-byte identical output and behaviour
— every existing acceptance golden is unchanged except the refreshed --help
listing.

Testing

  • Unit (libs/localenv): each axis leaves existing values untouched and omits
    them greenfield; the merge, fresh render, and warning detector skip the
    constraint / databricks-connect regions on the corresponding MergeOptions flag
    even when the Constraints carry values; a nil ConstraintDeps writes an
    empty managed block when constraints are not skipped.
  • Unit (cmd/environments): the telemetry event records skip_constraints.
  • Acceptance (acceptance/localenv): no-constraints and no-dbconnect
    goldens, plus the refreshed help output.
  • gofmt, go vet, and full go build ./... clean; rebased on latest main.

This pull request and its description were written by Isaac.

@rclarey rclarey left a comment

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.

Mostly LGTM, but some of the flag combinations have non-obvious consequences to me

  • --constraints-only + --no-constraints is nonsense, but I guess we don't want to remove --constraints-only for backwards compatibility?
  • is --no-constraints + --no-dbconnect + --no-provision meaningful? It seems this does some checks / fetches the constraints artifact but does nothing with it? Is there a case where someone would want all 3 together?

Comment thread libs/localenv/constraints.go Outdated
}

// Normalize a missing [tool.uv].constraint-dependencies to a non-nil empty
// slice. A nil ConstraintDeps is reserved as the --no-constraints "leave the

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 not thread the --no-constraints flag through so we don't need a special signal value? (same is true for other signal values used elsewhere)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — done.

*Why*

`databricks environments setup-local` had a single `--constraints-only` mode that
bundled "skip databricks-connect" with the rest of the setup. Callers need to
control the setup axes independently — skip the remote pins, or skip the
databricks-connect dependency — so the flags that control them should be
orthogonal.

*What*

Adds two orthogonal, composable negative flags:

- `--no-constraints` skips writing the remote Python-version and dependency pins
  (requires-python and the [tool.uv] constraint block); any existing values are
  left untouched, and provisioning still installs the resolved Python (the flag
  governs only what is written).
- `--no-dbconnect` skips the databricks-connect dependency. Equivalent to the
  existing `--constraints-only`, which stays as-is for now.

The `--no-constraints` "unmanaged" signal is a nil ConstraintDeps / empty
requires-python; parseConstraints normalizes a missing
[tool.uv].constraint-dependencies to a non-nil empty slice so that nil uniquely
means the flag, not merely an artifact that omits the section.

Default runs (no new flags) are byte-for-byte unchanged.

*Verification*

- Unit tests (libs/localenv): --no-constraints leaves existing pins untouched and
  omits them greenfield, and parseConstraints normalizes missing
  constraint-dependencies.
- Acceptance goldens: no-constraints and no-dbconnect, plus the refreshed help
  output.
- gofmt, go vet, and full go build ./... clean.

Co-authored-by: Isaac <no-reply@databricks.com>
@rugpanov
rugpanov force-pushed the setup-local/orthogonal-flags branch from 7d3ec5f to f9ffddd Compare September 3, 2026 15:24
@rugpanov rugpanov changed the title Add orthogonal setup-local flags (--no-constraints, --no-dbconnect, --no-provision) and a skipped phase status Add orthogonal setup-local flags: --no-constraints and --no-dbconnect Sep 3, 2026
rugpanov and others added 3 commits September 4, 2026 09:32
*Why*

Review feedback: the --no-constraints "leave the constraint block unmanaged"
intent was encoded by clearing effective.RequiresPython to "" and
effective.ConstraintDeps to nil, then having the merge, fresh render, and
warning detector infer "skip" from those shapes. That overloaded the values
(nil ConstraintDeps meant the flag, an empty slice meant "write an empty
block"), and only a parseConstraints normalization step kept the two apart.
Overloading a data value to carry a control signal is fragile — anyone
constructing a Constraints by hand, or refactoring the parse path, silently
re-collides the two and stops managing constraints with no test to catch it.

*What*

Thread an explicit skipConstraints bool from the pipeline into MergeManaged,
RenderFreshPyproject, and detectMergeWarnings; gate the requires-python and
[tool.uv] regions (and their two warnings) on it. The pipeline no longer
clears effective's values, so those functions always see the real artifact
values and the intent is unambiguous.

Consequences of removing the sentinel:
- parseConstraints no longer normalizes a missing constraint-dependencies to a
  non-nil empty slice; nil and empty now flow through identically.
- mergeToolUv drops its nil-guard so nil is treated like empty (both render an
  empty managed block); mergeRequiresPython drops its empty-value guard.

Behaviour is unchanged: default and --no-constraints output are byte-identical
(every acceptance golden passes untouched). This is an internal cleanup that
deletes the sentinel and its normalization.

*Verification*

- go build ./... and go vet ./libs/localenv/... ./cmd/environments/... clean.
- go test ./libs/localenv/... ./cmd/environments/... pass.
- go test ./acceptance -run TestAccept/localenv passes with goldens unchanged.
- New unit tests: MergeManaged/RenderFreshPyproject skip the regions on
  skipConstraints=true even when the Constraints carry values; a nil
  ConstraintDeps writes an empty managed block when not skipping;
  detectMergeWarnings suppresses the constraint warnings under skip. Verified
  each fails without the gate before it was added.

Co-authored-by: Isaac <no-reply@databricks.com>
*Why*

The rebase onto latest main picked up a stricter `validate_nextchanges.py`,
which now requires each fragment to be a single `* `-bullet line ending in a
period with a trailing PR link. The existing prose fragment failed that check,
breaking the `lint` and `preview` CI jobs.

*What*

Rewrite `.nextchanges/cli/setup-local-orthogonal-flags.md` as one bullet line
with the `([#6464](...))` trailing PR link. No content change to the described
behaviour.

*Verification*

- PR_NUMBER=6464 python3 tools/validate_nextchanges.py exits 0.

Co-authored-by: Isaac <no-reply@databricks.com>
*Why*

Review feedback: `--no-constraints` was threaded into the pipeline but never
surfaced in `SetupLocalEvent`. Because the flag leaves `Mode` at `default`
(Mode only records the databricks-connect axis), a `--no-constraints` run was
indistinguishable from a plain run in telemetry, so its adoption could not be
measured.

*What*

Add a `skip_constraints` bool to `SetupLocalEvent` (not omitempty, so a genuine
false is distinguishable from an older CLI that did not report it, matching the
existing `success`/`greenfield` fields). Carry it on `Result` as an internal-only
`json:"-"` field — like `PythonInterpreter` — so the telemetry event stays purely
Result-derived without expanding the `--output json` contract. The pipeline sets
it from `p.SkipConstraints`.

Note: `SetupLocalEvent` mirrors the server-side lumberjack proto; the matching
`skip_constraints` field must be added there for the value to be captured. Until
then the server ignores the unknown field (no harm), same as any CLI-first
telemetry addition.

*Verification*

- go build ./..., go vet, and gofmt clean.
- go test ./cmd/environments/... ./libs/localenv/... pass; new unit test asserts
  a --no-constraints run records skip_constraints=true and a plain run leaves it
  false (verified it fails without the mapping).
- go test ./acceptance -run TestAccept/localenv passes with goldens unchanged
  (the field is json:"-", so the JSON contract is untouched).

Co-authored-by: Isaac <no-reply@databricks.com>
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 03d88cf

Run: 33851362213

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 1 1 274 1222 5:33
💚​ aws windows 1 1 276 1220 4:25
💚​ azure linux 1 1 273 1222 5:32
💚​ azure windows 1 1 275 1220 4:06
💚​ gcp linux 1 1 274 1222 5:44
💚​ gcp windows 1 1 276 1220 4:03
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
Top 6 slowest tests (at least 2 minutes):
duration env testname
4:20 aws windows TestAccept
4:00 azure windows TestAccept
3:58 gcp windows TestAccept
3:53 aws linux TestAccept
3:51 azure linux TestAccept
3:50 gcp linux TestAccept

rugpanov and others added 2 commits September 4, 2026 13:21
…ect explicit

*Why*

The previous change threaded --no-constraints as a positional bool through
MergeManaged/RenderFreshPyproject/detectMergeWarnings. A positional bool per axis
does not scale: every new axis re-churns all ~75 call sites and yields
boolean-blind calls like MergeManaged(x, c, true, false). The remaining axis —
databricks-connect — was still driven by the empty-dbcPin signal value (exactly
the "other signal values elsewhere" the reviewer flagged), so it had the same
overloading problem --no-constraints just shed.

*What*

Introduce MergeOptions{SkipConstraints, SkipDBConnect bool}, passed to
MergeManaged, RenderFreshPyproject, and detectMergeWarnings. The zero value manages
every axis, so a future axis is a new field with no call-site churn, and each call
reads self-documentingly.

Thread SkipDBConnect explicitly everywhere the empty-dbcPin signal used to reach:
the merge gates the databricks-connect region on it, the fresh render emits an
empty dev group under it, the warning detector suppresses the databricks-connect /
standalone-pyspark warnings, planDBConnect returns the zero plan, and validate
skips the databricks-connect assertion and version reporting. The pipeline no
longer clears the pin — c carries the real artifact value throughout, and intent
is a flag, not an empty string. An empty databricks-connect value now means only
"the artifact has no pin" (a data no-op), never "skip".

Behaviour is unchanged: default, --no-constraints, and --no-dbconnect /
--constraints-only output are byte-identical (every acceptance golden passes
untouched).

*Verification*

- go build ./..., go vet, and gofmt clean.
- go test ./libs/localenv/... ./cmd/environments/... pass; new unit tests cover
  SkipDBConnect in the merge, fresh render, and warning detector (verified each
  fails without its gate before restoring).
- go test ./acceptance -run TestAccept/localenv passes with goldens unchanged.

Co-authored-by: Isaac <no-reply@databricks.com>
…h consistently

*Why*

Review follow-ups on the MergeOptions change:
- MergeManaged rejected a pyproject.toml without a [project] table even under
  --no-constraints, though [project] is required only to hold requires-python —
  which that mode does not write. A valid uv file with only dependency groups /
  tool config failed with E_MERGE for a reason that no longer applied. Confirmed
  uv itself syncs a [project]-less file, so relaxing the guard is safe end to end.
- planDBConnect and validate still took a bare skip bool, reintroducing the
  boolean-blind call sites MergeOptions was meant to remove.
- Several comments restated flag semantics already documented on MergeOptions.

*What*

- Gate the [project] guard on !opts.SkipConstraints, so a [project]-less file is
  merged for its other axes under --no-constraints instead of rejected.
- Pass MergeOptions through planDBConnect and validate instead of a bare bool.
- Trim comments that duplicated the MergeOptions field docs.

Behaviour is unchanged for every existing case (all have [project]); the only new
behaviour is that --no-constraints now succeeds on a [project]-less file.

*Verification*

- go build ./..., go vet, gofmt clean.
- go test ./libs/localenv/... ./cmd/environments/... pass; new unit test covers a
  [project]-less file merging under SkipConstraints (verified it fails with the
  guard ungated before restoring).
- go test ./acceptance -run TestAccept/localenv passes with goldens unchanged.

Co-authored-by: Isaac <no-reply@databricks.com>
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.

3 participants