reflex-sdk 0.1.0__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.
- reflex/__init__.py +63 -0
- reflex/_convex.py +140 -0
- reflex/_transport.py +86 -0
- reflex/actions.py +430 -0
- reflex/cli.py +1405 -0
- reflex/client.py +242 -0
- reflex/datasets.py +168 -0
- reflex/instances.py +62 -0
- reflex/product.py +277 -0
- reflex/training.py +552 -0
- reflex_sdk-0.1.0.dist-info/METADATA +31 -0
- reflex_sdk-0.1.0.dist-info/RECORD +15 -0
- reflex_sdk-0.1.0.dist-info/WHEEL +5 -0
- reflex_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- reflex_sdk-0.1.0.dist-info/top_level.txt +1 -0
reflex/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Public Reflex SDK."""
|
|
2
|
+
|
|
3
|
+
from .actions import ActionStream, action, connect, infer_actions, observation
|
|
4
|
+
from .client import Client
|
|
5
|
+
from .datasets import (
|
|
6
|
+
complete_dataset,
|
|
7
|
+
create_dataset,
|
|
8
|
+
get_dataset,
|
|
9
|
+
list_datasets,
|
|
10
|
+
register_huggingface_dataset,
|
|
11
|
+
upload_dataset,
|
|
12
|
+
validate_dataset,
|
|
13
|
+
)
|
|
14
|
+
from .instances import instance_status, provision_instance, teardown_instance
|
|
15
|
+
from .training import (
|
|
16
|
+
AdamParams,
|
|
17
|
+
AdapterHandle,
|
|
18
|
+
Datum,
|
|
19
|
+
ForwardBackwardResult,
|
|
20
|
+
LoraTrainingClient,
|
|
21
|
+
OptimStepResult,
|
|
22
|
+
ServiceClient,
|
|
23
|
+
cancel_training_job,
|
|
24
|
+
create_training_job,
|
|
25
|
+
full_finetune,
|
|
26
|
+
full_train,
|
|
27
|
+
get_training_job,
|
|
28
|
+
list_training_jobs,
|
|
29
|
+
lora_finetune,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"ActionStream",
|
|
34
|
+
"AdamParams",
|
|
35
|
+
"AdapterHandle",
|
|
36
|
+
"Client",
|
|
37
|
+
"Datum",
|
|
38
|
+
"ForwardBackwardResult",
|
|
39
|
+
"LoraTrainingClient",
|
|
40
|
+
"OptimStepResult",
|
|
41
|
+
"ServiceClient",
|
|
42
|
+
"action",
|
|
43
|
+
"cancel_training_job",
|
|
44
|
+
"complete_dataset",
|
|
45
|
+
"connect",
|
|
46
|
+
"create_dataset",
|
|
47
|
+
"create_training_job",
|
|
48
|
+
"full_finetune",
|
|
49
|
+
"full_train",
|
|
50
|
+
"get_dataset",
|
|
51
|
+
"get_training_job",
|
|
52
|
+
"infer_actions",
|
|
53
|
+
"instance_status",
|
|
54
|
+
"list_training_jobs",
|
|
55
|
+
"list_datasets",
|
|
56
|
+
"lora_finetune",
|
|
57
|
+
"observation",
|
|
58
|
+
"provision_instance",
|
|
59
|
+
"register_huggingface_dataset",
|
|
60
|
+
"teardown_instance",
|
|
61
|
+
"upload_dataset",
|
|
62
|
+
"validate_dataset",
|
|
63
|
+
]
|
reflex/_convex.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Minimal Convex HTTP client for public API-key SDK calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import struct
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib import error, parse, request
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONVEX_URL = "https://kindly-bullfrog-494.convex.cloud"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def convex_url(value: str | None = None) -> str:
|
|
18
|
+
resolved = (
|
|
19
|
+
value
|
|
20
|
+
or os.environ.get("REFLEX_CONVEX_URL", "")
|
|
21
|
+
or os.environ.get("CONVEX_URL", "")
|
|
22
|
+
or os.environ.get("NEXT_PUBLIC_CONVEX_URL", "")
|
|
23
|
+
or DEFAULT_CONVEX_URL
|
|
24
|
+
).strip().rstrip("/")
|
|
25
|
+
parsed = parse.urlparse(resolved)
|
|
26
|
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
|
27
|
+
raise ValueError("Convex URL must start with http:// or https://.")
|
|
28
|
+
return resolved
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_local_env(path: str | Path = ".env") -> None:
|
|
32
|
+
env_path = Path(path)
|
|
33
|
+
if not env_path.exists():
|
|
34
|
+
return
|
|
35
|
+
for line in env_path.read_text().splitlines():
|
|
36
|
+
stripped = line.strip()
|
|
37
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
38
|
+
continue
|
|
39
|
+
key, value = stripped.split("=", 1)
|
|
40
|
+
os.environ.setdefault(key.strip(), value.strip().strip("\"'"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_load_local_env()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _convex_to_json(value: Any) -> Any:
|
|
47
|
+
if value is None or isinstance(value, (bool, str)):
|
|
48
|
+
return value
|
|
49
|
+
if isinstance(value, int):
|
|
50
|
+
return value
|
|
51
|
+
if isinstance(value, float):
|
|
52
|
+
if math.isnan(value) or math.isinf(value) or value == 0.0 and math.copysign(1.0, value) < 0:
|
|
53
|
+
return {"$float": base64.b64encode(struct.pack("<d", value)).decode("ascii")}
|
|
54
|
+
return value
|
|
55
|
+
if isinstance(value, (bytes, bytearray)):
|
|
56
|
+
return {"$bytes": base64.b64encode(bytes(value)).decode("ascii")}
|
|
57
|
+
if isinstance(value, (list, tuple)):
|
|
58
|
+
return [_convex_to_json(item) for item in value]
|
|
59
|
+
if isinstance(value, dict):
|
|
60
|
+
return {
|
|
61
|
+
str(key): _convex_to_json(item)
|
|
62
|
+
for key, item in sorted(value.items(), key=lambda entry: str(entry[0]))
|
|
63
|
+
if item is not None
|
|
64
|
+
}
|
|
65
|
+
raise TypeError(f"{type(value).__name__} is not a supported Convex value")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _json_to_convex(value: Any) -> Any:
|
|
69
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
70
|
+
return value
|
|
71
|
+
if isinstance(value, list):
|
|
72
|
+
return [_json_to_convex(item) for item in value]
|
|
73
|
+
if isinstance(value, dict):
|
|
74
|
+
if set(value) == {"$integer"}:
|
|
75
|
+
return int.from_bytes(base64.b64decode(str(value["$integer"])), "little", signed=True)
|
|
76
|
+
if set(value) == {"$float"}:
|
|
77
|
+
return struct.unpack("<d", base64.b64decode(str(value["$float"])))[0]
|
|
78
|
+
if set(value) == {"$bytes"}:
|
|
79
|
+
return base64.b64decode(str(value["$bytes"]))
|
|
80
|
+
return {str(key): _json_to_convex(item) for key, item in value.items()}
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def convex_call(
|
|
85
|
+
kind: str,
|
|
86
|
+
function_name: str,
|
|
87
|
+
args: dict[str, Any] | None = None,
|
|
88
|
+
*,
|
|
89
|
+
url: str | None = None,
|
|
90
|
+
timeout: float = 60.0,
|
|
91
|
+
) -> Any:
|
|
92
|
+
if kind not in {"action", "mutation", "query"}:
|
|
93
|
+
raise ValueError("Convex function kind must be action, mutation, or query.")
|
|
94
|
+
body = json.dumps(
|
|
95
|
+
{
|
|
96
|
+
"path": function_name,
|
|
97
|
+
"format": "convex_encoded_json",
|
|
98
|
+
"args": [_convex_to_json(args or {})],
|
|
99
|
+
}
|
|
100
|
+
).encode("utf-8")
|
|
101
|
+
req = request.Request(
|
|
102
|
+
f"{convex_url(url)}/api/{kind}",
|
|
103
|
+
data=body,
|
|
104
|
+
method="POST",
|
|
105
|
+
headers={
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"Convex-Client": "python-reflex-sdk-0.1.0",
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
try:
|
|
111
|
+
with request.urlopen(req, timeout=timeout) as response:
|
|
112
|
+
raw = response.read().decode("utf-8")
|
|
113
|
+
except error.HTTPError as exc:
|
|
114
|
+
raw = exc.read().decode("utf-8", errors="replace")
|
|
115
|
+
if exc.code != 560:
|
|
116
|
+
raise RuntimeError(f"Convex {kind} {function_name} failed: {exc.code} {raw}".strip()) from exc
|
|
117
|
+
except error.URLError as exc:
|
|
118
|
+
raise RuntimeError(f"Failed to reach Convex: {exc.reason}") from exc
|
|
119
|
+
|
|
120
|
+
parsed = json.loads(raw) if raw else {}
|
|
121
|
+
if not isinstance(parsed, dict):
|
|
122
|
+
raise RuntimeError(f"Unexpected Convex response: {parsed!r}")
|
|
123
|
+
status = parsed.get("status")
|
|
124
|
+
if status == "success":
|
|
125
|
+
return _json_to_convex(parsed.get("value"))
|
|
126
|
+
if status == "error":
|
|
127
|
+
raise RuntimeError(str(parsed.get("errorMessage") or f"Convex {function_name} failed"))
|
|
128
|
+
raise RuntimeError(f"Unexpected Convex response: {parsed!r}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def convex_action(function_name: str, args: dict[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
132
|
+
return convex_call("action", function_name, args, **kwargs)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def convex_mutation(function_name: str, args: dict[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
136
|
+
return convex_call("mutation", function_name, args, **kwargs)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def convex_query(function_name: str, args: dict[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
140
|
+
return convex_call("query", function_name, args, **kwargs)
|
reflex/_transport.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Shared configuration and HTTP transport helpers for the public SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib import error, parse, request
|
|
10
|
+
|
|
11
|
+
DEFAULT_API_URL = "https://api.tryreflex.ai"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_local_env(path: str | Path = ".env") -> None:
|
|
15
|
+
env_path = Path(path)
|
|
16
|
+
if not env_path.exists():
|
|
17
|
+
return
|
|
18
|
+
for line in env_path.read_text().splitlines():
|
|
19
|
+
stripped = line.strip()
|
|
20
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
21
|
+
continue
|
|
22
|
+
key, value = stripped.split("=", 1)
|
|
23
|
+
os.environ.setdefault(key.strip(), value.strip().strip("\"'"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
load_local_env()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def api_key(value: str | None) -> str:
|
|
30
|
+
resolved = (
|
|
31
|
+
value
|
|
32
|
+
or os.environ.get("REFLEX_API_KEY", "")
|
|
33
|
+
or os.environ.get("RFX_API_KEY", "")
|
|
34
|
+
).strip()
|
|
35
|
+
if not resolved:
|
|
36
|
+
raise ValueError("API key is required. Set api_key or REFLEX_API_KEY.")
|
|
37
|
+
return resolved
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def base_url(value: str | None) -> str:
|
|
41
|
+
resolved = (
|
|
42
|
+
value
|
|
43
|
+
or os.environ.get("REFLEX_API_URL", "")
|
|
44
|
+
or os.environ.get("REFLEX_PLATFORM_URL", "")
|
|
45
|
+
or os.environ.get("RFX_API_URL", "")
|
|
46
|
+
or DEFAULT_API_URL
|
|
47
|
+
).strip().rstrip("/")
|
|
48
|
+
parsed = parse.urlparse(resolved)
|
|
49
|
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
|
50
|
+
raise ValueError("API URL must start with http:// or https://.")
|
|
51
|
+
return resolved
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def request_json(
|
|
55
|
+
*,
|
|
56
|
+
url: str | None,
|
|
57
|
+
api_key_value: str | None,
|
|
58
|
+
path: str,
|
|
59
|
+
payload: dict[str, Any] | None = None,
|
|
60
|
+
method: str = "POST",
|
|
61
|
+
timeout: float = 30.0,
|
|
62
|
+
user_agent: str = "reflex-sdk/0.1.0",
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
body = json.dumps(payload).encode("utf-8") if payload is not None else None
|
|
65
|
+
req = request.Request(
|
|
66
|
+
parse.urljoin(f"{base_url(url)}/", path.lstrip("/")),
|
|
67
|
+
data=body,
|
|
68
|
+
method=method,
|
|
69
|
+
headers={
|
|
70
|
+
"Authorization": f"Bearer {api_key(api_key_value)}",
|
|
71
|
+
"User-Agent": user_agent,
|
|
72
|
+
**({"Content-Type": "application/json"} if payload is not None else {}),
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
try:
|
|
76
|
+
with request.urlopen(req, timeout=timeout) as response:
|
|
77
|
+
raw = response.read().decode("utf-8")
|
|
78
|
+
except error.HTTPError as exc:
|
|
79
|
+
detail = exc.read().decode("utf-8", errors="replace")
|
|
80
|
+
raise RuntimeError(f"{exc.code} {detail}".strip()) from exc
|
|
81
|
+
except error.URLError as exc:
|
|
82
|
+
raise RuntimeError(f"Failed to reach Reflex API: {exc.reason}") from exc
|
|
83
|
+
parsed = json.loads(raw) if raw else {}
|
|
84
|
+
if not isinstance(parsed, dict):
|
|
85
|
+
raise RuntimeError(f"Unexpected Reflex API response: {parsed!r}")
|
|
86
|
+
return parsed
|