constants_utils.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import os
  2. import warnings
  3. from typing import Callable, List, Optional, Type, TypeVar
  4. def str_to_list(s: str) -> List[str]:
  5. """Return a list from a comma-separated string.
  6. Trims whitespace and skips empty entries.
  7. """
  8. return [part for part in (part.strip() for part in s.split(",")) if part]
  9. def parse_latency_buckets(bucket_str: str, default_buckets: List[float]) -> List[float]:
  10. """Parse a comma-separated string of latency bucket values.
  11. Args:
  12. bucket_str: A comma-separated string of positive numbers in ascending order.
  13. default_buckets: Default bucket values to use if bucket_str is empty.
  14. Returns:
  15. A list of parsed float values.
  16. Raises:
  17. ValueError: If the format is invalid or values don't meet requirements.
  18. """
  19. if bucket_str.strip() == "":
  20. return default_buckets
  21. try:
  22. # Convert string to list of floats
  23. buckets = [float(x.strip()) for x in bucket_str.split(",")]
  24. if not buckets:
  25. raise ValueError("Empty bucket list")
  26. if any(x <= 0 for x in buckets):
  27. raise ValueError("Bucket values must be positive")
  28. if sorted(set(buckets)) != buckets:
  29. raise ValueError("Bucket values must be in strictly ascending order")
  30. return buckets
  31. except Exception as e:
  32. raise ValueError(
  33. f"Invalid format for `{bucket_str}`. "
  34. f"Expected comma-separated positive numbers in ascending order. Error: {str(e)}"
  35. ) from e
  36. T = TypeVar("T")
  37. # todo: remove for the '3.0.0' release.
  38. _wrong_names_white_list = {
  39. "REQUEST_LATENCY_BUCKETS_MS",
  40. "MODEL_LOAD_LATENCY_BUCKETS_MS",
  41. "MAX_CACHED_HANDLES",
  42. "SERVE_REQUEST_PROCESSING_TIMEOUT_S",
  43. }
  44. def _validate_name(name: str) -> None:
  45. """Validate Ray Serve environment variable name."""
  46. required_prefix = "RAY_SERVE_"
  47. if not name.startswith(required_prefix):
  48. if name in _wrong_names_white_list:
  49. return
  50. raise ValueError(
  51. f"Got unexpected environment variable name `{name}`! "
  52. f"Ray Serve environment variables require prefix `{required_prefix}`. "
  53. )
  54. def _get_env_value(
  55. name: str,
  56. default: Optional[T],
  57. value_type: Type[T],
  58. validation_func: Optional[Callable[[T], bool]] = None,
  59. expected_value_description: Optional[str] = None,
  60. ) -> Optional[T]:
  61. """Get environment variable with type conversion and validation.
  62. This function retrieves an environment variable, converts it to the specified type,
  63. and optionally validates the converted value.
  64. Args:
  65. name: The name of the environment variable.
  66. default: Default value to use if the environment variable is not set.
  67. If None, the function will return None without validation.
  68. value_type: Type to convert the environment variable value to (e.g., int, float, str).
  69. validation_func: Optional function that takes the converted value and returns
  70. a boolean indicating whether the value is valid.
  71. expected_value_description: Description of the expected value characteristics
  72. (e.g., "positive", "non-negative") used in error messages.
  73. Optional, expected only if validation_func is provided.
  74. Returns:
  75. The environment variable value converted to the specified type and validated,
  76. or the default value if the environment variable is not set.
  77. Raises:
  78. ValueError: If the environment variable value cannot be converted to the specified
  79. type, or if it fails the optional validation check. Also, if name validation fails.
  80. """
  81. _validate_name(name)
  82. explicitly_defined_value = os.environ.get(name)
  83. if explicitly_defined_value is None:
  84. if default is None:
  85. return None
  86. else:
  87. raw = default
  88. else:
  89. _deprecation_warning(name)
  90. raw = explicitly_defined_value
  91. try:
  92. value = value_type(raw)
  93. except ValueError as e:
  94. raise ValueError(
  95. f"Environment variable `{name}` value `{raw}` cannot be converted to `{value_type.__name__}`!"
  96. ) from e
  97. if validation_func and not validation_func(value):
  98. raise ValueError(
  99. f"Got unexpected value `{value}` for `{name}` environment variable! "
  100. f"Expected {expected_value_description} `{value_type.__name__}`."
  101. )
  102. return value
  103. def get_env_int(name: str, default: Optional[int]) -> Optional[int]:
  104. """Get environment variable as an integer.
  105. Args:
  106. name: The name of the environment variable.
  107. default: Default value to use if the environment variable is not set.
  108. Returns:
  109. The environment variable value as an integer.
  110. Raises:
  111. ValueError: If the value cannot be converted to an integer.
  112. """
  113. return _get_env_value(name, default, int)
  114. def get_env_int_positive(name: str, default: Optional[int]) -> Optional[int]:
  115. """Get environment variable as a positive integer.
  116. Args:
  117. name: The name of the environment variable.
  118. default: Default value to use if the environment variable is not set.
  119. Returns:
  120. The environment variable value as a positive integer.
  121. Raises:
  122. ValueError: If the value cannot be converted to an integer or is not positive.
  123. """
  124. return _get_env_value(name, default, int, lambda x: x > 0, "positive")
  125. def get_env_int_non_negative(name: str, default: Optional[int]) -> Optional[int]:
  126. """Get environment variable as a non-negative integer.
  127. Args:
  128. name: The name of the environment variable.
  129. default: Default value to use if the environment variable is not set.
  130. Returns:
  131. The environment variable value as a non-negative integer.
  132. Raises:
  133. ValueError: If the value cannot be converted to an integer or is negative.
  134. """
  135. return _get_env_value(name, default, int, lambda x: x >= 0, "non negative")
  136. def get_env_float(name: str, default: Optional[float]) -> Optional[float]:
  137. """Get environment variable as a float.
  138. Args:
  139. name: The name of the environment variable.
  140. default: Default value to use if the environment variable is not set.
  141. Returns:
  142. The environment variable value as a float.
  143. Raises:
  144. ValueError: If the value cannot be converted to a float.
  145. """
  146. return _get_env_value(name, default, float)
  147. def get_env_float_positive(name: str, default: Optional[float]) -> Optional[float]:
  148. """Get environment variable as a positive float.
  149. Args:
  150. name: The name of the environment variable.
  151. default: Default value to use if the environment variable is not set.
  152. Returns:
  153. The environment variable value as a positive float.
  154. Raises:
  155. ValueError: If the value cannot be converted to a float or is not positive.
  156. """
  157. return _get_env_value(name, default, float, lambda x: x > 0, "positive")
  158. def get_env_float_non_negative(name: str, default: Optional[float]) -> Optional[float]:
  159. """Get environment variable as a non-negative float.
  160. Args:
  161. name: The name of the environment variable.
  162. default: Default value to use if the environment variable is not set.
  163. Returns:
  164. The environment variable value as a non-negative float.
  165. Raises:
  166. ValueError: If the value cannot be converted to a float or is negative.
  167. """
  168. return _get_env_value(name, default, float, lambda x: x >= 0, "non negative")
  169. def get_env_str(name: str, default: Optional[str]) -> Optional[str]:
  170. """Get environment variable as a string.
  171. Args:
  172. name: The name of the environment variable.
  173. default: Default value to use if the environment variable is not set.
  174. Returns:
  175. The environment variable value as a string.
  176. Returns `None` if default is `None` and value not found.
  177. """
  178. return _get_env_value(name, default, str)
  179. def get_env_bool(name: str, default: str) -> bool:
  180. """Get environment variable as a boolean.
  181. Environment variable values of "1" are interpreted as True, all others as False.
  182. Args:
  183. name: The name of the environment variable.
  184. default: Default value to use if the environment variable is not set.
  185. Expects "0" or "1".
  186. Returns:
  187. True if the environment variable value is "1", False otherwise.
  188. """
  189. env_value_str = _get_env_value(name, default, str)
  190. return env_value_str == "1"
  191. # Environment variables that are fully deprecated and will be ignored.
  192. _fully_deprecated_env_vars = {
  193. "RAY_SERVE_HTTP_KEEP_ALIVE_TIMEOUT_S": "http_options.keep_alive_timeout_s",
  194. }
  195. def _deprecation_warning(name: str) -> None:
  196. """Log replacement warning for wrong or legacy environment variables.
  197. TODO: remove this function for the '3.0.0' release.
  198. :param name: environment variable name
  199. """
  200. def get_new_name(name: str) -> str:
  201. if name == "RAY_SERVE_HANDLE_METRIC_PUSH_INTERVAL_S":
  202. return "RAY_SERVE_HANDLE_AUTOSCALING_METRIC_PUSH_INTERVAL_S"
  203. elif name == "SERVE_REQUEST_PROCESSING_TIMEOUT_S":
  204. return "RAY_SERVE_REQUEST_PROCESSING_TIMEOUT_S"
  205. else:
  206. return f"{required_prefix}{name}"
  207. change_version = "3.0.0"
  208. required_prefix = "RAY_SERVE_"
  209. if (
  210. name in _wrong_names_white_list
  211. or name == "RAY_SERVE_HANDLE_METRIC_PUSH_INTERVAL_S"
  212. ):
  213. new_name = get_new_name(name)
  214. warnings.warn(
  215. f"Starting from version `{change_version}` environment variable "
  216. f"`{name}` will be deprecated. Please use `{new_name}` instead.",
  217. FutureWarning,
  218. stacklevel=4,
  219. )
  220. def warn_if_deprecated_env_var_set(name: str) -> None:
  221. """Warn if a fully deprecated environment variable is set.
  222. :param name: environment variable name
  223. """
  224. if name in _fully_deprecated_env_vars and os.environ.get(name):
  225. config_option = _fully_deprecated_env_vars[name]
  226. warnings.warn(
  227. f"`{name}` environment variable will be deprecated in the future. "
  228. f"Use `{config_option}` in the Serve config instead.",
  229. DeprecationWarning,
  230. stacklevel=2,
  231. )