Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --reload flag to the serve run subcommand #38389

Merged
merged 33 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5270180
Add .venv to ignore list
Aug 13, 2023
9113fd4
Add watchfiles dependency
Aug 13, 2023
27ae1f1
Add `--reload` flag
Aug 13, 2023
ad75070
Add timeout to let Ray print logs
Aug 13, 2023
c7bca0d
Change help text
Aug 13, 2023
eb2e193
Remove Python file filter
Aug 13, 2023
5205aae
Update python/ray/serve/scripts.py
volks73 Aug 14, 2023
b17fb0f
Add short alias for option
Aug 14, 2023
0bce184
Add explicit exception for --non-blocking
Aug 14, 2023
0da3a62
Change help message
Aug 14, 2023
3f8fce2
Add default if working directory is not specified
Aug 14, 2023
d67ccce
Add test for reloading serve run
Aug 14, 2023
1b8c815
Refactor test
Aug 14, 2023
252e0c0
Remove working dir for basic test
Aug 14, 2023
8110d74
Merge branch 'master' into feature-serve-reload
volks73 Aug 15, 2023
f990307
Change message to watch directory
Aug 15, 2023
b2436c3
Update python/ray/serve/scripts.py
volks73 Aug 15, 2023
cb47171
Merge branch 'feature-serve-reload' of https://github.com/volks73/ray…
Aug 15, 2023
0ec4f6b
Update python/ray/serve/scripts.py
volks73 Aug 15, 2023
4a98307
Update python/ray/serve/scripts.py
volks73 Aug 15, 2023
9a74c3d
Update python/ray/serve/tests/test_cli_2.py
volks73 Aug 15, 2023
6007823
Merge branch 'feature-serve-reload' of https://github.com/volks73/ray…
Aug 15, 2023
0783845
Fix indenting
Aug 15, 2023
dcdac97
Fix missing import in test deployment
Aug 15, 2023
0e1b814
Fix pinging endpoint in test
Aug 15, 2023
b6d0f76
Merge branch 'master' into feature-serve-reload
volks73 Aug 16, 2023
b53967d
fixes
edoakes Aug 16, 2023
651e6b1
fix
edoakes Aug 17, 2023
c085723
Change dependency to serve specific
Aug 21, 2023
a66f92f
Merge branch 'master' of https://github.com/ray-project/ray into HEAD
edoakes Aug 22, 2023
c986d93
Merge branch 'master' into feature-serve-reload
edoakes Aug 22, 2023
5dc00ee
revert accidental setup.py change
edoakes Aug 22, 2023
82ae2c9
Merge branch 'master' of https://github.com/ray-project/ray into feat…
edoakes Aug 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ java/**/pom.xml

# python virtual env
venv
.venv

# pyenv version file
.python-version
Expand Down
5 changes: 4 additions & 1 deletion doc/requirements-doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,7 @@ myst-nb==0.13.1
jupytext==1.13.6

# Pin urllib to avoid downstream ssl incompatibility issues
urllib3 < 1.27
urllib3 < 1.27

# For `serve run --reload` CLI.
watchfiles==0.19.0
6 changes: 5 additions & 1 deletion python/ray/_private/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,9 +1285,11 @@ def new_func(*args, **kwargs):
return deprecated_wrapper


def import_attr(full_path: str):
def import_attr(full_path: str, *, reload_module: bool = False):
"""Given a full import path to a module attr, return the imported attr.

If `reload_module` is set, the module will be reloaded using `importlib.reload`.

For example, the following are equivalent:
MyClass = import_attr("module.submodule:MyClass")
MyClass = import_attr("module.submodule.MyClass")
Expand All @@ -1312,6 +1314,8 @@ def import_attr(full_path: str):
attr_name = full_path[last_period_idx + 1 :]

module = importlib.import_module(module_name)
if reload_module:
importlib.reload(module)
return getattr(module, attr_name)


Expand Down
48 changes: 43 additions & 5 deletions python/ray/serve/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import yaml
import traceback
import re
import watchfiles
from pydantic import ValidationError

import ray
Expand Down Expand Up @@ -323,6 +324,17 @@ def deploy(config_file_name: str, address: str):
"as the ingress deployment."
),
)
@click.option(
"--reload",
"-r",
is_flag=True,
help=(
"Listens for changes to files in the working directory, --working-dir "
"or the working_dir in the --runtime-env, and automatically redeploys "
"the application. This will block until Ctrl-C'd, then clean up the "
"app."
),
)
def run(
config_or_import_path: str,
arguments: Tuple[str],
Expand All @@ -335,6 +347,7 @@ def run(
port: int,
blocking: bool,
gradio: bool,
reload: bool,
):
sys.path.insert(0, app_dir)
args_dict = convert_args_to_dict(arguments)
Expand Down Expand Up @@ -453,11 +466,36 @@ def run(

visualizer = GraphVisualizer()
visualizer.visualize_with_gradio(handle)
else:
if blocking:
while True:
# Block, letting Ray print logs to the terminal.
time.sleep(10)
elif reload:
if not blocking:
raise click.ClickException(
"The --non-blocking option conflicts with the --reload option."
)
if working_dir:
watch_dir = working_dir
else:
watch_dir = app_dir

for changes in watchfiles.watch(
watch_dir,
rust_timeout=10000,
yield_on_timeout=True,
):
if changes:
cli_logger.info(
f"Detected file change in path {watch_dir}. Redeploying app."
)
# The module needs to be reloaded with `importlib` in order to pick
# up any changes.
app = _private_api.call_app_builder_with_args_if_necessary(
import_attr(import_path, reload_module=True), args_dict
)
serve.run(app, host=host, port=port)

if blocking:
while True:
# Block, letting Ray print logs to the terminal.
time.sleep(10)

except KeyboardInterrupt:
cli_logger.info("Got KeyboardInterrupt, shutting down...")
Expand Down
53 changes: 53 additions & 0 deletions python/ray/serve/tests/test_cli_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,5 +825,58 @@ def test_deployment_contains_utils(ray_start_stop):
)


@pytest.mark.skipif(sys.platform == "win32", reason="File path incorrect on Windows.")
def test_run_reload_basic(ray_start_stop, tmp_path):
"""Test `serve run` with reload."""

code_template = """
from ray import serve

@serve.deployment
class MessageDeployment:
def __init__(self, msg):
self.msg = msg

def __call__(self):
return self.msg


msg_app = MessageDeployment.bind("Hello {message}!")
"""

def write_file(message: str):
with open(os.path.join(tmp_path, "reload_serve.py"), "w") as f:
code = code_template.format(message=message)
print(f"Writing updated code:\n{code}")
f.write(code)
f.flush()

write_file("World")

p = subprocess.Popen(
[
"serve",
"run",
"--app-dir",
tmp_path,
"--reload",
"reload_serve:msg_app",
]
)
wait_for_condition(lambda: ping_endpoint("") == "Hello World!", timeout=10)

# Sleep to ensure the `serve run` command is in the file watching loop when we
# write the change, else it won't be picked up.
time.sleep(5)

# Write the file: an update should be auto-triggered.
write_file("Updated")
wait_for_condition(lambda: ping_endpoint("") == "Hello Updated!", timeout=10)

p.send_signal(signal.SIGINT)
p.wait()
assert ping_endpoint("") == CONNECTION_ERROR_MSG


if __name__ == "__main__":
sys.exit(pytest.main(["-v", "-s", __file__]))
2 changes: 2 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pyyaml
aiosignal
frozenlist
requests
watchfiles

# Python version-specific requirements
dataclasses; python_version < '3.7'
Expand Down Expand Up @@ -61,3 +62,4 @@ fsspec
pandas>=1.3
pydantic<2
py-spy>=0.2.0
watchfiles
5 changes: 4 additions & 1 deletion python/requirements/test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,7 @@ sympy==1.10.1; python_version <= '3.7'

# For test_basic.py::test_omp_threads_set
threadpoolctl==3.1.0
numexpr==2.8.4
numexpr==2.8.4

# For `serve run --reload` CLI.
watchfiles==0.19.0
9 changes: 8 additions & 1 deletion python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,14 @@ def get_packages(self):
if sys.platform == "darwin"
else "grpcio",
],
"serve": ["uvicorn", "requests", "starlette", "fastapi", "aiorwlock"],
"serve": [
"uvicorn",
"requests",
"starlette",
"fastapi",
"aiorwlock",
"watchfiles",
],
"tune": ["pandas", "tensorboardX>=1.9", "requests", pyarrow_dep, "fsspec"],
"observability": [
"opentelemetry-api",
Expand Down