Skip to content

Commit 8c0b692

Browse files
committed
Add native_tls option to Pythonx.uv_init/2 (#40)
1 parent 1398ea9 commit 8c0b692

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed

lib/pythonx.ex

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ defmodule Pythonx do
4545
]
4646
""")
4747
48+
In some environments, you may need to pass additional flags to the `uv sync`
49+
command. For example, in corporate environments with strict security policies,
50+
you might need to use native TLS:
51+
52+
Pythonx.uv_init(
53+
"""
54+
[project]
55+
name = "project"
56+
version = "0.0.0"
57+
requires-python = "==3.13.*"
58+
dependencies = [
59+
"numpy==2.2.2"
60+
]
61+
""",
62+
native_tls: true
63+
)
64+
4865
For more configuration options, refer to the [uv documentation](https://docs.astral.sh/uv/concepts/projects/dependencies/).
4966
5067
## Options
@@ -54,10 +71,14 @@ defmodule Pythonx do
5471
* `:uv_version` - select the version of the uv package manager to use.
5572
Defaults to `#{inspect(Pythonx.Uv.default_uv_version())}`.
5673
74+
* `:native_tls` - if true, uses the system's native TLS implementation instead
75+
of vendored rustls. This is useful in corporate environments where the system
76+
certificate store must be used. Defaults to `false`.
77+
5778
'''
5879
@spec uv_init(String.t(), keyword()) :: :ok
5980
def uv_init(pyproject_toml, opts \\ []) when is_binary(pyproject_toml) and is_list(opts) do
60-
opts = Keyword.validate!(opts, force: false, uv_version: Pythonx.Uv.default_uv_version())
81+
opts = Keyword.validate!(opts, force: false, uv_version: Pythonx.Uv.default_uv_version(), native_tls: false)
6182

6283
Pythonx.Uv.fetch(pyproject_toml, false, opts)
6384
install_paths = Pythonx.Uv.init(pyproject_toml, false, Keyword.take(opts, [:uv_version]))

lib/pythonx/uv.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Pythonx.Uv do
1010
"""
1111
@spec fetch(String.t(), boolean(), keyword()) :: :ok
1212
def fetch(pyproject_toml, priv?, opts \\ []) do
13-
opts = Keyword.validate!(opts, force: false, uv_version: default_uv_version())
13+
opts = Keyword.validate!(opts, force: false, uv_version: default_uv_version(), native_tls: false)
1414

1515
project_dir = project_dir(pyproject_toml, priv?, opts[:uv_version])
1616
python_install_dir = python_install_dir(priv?, opts[:uv_version])
@@ -28,7 +28,10 @@ defmodule Pythonx.Uv do
2828
File.write!(Path.join(project_dir, "pyproject.toml"), pyproject_toml)
2929

3030
# We always use uv-managed Python, so the paths are predictable.
31-
if run!(["sync", "--managed-python", "--no-config"],
31+
base_args = ["sync", "--managed-python", "--no-config"]
32+
uv_args = if opts[:native_tls], do: base_args ++ ["--native-tls"], else: base_args
33+
34+
if run!(uv_args,
3235
cd: project_dir,
3336
env: %{"UV_PYTHON_INSTALL_DIR" => python_install_dir},
3437
uv_version: opts[:uv_version]

test/pythonx_test.exs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,56 @@ defmodule PythonxTest do
477477
end
478478
end
479479

480+
describe "uv_init/2 native_tls option" do
481+
test "accepts native_tls option" do
482+
# Test that native_tls is recognized as a valid option
483+
opts = Keyword.validate!([native_tls: true, force: false],
484+
force: false,
485+
uv_version: Pythonx.Uv.default_uv_version(),
486+
native_tls: false)
487+
488+
assert opts[:native_tls] == true
489+
assert opts[:force] == false
490+
end
491+
492+
test "defaults native_tls to false" do
493+
opts = Keyword.validate!([],
494+
force: false,
495+
uv_version: Pythonx.Uv.default_uv_version(),
496+
native_tls: false)
497+
498+
assert opts[:native_tls] == false
499+
end
500+
501+
test "native_tls true adds --native-tls flag to uv command" do
502+
# Simulate how Pythonx.Uv.fetch constructs the uv command arguments
503+
base_args = ["sync", "--managed-python", "--no-config"]
504+
native_tls = true
505+
506+
uv_args = if native_tls, do: base_args ++ ["--native-tls"], else: base_args
507+
508+
assert uv_args == ["sync", "--managed-python", "--no-config", "--native-tls"]
509+
end
510+
511+
test "native_tls false does not add --native-tls flag" do
512+
base_args = ["sync", "--managed-python", "--no-config"]
513+
native_tls = false
514+
515+
uv_args = if native_tls, do: base_args ++ ["--native-tls"], else: base_args
516+
517+
assert uv_args == ["sync", "--managed-python", "--no-config"]
518+
end
519+
520+
test "raises error for unknown options" do
521+
assert_raise ArgumentError, ~r/unknown keys/, fn ->
522+
Keyword.validate!([unknown_option: true],
523+
force: false,
524+
uv_version: Pythonx.Uv.default_uv_version(),
525+
native_tls: false)
526+
end
527+
end
528+
end
529+
480530
defp repr(object) do
481531
assert %Pythonx.Object{} = object
482532

0 commit comments

Comments
 (0)