Skip to content

Commit

Permalink
Switch to profile with matching PREFECT_API_URL on server start (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
desertaxle authored Aug 28, 2024
1 parent fdd3c2c commit a428078
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 16 deletions.
64 changes: 49 additions & 15 deletions src/prefect/cli/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,53 @@ def generate_welcome_blurb(base_url, ui_enabled: bool):
return blurb


def prestart_check():
def prestart_check(base_url: str):
"""
Check if `PREFECT_API_URL` is set in the current profile. If not, prompt the user to set it.
Args:
base_url: The base URL the server will be running on
"""
api_url = f"{base_url}/api"
current_profile = load_current_profile()
if PREFECT_API_URL not in current_profile.settings:
profiles = load_profiles()
if current_profile and PREFECT_API_URL not in current_profile.settings:
profiles_with_matching_url = [
name
for name, profile in profiles.items()
if profile.settings.get(PREFECT_API_URL) == api_url
]
if len(profiles_with_matching_url) == 1:
profiles.set_active(profiles_with_matching_url[0])
save_profiles(profiles)
app.console.print(
f"Switched to profile {profiles_with_matching_url[0]!r}",
style="green",
)
return
elif len(profiles_with_matching_url) > 1:
app.console.print(
"Your current profile doesn't have `PREFECT_API_URL` set to the address"
" of the server that's running. Some of your other profiles do."
)
selected_profile = prompt_select_from_list(
app.console,
"Which profile would you like to switch to?",
sorted(
[profile for profile in profiles_with_matching_url],
),
)
profiles.set_active(selected_profile)
save_profiles(profiles)
app.console.print(
f"Switched to profile {selected_profile!r}", style="green"
)
return

app.console.print(
"`PREFECT_API_URL` is not set. You need to set it to communicate with the server.",
"The `PREFECT_API_URL` setting for your current profile doesn't match the"
" address of the server that's running. You need to set it to communicate"
" with the server.",
style="yellow",
)

Expand All @@ -126,7 +165,6 @@ def prestart_check():
),
],
)
profiles = load_profiles()

if choice == "create":
while True:
Expand All @@ -137,13 +175,12 @@ def prestart_check():
style="red",
)
else:
api_url = prompt(
"Enter the `PREFECT_API_URL` value",
default="http://127.0.0.1:4200/api",
)
break

profiles.add_profile(
Profile(name=profile_name, settings={"PREFECT_API_URL": api_url})
Profile(
name=profile_name, settings={PREFECT_API_URL: f"{base_url}/api"}
)
)
profiles.set_active(profile_name)
save_profiles(profiles)
Expand All @@ -155,8 +192,7 @@ def prestart_check():
api_url = prompt(
"Enter the `PREFECT_API_URL` value", default="http://127.0.0.1:4200/api"
)
prefect_api_url_setting = {"PREFECT_API_URL": api_url}
update_current_profile(prefect_api_url_setting)
update_current_profile({PREFECT_API_URL: api_url})
app.console.print(
f"Set `PREFECT_API_URL` to {api_url!r} in the current profile {current_profile.name!r}",
style="green",
Expand All @@ -182,10 +218,10 @@ async def start(
"""
Start a Prefect server instance
"""

base_url = f"http://{host}:{port}"
if is_interactive():
try:
prestart_check()
prestart_check(base_url)
except Exception:
pass

Expand All @@ -197,8 +233,6 @@ async def start(
server_env["PREFECT_UI_ENABLED"] = str(ui)
server_env["PREFECT_LOGGING_SERVER_LEVEL"] = log_level

base_url = f"http://{host}:{port}"

pid_file = anyio.Path(PREFECT_HOME.value() / PID_FILE)
# check if port is already in use
try:
Expand Down
101 changes: 100 additions & 1 deletion tests/cli/test_start_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,22 @@
import anyio
import httpx
import pytest
import readchar
from typer import Exit

from prefect.cli.server import PID_FILE
from prefect.settings import PREFECT_HOME, get_current_settings
from prefect.context import get_settings_context
from prefect.settings import (
PREFECT_API_URL,
PREFECT_HOME,
PREFECT_PROFILES_PATH,
Profile,
ProfilesCollection,
get_current_settings,
load_profiles,
save_profiles,
temporary_settings,
)
from prefect.testing.cli import invoke_and_assert
from prefect.testing.fixtures import is_port_in_use
from prefect.utilities.processutils import open_process
Expand Down Expand Up @@ -314,3 +327,89 @@ async def test_sends_ctrl_break_win32(self):
"When sending a SIGINT, the main process should send a"
f" CTRL_BREAK_EVENT to the uvicorn subprocess. Output:\n{out}"
)


class TestPrestartCheck:
@pytest.fixture(autouse=True)
def interactive_console(self, monkeypatch):
monkeypatch.setattr("prefect.cli.server.is_interactive", lambda: True)

# `readchar` does not like the fake stdin provided by typer isolation so we provide
# a version that does not require a fd to be attached
def readchar():
sys.stdin.flush()
position = sys.stdin.tell()
if not sys.stdin.read():
print("TEST ERROR: CLI is attempting to read input but stdin is empty.")
raise Exit(-2)
else:
sys.stdin.seek(position)
return sys.stdin.read(1)

monkeypatch.setattr("readchar._posix_read.readchar", readchar)

@pytest.fixture(autouse=True)
def temporary_profiles_path(self, tmp_path):
path = tmp_path / "profiles.toml"
with temporary_settings({PREFECT_PROFILES_PATH: path}):
save_profiles(
profiles=ProfilesCollection(profiles=[get_settings_context().profile])
)
yield path

@pytest.fixture(autouse=True)
def stop_server(self):
yield
invoke_and_assert(
command=[
"server",
"stop",
],
expected_output_contains="Server stopped!",
expected_code=0,
)

def test_switch_to_local_profile_by_default(self):
invoke_and_assert(
command=[
"server",
"start",
"--background",
],
expected_output_contains="Switched to profile 'local'",
expected_code=0,
)

profiles = load_profiles()
assert profiles.active_name == "local"

def test_choose_when_multiple_profiles_have_same_api_url(self):
save_profiles(
profiles=ProfilesCollection(
profiles=[
Profile(
name="local-server",
settings={PREFECT_API_URL: "http://127.0.0.1:4200/api"},
),
Profile(
name="local",
settings={PREFECT_API_URL: "http://127.0.0.1:4200/api"},
),
get_settings_context().profile,
]
)
)

invoke_and_assert(
command=[
"server",
"start",
"--background",
],
expected_output_contains="Switched to profile 'local'",
expected_code=0,
user_input=readchar.key.ENTER,
)

profiles = load_profiles()
assert profiles.active_name == "local"

0 comments on commit a428078

Please sign in to comment.