-
Notifications
You must be signed in to change notification settings - Fork 0
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 'final' and 'strip' to hooks, rework 'ensure_lab' and github on demand #11
Merged
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Logger for hooks.""" | ||
|
||
import structlog | ||
|
||
LOGGER = structlog.getLogger("ghostwriter") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Ensure that the user has a running lab. If they don't, they will be | ||
given `recommended` in `medium` size. | ||
""" | ||
|
||
import asyncio | ||
|
||
from rubin.nublado.client import NubladoClient | ||
from rubin.nublado.client.models import ( | ||
NubladoImage, | ||
NubladoImageByClass, | ||
NubladoImageClass, | ||
NubladoImageSize, | ||
) | ||
|
||
from ..models.substitution import Parameters | ||
from ._logger import LOGGER | ||
|
||
LAB_SPAWN_TIMEOUT = 90 | ||
|
||
|
||
async def ensure_autostart_lab(params: Parameters) -> None: | ||
athornton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Start a Lab if one is not present.""" | ||
LOGGER.debug(f"Checking for running Lab for {params}") | ||
client = params.client | ||
LOGGER.debug("Logging in to Hub") | ||
await client.auth_to_hub() | ||
stopped = await client.is_lab_stopped() | ||
if stopped: | ||
LOGGER.debug(f"Starting new 'recommended' Lab for {params.user}") | ||
await _spawn_lab(client) | ||
else: | ||
LOGGER.debug(f"{params.user} already has a running Lab") | ||
LOGGER.debug("Lab spawned; proceeding with redirection") | ||
|
||
|
||
async def _spawn_lab(client: NubladoClient) -> None: | ||
image = _choose_image() | ||
await client.spawn_lab(image) | ||
await _follow_progress(client) | ||
|
||
|
||
def _choose_image() -> NubladoImage: | ||
"""Because the primary use case of this redirection service is | ||
to launch tutorial notebooks, our "start a new lab" parameters | ||
are going to be the currently recommended lab, and Medium size, | ||
because that's how the tutorial notebooks are generally set up | ||
to run. Maybe we will do something more sophisticated later. | ||
""" | ||
return NubladoImageByClass( | ||
image_class=NubladoImageClass.RECOMMENDED, size=NubladoImageSize.Medium | ||
) | ||
|
||
|
||
async def _follow_progress(client: NubladoClient) -> None: | ||
LOGGER.debug("Waiting for lab to spawn") | ||
progress = client.watch_spawn_progress() | ||
try: | ||
async with asyncio.timeout(LAB_SPAWN_TIMEOUT): | ||
async for message in progress: | ||
LOGGER.debug(f"Lab spawn message: {message.message}") | ||
if message.ready: | ||
break | ||
except TimeoutError: | ||
LOGGER.exception(f"Lab did not spawn in {LAB_SPAWN_TIMEOUT} seconds") | ||
raise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,35 @@ | ||
"""Ensure that the user has a running lab.""" | ||
|
||
import asyncio | ||
|
||
import structlog | ||
from rubin.nublado.client import NubladoClient | ||
from rubin.nublado.client.models import ( | ||
NubladoImage, | ||
NubladoImageByClass, | ||
NubladoImageClass, | ||
NubladoImageSize, | ||
) | ||
"""Ensure that the user has a running lab by dropping them in the spawner form | ||
if they don't, and then slingshotting them through the lab ghostwriter endpoint | ||
to restart the redirect process. Requires d_2024_10_19 (corresponds to | ||
w_2024_10_43) or later. | ||
""" | ||
|
||
from ..models.substitution import Parameters | ||
|
||
LAB_SPAWN_TIMEOUT = 90 | ||
LOGGER = structlog.get_logger("ghostwriter") | ||
from ._logger import LOGGER | ||
|
||
|
||
async def ensure_running_lab(params: Parameters) -> None: | ||
async def ensure_running_lab(params: Parameters) -> Parameters | None: | ||
"""Start a Lab if one is not present.""" | ||
LOGGER.debug(f"Checking for running Lab for {params}") | ||
client = params.client | ||
LOGGER.debug("Logging in to Hub") | ||
await client.auth_to_hub() | ||
stopped = await client.is_lab_stopped() | ||
if stopped: | ||
LOGGER.debug(f"Starting new 'recommended' Lab for {params.user}") | ||
await _spawn_lab(client) | ||
else: | ||
if not stopped: | ||
LOGGER.debug(f"{params.user} already has a running Lab") | ||
LOGGER.debug("Lab spawned; proceeding with redirection") | ||
|
||
|
||
async def _spawn_lab(client: NubladoClient) -> None: | ||
image = _choose_image() | ||
await client.spawn_lab(image) | ||
await _follow_progress(client) | ||
|
||
|
||
def _choose_image() -> NubladoImage: | ||
"""Because the primary use case of this redirection service is | ||
to launch tutorial notebooks, our "start a new lab" parameters | ||
are going to be the currently recommended lab, and Medium size, | ||
because that's how the tutorial notebooks are generally set up | ||
to run. Maybe we will do something more sophisticated later. | ||
""" | ||
return NubladoImageByClass( | ||
image_class=NubladoImageClass.RECOMMENDED, size=NubladoImageSize.Medium | ||
return None | ||
LOGGER.debug(f"Sending {params.user} to spawner") | ||
LOGGER.debug(f"Input parameters {params}") | ||
new_p = Parameters( | ||
user=params.user, | ||
base_url=params.base_url, | ||
path=params.path, | ||
token=params.token, | ||
client=params.client, | ||
target="${base_url}/nb/user/${user}/rubin/ghostwriter/${path}", | ||
unique_id=params.unique_id, | ||
strip=False, | ||
final=True, | ||
) | ||
|
||
|
||
async def _follow_progress(client: NubladoClient) -> None: | ||
LOGGER.debug("Waiting for lab to spawn") | ||
progress = client.watch_spawn_progress() | ||
try: | ||
async with asyncio.timeout(LAB_SPAWN_TIMEOUT): | ||
async for message in progress: | ||
LOGGER.debug(f"Lab spawn message: {message.message}") | ||
if message.ready: | ||
break | ||
except TimeoutError: | ||
LOGGER.exception(f"Lab did not spawn in {LAB_SPAWN_TIMEOUT} seconds") | ||
raise | ||
LOGGER.debug(f"Output parameters {new_p}") | ||
return new_p |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,14 +9,11 @@ | |
from string import Template | ||
from urllib.parse import urljoin | ||
|
||
import structlog | ||
|
||
from ..models.substitution import Parameters | ||
|
||
LOGGER = structlog.get_logger("ghostwriter") | ||
from ._logger import LOGGER | ||
|
||
|
||
async def github_notebook(params: Parameters) -> Parameters | None: | ||
async def github_notebook(params: Parameters) -> Parameters: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a suggestion for when you have more development effort on this would be splitting the path processing here to its own method and writing some tests for that to make sure it can handle all unusual cases of paths |
||
"""Check out a particular notebook from GitHub.""" | ||
client = params.client | ||
LOGGER.debug("Logging in to hub", user=params.user) | ||
|
@@ -29,32 +26,42 @@ async def github_notebook(params: Parameters) -> Parameters | None: | |
# We know the stream output should be the serial number and | ||
# a newline. | ||
serial = (await lab_session.run_python(code)).strip() | ||
if serial and serial != "0" and params.target is not None: | ||
# | ||
# params.target is never None, because it's injected by run_hooks(), | ||
# but mypy doesn't know that. | ||
# | ||
# We had to change the filename and filename schema. | ||
# Return a modified Parameters object. | ||
# | ||
targetcomp = params.target.split("/") | ||
targetcomp[-1] = "${path}-${unique_id}.ipynb" | ||
target = "/".join(targetcomp) | ||
unique_id = str(serial) | ||
LOGGER.debug( | ||
"Continuing to redirect", target=target, unique_id=unique_id | ||
) | ||
return Parameters( | ||
user=params.user, | ||
base_url=params.base_url, | ||
token=params.token, | ||
client=params.client, | ||
path=params.path, | ||
target=target, | ||
unique_id=unique_id, | ||
) | ||
LOGGER.debug("Continuing to redirect; params unchanged") | ||
return None | ||
|
||
# Honestly it's easier to just unconditionally rewrite the target | ||
# than to figure out whether it needs rewriting. | ||
|
||
# Start with the common fragment | ||
target = ( | ||
athornton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
f"{params.base_url}nb/user/{params.user}/lab/tree/notebooks" | ||
"/on-demand/github.com/" | ||
) | ||
# Remove branch information, if any (the checkout will have handled | ||
# it in the code we ran to get the serial). | ||
path = params.path.split("@")[0] | ||
if path.startswith("notebooks/github.com/"): | ||
# Needs stripping | ||
path = path[(len("notebooks/github.com/")) :] | ||
athornton marked this conversation as resolved.
Show resolved
Hide resolved
athornton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if path.endswith(".ipynb"): | ||
# Also needs stripping | ||
path = path[: -(len(".ipynb"))] | ||
athornton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unique_id: str | None = None | ||
if serial and serial != "0": | ||
# Glue in serial if it is nonzero | ||
unique_id = serial | ||
path += f"-{unique_id}" | ||
path += ".ipynb" # And add the extension | ||
target += path | ||
new_param = Parameters( | ||
user=params.user, | ||
base_url=params.base_url, | ||
token=params.token, | ||
client=params.client, | ||
path=params.path, | ||
target=target, | ||
unique_id=unique_id, | ||
) | ||
LOGGER.debug("Continuing to redirect", param=new_param) | ||
return new_param | ||
|
||
|
||
def _get_user_endpoint(base_url: str, user: str) -> str: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,9 @@ | ||
"""Does nothing, but if it loads, the hook machinery is working.""" | ||
|
||
import structlog | ||
|
||
from ..models.substitution import Parameters | ||
from ._logger import LOGGER | ||
|
||
|
||
async def vacuous_hook(params: Parameters) -> None: | ||
"""Load and execute a no-op.""" | ||
logger = structlog.get_logger("ghostwriter") | ||
logger.debug(f"Vacuous hook called with {params}") | ||
LOGGER.debug(f"Vacuous hook called with {params}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo (not in your recent changes) just above:
modififactions -> modifications