olive-compute 0.1.1__py3-none-any.whl
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.
- olive/__init__.py +16 -0
- olive/cli.py +163 -0
- olive/client.py +874 -0
- olive/compute.py +295 -0
- olive_compute-0.1.1.dist-info/METADATA +126 -0
- olive_compute-0.1.1.dist-info/RECORD +8 -0
- olive_compute-0.1.1.dist-info/WHEEL +4 -0
- olive_compute-0.1.1.dist-info/entry_points.txt +2 -0
olive/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .client import (
|
|
2
|
+
OliveClient, AsyncOliveClient,
|
|
3
|
+
Job, AsyncJob,
|
|
4
|
+
OliveError, AuthError, JobError,
|
|
5
|
+
NotFoundError, RateLimitError, ServerError,
|
|
6
|
+
)
|
|
7
|
+
from .compute import App, Image, Function, ComputeError
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"OliveClient", "AsyncOliveClient",
|
|
11
|
+
"Job", "AsyncJob",
|
|
12
|
+
"OliveError", "AuthError", "JobError",
|
|
13
|
+
"NotFoundError", "RateLimitError", "ServerError",
|
|
14
|
+
"App", "Image", "Function", "ComputeError",
|
|
15
|
+
]
|
|
16
|
+
__version__ = "0.1.1"
|
olive/cli.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""olive CLI — `olive deploy script.py`, `olive call`, `olive list`, etc.
|
|
2
|
+
|
|
3
|
+
Wired up via the [project.scripts] entry point in pyproject.toml.
|
|
4
|
+
|
|
5
|
+
Reads ``OLIVE_API_KEY`` from the environment (or accepts ``--api-key``).
|
|
6
|
+
Reads ``OLIVE_API_URL`` if overriding the default backend.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import importlib.util
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from .client import OliveClient
|
|
20
|
+
from .compute import App
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load_module(path: str):
|
|
24
|
+
"""Import a Python file by path. Returns the module (with side effects
|
|
25
|
+
from any top-level code, including ``@app.function`` registrations)."""
|
|
26
|
+
p = Path(path).resolve()
|
|
27
|
+
if not p.exists():
|
|
28
|
+
sys.exit(f"error: file not found: {path}")
|
|
29
|
+
spec = importlib.util.spec_from_file_location(p.stem, p)
|
|
30
|
+
if spec is None or spec.loader is None:
|
|
31
|
+
sys.exit(f"error: failed to import {path}")
|
|
32
|
+
mod = importlib.util.module_from_spec(spec)
|
|
33
|
+
spec.loader.exec_module(mod)
|
|
34
|
+
return mod
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _find_apps(mod) -> list[App]:
|
|
38
|
+
"""Find all module-level App instances."""
|
|
39
|
+
return [v for v in vars(mod).values() if isinstance(v, App)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _client_from_args(args) -> OliveClient:
|
|
43
|
+
api_key = args.api_key or os.environ.get("OLIVE_API_KEY")
|
|
44
|
+
if not api_key:
|
|
45
|
+
sys.exit(
|
|
46
|
+
"error: no API key. Set OLIVE_API_KEY or pass --api-key. "
|
|
47
|
+
"Get one at https://customer.olivecompute.com"
|
|
48
|
+
)
|
|
49
|
+
base = args.api_url or os.environ.get("OLIVE_API_URL") or "https://api.olivecompute.com"
|
|
50
|
+
return OliveClient(api_key=api_key, base_url=base)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ── Commands ──────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_deploy(args):
|
|
57
|
+
mod = _load_module(args.file)
|
|
58
|
+
apps = _find_apps(mod)
|
|
59
|
+
if not apps:
|
|
60
|
+
sys.exit(
|
|
61
|
+
f"error: no App found in {args.file}. Define one with "
|
|
62
|
+
"`from olive import App; app = App('my-app')`."
|
|
63
|
+
)
|
|
64
|
+
client = _client_from_args(args)
|
|
65
|
+
for app in apps:
|
|
66
|
+
if not app.functions:
|
|
67
|
+
print(f"⚠ app {app.name!r}: no @app.function decorators found")
|
|
68
|
+
continue
|
|
69
|
+
records = app.deploy(client)
|
|
70
|
+
print(f"✓ app {app.name!r}: deployed {len(records)} function(s)")
|
|
71
|
+
for fn, r in zip(app.functions, records):
|
|
72
|
+
print(f" • {fn.remote_name} → {r.get('function_id')}")
|
|
73
|
+
print(f" image: {r.get('image_uri')}")
|
|
74
|
+
print(f" invoke: olive call {fn.remote_name} '<json input>'")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_list(args):
|
|
78
|
+
client = _client_from_args(args)
|
|
79
|
+
fns = client.list_functions()
|
|
80
|
+
if not fns:
|
|
81
|
+
print("no compute functions deployed")
|
|
82
|
+
return
|
|
83
|
+
print(f"{'NAME':<40} {'TIER':<8} {'TIMEOUT':<8} {'INVOCATIONS':<12} {'VERSION'}")
|
|
84
|
+
for f in fns:
|
|
85
|
+
print(
|
|
86
|
+
f"{f['name']:<40} {f['compute_tier']:<8} {str(f['timeout_seconds'])+'s':<8} "
|
|
87
|
+
f"{str(f['invocation_count']):<12} {f['version']}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cmd_call(args):
|
|
92
|
+
client = _client_from_args(args)
|
|
93
|
+
try:
|
|
94
|
+
input_value: Any = json.loads(args.input) if args.input else None
|
|
95
|
+
except json.JSONDecodeError as e:
|
|
96
|
+
sys.exit(f"error: input is not valid JSON: {e}")
|
|
97
|
+
result = client.compute_call(args.name, input_value)
|
|
98
|
+
if isinstance(result, (dict, list)):
|
|
99
|
+
print(json.dumps(result, indent=2))
|
|
100
|
+
else:
|
|
101
|
+
print(result)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cmd_spawn(args):
|
|
105
|
+
client = _client_from_args(args)
|
|
106
|
+
try:
|
|
107
|
+
input_value: Any = json.loads(args.input) if args.input else None
|
|
108
|
+
except json.JSONDecodeError as e:
|
|
109
|
+
sys.exit(f"error: input is not valid JSON: {e}")
|
|
110
|
+
r = client.compute_spawn(args.name, input_value)
|
|
111
|
+
print(json.dumps(r, indent=2))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def cmd_delete(args):
|
|
115
|
+
client = _client_from_args(args)
|
|
116
|
+
ok = client.delete_function(args.name)
|
|
117
|
+
if ok:
|
|
118
|
+
print(f"✓ deleted: {args.name}")
|
|
119
|
+
else:
|
|
120
|
+
sys.exit(f"error: function not found: {args.name}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── Top-level parser ──────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
127
|
+
p = argparse.ArgumentParser(prog="olive", description="Olive Compute CLI")
|
|
128
|
+
p.add_argument("--api-key", help="Olive API key (default: $OLIVE_API_KEY)")
|
|
129
|
+
p.add_argument("--api-url", help="Olive API base URL (default: $OLIVE_API_URL or production)")
|
|
130
|
+
|
|
131
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
132
|
+
|
|
133
|
+
d = sub.add_parser("deploy", help="Deploy compute functions from a Python file")
|
|
134
|
+
d.add_argument("file", help="Path to script.py containing an App + @app.function decorators")
|
|
135
|
+
d.set_defaults(func=cmd_deploy)
|
|
136
|
+
|
|
137
|
+
l = sub.add_parser("list", help="List your deployed compute functions")
|
|
138
|
+
l.set_defaults(func=cmd_list)
|
|
139
|
+
|
|
140
|
+
c = sub.add_parser("call", help="Synchronously invoke a function")
|
|
141
|
+
c.add_argument("name", help="Function name (or function_id)")
|
|
142
|
+
c.add_argument("input", nargs="?", default="", help="JSON input value (optional)")
|
|
143
|
+
c.set_defaults(func=cmd_call)
|
|
144
|
+
|
|
145
|
+
s = sub.add_parser("spawn", help="Async invoke — returns job id immediately")
|
|
146
|
+
s.add_argument("name", help="Function name (or function_id)")
|
|
147
|
+
s.add_argument("input", nargs="?", default="", help="JSON input value (optional)")
|
|
148
|
+
s.set_defaults(func=cmd_spawn)
|
|
149
|
+
|
|
150
|
+
rm = sub.add_parser("delete", help="Delete a compute function")
|
|
151
|
+
rm.add_argument("name", help="Function name (or function_id)")
|
|
152
|
+
rm.set_defaults(func=cmd_delete)
|
|
153
|
+
|
|
154
|
+
return p
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def main(argv: list[str] | None = None) -> None:
|
|
158
|
+
args = build_parser().parse_args(argv)
|
|
159
|
+
args.func(args)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
main()
|