Skip to content

Commit fbd4d1c

Browse files
committed
feat: add experimental uv support
1 parent eb1da48 commit fbd4d1c

File tree

6 files changed

+83
-24
lines changed

6 files changed

+83
-24
lines changed

samcli/commands/_utils/experimental.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class ExperimentalFlag:
5555
)
5656
}
5757
RustCargoLambda = ExperimentalEntry("experimentalCargoLambda", EXPERIMENTAL_ENV_VAR_PREFIX + "RUST_CARGO_LAMBDA")
58+
UvPackageManager = ExperimentalEntry(
59+
"experimentalUvPackageManager", EXPERIMENTAL_ENV_VAR_PREFIX + "UV_PACKAGE_MANAGER"
60+
)
5861

5962

6063
def is_experimental_enabled(config_entry: ExperimentalEntry) -> bool:

samcli/lib/build/workflow_config.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
from typing import Dict, List, Optional, Union, cast
88

9+
from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled
910
from samcli.lib.build.workflows import (
1011
CONFIG,
1112
DOTNET_CLIPACKAGE_CONFIG,
@@ -17,6 +18,7 @@
1718
NODEJS_NPM_ESBUILD_CONFIG,
1819
PROVIDED_MAKE_CONFIG,
1920
PYTHON_PIP_CONFIG,
21+
PYTHON_UV_CONFIG,
2022
RUBY_BUNDLER_CONFIG,
2123
RUST_CARGO_LAMBDA_CONFIG,
2224
)
@@ -157,14 +159,26 @@ def get_workflow_config(
157159
"rust-cargolambda": BasicWorkflowSelector(RUST_CARGO_LAMBDA_CONFIG),
158160
}
159161

162+
use_uv = is_experimental_enabled(ExperimentalFlag.UvPackageManager)
163+
160164
selectors_by_runtime = {
161-
"python3.8": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
162-
"python3.9": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
163-
"python3.10": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
164-
"python3.11": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
165-
"python3.12": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
166-
"python3.13": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
167-
"python3.14": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
165+
"python3.8": BasicWorkflowSelector([PYTHON_PIP_CONFIG]),
166+
"python3.9": BasicWorkflowSelector([PYTHON_PIP_CONFIG]),
167+
"python3.10": ConditionalWorkflowSelector(
168+
default=PYTHON_PIP_CONFIG, alternative=PYTHON_UV_CONFIG, use_alternative=use_uv
169+
),
170+
"python3.11": ConditionalWorkflowSelector(
171+
default=PYTHON_PIP_CONFIG, alternative=PYTHON_UV_CONFIG, use_alternative=use_uv
172+
),
173+
"python3.12": ConditionalWorkflowSelector(
174+
default=PYTHON_PIP_CONFIG, alternative=PYTHON_UV_CONFIG, use_alternative=use_uv
175+
),
176+
"python3.13": ConditionalWorkflowSelector(
177+
default=PYTHON_PIP_CONFIG, alternative=PYTHON_UV_CONFIG, use_alternative=use_uv
178+
),
179+
"python3.14": ConditionalWorkflowSelector(
180+
default=PYTHON_PIP_CONFIG, alternative=PYTHON_UV_CONFIG, use_alternative=use_uv
181+
),
168182
"nodejs16.x": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
169183
"nodejs18.x": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
170184
"nodejs20.x": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
@@ -335,3 +349,17 @@ def get_config(self, code_dir: str, project_dir: str) -> CONFIG:
335349
@staticmethod
336350
def _has_manifest(config: CONFIG, directory: str) -> bool:
337351
return os.path.exists(os.path.join(directory, config.manifest_name))
352+
353+
354+
class ConditionalWorkflowSelector(BasicWorkflowSelector):
355+
"""
356+
Selects between two workflow configs based on a condition
357+
"""
358+
359+
def __init__(self, default: CONFIG, alternative: CONFIG, use_alternative: bool = False):
360+
self.default = default
361+
self.alternative = alternative
362+
self.use_alternative = use_alternative
363+
364+
def get_config(self, code_dir: str, project_dir: str) -> CONFIG:
365+
return self.alternative if self.use_alternative else self.default

samcli/lib/build/workflows.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424
must_mount_with_write_in_container=False,
2525
)
2626

27+
PYTHON_UV_CONFIG = CONFIG(
28+
language="python",
29+
dependency_manager="uv",
30+
application_framework=None,
31+
manifest_name="pyproject.toml",
32+
executable_search_paths=None,
33+
must_mount_with_write_in_container=False,
34+
)
35+
2736
NODEJS_NPM_CONFIG = CONFIG(
2837
language="nodejs",
2938
dependency_manager="npm",
@@ -117,6 +126,7 @@
117126

118127
ALL_CONFIGS: List[CONFIG] = [
119128
PYTHON_PIP_CONFIG,
129+
PYTHON_UV_CONFIG,
120130
NODEJS_NPM_CONFIG,
121131
RUBY_BUNDLER_CONFIG,
122132
JAVA_GRADLE_CONFIG,

schema/samcli.json

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,11 +2202,12 @@
22022202
"title": "parameter_overrides",
22032203
"type": [
22042204
"array",
2205+
"object",
22052206
"string"
22062207
],
22072208
"description": "String that contains AWS CloudFormation parameter overrides encoded as key=value pairs.",
22082209
"items": {
2209-
"type": "string"
2210+
"$ref": "#/$defs/parameter_overrides_items"
22102211
}
22112212
},
22122213
"stack_name": {
@@ -2271,18 +2272,6 @@
22712272
"description": "Available parameters for the list stack outputs command:\n* stack_name:\nName of corresponding deployed stack.\n* output:\nOutput the results from the command in a given output format (json or table).\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
22722273
"type": "object",
22732274
"properties": {
2274-
"parameter_overrides": {
2275-
"title": "parameter_overrides",
2276-
"type": [
2277-
"array",
2278-
"object",
2279-
"string"
2280-
],
2281-
"description": "String that contains AWS CloudFormation parameter overrides encoded as key=value pairs.",
2282-
"items": {
2283-
"$ref": "#/$defs/parameter_overrides_items"
2284-
}
2285-
},
22862275
"stack_name": {
22872276
"title": "stack_name",
22882277
"type": "string",

tests/unit/commands/_utils/test_experimental.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ def test_set_experimental(self):
5757
self.gc_mock.return_value.set_value.assert_called_once_with(config_entry, False, is_flag=True, flush=False)
5858

5959
def test_get_all_experimental(self):
60-
self.assertEqual(len(get_all_experimental()), 5)
60+
self.assertEqual(len(get_all_experimental()), 6)
6161

6262
def test_get_all_experimental_statues(self):
63-
self.assertEqual(len(get_all_experimental_statues()), 5)
63+
self.assertEqual(len(get_all_experimental_statues()), 6)
6464

6565
def test_get_all_experimental_env_vars(self):
66-
self.assertEqual(len(get_all_experimental_env_vars()), 5)
66+
self.assertEqual(len(get_all_experimental_env_vars()), 6)
6767

6868
def test_get_enabled_experimental_flags(self):
69-
self.assertEqual(len(get_enabled_experimental_flags()), 5)
69+
self.assertEqual(len(get_enabled_experimental_flags()), 6)
7070

7171
@patch("samcli.commands._utils.experimental.set_experimental")
7272
@patch("samcli.commands._utils.experimental.get_all_experimental")

tests/unit/lib/build_module/test_workflow_config.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
get_workflow_config,
77
UnsupportedRuntimeException,
88
UnsupportedBuilderException,
9+
ConditionalWorkflowSelector,
910
)
11+
from samcli.lib.build.workflows import PYTHON_PIP_CONFIG, PYTHON_UV_CONFIG
1012
from samcli.lib.telemetry.event import Event, EventTracker
1113

1214

@@ -178,3 +180,30 @@ def test_must_raise_for_unsupported_runtimes(self):
178180
get_workflow_config(runtime, self.code_dir, self.project_dir)
179181

180182
self.assertEqual(str(ctx.exception), "'foobar' runtime is not supported")
183+
184+
185+
class TestPythonUvWorkflowConfig(TestCase):
186+
def setUp(self):
187+
self.code_dir = ""
188+
self.project_dir = ""
189+
EventTracker.clear_trackers()
190+
191+
@patch("samcli.lib.build.workflow_config.is_experimental_enabled")
192+
def test_python_uses_pip_when_uv_experimental_disabled(self, mock_experimental):
193+
mock_experimental.return_value = False
194+
195+
result = get_workflow_config("python3.11", self.code_dir, self.project_dir)
196+
197+
self.assertEqual(result.language, "python")
198+
self.assertEqual(result.dependency_manager, "pip")
199+
self.assertEqual(result.manifest_name, "requirements.txt")
200+
201+
@patch("samcli.lib.build.workflow_config.is_experimental_enabled")
202+
def test_python_uses_uv_when_experimental_enabled(self, mock_experimental):
203+
mock_experimental.return_value = True
204+
205+
result = get_workflow_config("python3.11", self.code_dir, self.project_dir)
206+
207+
self.assertEqual(result.language, "python")
208+
self.assertEqual(result.dependency_manager, "uv")
209+
self.assertEqual(result.manifest_name, "pyproject.toml")

0 commit comments

Comments
 (0)