runtime_env.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. import json
  2. import logging
  3. import os
  4. from copy import deepcopy
  5. from dataclasses import asdict, is_dataclass
  6. from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
  7. import ray
  8. from ray._private.ray_constants import DEFAULT_RUNTIME_ENV_TIMEOUT_SECONDS
  9. from ray._private.runtime_env.conda import get_uri as get_conda_uri
  10. from ray._private.runtime_env.default_impl import get_image_uri_plugin_cls
  11. from ray._private.runtime_env.pip import get_uri as get_pip_uri
  12. from ray._private.runtime_env.plugin_schema_manager import RuntimeEnvPluginSchemaManager
  13. from ray._private.runtime_env.uv import get_uri as get_uv_uri
  14. from ray._private.runtime_env.validation import (
  15. OPTION_TO_NO_PATH_VALIDATION_FN,
  16. OPTION_TO_VALIDATION_FN,
  17. )
  18. from ray._private.thirdparty.dacite import from_dict
  19. from ray.core.generated.runtime_environment_pb2 import (
  20. RuntimeEnvConfig as ProtoRuntimeEnvConfig,
  21. )
  22. from ray.util.annotations import PublicAPI
  23. logger = logging.getLogger(__name__)
  24. @PublicAPI(stability="stable")
  25. class RuntimeEnvConfig(dict):
  26. """Used to specify configuration options for a runtime environment.
  27. The config is not included when calculating the runtime_env hash,
  28. which means that two runtime_envs with the same options but different
  29. configs are considered the same for caching purposes.
  30. Args:
  31. setup_timeout_seconds: The timeout of runtime environment
  32. creation, timeout is in seconds. The value `-1` means disable
  33. timeout logic, except `-1`, `setup_timeout_seconds` cannot be
  34. less than or equal to 0. The default value of `setup_timeout_seconds`
  35. is 600 seconds.
  36. eager_install: Indicates whether to install the runtime environment
  37. on the cluster at `ray.init()` time, before the workers are leased.
  38. This flag is set to `True` by default.
  39. """
  40. known_fields: Set[str] = {"setup_timeout_seconds", "eager_install", "log_files"}
  41. _default_config: Dict = {
  42. "setup_timeout_seconds": DEFAULT_RUNTIME_ENV_TIMEOUT_SECONDS,
  43. "eager_install": True,
  44. "log_files": [],
  45. }
  46. def __init__(
  47. self,
  48. setup_timeout_seconds: int = DEFAULT_RUNTIME_ENV_TIMEOUT_SECONDS,
  49. eager_install: bool = True,
  50. log_files: Optional[List[str]] = None,
  51. ):
  52. super().__init__()
  53. if not isinstance(setup_timeout_seconds, int):
  54. raise TypeError(
  55. "setup_timeout_seconds must be of type int, "
  56. f"got: {type(setup_timeout_seconds)}"
  57. )
  58. elif setup_timeout_seconds <= 0 and setup_timeout_seconds != -1:
  59. raise ValueError(
  60. "setup_timeout_seconds must be greater than zero "
  61. f"or equals to -1, got: {setup_timeout_seconds}"
  62. )
  63. self["setup_timeout_seconds"] = setup_timeout_seconds
  64. if not isinstance(eager_install, bool):
  65. raise TypeError(
  66. f"eager_install must be a boolean. got {type(eager_install)}"
  67. )
  68. self["eager_install"] = eager_install
  69. if log_files is not None:
  70. if not isinstance(log_files, list):
  71. raise TypeError(
  72. "log_files must be a list of strings or None, got "
  73. f"{log_files} with type {type(log_files)}."
  74. )
  75. for file_name in log_files:
  76. if not isinstance(file_name, str):
  77. raise TypeError("Each item in log_files must be a string.")
  78. else:
  79. log_files = self._default_config["log_files"]
  80. self["log_files"] = log_files
  81. @staticmethod
  82. def parse_and_validate_runtime_env_config(
  83. config: Union[Dict, "RuntimeEnvConfig"]
  84. ) -> "RuntimeEnvConfig":
  85. if isinstance(config, RuntimeEnvConfig):
  86. return config
  87. elif isinstance(config, Dict):
  88. unknown_fields = set(config.keys()) - RuntimeEnvConfig.known_fields
  89. if len(unknown_fields):
  90. logger.warning(
  91. "The following unknown entries in the runtime_env_config "
  92. f"dictionary will be ignored: {unknown_fields}."
  93. )
  94. config_dict = dict()
  95. for field in RuntimeEnvConfig.known_fields:
  96. if field in config:
  97. config_dict[field] = config[field]
  98. return RuntimeEnvConfig(**config_dict)
  99. else:
  100. raise TypeError(
  101. "runtime_env['config'] must be of type dict or RuntimeEnvConfig, "
  102. f"got: {type(config)}"
  103. )
  104. @classmethod
  105. def default_config(cls):
  106. return RuntimeEnvConfig(**cls._default_config)
  107. def build_proto_runtime_env_config(self) -> ProtoRuntimeEnvConfig:
  108. runtime_env_config = ProtoRuntimeEnvConfig()
  109. runtime_env_config.setup_timeout_seconds = self["setup_timeout_seconds"]
  110. runtime_env_config.eager_install = self["eager_install"]
  111. if self["log_files"] is not None:
  112. runtime_env_config.log_files.extend(self["log_files"])
  113. return runtime_env_config
  114. @classmethod
  115. def from_proto(cls, runtime_env_config: ProtoRuntimeEnvConfig):
  116. setup_timeout_seconds = runtime_env_config.setup_timeout_seconds
  117. # Cause python class RuntimeEnvConfig has validate to avoid
  118. # setup_timeout_seconds equals zero, so setup_timeout_seconds
  119. # on RuntimeEnvConfig is zero means other Language(except python)
  120. # dosn't assign value to setup_timeout_seconds. So runtime_env_agent
  121. # assign the default value to setup_timeout_seconds.
  122. if setup_timeout_seconds == 0:
  123. setup_timeout_seconds = cls._default_config["setup_timeout_seconds"]
  124. return cls(
  125. setup_timeout_seconds=setup_timeout_seconds,
  126. eager_install=runtime_env_config.eager_install,
  127. log_files=list(runtime_env_config.log_files),
  128. )
  129. def to_dict(self) -> Dict:
  130. return dict(deepcopy(self))
  131. # Due to circular reference, field config can only be assigned a value here
  132. OPTION_TO_VALIDATION_FN[
  133. "config"
  134. ] = RuntimeEnvConfig.parse_and_validate_runtime_env_config
  135. @PublicAPI
  136. class RuntimeEnv(dict):
  137. """This class is used to define a runtime environment for a job, task,
  138. or actor.
  139. See :ref:`runtime-environments` for detailed documentation.
  140. This class can be used interchangeably with an unstructured dictionary
  141. in the relevant API calls.
  142. Can specify a runtime environment whole job, whether running a script
  143. directly on the cluster, using Ray Job submission, or using Ray Client:
  144. .. code-block:: python
  145. from ray.runtime_env import RuntimeEnv
  146. # Starting a single-node local Ray cluster
  147. ray.init(runtime_env=RuntimeEnv(...))
  148. .. code-block:: python
  149. from ray.runtime_env import RuntimeEnv
  150. # Connecting to remote cluster using Ray Client
  151. ray.init("ray://123.456.7.89:10001", runtime_env=RuntimeEnv(...))
  152. Can specify different runtime environments per-actor or per-task using
  153. ``.options()`` or the ``@ray.remote`` decorator:
  154. .. code-block:: python
  155. from ray.runtime_env import RuntimeEnv
  156. # Invoke a remote task that runs in a specified runtime environment.
  157. f.options(runtime_env=RuntimeEnv(...)).remote()
  158. # Instantiate an actor that runs in a specified runtime environment.
  159. actor = SomeClass.options(runtime_env=RuntimeEnv(...)).remote()
  160. # Specify a runtime environment in the task definition. Future invocations via
  161. # `g.remote()` use this runtime environment unless overridden by using
  162. # `.options()` as above.
  163. @ray.remote(runtime_env=RuntimeEnv(...))
  164. def g():
  165. pass
  166. # Specify a runtime environment in the actor definition. Future instantiations
  167. # via `MyClass.remote()` use this runtime environment unless overridden by
  168. # using `.options()` as above.
  169. @ray.remote(runtime_env=RuntimeEnv(...))
  170. class MyClass:
  171. pass
  172. Here are some examples of RuntimeEnv initialization:
  173. .. code-block:: python
  174. # Example for using conda
  175. RuntimeEnv(conda={
  176. "channels": ["defaults"], "dependencies": ["codecov"]})
  177. RuntimeEnv(conda="pytorch_p36") # Found on DLAMIs
  178. # Example for using container
  179. RuntimeEnv(
  180. container={"image": "anyscale/ray-ml:nightly-py38-cpu",
  181. "run_options": ["--cap-drop SYS_ADMIN","--log-level=debug"]})
  182. # Example for set env_vars
  183. RuntimeEnv(env_vars={"OMP_NUM_THREADS": "32", "TF_WARNINGS": "none"})
  184. # Example for set pip
  185. RuntimeEnv(
  186. pip={"packages":["tensorflow", "requests"], "pip_check": False,
  187. "pip_version": "==22.0.2;python_version=='3.8.11'"})
  188. # Example for using image_uri
  189. RuntimeEnv(
  190. image_uri="rayproject/ray:2.39.0-py312-cu123")
  191. Args:
  192. py_modules: List of local paths or remote URIs (either in the GCS or external
  193. storage), each of which is a zip file that Ray unpacks and
  194. inserts into the PYTHONPATH of the workers.
  195. working_dir: Local path or remote URI (either in the GCS or external storage) of a zip
  196. file that Ray unpacks in the directory of each task/actor.
  197. pip: Either a list of pip packages, a string
  198. containing the path to a pip requirements.txt file, or a Python
  199. dictionary that has three fields: 1) ``packages`` (required, List[str]): a
  200. list of pip packages, 2) ``pip_check`` (optional, bool): whether enable
  201. pip check at the end of pip install, defaults to False.
  202. 3) ``pip_version`` (optional, str): the version of pip, Ray prepends
  203. the package name "pip" in front of the ``pip_version`` to form the final
  204. requirement string, the syntax of a requirement specifier is defined in
  205. full in PEP 508.
  206. uv: Either a list of pip packages, or a Python dictionary that has one field:
  207. 1) ``packages`` (required, List[str]).
  208. conda: Either the conda YAML config, the name of a
  209. local conda env (e.g., "pytorch_p36"), or the path to a conda
  210. environment.yaml file.
  211. Ray automatically injects the dependency into the conda
  212. env to ensure compatibility with the cluster Ray. Ray may automatically
  213. mangle the conda name to avoid conflicts between runtime envs.
  214. This field can't be specified at the same time as the 'pip' field.
  215. To use pip with conda, specify your pip dependencies within
  216. the conda YAML config:
  217. https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually
  218. container: Require a given (Docker) container image,
  219. The Ray worker process runs in a container with this image.
  220. This parameter only works alone, or with the ``config`` or
  221. ``env_vars`` parameters.
  222. The `run_options` list spec is here:
  223. https://docs.docker.com/engine/reference/run/
  224. env_vars: Environment variables to set.
  225. worker_process_setup_hook: (Experimental) The setup hook that's
  226. called after workers start and before Tasks and Actors are scheduled.
  227. A module name (string type) or callable (function) can be passed.
  228. When a module name is passed, Ray worker should be able to access the
  229. module name. When a callable is passed, callable should be serializable.
  230. When a runtime env is specified by job submission API,
  231. only a module name (string) is allowed.
  232. nsight: Dictionary mapping nsight profile option name to it's value.
  233. rocprof_sys: Dictionary mapping rocprof-sys profile option name and environment
  234. variables to it's value.
  235. config: config for runtime environment. Either
  236. a dict or a RuntimeEnvConfig. Field: (1) setup_timeout_seconds, the
  237. timeout of runtime environment creation, timeout is in seconds.
  238. image_uri: URI to a container image. The Ray worker process runs
  239. in a container with this image. This parameter only works alone,
  240. or with the ``config`` or ``env_vars`` parameters.
  241. """
  242. known_fields: Set[str] = {
  243. "py_modules",
  244. "py_executable",
  245. "java_jars",
  246. "working_dir",
  247. "conda",
  248. "pip",
  249. "uv",
  250. "container",
  251. "excludes",
  252. "env_vars",
  253. "_ray_release",
  254. "_ray_commit",
  255. "_inject_current_ray",
  256. "config",
  257. "worker_process_setup_hook",
  258. "_nsight",
  259. "_rocprof_sys",
  260. "image_uri",
  261. }
  262. extensions_fields: Set[str] = {
  263. "_ray_release",
  264. "_ray_commit",
  265. "_inject_current_ray",
  266. }
  267. def __init__(
  268. self,
  269. *,
  270. py_modules: Optional[List[str]] = None,
  271. py_executable: Optional[str] = None,
  272. working_dir: Optional[str] = None,
  273. pip: Optional[List[str]] = None,
  274. conda: Optional[Union[Dict[str, str], str]] = None,
  275. container: Optional[Dict[str, str]] = None,
  276. env_vars: Optional[Dict[str, str]] = None,
  277. worker_process_setup_hook: Optional[Union[Callable, str]] = None,
  278. nsight: Optional[Union[str, Dict[str, str]]] = None,
  279. rocprof_sys: Optional[Union[str, Dict[str, Dict[str, str]]]] = None,
  280. config: Optional[Union[Dict, RuntimeEnvConfig]] = None,
  281. _validate: bool = True,
  282. image_uri: Optional[str] = None,
  283. uv: Optional[List[str]] = None,
  284. **kwargs,
  285. ):
  286. super().__init__()
  287. runtime_env = kwargs
  288. if py_modules is not None:
  289. runtime_env["py_modules"] = py_modules
  290. if py_executable is not None:
  291. runtime_env["py_executable"] = py_executable
  292. if working_dir is not None:
  293. runtime_env["working_dir"] = working_dir
  294. if pip is not None:
  295. runtime_env["pip"] = pip
  296. if uv is not None:
  297. runtime_env["uv"] = uv
  298. if conda is not None:
  299. runtime_env["conda"] = conda
  300. if nsight is not None:
  301. runtime_env["_nsight"] = nsight
  302. if rocprof_sys is not None:
  303. runtime_env["_rocprof_sys"] = rocprof_sys
  304. if container is not None:
  305. runtime_env["container"] = container
  306. if env_vars is not None:
  307. runtime_env["env_vars"] = env_vars
  308. if config is not None:
  309. runtime_env["config"] = config
  310. if worker_process_setup_hook is not None:
  311. runtime_env["worker_process_setup_hook"] = worker_process_setup_hook
  312. if image_uri is not None:
  313. runtime_env["image_uri"] = image_uri
  314. self.update(runtime_env)
  315. # Blindly trust that the runtime_env has already been validated.
  316. # This is dangerous and should only be used internally (e.g., on the
  317. # deserialization codepath.
  318. if not _validate:
  319. return
  320. if (self.get("conda") is not None) + (self.get("pip") is not None) + (
  321. self.get("uv") is not None
  322. ) > 1:
  323. raise ValueError(
  324. "The 'pip' field, 'uv' field, and 'conda' field of "
  325. "runtime_env cannot be specified at the same time.\n"
  326. f"specified pip field: {self.get('pip')}\n"
  327. f"specified conda field: {self.get('conda')}\n"
  328. f"specified uv field: {self.get('uv')}\n"
  329. "To use pip with conda, please only set the 'conda'"
  330. "field, and specify your pip dependencies within the conda YAML "
  331. "config dict: see https://conda.io/projects/conda/en/latest/"
  332. "user-guide/tasks/manage-environments.html"
  333. "#create-env-file-manually"
  334. )
  335. if self.get("container"):
  336. invalid_keys = set(runtime_env.keys()) - {"container", "config", "env_vars"}
  337. if len(invalid_keys):
  338. raise ValueError(
  339. "The 'container' field currently cannot be used "
  340. "together with other fields of runtime_env. "
  341. f"Specified fields: {invalid_keys}"
  342. )
  343. logger.warning(
  344. "The `container` runtime environment field is DEPRECATED and will be "
  345. "removed after July 31, 2025. Use `image_uri` instead. See "
  346. "https://docs.ray.io/en/latest/serve/advanced-guides/multi-app-container.html." # noqa
  347. )
  348. if self.get("image_uri"):
  349. image_uri_plugin_cls = get_image_uri_plugin_cls()
  350. invalid_keys = (
  351. set(runtime_env.keys()) - image_uri_plugin_cls.get_compatible_keys()
  352. )
  353. if len(invalid_keys):
  354. raise ValueError(
  355. "The 'image_uri' field currently cannot be used "
  356. "together with other fields of runtime_env. "
  357. f"Specified fields: {invalid_keys}"
  358. )
  359. for option, validate_fn in OPTION_TO_VALIDATION_FN.items():
  360. option_val = self.get(option)
  361. if option_val is not None:
  362. del self[option]
  363. self[option] = option_val
  364. if "_ray_commit" not in self:
  365. if self.get("pip") or self.get("conda"):
  366. self["_ray_commit"] = ray.__commit__
  367. # Used for testing wheels that have not yet been merged into master.
  368. # If this is set to True, then we do not inject Ray into the conda
  369. # or pip dependencies.
  370. if "_inject_current_ray" not in self:
  371. if "RAY_RUNTIME_ENV_LOCAL_DEV_MODE" in os.environ:
  372. self["_inject_current_ray"] = True
  373. # NOTE(architkulkarni): This allows worker caching code in C++ to check
  374. # if a runtime env is empty without deserializing it. This is a catch-
  375. # all; for validated inputs we won't set the key if the value is None.
  376. if all(val is None for val in self.values()):
  377. self.clear()
  378. def __setitem__(self, key: str, value: Any) -> None:
  379. if is_dataclass(value):
  380. jsonable_type = asdict(value)
  381. else:
  382. jsonable_type = value
  383. RuntimeEnvPluginSchemaManager.validate(key, jsonable_type)
  384. res_value = jsonable_type
  385. if key in RuntimeEnv.known_fields and key in OPTION_TO_VALIDATION_FN:
  386. res_value = OPTION_TO_VALIDATION_FN[key](jsonable_type)
  387. if res_value is None:
  388. return
  389. return super().__setitem__(key, res_value)
  390. def set(self, name: str, value: Any) -> None:
  391. self.__setitem__(name, value)
  392. def get(self, name, default=None, data_class=None):
  393. if name not in self:
  394. return default
  395. if not data_class:
  396. return self.__getitem__(name)
  397. else:
  398. return from_dict(data_class=data_class, data=self.__getitem__(name))
  399. @classmethod
  400. def deserialize(cls, serialized_runtime_env: str) -> "RuntimeEnv": # noqa: F821
  401. return cls(_validate=False, **json.loads(serialized_runtime_env))
  402. def serialize(self) -> str:
  403. # To ensure the accuracy of Proto, `__setitem__` can only guarantee the
  404. # accuracy of a certain field, not the overall accuracy
  405. runtime_env = type(self)(_validate=True, **self)
  406. return json.dumps(
  407. runtime_env,
  408. sort_keys=True,
  409. )
  410. def to_dict(self) -> Dict:
  411. runtime_env_dict = dict(deepcopy(self))
  412. # Replace strongly-typed RuntimeEnvConfig with a dict to allow the returned
  413. # dict to work properly as a field in a dataclass. Details in issue #26986
  414. if runtime_env_dict.get("config"):
  415. runtime_env_dict["config"] = runtime_env_dict["config"].to_dict()
  416. return runtime_env_dict
  417. def has_working_dir(self) -> bool:
  418. return self.get("working_dir") is not None
  419. def working_dir_uri(self) -> Optional[str]:
  420. return self.get("working_dir")
  421. def py_modules_uris(self) -> List[str]:
  422. if "py_modules" in self:
  423. return list(self["py_modules"])
  424. return []
  425. def conda_uri(self) -> Optional[str]:
  426. if "conda" in self:
  427. return get_conda_uri(self)
  428. return None
  429. def pip_uri(self) -> Optional[str]:
  430. if "pip" in self:
  431. return get_pip_uri(self)
  432. return None
  433. def uv_uri(self) -> Optional[str]:
  434. if "uv" in self:
  435. return get_uv_uri(self)
  436. return None
  437. def plugin_uris(self) -> List[str]:
  438. """Not implemented yet, always return a empty list"""
  439. return []
  440. def working_dir(self) -> str:
  441. return self.get("working_dir", "")
  442. def py_modules(self) -> List[str]:
  443. if "py_modules" in self:
  444. return list(self["py_modules"])
  445. return []
  446. def py_executable(self) -> Optional[str]:
  447. return self.get("py_executable", None)
  448. def java_jars(self) -> List[str]:
  449. if "java_jars" in self:
  450. return list(self["java_jars"])
  451. return []
  452. def nsight(self) -> Optional[Union[str, Dict[str, str]]]:
  453. return self.get("_nsight", None)
  454. def rocprof_sys(self) -> Optional[Union[str, Dict[str, Dict[str, str]]]]:
  455. return self.get("_rocprof_sys", None)
  456. def env_vars(self) -> Dict:
  457. return self.get("env_vars", {})
  458. def has_conda(self) -> str:
  459. if self.get("conda"):
  460. return True
  461. return False
  462. def conda_env_name(self) -> str:
  463. if not self.has_conda() or not isinstance(self["conda"], str):
  464. return None
  465. return self["conda"]
  466. def conda_config(self) -> str:
  467. if not self.has_conda() or not isinstance(self["conda"], dict):
  468. return None
  469. return json.dumps(self["conda"], sort_keys=True)
  470. def has_pip(self) -> bool:
  471. if self.get("pip"):
  472. return True
  473. return False
  474. def has_uv(self) -> bool:
  475. if self.get("uv"):
  476. return True
  477. return False
  478. def virtualenv_name(self) -> Optional[str]:
  479. if not self.has_pip() or not isinstance(self["pip"], str):
  480. return None
  481. return self["pip"]
  482. def pip_config(self) -> Dict:
  483. if not self.has_pip() or isinstance(self["pip"], str):
  484. return {}
  485. # Parse and validate field pip on method `__setitem__`
  486. self["pip"] = self["pip"]
  487. return self["pip"]
  488. def uv_config(self) -> Dict:
  489. if not self.has_uv() or isinstance(self["uv"], str):
  490. return {}
  491. # Parse and validate field pip on method `__setitem__`
  492. self["uv"] = self["uv"]
  493. return self["uv"]
  494. def get_extension(self, key) -> Optional[str]:
  495. if key not in RuntimeEnv.extensions_fields:
  496. raise ValueError(
  497. f"Extension key must be one of {RuntimeEnv.extensions_fields}, "
  498. f"got: {key}"
  499. )
  500. return self.get(key)
  501. def has_py_container(self) -> bool:
  502. if self.get("container"):
  503. return True
  504. return False
  505. def py_container_image(self) -> Optional[str]:
  506. if not self.has_py_container():
  507. return None
  508. return self["container"].get("image", "")
  509. def py_container_worker_path(self) -> Optional[str]:
  510. if not self.has_py_container():
  511. return None
  512. return self["container"].get("worker_path", "")
  513. def py_container_run_options(self) -> List:
  514. if not self.has_py_container():
  515. return None
  516. return self["container"].get("run_options", [])
  517. def image_uri(self) -> Optional[str]:
  518. return self.get("image_uri")
  519. def plugins(self) -> List[Tuple[str, Any]]:
  520. result = list()
  521. for key, value in self.items():
  522. if key not in self.known_fields:
  523. result.append((key, value))
  524. return result
  525. def _validate_no_local_paths(runtime_env: RuntimeEnv):
  526. """Checks that options such as working_dir and py_modules only contain URIs."""
  527. if not isinstance(runtime_env, RuntimeEnv):
  528. raise TypeError(
  529. f"Expected type to be RuntimeEnv but received {type(runtime_env)} instead."
  530. )
  531. for option, validate_fn in OPTION_TO_NO_PATH_VALIDATION_FN.items():
  532. option_val = runtime_env.get(option)
  533. if option_val:
  534. validate_fn(option_val)
  535. def _merge_runtime_env(
  536. parent: Optional[RuntimeEnv],
  537. child: Optional[RuntimeEnv],
  538. override: bool = False,
  539. ) -> Optional[RuntimeEnv]:
  540. """Merge the parent and child runtime environments.
  541. If override = True, the child's runtime env overrides the parent's
  542. runtime env in the event of a conflict.
  543. Merging happens per key (i.e., "conda", "pip", ...), but
  544. "env_vars" are merged per env var key.
  545. It returns None if Ray fails to merge runtime environments because
  546. of a conflict and `override = False`.
  547. Args:
  548. parent: Parent runtime env.
  549. child: Child runtime env.
  550. override: If True, the child's runtime env overrides
  551. conflicting fields.
  552. Returns:
  553. The merged runtime env's if Ray successfully merges them.
  554. None if the runtime env's conflict. Empty dict if
  555. parent and child are both None.
  556. """
  557. if parent is None:
  558. parent = {}
  559. if child is None:
  560. child = {}
  561. parent = deepcopy(parent)
  562. child = deepcopy(child)
  563. parent_env_vars = parent.pop("env_vars", {})
  564. child_env_vars = child.pop("env_vars", {})
  565. if not override:
  566. if set(parent.keys()).intersection(set(child.keys())):
  567. return None
  568. if set(parent_env_vars.keys()).intersection(set(child_env_vars.keys())): # noqa
  569. return None
  570. parent.update(child)
  571. parent_env_vars.update(child_env_vars)
  572. if parent_env_vars:
  573. parent["env_vars"] = parent_env_vars
  574. return parent