groundhog-hpc 0.5.7__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/PKG-INFO +1 -1
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/main.py +5 -1
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/run.py +59 -2
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/decorators.py +16 -2
- groundhog_hpc-0.7.1/src/groundhog_hpc/harness.py +44 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/shell_command.sh.jinja +3 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templating.py +6 -0
- groundhog_hpc-0.5.7/src/groundhog_hpc/harness.py +0 -48
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/.gitignore +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/LICENSE +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/README.md +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/pyproject.toml +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/__init__.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/__init__.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/add.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/init.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/remove.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/app/utils.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/compute.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/__init__.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/defaults.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/endpoints.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/models.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/pep723.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/configuration/resolver.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/console.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/errors.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/function.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/future.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/import_hook.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/logging.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/serialization.py +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/groundhog_run.py.jinja +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/init_script.py.jinja +0 -0
- {groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/utils.py +0 -0
|
@@ -16,7 +16,11 @@ from groundhog_hpc.app.run import run
|
|
|
16
16
|
|
|
17
17
|
app = typer.Typer(pretty_exceptions_show_locals=False)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Enable extra args for run command to capture harness arguments after --
|
|
20
|
+
app.command(
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
context_settings={"allow_extra_args": True, "allow_interspersed_args": False},
|
|
23
|
+
)(run)
|
|
20
24
|
app.command(no_args_is_help=True)(init)
|
|
21
25
|
app.command(no_args_is_help=True)(add)
|
|
22
26
|
app.command(no_args_is_help=True)(remove)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Run command for executing Groundhog scripts on Globus Compute endpoints."""
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
9
|
import typer
|
|
8
10
|
|
|
@@ -21,7 +23,38 @@ from groundhog_hpc.utils import (
|
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def invoke_harness_with_args(harness: Harness, args: list[str]) -> Any:
|
|
27
|
+
"""Parse CLI args and invoke harness function.
|
|
28
|
+
|
|
29
|
+
Reproduces typer.run() logic but with explicit args and standalone_mode=False
|
|
30
|
+
to capture return values and exceptions instead of sys.exit().
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
harness: The harness to invoke
|
|
34
|
+
args: CLI arguments to parse (e.g., ["arg1", "--count=5"])
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The return value from the harness function
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
SystemExit: If argument parsing fails (from Click/Typer)
|
|
41
|
+
Any exception raised by the harness function
|
|
42
|
+
"""
|
|
43
|
+
original_argv = sys.argv
|
|
44
|
+
# Use harness name for better help/error messages
|
|
45
|
+
sys.argv = [harness.func.__name__] + args
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
app = typer.Typer(add_completion=False)
|
|
49
|
+
app.command()(harness.func)
|
|
50
|
+
result = app(standalone_mode=False)
|
|
51
|
+
return result
|
|
52
|
+
finally:
|
|
53
|
+
sys.argv = original_argv
|
|
54
|
+
|
|
55
|
+
|
|
24
56
|
def run(
|
|
57
|
+
ctx: typer.Context,
|
|
25
58
|
script: Path = typer.Argument(
|
|
26
59
|
..., help="Path to script with PEP 723 dependencies to deploy to the endpoint"
|
|
27
60
|
),
|
|
@@ -39,7 +72,20 @@ def run(
|
|
|
39
72
|
help="Set logging level (DEBUG, INFO, WARNING, ERROR)\n\n[env: GROUNDHOG_LOG_LEVEL=]",
|
|
40
73
|
),
|
|
41
74
|
) -> None:
|
|
42
|
-
"""Run a Python script on a Globus Compute endpoint.
|
|
75
|
+
"""Run a Python script on a Globus Compute endpoint.
|
|
76
|
+
|
|
77
|
+
Use -- to pass arguments to parameterized harnesses:
|
|
78
|
+
hog run script.py harness -- arg1 --option=value
|
|
79
|
+
"""
|
|
80
|
+
# Handle the -- separator for harness arguments
|
|
81
|
+
# ctx.args may contain ['--', 'arg1', 'arg2'] - strip the '--' if present
|
|
82
|
+
harness_args = ctx.args # List of extra args, or empty list
|
|
83
|
+
if harness_args and harness_args[0] == "--":
|
|
84
|
+
harness_args = harness_args[1:] # Strip leading '--'
|
|
85
|
+
if harness == "--":
|
|
86
|
+
# User typed: hog run script.py -- args
|
|
87
|
+
# Use default harness "main" and shift args
|
|
88
|
+
harness = "main"
|
|
43
89
|
if no_fun_allowed:
|
|
44
90
|
os.environ["GROUNDHOG_NO_FUN_ALLOWED"] = str(no_fun_allowed)
|
|
45
91
|
|
|
@@ -98,7 +144,18 @@ def run(
|
|
|
98
144
|
)
|
|
99
145
|
raise typer.Exit(1)
|
|
100
146
|
|
|
101
|
-
|
|
147
|
+
# Dispatch based on whether harness arguments were provided
|
|
148
|
+
if not harness_args:
|
|
149
|
+
# No extra args: zero-arg call (backward compatible)
|
|
150
|
+
result = harness_func()
|
|
151
|
+
else:
|
|
152
|
+
# Has extra args: parse and invoke parameterized harness
|
|
153
|
+
sig = inspect.signature(harness_func.func)
|
|
154
|
+
if len(sig.parameters) == 0:
|
|
155
|
+
typer.echo(f"Error: Harness '{harness}' takes no arguments", err=True)
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
result = invoke_harness_with_args(harness_func, harness_args)
|
|
158
|
+
|
|
102
159
|
typer.echo(result)
|
|
103
160
|
except RemoteExecutionError as e:
|
|
104
161
|
if e.returncode == 124:
|
|
@@ -20,22 +20,36 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
def harness() -> Callable[[FunctionType], Harness]:
|
|
21
21
|
"""Decorator to mark a function as a local orchestrator harness.
|
|
22
22
|
|
|
23
|
+
Harness functions are entry points that coordinate remote function calls.
|
|
24
|
+
They run locally and can accept parameters passed as CLI arguments.
|
|
25
|
+
|
|
23
26
|
Harness functions:
|
|
24
27
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
28
|
+
- Are invoked via the CLI: `hog run script.py [harness_name]`
|
|
29
|
+
- Can accept parameters, which map to CLI arguments
|
|
27
30
|
- Can call `.remote()` or `.submit()` on `@hog.function`-decorated functions
|
|
28
31
|
|
|
29
32
|
Returns:
|
|
30
33
|
A decorator function that wraps the harness
|
|
31
34
|
|
|
32
35
|
Example:
|
|
36
|
+
Zero-argument harness:
|
|
33
37
|
```python
|
|
34
38
|
@hog.harness()
|
|
35
39
|
def main():
|
|
36
40
|
result = my_function.remote("far out, man!")
|
|
37
41
|
return result
|
|
38
42
|
```
|
|
43
|
+
|
|
44
|
+
Parameterized harness:
|
|
45
|
+
```python
|
|
46
|
+
@hog.harness()
|
|
47
|
+
def train(dataset: str, epochs: int = 10):
|
|
48
|
+
result = train_model.remote(dataset, epochs)
|
|
49
|
+
return result
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run with: `hog run script.py train -- my_data --epochs=20`
|
|
39
53
|
"""
|
|
40
54
|
|
|
41
55
|
def decorator(func: FunctionType) -> Harness:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Harness wrapper for orchestrating remote function execution.
|
|
2
|
+
|
|
3
|
+
This module provides the Harness class, which wraps entry point functions that
|
|
4
|
+
orchestrate calls to remote @hog.function decorated functions. Harnesses can
|
|
5
|
+
accept parameters which are parsed from CLI arguments via `hog run`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
from types import FunctionType
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Harness:
|
|
14
|
+
"""Wrapper for an orchestrator function.
|
|
15
|
+
|
|
16
|
+
Harness functions are entry points that typically coordinate calls to
|
|
17
|
+
@hog.function decorated functions. They can accept parameters that are
|
|
18
|
+
parsed from CLI arguments when invoked via `hog run script.py -- args`.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
func: The wrapped orchestrator function
|
|
22
|
+
signature: The function's signature for CLI argument parsing
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, func: FunctionType):
|
|
26
|
+
"""Initialize a Harness wrapper.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
func: The orchestrator function to wrap
|
|
30
|
+
"""
|
|
31
|
+
self.func: FunctionType = func
|
|
32
|
+
self.signature: inspect.Signature = inspect.signature(func)
|
|
33
|
+
|
|
34
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
35
|
+
"""Execute the harness function with optional arguments.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
*args: Positional arguments to pass to the harness function
|
|
39
|
+
**kwargs: Keyword arguments to pass to the harness function
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The result of the harness function execution
|
|
43
|
+
"""
|
|
44
|
+
return self.func(*args, **kwargs)
|
{groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/shell_command.sh.jinja
RENAMED
|
@@ -37,10 +37,12 @@ mkdir -p "$UV_CACHE_DIR" "$UV_PYTHON_INSTALL_DIR"
|
|
|
37
37
|
{% if log_level %}
|
|
38
38
|
# Local override - use value from dispatching environment
|
|
39
39
|
export GROUNDHOG_LOG_LEVEL="{{ log_level }}"
|
|
40
|
+
export RUST_LOG=uv="${{GROUNDHOG_LOG_LEVEL}}"
|
|
40
41
|
{% else %}
|
|
41
42
|
{% raw %}
|
|
42
43
|
# Respect remote environment if set, otherwise default to WARNING
|
|
43
44
|
export GROUNDHOG_LOG_LEVEL="${{GROUNDHOG_LOG_LEVEL:-WARNING}}"
|
|
45
|
+
export RUST_LOG=uv="${{GROUNDHOG_LOG_LEVEL:-WARNING}}"
|
|
44
46
|
{% endraw %}
|
|
45
47
|
{% endif %}
|
|
46
48
|
|
|
@@ -57,6 +59,7 @@ cat > {{ script_name }}.in << 'PAYLOAD_EOF'
|
|
|
57
59
|
PAYLOAD_EOF
|
|
58
60
|
|
|
59
61
|
"$UV_BIN" run --with {{ version_spec }} \
|
|
62
|
+
--exclude-newer-package groundhog-hpc={{ groundhog_timestamp }} \
|
|
60
63
|
{{ runner_name }}.py
|
|
61
64
|
|
|
62
65
|
echo "__GROUNDHOG_RESULT__"
|
|
@@ -10,6 +10,7 @@ user functions remotely. It creates shell commands that:
|
|
|
10
10
|
import logging
|
|
11
11
|
import os
|
|
12
12
|
import uuid
|
|
13
|
+
from datetime import datetime, timezone
|
|
13
14
|
from hashlib import sha1
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
|
|
@@ -74,6 +75,10 @@ def template_shell_command(script_path: str, function_name: str, payload: str) -
|
|
|
74
75
|
version_spec = get_groundhog_version_spec()
|
|
75
76
|
logger.debug(f"Using groundhog version spec: {version_spec}")
|
|
76
77
|
|
|
78
|
+
# Generate timestamp for groundhog-hpc exclude-newer override
|
|
79
|
+
# This allows groundhog to bypass user's exclude-newer restrictions
|
|
80
|
+
groundhog_timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
81
|
+
|
|
77
82
|
# Load runner template
|
|
78
83
|
templates_dir = Path(__file__).parent / "templates"
|
|
79
84
|
jinja_env = Environment(loader=FileSystemLoader(templates_dir))
|
|
@@ -106,6 +111,7 @@ def template_shell_command(script_path: str, function_name: str, payload: str) -
|
|
|
106
111
|
version_spec=version_spec,
|
|
107
112
|
payload=payload,
|
|
108
113
|
log_level=local_log_level,
|
|
114
|
+
groundhog_timestamp=groundhog_timestamp,
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
logger.debug(f"Generated shell command ({len(shell_command_string)} chars)")
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""Harness wrapper for orchestrating remote function execution.
|
|
2
|
-
|
|
3
|
-
This module provides the Harness class, which wraps zero-argument entry point
|
|
4
|
-
functions that orchestrate calls to remote @hog.function decorated functions.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import inspect
|
|
8
|
-
from types import FunctionType
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Harness:
|
|
13
|
-
"""Wrapper for a zero-argument orchestrator function.
|
|
14
|
-
|
|
15
|
-
Harness functions are entry points that typically coordinate calls to
|
|
16
|
-
@hog.function decorated functions. They must not accept any arguments.
|
|
17
|
-
|
|
18
|
-
Attributes:
|
|
19
|
-
func: The wrapped orchestrator function
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def __init__(self, func: FunctionType):
|
|
23
|
-
"""Initialize a Harness wrapper.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
func: The orchestrator function to wrap
|
|
27
|
-
|
|
28
|
-
Raises:
|
|
29
|
-
TypeError: If the function accepts any arguments
|
|
30
|
-
"""
|
|
31
|
-
self.func: FunctionType = func
|
|
32
|
-
self._validate_signature()
|
|
33
|
-
|
|
34
|
-
def __call__(self) -> Any:
|
|
35
|
-
"""Execute the harness function.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
The result of the harness function execution
|
|
39
|
-
"""
|
|
40
|
-
return self.func()
|
|
41
|
-
|
|
42
|
-
def _validate_signature(self) -> None:
|
|
43
|
-
sig = inspect.signature(self.func)
|
|
44
|
-
if len(sig.parameters) > 0:
|
|
45
|
-
raise TypeError(
|
|
46
|
-
f"Harness function '{self.func.__qualname__}' must not accept any arguments, "
|
|
47
|
-
f"but has parameters: {list(sig.parameters.keys())}"
|
|
48
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/groundhog_run.py.jinja
RENAMED
|
File without changes
|
{groundhog_hpc-0.5.7 → groundhog_hpc-0.7.1}/src/groundhog_hpc/templates/init_script.py.jinja
RENAMED
|
File without changes
|
|
File without changes
|