moru 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.
- moru/__init__.py +174 -0
- moru/api/__init__.py +164 -0
- moru/api/client/__init__.py +8 -0
- moru/api/client/api/__init__.py +1 -0
- moru/api/client/api/sandboxes/__init__.py +1 -0
- moru/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- moru/api/client/api/sandboxes/get_sandboxes.py +176 -0
- moru/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
- moru/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
- moru/api/client/api/sandboxes/post_sandboxes.py +172 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
- moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
- moru/api/client/api/templates/__init__.py +1 -0
- moru/api/client/api/templates/delete_templates_template_id.py +157 -0
- moru/api/client/api/templates/get_templates.py +172 -0
- moru/api/client/api/templates/get_templates_template_id.py +195 -0
- moru/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
- moru/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
- moru/api/client/api/templates/patch_templates_template_id.py +183 -0
- moru/api/client/api/templates/post_templates.py +172 -0
- moru/api/client/api/templates/post_templates_template_id.py +181 -0
- moru/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
- moru/api/client/api/templates/post_v2_templates.py +172 -0
- moru/api/client/api/templates/post_v3_templates.py +172 -0
- moru/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
- moru/api/client/client.py +286 -0
- moru/api/client/errors.py +16 -0
- moru/api/client/models/__init__.py +123 -0
- moru/api/client/models/aws_registry.py +85 -0
- moru/api/client/models/aws_registry_type.py +8 -0
- moru/api/client/models/build_log_entry.py +89 -0
- moru/api/client/models/build_status_reason.py +95 -0
- moru/api/client/models/connect_sandbox.py +59 -0
- moru/api/client/models/created_access_token.py +100 -0
- moru/api/client/models/created_team_api_key.py +166 -0
- moru/api/client/models/disk_metrics.py +91 -0
- moru/api/client/models/error.py +67 -0
- moru/api/client/models/gcp_registry.py +69 -0
- moru/api/client/models/gcp_registry_type.py +8 -0
- moru/api/client/models/general_registry.py +77 -0
- moru/api/client/models/general_registry_type.py +8 -0
- moru/api/client/models/identifier_masking_details.py +83 -0
- moru/api/client/models/listed_sandbox.py +154 -0
- moru/api/client/models/log_level.py +11 -0
- moru/api/client/models/max_team_metric.py +78 -0
- moru/api/client/models/mcp_type_0.py +44 -0
- moru/api/client/models/new_access_token.py +59 -0
- moru/api/client/models/new_sandbox.py +172 -0
- moru/api/client/models/new_team_api_key.py +59 -0
- moru/api/client/models/node.py +155 -0
- moru/api/client/models/node_detail.py +165 -0
- moru/api/client/models/node_metrics.py +122 -0
- moru/api/client/models/node_status.py +11 -0
- moru/api/client/models/node_status_change.py +79 -0
- moru/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- moru/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- moru/api/client/models/resumed_sandbox.py +68 -0
- moru/api/client/models/sandbox.py +145 -0
- moru/api/client/models/sandbox_detail.py +183 -0
- moru/api/client/models/sandbox_log.py +70 -0
- moru/api/client/models/sandbox_log_entry.py +93 -0
- moru/api/client/models/sandbox_log_entry_fields.py +44 -0
- moru/api/client/models/sandbox_logs.py +91 -0
- moru/api/client/models/sandbox_metric.py +118 -0
- moru/api/client/models/sandbox_network_config.py +92 -0
- moru/api/client/models/sandbox_state.py +9 -0
- moru/api/client/models/sandboxes_with_metrics.py +59 -0
- moru/api/client/models/team.py +83 -0
- moru/api/client/models/team_api_key.py +158 -0
- moru/api/client/models/team_metric.py +86 -0
- moru/api/client/models/team_user.py +68 -0
- moru/api/client/models/template.py +217 -0
- moru/api/client/models/template_build.py +139 -0
- moru/api/client/models/template_build_file_upload.py +70 -0
- moru/api/client/models/template_build_info.py +126 -0
- moru/api/client/models/template_build_request.py +115 -0
- moru/api/client/models/template_build_request_v2.py +88 -0
- moru/api/client/models/template_build_request_v3.py +88 -0
- moru/api/client/models/template_build_start_v2.py +184 -0
- moru/api/client/models/template_build_status.py +11 -0
- moru/api/client/models/template_legacy.py +207 -0
- moru/api/client/models/template_request_response_v3.py +83 -0
- moru/api/client/models/template_step.py +91 -0
- moru/api/client/models/template_update_request.py +59 -0
- moru/api/client/models/template_with_builds.py +148 -0
- moru/api/client/models/update_team_api_key.py +59 -0
- moru/api/client/py.typed +1 -0
- moru/api/client/types.py +54 -0
- moru/api/client_async/__init__.py +50 -0
- moru/api/client_sync/__init__.py +52 -0
- moru/api/metadata.py +14 -0
- moru/connection_config.py +217 -0
- moru/envd/api.py +59 -0
- moru/envd/filesystem/filesystem_connect.py +193 -0
- moru/envd/filesystem/filesystem_pb2.py +76 -0
- moru/envd/filesystem/filesystem_pb2.pyi +233 -0
- moru/envd/process/process_connect.py +155 -0
- moru/envd/process/process_pb2.py +92 -0
- moru/envd/process/process_pb2.pyi +304 -0
- moru/envd/rpc.py +61 -0
- moru/envd/versions.py +6 -0
- moru/exceptions.py +95 -0
- moru/sandbox/commands/command_handle.py +69 -0
- moru/sandbox/commands/main.py +39 -0
- moru/sandbox/filesystem/filesystem.py +94 -0
- moru/sandbox/filesystem/watch_handle.py +60 -0
- moru/sandbox/main.py +210 -0
- moru/sandbox/mcp.py +1120 -0
- moru/sandbox/network.py +8 -0
- moru/sandbox/sandbox_api.py +210 -0
- moru/sandbox/signature.py +45 -0
- moru/sandbox/utils.py +34 -0
- moru/sandbox_async/commands/command.py +336 -0
- moru/sandbox_async/commands/command_handle.py +196 -0
- moru/sandbox_async/commands/pty.py +240 -0
- moru/sandbox_async/filesystem/filesystem.py +531 -0
- moru/sandbox_async/filesystem/watch_handle.py +62 -0
- moru/sandbox_async/main.py +734 -0
- moru/sandbox_async/paginator.py +69 -0
- moru/sandbox_async/sandbox_api.py +325 -0
- moru/sandbox_async/utils.py +7 -0
- moru/sandbox_sync/commands/command.py +328 -0
- moru/sandbox_sync/commands/command_handle.py +150 -0
- moru/sandbox_sync/commands/pty.py +230 -0
- moru/sandbox_sync/filesystem/filesystem.py +518 -0
- moru/sandbox_sync/filesystem/watch_handle.py +69 -0
- moru/sandbox_sync/main.py +726 -0
- moru/sandbox_sync/paginator.py +69 -0
- moru/sandbox_sync/sandbox_api.py +308 -0
- moru/template/consts.py +30 -0
- moru/template/dockerfile_parser.py +275 -0
- moru/template/logger.py +232 -0
- moru/template/main.py +1360 -0
- moru/template/readycmd.py +138 -0
- moru/template/types.py +105 -0
- moru/template/utils.py +320 -0
- moru/template_async/build_api.py +202 -0
- moru/template_async/main.py +366 -0
- moru/template_sync/build_api.py +199 -0
- moru/template_sync/main.py +371 -0
- moru-0.1.0.dist-info/METADATA +63 -0
- moru-0.1.0.dist-info/RECORD +152 -0
- moru-0.1.0.dist-info/WHEEL +4 -0
- moru-0.1.0.dist-info/licenses/LICENSE +9 -0
- moru_connect/__init__.py +1 -0
- moru_connect/client.py +493 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from types import TracebackType
|
|
3
|
+
from typing import Callable, Literal, Optional, List, Union
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from moru.api import handle_api_exception
|
|
8
|
+
from moru.api.client.api.templates import (
|
|
9
|
+
post_v3_templates,
|
|
10
|
+
get_templates_template_id_files_hash,
|
|
11
|
+
post_v_2_templates_template_id_builds_build_id,
|
|
12
|
+
get_templates_template_id_builds_build_id_status,
|
|
13
|
+
)
|
|
14
|
+
from moru.api.client.client import AuthenticatedClient
|
|
15
|
+
from moru.api.client.models import (
|
|
16
|
+
TemplateBuildRequestV3,
|
|
17
|
+
TemplateBuildStartV2,
|
|
18
|
+
TemplateBuildFileUpload,
|
|
19
|
+
TemplateBuild,
|
|
20
|
+
Error,
|
|
21
|
+
)
|
|
22
|
+
from moru.exceptions import BuildException, FileUploadException
|
|
23
|
+
from moru.template.logger import LogEntry
|
|
24
|
+
from moru.template.types import TemplateType
|
|
25
|
+
from moru.template.utils import get_build_step_index, tar_file_stream
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def request_build(
|
|
29
|
+
client: AuthenticatedClient, name: str, cpu_count: int, memory_mb: int
|
|
30
|
+
):
|
|
31
|
+
res = post_v3_templates.sync_detailed(
|
|
32
|
+
client=client,
|
|
33
|
+
body=TemplateBuildRequestV3(
|
|
34
|
+
alias=name,
|
|
35
|
+
cpu_count=cpu_count,
|
|
36
|
+
memory_mb=memory_mb,
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if res.status_code >= 300:
|
|
41
|
+
raise handle_api_exception(res, BuildException)
|
|
42
|
+
|
|
43
|
+
if isinstance(res.parsed, Error):
|
|
44
|
+
raise BuildException(f"API error: {res.parsed.message}")
|
|
45
|
+
|
|
46
|
+
if res.parsed is None:
|
|
47
|
+
raise BuildException("Failed to request build")
|
|
48
|
+
|
|
49
|
+
return res.parsed
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_file_upload_link(
|
|
53
|
+
client: AuthenticatedClient,
|
|
54
|
+
template_id: str,
|
|
55
|
+
files_hash: str,
|
|
56
|
+
stack_trace: Optional[TracebackType] = None,
|
|
57
|
+
) -> TemplateBuildFileUpload:
|
|
58
|
+
res = get_templates_template_id_files_hash.sync_detailed(
|
|
59
|
+
template_id=template_id,
|
|
60
|
+
hash_=files_hash,
|
|
61
|
+
client=client,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if res.status_code >= 300:
|
|
65
|
+
raise handle_api_exception(res, FileUploadException, stack_trace)
|
|
66
|
+
|
|
67
|
+
if isinstance(res.parsed, Error):
|
|
68
|
+
raise FileUploadException(f"API error: {res.parsed.message}").with_traceback(
|
|
69
|
+
stack_trace
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if res.parsed is None:
|
|
73
|
+
raise FileUploadException("Failed to get file upload link").with_traceback(
|
|
74
|
+
stack_trace
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return res.parsed
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def upload_file(
|
|
81
|
+
api_client: AuthenticatedClient,
|
|
82
|
+
file_name: str,
|
|
83
|
+
context_path: str,
|
|
84
|
+
url: str,
|
|
85
|
+
ignore_patterns: List[str],
|
|
86
|
+
resolve_symlinks: bool,
|
|
87
|
+
stack_trace: Optional[TracebackType],
|
|
88
|
+
):
|
|
89
|
+
try:
|
|
90
|
+
tar_buffer = tar_file_stream(
|
|
91
|
+
file_name, context_path, ignore_patterns, resolve_symlinks
|
|
92
|
+
)
|
|
93
|
+
client = api_client.get_httpx_client()
|
|
94
|
+
response = client.put(url, content=tar_buffer.getvalue())
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
except httpx.HTTPStatusError as e:
|
|
97
|
+
raise FileUploadException(f"Failed to upload file: {e}").with_traceback(
|
|
98
|
+
stack_trace
|
|
99
|
+
)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
raise FileUploadException(f"Failed to upload file: {e}").with_traceback(
|
|
102
|
+
stack_trace
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def trigger_build(
|
|
107
|
+
client: AuthenticatedClient,
|
|
108
|
+
template_id: str,
|
|
109
|
+
build_id: str,
|
|
110
|
+
template: TemplateType,
|
|
111
|
+
) -> None:
|
|
112
|
+
# Convert template dict to TemplateBuildStartV2 model using from_dict
|
|
113
|
+
template_data = TemplateBuildStartV2.from_dict(template)
|
|
114
|
+
|
|
115
|
+
res = post_v_2_templates_template_id_builds_build_id.sync_detailed(
|
|
116
|
+
template_id=template_id,
|
|
117
|
+
build_id=build_id,
|
|
118
|
+
client=client,
|
|
119
|
+
body=template_data,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if res.status_code >= 300:
|
|
123
|
+
raise handle_api_exception(res, BuildException)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_build_status(
|
|
127
|
+
client: AuthenticatedClient, template_id: str, build_id: str, logs_offset: int
|
|
128
|
+
) -> TemplateBuild:
|
|
129
|
+
res = get_templates_template_id_builds_build_id_status.sync_detailed(
|
|
130
|
+
template_id=template_id,
|
|
131
|
+
build_id=build_id,
|
|
132
|
+
client=client,
|
|
133
|
+
logs_offset=logs_offset,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if res.status_code >= 300:
|
|
137
|
+
raise handle_api_exception(res, BuildException)
|
|
138
|
+
|
|
139
|
+
if isinstance(res.parsed, Error):
|
|
140
|
+
raise BuildException(f"API error: {res.parsed.message}")
|
|
141
|
+
|
|
142
|
+
if res.parsed is None:
|
|
143
|
+
raise BuildException("Failed to get build status")
|
|
144
|
+
|
|
145
|
+
return res.parsed
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def wait_for_build_finish(
|
|
149
|
+
client: AuthenticatedClient,
|
|
150
|
+
template_id: str,
|
|
151
|
+
build_id: str,
|
|
152
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
153
|
+
logs_refresh_frequency: float = 0.2,
|
|
154
|
+
stack_traces: List[Union[TracebackType, None]] = [],
|
|
155
|
+
):
|
|
156
|
+
logs_offset = 0
|
|
157
|
+
status: Literal["building", "waiting", "ready", "error"] = "building"
|
|
158
|
+
|
|
159
|
+
while status in ["building", "waiting"]:
|
|
160
|
+
build_status = get_build_status(client, template_id, build_id, logs_offset)
|
|
161
|
+
|
|
162
|
+
logs_offset += len(build_status.log_entries)
|
|
163
|
+
|
|
164
|
+
for log_entry in build_status.log_entries:
|
|
165
|
+
if on_build_logs:
|
|
166
|
+
on_build_logs(
|
|
167
|
+
LogEntry(
|
|
168
|
+
timestamp=log_entry.timestamp,
|
|
169
|
+
level=log_entry.level.value,
|
|
170
|
+
message=log_entry.message,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
status = build_status.status.value
|
|
175
|
+
|
|
176
|
+
if status == "ready":
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
elif status == "waiting":
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
elif status == "error":
|
|
183
|
+
traceback = None
|
|
184
|
+
if build_status.reason and build_status.reason.step:
|
|
185
|
+
# Find the corresponding stack trace for the failed step
|
|
186
|
+
step_index = get_build_step_index(
|
|
187
|
+
build_status.reason.step, len(stack_traces)
|
|
188
|
+
)
|
|
189
|
+
if step_index < len(stack_traces):
|
|
190
|
+
traceback = stack_traces[step_index]
|
|
191
|
+
|
|
192
|
+
raise BuildException(
|
|
193
|
+
build_status.reason.message if build_status.reason else "Build failed"
|
|
194
|
+
).with_traceback(traceback)
|
|
195
|
+
|
|
196
|
+
# Wait for a short period before checking the status again
|
|
197
|
+
time.sleep(logs_refresh_frequency)
|
|
198
|
+
|
|
199
|
+
raise BuildException("Unknown build error occurred.")
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
from moru.api.client.client import AuthenticatedClient
|
|
6
|
+
from moru.connection_config import ConnectionConfig
|
|
7
|
+
|
|
8
|
+
from moru.api.client_sync import get_api_client
|
|
9
|
+
from moru.template.consts import RESOLVE_SYMLINKS
|
|
10
|
+
from moru.template.logger import LogEntry, LogEntryEnd, LogEntryStart
|
|
11
|
+
from moru.template.main import TemplateBase, TemplateClass
|
|
12
|
+
from moru.template.types import BuildInfo, InstructionType
|
|
13
|
+
from moru.template_sync.build_api import (
|
|
14
|
+
get_build_status,
|
|
15
|
+
get_file_upload_link,
|
|
16
|
+
request_build,
|
|
17
|
+
trigger_build,
|
|
18
|
+
upload_file,
|
|
19
|
+
wait_for_build_finish,
|
|
20
|
+
)
|
|
21
|
+
from moru.template.utils import read_dockerignore
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Template(TemplateBase):
|
|
25
|
+
"""
|
|
26
|
+
Synchronous template builder for Moru sandboxes.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _build(
|
|
31
|
+
template: TemplateClass,
|
|
32
|
+
api_client: AuthenticatedClient,
|
|
33
|
+
alias: str,
|
|
34
|
+
cpu_count: int = 2,
|
|
35
|
+
memory_mb: int = 1024,
|
|
36
|
+
skip_cache: bool = False,
|
|
37
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
38
|
+
) -> BuildInfo:
|
|
39
|
+
"""
|
|
40
|
+
Internal implementation of the template build process
|
|
41
|
+
|
|
42
|
+
:param template: The template to build
|
|
43
|
+
:param api_client: Authenticated API client
|
|
44
|
+
:param alias: Alias name for the template
|
|
45
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
46
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
47
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
48
|
+
:param on_build_logs: Callback function to receive build logs during the build process
|
|
49
|
+
"""
|
|
50
|
+
if skip_cache:
|
|
51
|
+
template._template._force = True
|
|
52
|
+
|
|
53
|
+
# Create template
|
|
54
|
+
if on_build_logs:
|
|
55
|
+
on_build_logs(
|
|
56
|
+
LogEntry(
|
|
57
|
+
timestamp=datetime.now(),
|
|
58
|
+
level="info",
|
|
59
|
+
message=f"Requesting build for template: {alias}",
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
response = request_build(
|
|
64
|
+
api_client,
|
|
65
|
+
name=alias,
|
|
66
|
+
cpu_count=cpu_count,
|
|
67
|
+
memory_mb=memory_mb,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
template_id = response.template_id
|
|
71
|
+
build_id = response.build_id
|
|
72
|
+
|
|
73
|
+
if on_build_logs:
|
|
74
|
+
on_build_logs(
|
|
75
|
+
LogEntry(
|
|
76
|
+
timestamp=datetime.now(),
|
|
77
|
+
level="info",
|
|
78
|
+
message=f"Template created with ID: {template_id}, Build ID: {build_id}",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
instructions_with_hashes = template._template._instructions_with_hashes()
|
|
83
|
+
|
|
84
|
+
# Upload files
|
|
85
|
+
for index, file_upload in enumerate(instructions_with_hashes):
|
|
86
|
+
if file_upload["type"] != InstructionType.COPY:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
args = file_upload.get("args", [])
|
|
90
|
+
src = args[0] if len(args) > 0 else None
|
|
91
|
+
force_upload = file_upload.get("forceUpload")
|
|
92
|
+
files_hash = file_upload.get("filesHash", None)
|
|
93
|
+
resolve_symlinks = file_upload.get("resolveSymlinks", RESOLVE_SYMLINKS)
|
|
94
|
+
|
|
95
|
+
if src is None or files_hash is None:
|
|
96
|
+
raise ValueError("Source path and files hash are required")
|
|
97
|
+
|
|
98
|
+
stack_trace = None
|
|
99
|
+
if index + 1 < len(template._template._stack_traces):
|
|
100
|
+
stack_trace = template._template._stack_traces[index + 1]
|
|
101
|
+
|
|
102
|
+
file_info = get_file_upload_link(
|
|
103
|
+
api_client, template_id, files_hash, stack_trace
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (force_upload and file_info.url) or (
|
|
107
|
+
file_info.present is False and file_info.url
|
|
108
|
+
):
|
|
109
|
+
upload_file(
|
|
110
|
+
api_client,
|
|
111
|
+
src,
|
|
112
|
+
template._template._file_context_path,
|
|
113
|
+
file_info.url,
|
|
114
|
+
[
|
|
115
|
+
*template._template._file_ignore_patterns,
|
|
116
|
+
*read_dockerignore(template._template._file_context_path),
|
|
117
|
+
],
|
|
118
|
+
resolve_symlinks,
|
|
119
|
+
stack_trace,
|
|
120
|
+
)
|
|
121
|
+
if on_build_logs:
|
|
122
|
+
on_build_logs(
|
|
123
|
+
LogEntry(
|
|
124
|
+
timestamp=datetime.now(),
|
|
125
|
+
level="info",
|
|
126
|
+
message=f"Uploaded '{src}'",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
if on_build_logs:
|
|
131
|
+
on_build_logs(
|
|
132
|
+
LogEntry(
|
|
133
|
+
timestamp=datetime.now(),
|
|
134
|
+
level="info",
|
|
135
|
+
message=f"Skipping upload of '{src}', already cached",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if on_build_logs:
|
|
140
|
+
on_build_logs(
|
|
141
|
+
LogEntry(
|
|
142
|
+
timestamp=datetime.now(),
|
|
143
|
+
level="info",
|
|
144
|
+
message="All file uploads completed",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Start build
|
|
149
|
+
if on_build_logs:
|
|
150
|
+
on_build_logs(
|
|
151
|
+
LogEntry(
|
|
152
|
+
timestamp=datetime.now(),
|
|
153
|
+
level="info",
|
|
154
|
+
message="Starting building...",
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
trigger_build(
|
|
159
|
+
api_client,
|
|
160
|
+
template_id,
|
|
161
|
+
build_id,
|
|
162
|
+
template._template._serialize(instructions_with_hashes),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return BuildInfo(
|
|
166
|
+
alias=alias,
|
|
167
|
+
template_id=template_id,
|
|
168
|
+
build_id=build_id,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def build(
|
|
173
|
+
template: TemplateClass,
|
|
174
|
+
alias: str,
|
|
175
|
+
cpu_count: int = 2,
|
|
176
|
+
memory_mb: int = 1024,
|
|
177
|
+
skip_cache: bool = False,
|
|
178
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
179
|
+
api_key: Optional[str] = None,
|
|
180
|
+
domain: Optional[str] = None,
|
|
181
|
+
) -> BuildInfo:
|
|
182
|
+
"""
|
|
183
|
+
Build and deploy a template to Moru infrastructure.
|
|
184
|
+
|
|
185
|
+
:param template: The template to build
|
|
186
|
+
:param alias: Alias name for the template
|
|
187
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
188
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
189
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
190
|
+
:param on_build_logs: Callback function to receive build logs during the build process
|
|
191
|
+
:param api_key: Moru API key for authentication
|
|
192
|
+
:param domain: Domain of the Moru API
|
|
193
|
+
|
|
194
|
+
Example
|
|
195
|
+
```python
|
|
196
|
+
from moru import Template
|
|
197
|
+
|
|
198
|
+
template = (
|
|
199
|
+
Template()
|
|
200
|
+
.from_python_image('3')
|
|
201
|
+
.copy('requirements.txt', '/home/user/')
|
|
202
|
+
.run_cmd('pip install -r /home/user/requirements.txt')
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
Template.build(
|
|
206
|
+
template,
|
|
207
|
+
alias='my-python-env',
|
|
208
|
+
cpu_count=2,
|
|
209
|
+
memory_mb=1024
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
if on_build_logs:
|
|
215
|
+
on_build_logs(
|
|
216
|
+
LogEntryStart(
|
|
217
|
+
timestamp=datetime.now(),
|
|
218
|
+
message="Build started",
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
223
|
+
config = ConnectionConfig(
|
|
224
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
225
|
+
)
|
|
226
|
+
api_client = get_api_client(
|
|
227
|
+
config,
|
|
228
|
+
require_api_key=True,
|
|
229
|
+
require_access_token=False,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
data = Template._build(
|
|
233
|
+
template,
|
|
234
|
+
api_client,
|
|
235
|
+
alias,
|
|
236
|
+
cpu_count,
|
|
237
|
+
memory_mb,
|
|
238
|
+
skip_cache,
|
|
239
|
+
on_build_logs,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if on_build_logs:
|
|
243
|
+
on_build_logs(
|
|
244
|
+
LogEntry(
|
|
245
|
+
timestamp=datetime.now(),
|
|
246
|
+
level="info",
|
|
247
|
+
message="Waiting for logs...",
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
wait_for_build_finish(
|
|
252
|
+
api_client,
|
|
253
|
+
data.template_id,
|
|
254
|
+
data.build_id,
|
|
255
|
+
on_build_logs,
|
|
256
|
+
logs_refresh_frequency=TemplateBase._logs_refresh_frequency,
|
|
257
|
+
stack_traces=template._template._stack_traces,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return data
|
|
261
|
+
finally:
|
|
262
|
+
if on_build_logs:
|
|
263
|
+
on_build_logs(
|
|
264
|
+
LogEntryEnd(
|
|
265
|
+
timestamp=datetime.now(),
|
|
266
|
+
message="Build finished",
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def build_in_background(
|
|
272
|
+
template: TemplateClass,
|
|
273
|
+
alias: str,
|
|
274
|
+
cpu_count: int = 2,
|
|
275
|
+
memory_mb: int = 1024,
|
|
276
|
+
skip_cache: bool = False,
|
|
277
|
+
on_build_logs: Optional[Callable[[LogEntry], None]] = None,
|
|
278
|
+
api_key: Optional[str] = None,
|
|
279
|
+
domain: Optional[str] = None,
|
|
280
|
+
) -> BuildInfo:
|
|
281
|
+
"""
|
|
282
|
+
Build and deploy a template to Moru infrastructure without waiting for completion.
|
|
283
|
+
|
|
284
|
+
:param template: The template to build
|
|
285
|
+
:param alias: Alias name for the template
|
|
286
|
+
:param cpu_count: Number of CPUs allocated to the sandbox
|
|
287
|
+
:param memory_mb: Amount of memory in MB allocated to the sandbox
|
|
288
|
+
:param skip_cache: If True, forces a complete rebuild ignoring cache
|
|
289
|
+
:param api_key: Moru API key for authentication
|
|
290
|
+
:param domain: Domain of the Moru API
|
|
291
|
+
:return: BuildInfo containing the template ID and build ID
|
|
292
|
+
|
|
293
|
+
Example
|
|
294
|
+
```python
|
|
295
|
+
from moru import Template
|
|
296
|
+
|
|
297
|
+
template = (
|
|
298
|
+
Template()
|
|
299
|
+
.from_python_image('3')
|
|
300
|
+
.run_cmd('echo "test"')
|
|
301
|
+
.set_start_cmd('echo "Hello"', 'sleep 1')
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
build_info = Template.build_in_background(
|
|
305
|
+
template,
|
|
306
|
+
alias='my-python-env',
|
|
307
|
+
cpu_count=2,
|
|
308
|
+
memory_mb=1024
|
|
309
|
+
)
|
|
310
|
+
```
|
|
311
|
+
"""
|
|
312
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
313
|
+
config = ConnectionConfig(
|
|
314
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
315
|
+
)
|
|
316
|
+
api_client = get_api_client(
|
|
317
|
+
config,
|
|
318
|
+
require_api_key=True,
|
|
319
|
+
require_access_token=False,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return Template._build(
|
|
323
|
+
template,
|
|
324
|
+
api_client,
|
|
325
|
+
alias,
|
|
326
|
+
cpu_count,
|
|
327
|
+
memory_mb,
|
|
328
|
+
skip_cache,
|
|
329
|
+
on_build_logs,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def get_build_status(
|
|
334
|
+
build_info: BuildInfo,
|
|
335
|
+
logs_offset: int = 0,
|
|
336
|
+
api_key: Optional[str] = None,
|
|
337
|
+
domain: Optional[str] = None,
|
|
338
|
+
):
|
|
339
|
+
"""
|
|
340
|
+
Get the status of a build.
|
|
341
|
+
|
|
342
|
+
:param build_info: Build identifiers returned from build_in_background
|
|
343
|
+
:param logs_offset: Offset for fetching logs
|
|
344
|
+
:param api_key: Moru API key for authentication
|
|
345
|
+
:param domain: Domain of the Moru API
|
|
346
|
+
:return: TemplateBuild containing the build status and logs
|
|
347
|
+
|
|
348
|
+
Example
|
|
349
|
+
```python
|
|
350
|
+
from moru import Template
|
|
351
|
+
|
|
352
|
+
build_info = Template.build_in_background(template, alias='my-template')
|
|
353
|
+
status = Template.get_build_status(build_info, logs_offset=0)
|
|
354
|
+
```
|
|
355
|
+
"""
|
|
356
|
+
domain = domain or os.environ.get("MORU_DOMAIN", "moru.io")
|
|
357
|
+
config = ConnectionConfig(
|
|
358
|
+
domain=domain, api_key=api_key or os.environ.get("MORU_API_KEY")
|
|
359
|
+
)
|
|
360
|
+
api_client = get_api_client(
|
|
361
|
+
config,
|
|
362
|
+
require_api_key=True,
|
|
363
|
+
require_access_token=False,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return get_build_status(
|
|
367
|
+
api_client,
|
|
368
|
+
build_info.template_id,
|
|
369
|
+
build_info.build_id,
|
|
370
|
+
logs_offset,
|
|
371
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moru
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Moru SDK that gives agents cloud environments
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Moru AI
|
|
8
|
+
Author-email: hello@moru.ai
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Dist: attrs (>=23.2.0)
|
|
19
|
+
Requires-Dist: dockerfile-parse (>=2.0.1,<3.0.0)
|
|
20
|
+
Requires-Dist: httpcore (>=1.0.5,<2.0.0)
|
|
21
|
+
Requires-Dist: httpx (>=0.27.0,<1.0.0)
|
|
22
|
+
Requires-Dist: packaging (>=24.1)
|
|
23
|
+
Requires-Dist: protobuf (>=4.21.0)
|
|
24
|
+
Requires-Dist: python-dateutil (>=2.8.2)
|
|
25
|
+
Requires-Dist: rich (>=14.0.0)
|
|
26
|
+
Requires-Dist: typing-extensions (>=4.1.0)
|
|
27
|
+
Requires-Dist: wcmatch (>=10.1,<11.0)
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/moru-ai/sdks/issues
|
|
29
|
+
Project-URL: Homepage, https://github.com/moru-ai/sdks
|
|
30
|
+
Project-URL: Repository, https://github.com/moru-ai/sdks/tree/main/packages/python-sdk
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Moru Python SDK
|
|
34
|
+
|
|
35
|
+
Moru SDK for Python provides cloud environments for AI agents.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install moru
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### 1. Set your API key
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export MORU_API_KEY=your_api_key
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Create a sandbox
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from moru import Sandbox
|
|
55
|
+
|
|
56
|
+
with Sandbox() as sandbox:
|
|
57
|
+
sandbox.run_code('print("Hello from Moru!")')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Acknowledgement
|
|
61
|
+
|
|
62
|
+
This project is a fork of [E2B](https://github.com/e2b-dev/E2B).
|
|
63
|
+
|