Skip to content

Commit

Permalink
validate uv options
Browse files Browse the repository at this point in the history
Signed-off-by: hjiang <hjiang@anyscale.com>
  • Loading branch information
dentiny committed Oct 31, 2024
1 parent f0efe18 commit 97bdec9
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
17 changes: 17 additions & 0 deletions python/ray/_private/runtime_env/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

package(default_visibility = ["//visibility:public"])

py_library(
name = "validation",
srcs = ["validation.py"],
)

py_test(
name = "validation_test",
srcs = ["validation_test.py"],
tags = ["team:core"],
deps = [
":validation",
],
)
61 changes: 61 additions & 0 deletions python/ray/_private/runtime_env/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,66 @@ def parse_and_validate_conda(conda: Union[str, dict]) -> Union[str, dict]:
return result


# TODO(hjiang): More package installation options to implement:
# 1. Allow users to pass in a local requirements.txt file, which relates to all
# packages to install;
# 2. Allow specific version of `uv` to use; as of now we only use latest version.
def parse_and_validate_uv(uv: Union[str, List[str], Dict]) -> Optional[Dict]:
"""Parses and validates a user-provided 'uv' option.
The value of the input 'uv' field can be one of two cases:
1) A List[str] describing the requirements. This is passed through.
Example usage: "packages":["tensorflow", "requests"]
2) A python dictionary that has three fields:
a) packages (required, List[str]): a list of uv packages, it same as 1).
The returned parsed value will be a list of packages. If a Ray library
(e.g. "ray[serve]") is specified, it will be deleted and replaced by its
dependencies (e.g. "uvicorn", "requests").
"""
assert uv is not None
if sys.platform == "win32":
logger.warning(
"runtime environment support is experimental on Windows. "
"If you run into issues please file a report at "
"https://github.com/ray-project/ray/issues."
)

result: str = ""
if isinstance(uv, list) and all(isinstance(dep, str) for dep in uv):
result = dict(packages=uv)
elif isinstance(uv, dict):
if set(uv.keys()) - {"packages"}:
raise ValueError(
"runtime_env['uv'] can only have these fields: "
"packages, but got: "
f"{list(uv.keys())}"
)
if "packages" not in uv:
raise ValueError(
f"runtime_env['uv'] must include field 'packages', but got {uv}"
)

result = uv.copy()
if not isinstance(uv["packages"], list):
raise ValueError(
"runtime_env['uv']['packages'] must be of type list, "
f"got: {type(uv['packages'])}"
)
else:
raise TypeError(
"runtime_env['uv'] must be of type " f"List[str], got {type(uv)}"
)

# Deduplicate packages for package lists.
result["packages"] = list(OrderedDict.fromkeys(result["packages"]))

if len(result["packages"]) == 0:
result = None
logger.debug(f"Rewrote runtime_env `uv` field from {uv} to {result}.")
return result


def parse_and_validate_pip(pip: Union[str, List[str], Dict]) -> Optional[Dict]:
"""Parses and validates a user-provided 'pip' option.
Expand Down Expand Up @@ -280,6 +340,7 @@ def parse_and_validate_env_vars(env_vars: Dict[str, str]) -> Optional[Dict[str,
"excludes": parse_and_validate_excludes,
"conda": parse_and_validate_conda,
"pip": parse_and_validate_pip,
"uv": parse_and_validate_uv,
"env_vars": parse_and_validate_env_vars,
"container": parse_and_validate_container,
}
36 changes: 36 additions & 0 deletions python/ray/_private/runtime_env/validation_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from python.ray._private.runtime_env import validation

import unittest


class TestVaidation(unittest.TestCase):
def test_parse_and_validate_uv(self):
# Valid case w/o duplication.
result = validation.parse_and_validate_uv({"packages": ["tensorflow"]})
self.assertEqual(result, {"packages": ["tensorflow"]})

# Valid case w/ duplication.
result = validation.parse_and_validate_uv(
{"packages": ["tensorflow", "tensorflow"]}
)
self.assertEqual(result, {"packages": ["tensorflow"]})

# Valid case, use `list` to represent necessary packages.
result = validation.parse_and_validate_uv(
["requests==1.0.0", "aiohttp", "ray[serve]"]
)
self.assertEqual(
result, {"packages": ["requests==1.0.0", "aiohttp", "ray[serve]"]}
)

# Invalid case, `str` is not supported for now.
with self.assertRaises(TypeError) as _:
result = validation.parse_and_validate_uv("./requirements.txt")

# Invalid case, unsupport keys.
with self.assertRaises(ValueError) as _:
result = validation.parse_and_validate_uv({"random_key": "random_value"})


if __name__ == "__main__":
unittest.main()

0 comments on commit 97bdec9

Please sign in to comment.