Skip to content
Open
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
1 change: 1 addition & 0 deletions .nextchanges/cli/setup-local-orthogonal-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Add orthogonal `--no-constraints` and `--no-dbconnect` flags to `databricks environments setup-local`: `--no-constraints` skips writing the remote Python-version and dependency pins, and `--no-dbconnect` skips the databricks-connect dependency. ([#6464](https://github.com/databricks/cli/pull/6464))
2 changes: 2 additions & 0 deletions acceptance/localenv/help/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Flags:
--dry-run compute the plan without writing files or provisioning
-h, --help help for setup-local
--job-task string job task to use as the compute target, as <job-id>.<task-key> (the task key is required)
--no-constraints skip writing the remote Python version and dependency constraints
--no-dbconnect skip adding the databricks-connect dependency
--serverless-version string serverless version to use as the compute target (e.g. 5)

Global Flags:
Expand Down
2 changes: 2 additions & 0 deletions acceptance/localenv/no-constraints/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions acceptance/localenv/no-constraints/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

>>> [CLI] environments setup-local --serverless-version 4 --no-constraints --dry-run --output json
{
"schemaVersion": 1,
"command": "environments setup-local",
"ok": true,
"mode": "default",
"dryRun": true,
"compute": {
"source": "serverless",
"serverlessVersion": "v4",
"envKey": "serverless/serverless-v4"
},
"resolved": {
"pythonVersion": "3.12",
"dbconnectVersion": "17.2.0",
"artifactSource": "network"
},
"greenfield": false,
"plan": {
"wouldWrite": "[TEST_TMP_DIR]/pyproject.toml",
"wouldBackup": "[TEST_TMP_DIR]/pyproject.toml.bak",
"wouldInstallPython": "3.12",
"diff": "--- pyproject.toml\n+++ pyproject.toml.new\n@@ -3,4 +3,7 @@\n requires-python = \"\u003e=3.10\"\n \n [dependency-groups]\n-dev = [\"databricks-connect~=16.0\"]\n+dev = [\"databricks-connect~=17.2.0\"]\n+\n+[tool.databricks.environment]\n+environment_version = \"4\"\n"
},
"phases": [
{
"phase": "preflight",
"status": "ok"
},
{
"phase": "resolve",
"status": "ok"
},
{
"phase": "fetch",
"status": "ok"
},
{
"phase": "merge",
"status": "ok"
},
{
"phase": "provision",
"status": "ok"
},
{
"phase": "validate",
"status": "ok"
}
],
"warnings": [
{
"code": "W_DBCONNECT_PIN_OVERRIDDEN",
"message": "databricks-connect \"databricks-connect~=16.0\" is replaced by the environment's \"databricks-connect~=17.2.0\""
}
],
"error": null,
"durationMs": [DURATION_MS]
}
15 changes: 15 additions & 0 deletions acceptance/localenv/no-constraints/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# --no-constraints leaves the remote Python version and dependency pins
# unmanaged: the plan writes neither requires-python nor the [tool.uv] constraint
# block, and the user's existing requires-python is left untouched. The
# databricks-connect dependency (orthogonal to --no-constraints) is still managed.
# The JSON plan shows the diff.
cat > pyproject.toml <<'PY'
[project]
name = "demo"
requires-python = ">=3.10"

[dependency-groups]
dev = ["databricks-connect~=16.0"]
PY

trace $CLI environments setup-local --serverless-version 4 --no-constraints --dry-run --output json
23 changes: 23 additions & 0 deletions acceptance/localenv/no-constraints/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# The script writes pyproject.toml as the merge input; --dry-run leaves it unchanged.
Ignore = ["pyproject.toml"]

Env.DATABRICKS_LOCALENV_CONSTRAINT_SOURCE_URL_TEST_OVERRIDE = "$DATABRICKS_HOST"

[[Server]]
Pattern = "GET /serverless/serverless-v4/pyproject.toml"
Response.Body = '''
[project]
requires-python = ">=3.12"

[dependency-groups]
dev = ["databricks-connect~=17.2.0"]

[tool.uv]
constraint-dependencies = ["pyarrow<19"]
'''

[[Repls]]
Old = 'uv uv \S+(?: \([^)]+\))?'
New = 'uv [UV_VERSION]'
2 changes: 2 additions & 0 deletions acceptance/localenv/no-dbconnect/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions acceptance/localenv/no-dbconnect/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

>>> [CLI] environments setup-local --serverless-version 4 --no-dbconnect --dry-run --output json
{
"schemaVersion": 1,
"command": "environments setup-local",
"ok": true,
"mode": "constraints-only",
"dryRun": true,
"compute": {
"source": "serverless",
"serverlessVersion": "v4",
"envKey": "serverless/serverless-v4"
},
"resolved": {
"pythonVersion": "3.12",
"artifactSource": "network"
},
"greenfield": false,
"plan": {
"wouldWrite": "[TEST_TMP_DIR]/pyproject.toml",
"wouldBackup": "[TEST_TMP_DIR]/pyproject.toml.bak",
"wouldInstallPython": "3.12",
"diff": "--- pyproject.toml\n+++ pyproject.toml.new\n@@ -1,7 +1,17 @@\n [project]\n name = \"demo\"\n-requires-python = \"\u003e=3.10\"\n+requires-python = \"\u003e=3.12\"\n dependencies = [\"databricks-connect==15.1.*\"]\n \n [dependency-groups]\n dev = [\"databricks-connect~=16.0\"]\n+\n+[tool.databricks.environment]\n+environment_version = \"4\"\n+\n+# managed by databricks environments setup-local — do not edit\n+[tool.uv]\n+constraint-dependencies = [\n+ \"pyarrow\u003c19\",\n+]\n+# end managed by databricks environments setup-local\n"
},
"phases": [
{
"phase": "preflight",
"status": "ok"
},
{
"phase": "resolve",
"status": "ok"
},
{
"phase": "fetch",
"status": "ok"
},
{
"phase": "merge",
"status": "ok"
},
{
"phase": "provision",
"status": "ok"
},
{
"phase": "validate",
"status": "ok"
}
],
"warnings": [
{
"code": "W_REQUIRES_PYTHON_OVERRIDDEN",
"message": "requires-python \"\u003e=3.10\" is replaced by the environment's \"\u003e=3.12\""
}
],
"error": null,
"durationMs": [DURATION_MS]
}
15 changes: 15 additions & 0 deletions acceptance/localenv/no-dbconnect/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# --no-dbconnect is the orthogonal spelling of --constraints-only: it omits the
# databricks-connect dependency (mode "constraints-only") while still managing
# requires-python and the [tool.uv] constraints. Existing databricks-connect
# requirements the user already had are left untouched.
cat > pyproject.toml <<'PY'
[project]
name = "demo"
requires-python = ">=3.10"
dependencies = ["databricks-connect==15.1.*"]

[dependency-groups]
dev = ["databricks-connect~=16.0"]
PY

trace $CLI environments setup-local --serverless-version 4 --no-dbconnect --dry-run --output json
23 changes: 23 additions & 0 deletions acceptance/localenv/no-dbconnect/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# The script writes pyproject.toml as the merge input; --dry-run leaves it unchanged.
Ignore = ["pyproject.toml"]

Env.DATABRICKS_LOCALENV_CONSTRAINT_SOURCE_URL_TEST_OVERRIDE = "$DATABRICKS_HOST"

[[Server]]
Pattern = "GET /serverless/serverless-v4/pyproject.toml"
Response.Body = '''
[project]
requires-python = ">=3.12"

[dependency-groups]
dev = ["databricks-connect~=17.2.0"]

[tool.uv]
constraint-dependencies = ["pyarrow<19"]
'''

[[Repls]]
Old = 'uv uv \S+(?: \([^)]+\))?'
New = 'uv [UV_VERSION]'
2 changes: 1 addition & 1 deletion cmd/environments/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func renderResult(ctx context.Context, cmd *cobra.Command, res *libslocalenv.Res
return nil
}

// renderSuccess prints the friendly post-provision summary (DECO-27977).
// renderSuccess prints the friendly post-provision summary.
//
// It runs only on a non-dry-run success (renderResult returns earlier for JSON,
// failures, and dry runs), so res.VenvPath is always set: the validate phase — the
Expand Down
12 changes: 11 additions & 1 deletion cmd/environments/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func addComputeFlags(cmd *cobra.Command) {
cmd.Flags().String("serverless-version", "", "serverless version to use as the compute target (e.g. 5)")
cmd.Flags().String("job-task", "", "job task to use as the compute target, as <job-id>.<task-key> (the task key is required)")
cmd.Flags().Bool("constraints-only", false, "apply the Python version and constraints without adding the databricks-connect dependency")
// The negative flags (--no-constraints, --no-dbconnect) are orthogonal and
// compose. --no-dbconnect and the older --constraints-only are equivalent (both
// skip the databricks-connect dependency).
cmd.Flags().Bool("no-constraints", false, "skip writing the remote Python version and dependency constraints")
cmd.Flags().Bool("no-dbconnect", false, "skip adding the databricks-connect dependency")
cmd.Flags().Bool("dry-run", false, "compute the plan without writing files or provisioning")
// The mutual exclusivity of the target flags is enforced in the pipeline's
// preflight (as E_USAGE) rather than via cmd.MarkFlagsMutuallyExclusive, so
Expand Down Expand Up @@ -122,6 +127,8 @@ func runPipeline(cmd *cobra.Command) error {
serverless, _ := cmd.Flags().GetString("serverless-version")
jobTask, _ := cmd.Flags().GetString("job-task")
constraintsOnly, _ := cmd.Flags().GetBool("constraints-only")
noConstraints, _ := cmd.Flags().GetBool("no-constraints")
noDBConnect, _ := cmd.Flags().GetBool("no-dbconnect")
check, _ := cmd.Flags().GetBool("dry-run")

computeFlags := libslocalenv.ComputeFlags{
Expand All @@ -134,8 +141,10 @@ func runPipeline(cmd *cobra.Command) error {
// preflight, so a conflict is reported as E_USAGE through the phase/JSON
// contract rather than as a bare error here.

// --no-dbconnect is the orthogonal spelling of --constraints-only; either skips
// the databricks-connect dependency, which the pipeline models as the mode.
mode := libslocalenv.ModeDefault
if constraintsOnly {
if constraintsOnly || noDBConnect {
mode = libslocalenv.ModeConstraintsOnly
}

Expand Down Expand Up @@ -177,6 +186,7 @@ func runPipeline(cmd *cobra.Command) error {
p := &libslocalenv.Pipeline{
Mode: mode,
Check: check,
SkipConstraints: noConstraints,
ProjectDir: projectDir,
ConstraintBaseURL: constraintBaseURL,
CacheDir: cacheDir,
Expand Down
9 changes: 5 additions & 4 deletions cmd/environments/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ func buildSetupLocalEvent(res *libslocalenv.Result) *protos.SetupLocalEvent {
return nil
}
event := &protos.SetupLocalEvent{
Success: res.OK,
Mode: modeType(res.Mode),
Greenfield: res.Greenfield,
ComputeSource: protos.SetupLocalComputeSourceUnspecified,
Success: res.OK,
Mode: modeType(res.Mode),
SkipConstraints: res.SkipConstraints,
Greenfield: res.Greenfield,
ComputeSource: protos.SetupLocalComputeSourceUnspecified,
}
if res.Compute != nil {
event.ComputeSource = computeSourceType(res.Compute.Source)
Expand Down
21 changes: 21 additions & 0 deletions cmd/environments/telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ func TestBuildSetupLocalEvent(t *testing.T) {
}, got)
})

t.Run("no-constraints run records skipConstraints", func(t *testing.T) {
res := &libslocalenv.Result{
OK: true,
Mode: libslocalenv.ModeDefault.String(),
SkipConstraints: true,
Compute: &libslocalenv.ComputeInfo{Source: "cluster", EnvKey: "dbr/16.4"},
}
got := buildSetupLocalEvent(res)
assert.True(t, got.SkipConstraints)
})

t.Run("run without --no-constraints leaves skipConstraints false", func(t *testing.T) {
res := &libslocalenv.Result{
OK: true,
Mode: libslocalenv.ModeDefault.String(),
Compute: &libslocalenv.ComputeInfo{Source: "cluster", EnvKey: "dbr/16.4"},
}
got := buildSetupLocalEvent(res)
assert.False(t, got.SkipConstraints)
})

t.Run("nil compute leaves source and envKey empty", func(t *testing.T) {
res := &libslocalenv.Result{
OK: false,
Expand Down
7 changes: 5 additions & 2 deletions libs/localenv/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,11 @@ func parseConstraints(data []byte) (requiresPython, dbconnect string, deps []str
}
}

deps = p.Tool.UV.ConstraintDependencies
return requiresPython, dbconnect, deps, nil
// A missing [tool.uv].constraint-dependencies yields a nil slice, which the
// merge treats identically to an empty one (an empty managed block). Whether
// the constraint region is managed at all is decided by the --no-constraints
// flag, threaded explicitly, not by the shape of this value.
return requiresPython, dbconnect, p.Tool.UV.ConstraintDependencies, nil
}

// depNameSepRe matches the first PEP 508 delimiter that ends a requirement's
Expand Down
15 changes: 15 additions & 0 deletions libs/localenv/constraints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,18 @@ func TestFetchConstraintsUnusableBodyDoesNotPoisonCache(t *testing.T) {
assert.True(t, c.FromCache)
assert.Equal(t, "==3.12.*", c.RequiresPython)
}

func TestParseConstraintsMissingConstraintDepsIsValid(t *testing.T) {
// An artifact without [tool.uv].constraint-dependencies is valid; the parsed
// slice is empty. Its nil-vs-empty shape carries no meaning — whether the
// constraint region is managed is decided by the --no-constraints flag, threaded
// explicitly, so the merge treats nil and empty identically.
_, _, deps, err := parseConstraints([]byte(`[project]
requires-python = ">=3.12"

[dependency-groups]
dev = ["databricks-connect~=17.2.0"]
`))
require.NoError(t, err)
assert.Empty(t, deps)
}
Loading
Loading