cycls 0.0.2.34__py3-none-any.whl → 0.0.2.35__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.
cycls/runtime.py
CHANGED
|
@@ -9,14 +9,6 @@ from pathlib import Path
|
|
|
9
9
|
from contextlib import contextmanager
|
|
10
10
|
import tarfile
|
|
11
11
|
|
|
12
|
-
# --- Docker Client Initialization ---
|
|
13
|
-
try:
|
|
14
|
-
docker_client = docker.from_env()
|
|
15
|
-
except docker.errors.DockerException:
|
|
16
|
-
print("❌ Error: Docker is not running or not installed.")
|
|
17
|
-
print("Please start the Docker daemon and try again.")
|
|
18
|
-
sys.exit(1)
|
|
19
|
-
|
|
20
12
|
# --- Top-Level Helper Functions ---
|
|
21
13
|
|
|
22
14
|
def _bootstrap_script(payload_file: str, result_file: str) -> str:
|
|
@@ -84,7 +76,7 @@ class Runtime:
|
|
|
84
76
|
"""
|
|
85
77
|
def __init__(self, func, name, python_version=None, pip_packages=None, apt_packages=None, run_commands=None, copy=None, base_url=None, api_key=None):
|
|
86
78
|
self.func = func
|
|
87
|
-
self.python_version = python_version or "
|
|
79
|
+
self.python_version = python_version or f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
88
80
|
self.pip_packages = sorted(pip_packages or [])
|
|
89
81
|
self.apt_packages = sorted(apt_packages or [])
|
|
90
82
|
self.run_commands = sorted(run_commands or [])
|
|
@@ -104,6 +96,50 @@ class Runtime:
|
|
|
104
96
|
self.tag = self._generate_base_tag()
|
|
105
97
|
|
|
106
98
|
self.api_key = api_key
|
|
99
|
+
self._docker_client = None
|
|
100
|
+
self.managed_label = f"cycls.runtime"
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def docker_client(self):
|
|
104
|
+
"""
|
|
105
|
+
Lazily initializes and returns a Docker client.
|
|
106
|
+
This ensures Docker is only required for methods that actually use it.
|
|
107
|
+
"""
|
|
108
|
+
if self._docker_client is None:
|
|
109
|
+
try:
|
|
110
|
+
print("🐳 Initializing Docker client...")
|
|
111
|
+
client = docker.from_env()
|
|
112
|
+
client.ping()
|
|
113
|
+
self._docker_client = client
|
|
114
|
+
except docker.errors.DockerException:
|
|
115
|
+
print("\n❌ Error: Docker is not running or is not installed.")
|
|
116
|
+
print(" This is required for local 'run' and 'build' operations.")
|
|
117
|
+
print(" Please start the Docker daemon and try again.")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
return self._docker_client
|
|
120
|
+
|
|
121
|
+
def _perform_auto_cleanup(self):
|
|
122
|
+
"""Performs a simple, automatic cleanup of old Docker resources."""
|
|
123
|
+
try:
|
|
124
|
+
for container in self.docker_client.containers.list(all=True, filters={"label": self.managed_label}):
|
|
125
|
+
container.remove(force=True)
|
|
126
|
+
|
|
127
|
+
cleaned_images = 0
|
|
128
|
+
for image in self.docker_client.images.list(name=self.image_prefix):
|
|
129
|
+
is_current = self.tag in image.tags
|
|
130
|
+
is_deployable = any(t.startswith(f"{self.image_prefix}:deploy-") for t in image.tags)
|
|
131
|
+
|
|
132
|
+
if not is_current and not is_deployable:
|
|
133
|
+
self.docker_client.images.remove(image.id, force=True)
|
|
134
|
+
cleaned_images += 1
|
|
135
|
+
|
|
136
|
+
if cleaned_images > 0:
|
|
137
|
+
print(f"🧹 Cleaned up {cleaned_images} old image version(s).")
|
|
138
|
+
|
|
139
|
+
self.docker_client.images.prune(filters={'label': self.managed_label})
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"⚠️ An error occurred during cleanup: {e}")
|
|
107
143
|
|
|
108
144
|
def _generate_base_tag(self) -> str:
|
|
109
145
|
"""Creates a unique tag for the base Docker image based on its dependencies."""
|
|
@@ -182,7 +218,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
182
218
|
def _build_image_if_needed(self):
|
|
183
219
|
"""Checks if the base Docker image exists locally and builds it if not."""
|
|
184
220
|
try:
|
|
185
|
-
docker_client.images.get(self.tag)
|
|
221
|
+
self.docker_client.images.get(self.tag)
|
|
186
222
|
print(f"✅ Found cached base image: {self.tag}")
|
|
187
223
|
return
|
|
188
224
|
except docker.errors.ImageNotFound:
|
|
@@ -194,7 +230,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
194
230
|
self._prepare_build_context(tmpdir)
|
|
195
231
|
|
|
196
232
|
print("--- 🐳 Docker Build Logs (Base Image) ---")
|
|
197
|
-
response_generator = docker_client.api.build(
|
|
233
|
+
response_generator = self.docker_client.api.build(
|
|
198
234
|
path=str(tmpdir),
|
|
199
235
|
tag=self.tag,
|
|
200
236
|
forcerm=True,
|
|
@@ -215,6 +251,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
215
251
|
def runner(self, *args, **kwargs):
|
|
216
252
|
"""Context manager to set up, run, and tear down the container for local execution."""
|
|
217
253
|
port = kwargs.get('port', None)
|
|
254
|
+
self._perform_auto_cleanup()
|
|
218
255
|
self._build_image_if_needed()
|
|
219
256
|
container = None
|
|
220
257
|
ports_mapping = {f'{port}/tcp': port} if port else None
|
|
@@ -228,10 +265,11 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
228
265
|
cloudpickle.dump((self.func, args, kwargs), f)
|
|
229
266
|
|
|
230
267
|
try:
|
|
231
|
-
container = docker_client.containers.create(
|
|
268
|
+
container = self.docker_client.containers.create(
|
|
232
269
|
image=self.tag,
|
|
233
270
|
volumes={str(tmpdir): {'bind': self.io_dir, 'mode': 'rw'}},
|
|
234
|
-
ports=ports_mapping
|
|
271
|
+
ports=ports_mapping,
|
|
272
|
+
labels={self.managed_label: "true"}
|
|
235
273
|
)
|
|
236
274
|
container.start()
|
|
237
275
|
yield container, result_path
|
|
@@ -279,7 +317,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
279
317
|
final_tag = f"{self.image_prefix}:deploy-{payload_hash}"
|
|
280
318
|
|
|
281
319
|
try:
|
|
282
|
-
docker_client.images.get(final_tag)
|
|
320
|
+
self.docker_client.images.get(final_tag)
|
|
283
321
|
print(f"✅ Found cached deployable image: {final_tag}")
|
|
284
322
|
return final_tag
|
|
285
323
|
except docker.errors.ImageNotFound:
|
|
@@ -290,7 +328,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
290
328
|
self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
|
|
291
329
|
|
|
292
330
|
print("--- 🐳 Docker Build Logs (Final Image) ---")
|
|
293
|
-
response_generator = docker_client.api.build(
|
|
331
|
+
response_generator = self.docker_client.api.build(
|
|
294
332
|
path=str(tmpdir), tag=final_tag, forcerm=True, decode=True
|
|
295
333
|
)
|
|
296
334
|
try:
|
cycls/sdk.py
CHANGED
|
@@ -16,12 +16,13 @@ class Agent:
|
|
|
16
16
|
|
|
17
17
|
self.registered_functions = []
|
|
18
18
|
|
|
19
|
-
def __call__(self, name=
|
|
19
|
+
def __call__(self, name=None, header="", intro="", domain=None, auth=False):
|
|
20
20
|
def decorator(f):
|
|
21
21
|
self.registered_functions.append({
|
|
22
22
|
"func": f,
|
|
23
23
|
"config": ["public", False, self.org, self.api_token, header, intro, auth],
|
|
24
|
-
"name": name,
|
|
24
|
+
# "name": name,
|
|
25
|
+
"name": name or (f.__name__).replace('_', '-'),
|
|
25
26
|
"domain": domain or f"{name}.cycls.ai",
|
|
26
27
|
})
|
|
27
28
|
return f
|
|
@@ -40,7 +41,7 @@ class Agent:
|
|
|
40
41
|
uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
|
|
41
42
|
return
|
|
42
43
|
|
|
43
|
-
def
|
|
44
|
+
def cycls(self, prod=False, port=8080):
|
|
44
45
|
if not self.registered_functions:
|
|
45
46
|
print("Error: No @agent decorated function found.")
|
|
46
47
|
return
|
|
@@ -54,11 +55,15 @@ class Agent:
|
|
|
54
55
|
|
|
55
56
|
i["config"][6] = False
|
|
56
57
|
|
|
58
|
+
copy={str(cycls_path.joinpath('theme')):"public", str(cycls_path)+"/web.py":"app/web.py"}
|
|
59
|
+
copy.update({i:i for i in self.copy})
|
|
60
|
+
|
|
57
61
|
new = Runtime(
|
|
58
62
|
func=lambda port: __import__("uvicorn").run(__import__("web").web(i["func"], *i["config"]), host="0.0.0.0", port=port),
|
|
59
|
-
name="
|
|
63
|
+
name=i["name"],
|
|
64
|
+
apt_packages=self.apt,
|
|
60
65
|
pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
|
|
61
|
-
copy=
|
|
66
|
+
copy=copy,
|
|
62
67
|
api_key=self.api_key
|
|
63
68
|
)
|
|
64
69
|
new.deploy(port=port) if prod else new.run(port=port)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
cycls/__init__.py,sha256=ysfA4R_r_INokmpZGK2S8wf2ypLCqwyj0WdfX-hx0bs,51
|
|
2
|
-
cycls/runtime.py,sha256=
|
|
3
|
-
cycls/sdk.py,sha256=
|
|
2
|
+
cycls/runtime.py,sha256=U0dMUs9dxgjMTbtv9dzK3MQ57g8kun0ICqA30Cx9cBA,18187
|
|
3
|
+
cycls/sdk.py,sha256=gEdxYDtYE6FOWwLS33k0h5wuzwdcB9kvTMrkEBmz2iE,4573
|
|
4
4
|
cycls/theme/assets/index-D0-uI8sw.js,sha256=aUsqm9HZtEJz38o-0MW12ZVeOlSeKigwc_fYJBntiyI,1068551
|
|
5
5
|
cycls/theme/index.html,sha256=epB4cgSjC7xJOXpVuCwt9r7ivoGvLiXSrxsoOgINw58,895
|
|
6
6
|
cycls/web.py,sha256=nSEJMUQoPaz8qjgVmsC1JiDRv9Y1UKVzTH4_pRHp_VE,4260
|
|
7
|
-
cycls-0.0.2.
|
|
8
|
-
cycls-0.0.2.
|
|
9
|
-
cycls-0.0.2.
|
|
7
|
+
cycls-0.0.2.35.dist-info/METADATA,sha256=DztQ_n4XO8rvuwGq-bN7Y028yINUNjcH3XYbjF-OKsI,5666
|
|
8
|
+
cycls-0.0.2.35.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
9
|
+
cycls-0.0.2.35.dist-info/RECORD,,
|
|
File without changes
|