cycls 0.0.2.68__py3-none-any.whl → 0.0.2.69__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,6 +9,9 @@ from pathlib import Path
|
|
|
9
9
|
from contextlib import contextmanager
|
|
10
10
|
import tarfile
|
|
11
11
|
|
|
12
|
+
# Enable BuildKit for faster builds with better caching
|
|
13
|
+
os.environ["DOCKER_BUILDKIT"] = "1"
|
|
14
|
+
|
|
12
15
|
# --- Top-Level Helper Functions ---
|
|
13
16
|
|
|
14
17
|
def _bootstrap_script(payload_file: str, result_file: str) -> str:
|
|
@@ -325,6 +328,110 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
325
328
|
print(f"\n🛑 Operation stopped: {e}")
|
|
326
329
|
return None
|
|
327
330
|
|
|
331
|
+
def watch(self, *args, **kwargs):
|
|
332
|
+
"""Runs the container with file watching - restarts script on changes."""
|
|
333
|
+
try:
|
|
334
|
+
from watchfiles import watch as watchfiles_watch
|
|
335
|
+
except ImportError:
|
|
336
|
+
print("❌ watchfiles not installed. Run: pip install watchfiles")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
import inspect
|
|
340
|
+
|
|
341
|
+
# Get the main script (the outermost .py file in the stack)
|
|
342
|
+
main_script = None
|
|
343
|
+
for frame_info in inspect.stack():
|
|
344
|
+
filename = frame_info.filename
|
|
345
|
+
if filename.endswith('.py') and not filename.startswith('<'):
|
|
346
|
+
main_script = Path(filename).resolve()
|
|
347
|
+
# main_script is now the outermost/first script in the call chain
|
|
348
|
+
|
|
349
|
+
# Build watch paths: main script + copy sources
|
|
350
|
+
watch_paths = []
|
|
351
|
+
if main_script and main_script.exists():
|
|
352
|
+
watch_paths.append(main_script)
|
|
353
|
+
watch_paths.extend([Path(src).resolve() for src in self.copy.keys() if Path(src).exists()])
|
|
354
|
+
|
|
355
|
+
if not watch_paths:
|
|
356
|
+
print("⚠️ No files to watch. Running without watch mode.")
|
|
357
|
+
return self.run(*args, **kwargs)
|
|
358
|
+
|
|
359
|
+
print(f"👀 Watching for changes:")
|
|
360
|
+
for p in watch_paths:
|
|
361
|
+
print(f" {p}")
|
|
362
|
+
print()
|
|
363
|
+
|
|
364
|
+
while True:
|
|
365
|
+
# Run the container in a subprocess-like manner
|
|
366
|
+
print(f"🚀 Running function '{self.name}' in container...")
|
|
367
|
+
self._perform_auto_cleanup()
|
|
368
|
+
self._build_image_if_needed()
|
|
369
|
+
|
|
370
|
+
port = kwargs.get('port', None)
|
|
371
|
+
ports_mapping = {f'{port}/tcp': port} if port else None
|
|
372
|
+
|
|
373
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
374
|
+
tmpdir = Path(tmpdir_str)
|
|
375
|
+
payload_path = tmpdir / self.payload_file
|
|
376
|
+
|
|
377
|
+
with payload_path.open('wb') as f:
|
|
378
|
+
cloudpickle.dump((self.func, args, kwargs), f)
|
|
379
|
+
|
|
380
|
+
container = self.docker_client.containers.create(
|
|
381
|
+
image=self.tag,
|
|
382
|
+
volumes={str(tmpdir): {'bind': self.io_dir, 'mode': 'rw'}},
|
|
383
|
+
ports=ports_mapping,
|
|
384
|
+
labels={self.managed_label: "true"}
|
|
385
|
+
)
|
|
386
|
+
container.start()
|
|
387
|
+
print(f"✅ Container running on port {port}")
|
|
388
|
+
print("👀 Waiting for file changes... (Ctrl+C to stop)\n")
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
# Watch for changes in a separate thread
|
|
392
|
+
import threading
|
|
393
|
+
import time
|
|
394
|
+
change_detected = threading.Event()
|
|
395
|
+
|
|
396
|
+
def watch_thread():
|
|
397
|
+
for changes in watchfiles_watch(*watch_paths):
|
|
398
|
+
changed_files = [str(c[1]) for c in changes]
|
|
399
|
+
print(f"\n🔄 Changes detected:")
|
|
400
|
+
for f in changed_files:
|
|
401
|
+
print(f" {f}")
|
|
402
|
+
change_detected.set()
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
watcher = threading.Thread(target=watch_thread, daemon=True)
|
|
406
|
+
watcher.start()
|
|
407
|
+
|
|
408
|
+
# Stream container logs in separate thread
|
|
409
|
+
def log_thread():
|
|
410
|
+
for chunk in container.logs(stream=True, follow=True):
|
|
411
|
+
if change_detected.is_set():
|
|
412
|
+
break
|
|
413
|
+
print(chunk.decode('utf-8').strip())
|
|
414
|
+
|
|
415
|
+
logger = threading.Thread(target=log_thread, daemon=True)
|
|
416
|
+
logger.start()
|
|
417
|
+
|
|
418
|
+
# Wait for change or interrupt
|
|
419
|
+
while not change_detected.is_set():
|
|
420
|
+
time.sleep(0.5)
|
|
421
|
+
|
|
422
|
+
print("\n🔄 Restarting...")
|
|
423
|
+
container.stop(timeout=2)
|
|
424
|
+
container.remove()
|
|
425
|
+
# Re-exec the main script
|
|
426
|
+
print(f"🔄 Re-executing {main_script.name}...\n")
|
|
427
|
+
os.execv(sys.executable, [sys.executable, str(main_script)])
|
|
428
|
+
|
|
429
|
+
except KeyboardInterrupt:
|
|
430
|
+
print("\n🛑 Stopping...")
|
|
431
|
+
container.stop(timeout=2)
|
|
432
|
+
container.remove()
|
|
433
|
+
return
|
|
434
|
+
|
|
328
435
|
def build(self, *args, **kwargs):
|
|
329
436
|
"""Builds a self-contained, deployable Docker image locally."""
|
|
330
437
|
print("📦 Building self-contained image for deployment...")
|
cycls/sdk.py
CHANGED
|
@@ -88,13 +88,16 @@ class Agent:
|
|
|
88
88
|
uvicorn.run(web(agent.func, agent.config), host="0.0.0.0", port=port)
|
|
89
89
|
return
|
|
90
90
|
|
|
91
|
-
def deploy(self, prod=False, port=8080):
|
|
91
|
+
def deploy(self, prod=False, port=8080, watch=False):
|
|
92
92
|
if not self.registered_functions:
|
|
93
93
|
print("Error: No @agent decorated function found.")
|
|
94
94
|
return
|
|
95
95
|
if (self.key is None) and prod:
|
|
96
96
|
print("🛑 Error: Please add your Cycls API key")
|
|
97
97
|
return
|
|
98
|
+
if prod and watch:
|
|
99
|
+
print("⚠️ Warning: watch=True ignored in production mode.")
|
|
100
|
+
watch = False
|
|
98
101
|
|
|
99
102
|
agent = self.registered_functions[0]
|
|
100
103
|
if len(self.registered_functions) > 1:
|
|
@@ -118,7 +121,12 @@ class Agent:
|
|
|
118
121
|
base_url=self.base_url,
|
|
119
122
|
api_key=self.key
|
|
120
123
|
)
|
|
121
|
-
|
|
124
|
+
if prod:
|
|
125
|
+
new.deploy(port=port)
|
|
126
|
+
elif watch:
|
|
127
|
+
new.watch(port=port)
|
|
128
|
+
else:
|
|
129
|
+
new.run(port=port)
|
|
122
130
|
return
|
|
123
131
|
|
|
124
132
|
def modal(self, prod=False):
|
|
@@ -4,9 +4,9 @@ cycls/default-theme/assets/index-B0ZKcm_V.css,sha256=wK9-NhEB8xPcN9Zv69zpOcfGTlF
|
|
|
4
4
|
cycls/default-theme/assets/index-D5EDcI4J.js,sha256=sN4qRcAXa7DBd9JzmVcCoCwH4l8cNCM-U9QGUjBvWSo,1346506
|
|
5
5
|
cycls/default-theme/index.html,sha256=bM-yW_g0cGrV40Q5yY3ccY0fM4zI1Wuu5I8EtGFJIxs,828
|
|
6
6
|
cycls/dev-theme/index.html,sha256=QJBHkdNuMMiwQU7o8dN8__8YQeQB45D37D-NCXIWB2Q,11585
|
|
7
|
-
cycls/runtime.py,sha256=
|
|
8
|
-
cycls/sdk.py,sha256=
|
|
7
|
+
cycls/runtime.py,sha256=BaP5sLG3NLPQZwuKwoYueC3R5WeuNg2-YzOQes6paUE,23221
|
|
8
|
+
cycls/sdk.py,sha256=iOSFFPSN2iGCTIL2ZBOpb2X5jjUEm7gQbAuanYHF4qg,6974
|
|
9
9
|
cycls/web.py,sha256=3M3qaWTNY3dpgd7Vq5aXREp-cIFsHrDqBQ1YkGrOaUk,4659
|
|
10
|
-
cycls-0.0.2.
|
|
11
|
-
cycls-0.0.2.
|
|
12
|
-
cycls-0.0.2.
|
|
10
|
+
cycls-0.0.2.69.dist-info/METADATA,sha256=3nG949VOjQ1K-sftwDGsAQheGAt-GCKWGUmDgUBgOP4,7943
|
|
11
|
+
cycls-0.0.2.69.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
12
|
+
cycls-0.0.2.69.dist-info/RECORD,,
|
|
File without changes
|