Skip to content

Commit

Permalink
Removed embedded git repo, addressed review commentary
Browse files Browse the repository at this point in the history
  • Loading branch information
athornton committed Apr 10, 2024
1 parent 8b17c4a commit a7803f1
Show file tree
Hide file tree
Showing 41 changed files with 629 additions and 2,137 deletions.
26 changes: 1 addition & 25 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ help:
@echo "Make targets for lsst-rsp:"
@echo "make clean - Remove generated files"
@echo "make init - Set up dev environment (install pre-commit hooks)"
@echo "make update - Update pre-commit dependencies and run make init"
@echo "make update-deps - Update pre-commit dependencies"

.PHONY: clean
clean:
Expand All @@ -14,29 +12,7 @@ clean:
init:
pip install --upgrade uv
uv pip install --upgrade pip tox tox-uv pre-commit
uv pip install --editable .
uv pip install -r requirements/main.txt -r requirements/dev.txt
uv pip install --editable ".[dev]"
rm -rf .tox
pre-commit install

.PHONY: update
update: update-deps init

.PHONY: update-deps
update-deps:
pip install --upgrade uv
uv pip install pre-commit tox-uv
pre-commit autoupdate
uv pip compile --upgrade --generate-hashes \
--output-file requirements/main.txt requirements/main.in
uv pip compile --upgrade --generate-hashes \
--output-file requirements/dev.txt requirements/dev.in

# Useful for testing against Git versions of dependencies.
.PHONY: update-deps-no-hashes
update-deps-no-hashes:
pip install --upgrade uv
uv pip compile --upgrade \
--output-file requirements/main.txt requirements/main.in
uv pip compile --upgrade \
--output-file requirements/dev.txt requirements/dev.in
25 changes: 22 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,35 @@ classifiers = [
"Typing :: Typed",
]
requires-python = ">=3.11"
# Use requirements/main.in for runtime dependencies instead
dependencies = []
dependencies = [
"Deprecated",
"IPython",
"pyvo",
"structlog",
"symbolicmode",
]
dynamic = ["version"]

[[project.authors]]
name = "Association of Universities for Research in Astronomy, Inc. (AURA)"
email = "sqre-admin@lists.lsst.org"

[project.scripts]
runlab = "lsst.rsp.startup.cli:main"
launch-rubin-jupyterlab = "lsst.rsp.startup.cli:main"

[project.optional-dependencies]
dev = [
# Typing
"types-deprecated",
"types-requests",
# Testing
"coverage[toml]",
"pytest",
"pytest-asyncio",
"mypy",
# Documentation
"scriv",
]

[project.urls]
Homepage = "https://rsp.lsst.io/"
Expand Down
20 changes: 0 additions & 20 deletions requirements/dev.in

This file was deleted.

317 changes: 0 additions & 317 deletions requirements/dev.txt

This file was deleted.

12 changes: 0 additions & 12 deletions requirements/main.in

This file was deleted.

404 changes: 0 additions & 404 deletions requirements/main.txt

This file was deleted.

5 changes: 0 additions & 5 deletions src/lsst/rsp/startup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
"""pkgutil-style namespace package."""

import pkgutil

__path__ = pkgutil.extend_path(__path__, __name__)
3 changes: 1 addition & 2 deletions src/lsst/rsp/startup/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ def main() -> None:
"""Make a LabRunner and call its single public method. All settings are
in the environment.
"""
lr = LabRunner()
lr.go()
LabRunner().go()
51 changes: 38 additions & 13 deletions src/lsst/rsp/startup/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,46 @@
from pathlib import Path

__all__ = [
"app_name",
"etc",
"logging_checksums",
"max_number_outputs",
"noninteractive_config",
"top_dir",
"APP_NAME",
"ETC_PATH",
"PREVIOUS_LOGGING_CHECKSUMS",
"MAX_NUMBER_OUTPUTS",
"NONINTERACTIVE_CONFIG_PATH",
"TOP_DIR_PATH",
]

app_name = "nublado"
etc = Path("/etc")
logging_checksums = [
APP_NAME = "nublado"
"""Application name, used for logging."""

ETC_PATH = Path("/etc")
"""Configuration directory, usually /etc, but overrideable for tests."""

PREVIOUS_LOGGING_CHECKSUMS = [
"2997fe99eb12846a1b724f0b82b9e5e6acbd1d4c29ceb9c9ae8f1ef5503892ec"
]
max_number_outputs = 10000
top_dir = Path("/opt/lsst/software")
noninteractive_config = Path(
top_dir / "jupyterlab" / "noninteractive" / "command" / "command.json"
"""sha256 sums of previous iterations of ``20-logging.py``.
Used to determine whether upgrading the logging configuration is
needed, or whether the user has made local modifications that
therefore should not be touched.
"""

MAX_NUMBER_OUTPUTS = 10000
"""Maximum number of output lines to display in a Jupyter notebook cell.
Used to prevent OOM-killing if some cell generates a lot of output.
"""

TOP_DIR_PATH = Path("/opt/lsst/software")
"""
Location where the DM stack and our Lab machinery are rooted.
Overrideable for testing.
"""

NONINTERACTIVE_CONFIG_PATH = Path(
TOP_DIR_PATH / "jupyterlab" / "noninteractive" / "command" / "command.json"
)
"""
Location where a noninteractive pod will mount its command configuration.
"""
72 changes: 72 additions & 0 deletions src/lsst/rsp/startup/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Exceptions for the LabRunner startup service."""

from __future__ import annotations

import subprocess
from collections.abc import Iterable
from shlex import join

__all__ = [
"CommandFailedError",
"CommandTimedOutError",
]


class CommandFailedError(Exception):
"""Execution of a command failed.
Parameters
----------
args
Command (args[0]) and arguments to that command.
exc
Exception reporting the failure.
Attributes
----------
stdout
Standard output from the failed command.
stderr
Standard error from the failed command.
"""

def __init__(
self,
args: Iterable[str],
exc: subprocess.CalledProcessError,
) -> None:
args_str = join(args)
msg = f"'{args_str}' failed with status {exc.returncode}"
super().__init__(msg)
self.stdout = exc.stdout
self.stderr = exc.stderr


class CommandTimedOutError(Exception):
"""Execution of a command failed.
Parameters
----------
args
Command (args[0]) and arguments to that command.
exc
Exception reporting the failure.
Attributes
----------
stdout
Standard output from the failed command.
stderr
Standard error from the failed command.
"""

def __init__(
self,
args: Iterable[str],
exc: subprocess.TimeoutExpired,
) -> None:
args_str = join(args)
msg = f"'{args_str}' timed out after {exc.timeout}s"
super().__init__(msg)
self.stdout = exc.stdout
self.stderr = exc.stderr
62 changes: 20 additions & 42 deletions src/lsst/rsp/startup/models/noninteractive.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,41 @@

import json
import os
from dataclasses import dataclass
from enum import Enum
import sys
from pathlib import Path
from typing import Self


class NonInteractiveExecutionType(Enum):
"""Command types for noninteractive execution."""

nb = "nb"
"""Notebook execution type."""

command = "command"
"""Command execution type."""


@dataclass
class NonInteractiveExecution:
class NonInteractiveExecutor:
"""Launch noninteractive Lab container execution from a
configuration document.
"""

type: NonInteractiveExecutionType | str
kernel: str
command: list[str]

def __post_init__(self) -> None:
#
# Sure, Pydantic does validation like this more easily, but nothing
# else in the package uses Pydantic, and other than the
# noninteractive command structure there's really nothing
# user-facing, so it's a lot of stuff for not much benefit.
#
if isinstance(self.type, str):
self.type = NonInteractiveExecutionType(self.type)
if self.type != NonInteractiveExecutionType.command:
raise NotImplementedError(
"Only 'command' type is currently supported for"
" noninteractive execution."
)
def __init__(
self,
kernel: str,
command: list[str],
) -> None:
self._kernel = kernel
self._command = command

@classmethod
def from_config(cls, config: Path) -> Self:
"""Load configuration from a JSON document."""
with config.open() as f:
obj = json.load(f)
return cls(
type=obj["type"], kernel=obj["kernel"], command=obj["command"]
)
if obj["type"] != "command":
raise NotImplementedError(
"Only 'command' type noninteractive execution is supported"
)
return cls(kernel=obj["kernel"], command=obj["command"])

def execute(
self,
env: dict[str, str] = dict(os.environ), # noqa: B006
) -> None:
def execute(self, env: dict[str, str] | None = None) -> None:
"""Run the command specified in the object, with a supplied
environment (defaulting to the ambient environment).
"""
# Flush any open files before exec()
os.sync()
os.execve(self.command[0], self.command, env=env)
if env is None:
env = dict(os.environ)
sys.stdout.flush()
sys.stderr.flush()
os.execve(self._command[0], self._command, env=env)
Loading

0 comments on commit a7803f1

Please sign in to comment.