cycls 0.0.2.33__tar.gz → 0.0.2.34__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycls
3
- Version: 0.0.2.33
3
+ Version: 0.0.2.34
4
4
  Summary: Cycls SDK
5
5
  Author: Mohammed J. AlRujayi
6
6
  Author-email: mj@cycls.com
@@ -0,0 +1,2 @@
1
+ from .sdk import Agent
2
+ from .runtime import Runtime
@@ -132,7 +132,11 @@ class Runtime:
132
132
  if self.apt_packages else ""
133
133
  )
134
134
  run_shell_commands = "\n".join([f"RUN {cmd}" for cmd in self.run_commands]) if self.run_commands else ""
135
- copy_lines = "\n".join([f"COPY {src} {dst}" for src, dst in self.copy.items()])
135
+
136
+ # fixes absolute path copy, but causes collision
137
+ copy_lines = "\n".join([f"COPY {Path(src).name} {dst}" for src, dst in self.copy.items()])
138
+ # copy_lines = "\n".join([f"COPY {src} {dst}" for src, dst in self.copy.items()])
139
+
136
140
  expose_line = f"EXPOSE {port}" if port else ""
137
141
 
138
142
  return f"""
@@ -166,8 +170,14 @@ COPY {self.payload_file} {self.io_dir}/
166
170
  (workdir / self.payload_file).write_bytes(payload_bytes)
167
171
 
168
172
  if self.copy:
173
+ # for src in self.copy.keys():
174
+ # _copy_path(Path(src), workdir / src)
175
+
176
+ # fixes absolute path copy
169
177
  for src in self.copy.keys():
170
- _copy_path(Path(src), workdir / src)
178
+ src_path = Path(src)
179
+ dest_path = workdir / src_path.name if src_path.is_absolute() else workdir / src
180
+ _copy_path(src_path, dest_path)
171
181
 
172
182
  def _build_image_if_needed(self):
173
183
  """Checks if the base Docker image exists locally and builds it if not."""
@@ -178,8 +188,7 @@ COPY {self.payload_file} {self.io_dir}/
178
188
  except docker.errors.ImageNotFound:
179
189
  print(f"🛠️ Building new base image: {self.tag}")
180
190
 
181
- # with tempfile.TemporaryDirectory() as tmpdir_str:
182
- with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
191
+ with tempfile.TemporaryDirectory() as tmpdir_str:
183
192
  tmpdir = Path(tmpdir_str)
184
193
  # Prepare context without payload for the base image
185
194
  self._prepare_build_context(tmpdir)
@@ -210,8 +219,7 @@ COPY {self.payload_file} {self.io_dir}/
210
219
  container = None
211
220
  ports_mapping = {f'{port}/tcp': port} if port else None
212
221
 
213
- # with tempfile.TemporaryDirectory() as tmpdir_str:
214
- with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
222
+ with tempfile.TemporaryDirectory() as tmpdir_str:
215
223
  tmpdir = Path(tmpdir_str)
216
224
  payload_path = tmpdir / self.payload_file
217
225
  result_path = tmpdir / self.result_file
@@ -277,8 +285,7 @@ COPY {self.payload_file} {self.io_dir}/
277
285
  except docker.errors.ImageNotFound:
278
286
  print(f"🛠️ Building new deployable image: {final_tag}")
279
287
 
280
- # with tempfile.TemporaryDirectory() as tmpdir_str:
281
- with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
288
+ with tempfile.TemporaryDirectory() as tmpdir_str:
282
289
  tmpdir = Path(tmpdir_str)
283
290
  self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
284
291
 
@@ -309,8 +316,7 @@ COPY {self.payload_file} {self.io_dir}/
309
316
  payload_hash = hashlib.sha256(cloudpickle.dumps((self.func, args, kwargs))).hexdigest()[:16]
310
317
  archive_name = f"source-{self.tag.split(':')[1]}-{payload_hash}.tar.gz"
311
318
 
312
- # with tempfile.TemporaryDirectory() as tmpdir_str:
313
- with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
319
+ with tempfile.TemporaryDirectory() as tmpdir_str:
314
320
  tmpdir = Path(tmpdir_str)
315
321
  self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
316
322
 
@@ -375,8 +381,7 @@ COPY {self.payload_file} {self.io_dir}/
375
381
 
376
382
  port = kwargs.get('port', 8080)
377
383
 
378
- # with tempfile.TemporaryDirectory() as tmpdir_str:
379
- with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
384
+ with tempfile.TemporaryDirectory() as tmpdir_str:
380
385
  tmpdir = Path(tmpdir_str)
381
386
  self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
382
387
 
@@ -0,0 +1,102 @@
1
+ import json, time, modal, inspect, uvicorn
2
+ from .runtime import Runtime
3
+ from modal.runner import run_app
4
+ from .web import web
5
+ import importlib.resources
6
+
7
+ theme_path = importlib.resources.files('cycls').joinpath('theme')
8
+ cycls_path = importlib.resources.files('cycls')
9
+
10
+ class Agent:
11
+ def __init__(self, theme=theme_path, org=None, api_token=None, pip=[], apt=[], copy=[], keys=["",""], api_key=None):
12
+ self.org, self.api_token = org, api_token
13
+ self.theme = theme
14
+ self.keys, self.pip, self.apt, self.copy = keys, pip, apt, copy
15
+ self.api_key = api_key
16
+
17
+ self.registered_functions = []
18
+
19
+ def __call__(self, name="", header="", intro="", domain=None, auth=False):
20
+ def decorator(f):
21
+ self.registered_functions.append({
22
+ "func": f,
23
+ "config": ["public", False, self.org, self.api_token, header, intro, auth],
24
+ "name": name,
25
+ "domain": domain or f"{name}.cycls.ai",
26
+ })
27
+ return f
28
+ return decorator
29
+
30
+ def run(self, port=8080):
31
+ if not self.registered_functions:
32
+ print("Error: No @agent decorated function found.")
33
+ return
34
+
35
+ i = self.registered_functions[0]
36
+ if len(self.registered_functions) > 1:
37
+ print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
38
+ print(f"🚀 Starting local server at localhost:{port}")
39
+ i["config"][0], i["config"][6] = self.theme, False
40
+ uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
41
+ return
42
+
43
+ def deploy(self, prod=False, port=8080):
44
+ if not self.registered_functions:
45
+ print("Error: No @agent decorated function found.")
46
+ return
47
+ if (self.api_key is None) and prod:
48
+ print("🛑 Error: Please add your Cycls API key")
49
+ return
50
+
51
+ i = self.registered_functions[0]
52
+ if len(self.registered_functions) > 1:
53
+ print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
54
+
55
+ i["config"][6] = False
56
+
57
+ new = Runtime(
58
+ func=lambda port: __import__("uvicorn").run(__import__("web").web(i["func"], *i["config"]), host="0.0.0.0", port=port),
59
+ name="web-agent",
60
+ pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
61
+ copy={str(cycls_path.joinpath('theme')):"public", str(cycls_path)+"/web.py":"app/web.py"},
62
+ api_key=self.api_key
63
+ )
64
+ new.deploy(port=port) if prod else new.run(port=port)
65
+ return
66
+
67
+ def push(self, prod=False):
68
+ self.client = modal.Client.from_credentials(*self.keys)
69
+ image = (modal.Image.debian_slim()
70
+ .pip_install("fastapi[standard]", "pyjwt", "cryptography", *self.pip)
71
+ .apt_install(*self.apt)
72
+ .add_local_dir(self.theme, "/root/public")
73
+ .add_local_file(str(cycls_path)+"/web.py", "/root/web.py"))
74
+ for item in self.copy:
75
+ image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
76
+ self.app = modal.App("development", image=image)
77
+
78
+ if not self.registered_functions:
79
+ print("Error: No @agent decorated function found.")
80
+ return
81
+
82
+ for i in self.registered_functions:
83
+ i["config"][1] = True if prod else False
84
+ self.app.function(serialized=True, name=i["name"])(
85
+ modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
86
+ (lambda: __import__("web").web(i["func"], *i["config"]))
87
+ )
88
+ if prod:
89
+ for i in self.registered_functions:
90
+ print(f"✅ Deployed to ⇒ https://{i['domain']}")
91
+ self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
92
+ return
93
+ else:
94
+ with modal.enable_output():
95
+ run_app(app=self.app, client=self.client)
96
+ print(" Modal development server is running. Press Ctrl+C to stop.")
97
+ with modal.enable_output(), run_app(app=self.app, client=self.client):
98
+ while True: time.sleep(10)
99
+
100
+ # poetry config pypi-token.pypi <your-token>
101
+ # poetry run python agent.py
102
+ # poetry publish --build
@@ -1,8 +1,4 @@
1
- import json, time, modal, inspect, uvicorn
2
- from modal.runner import run_app
3
-
4
- import importlib.resources
5
- theme_path = importlib.resources.files('cycls').joinpath('theme')
1
+ import json, inspect
6
2
 
7
3
  async def openai_encoder(stream): # clean up the meta data / new API?
8
4
  async for message in stream:
@@ -41,7 +37,6 @@ XwIDAQAB
41
37
  """
42
38
 
43
39
  def web(func, front_end_path="", prod=False, org=None, api_token=None, header="", intro="", auth=True): # API auth
44
- print(front_end_path)
45
40
  from fastapi import FastAPI, Request, HTTPException, status, Depends
46
41
  from fastapi.responses import StreamingResponse , HTMLResponse
47
42
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
@@ -97,73 +92,4 @@ def web(func, front_end_path="", prod=False, org=None, api_token=None, header=""
97
92
  "pk_live": "pk_live_Y2xlcmsuY3ljbHMuY29tJA", "pk_test": "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"
98
93
  })
99
94
  app.mount("/", StaticFiles(directory=front_end_path, html=False))
100
- return app
101
-
102
- class Agent:
103
- def __init__(self, theme=theme_path, org=None, api_token=None, pip=[], apt=[], copy=[], keys=["",""]):
104
- self.org, self.api_token = org, api_token
105
- self.theme = theme
106
- self.keys, self.pip, self.apt, self.copy = keys, pip, apt, copy
107
-
108
- self.registered_functions = []
109
-
110
- def __call__(self, name="", header="", intro="", domain=None, auth=False):
111
- def decorator(f):
112
- self.registered_functions.append({
113
- "func": f,
114
- "config": ["public", False, self.org, self.api_token, header, intro, auth],
115
- "name": name,
116
- "domain": domain or f"{name}.cycls.ai",
117
- })
118
- return f
119
- return decorator
120
-
121
- def run(self, port=8000):
122
- if not self.registered_functions:
123
- print("Error: No @agent decorated function found.")
124
- return
125
-
126
- i = self.registered_functions[0]
127
- if len(self.registered_functions) > 1:
128
- print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
129
- print(f"🚀 Starting local server at localhost:{port}")
130
- i["config"][0], i["config"][6] = self.theme, False
131
- uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
132
- return
133
-
134
- def push(self, prod=False):
135
- self.client = modal.Client.from_credentials(*self.keys)
136
- image = (modal.Image.debian_slim()
137
- .pip_install("fastapi[standard]", "pyjwt", "cryptography", *self.pip)
138
- .apt_install(*self.apt)
139
- .add_local_dir(self.theme, "/root/public")
140
- .add_local_python_source("cycls"))
141
- for item in self.copy:
142
- image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
143
- self.app = modal.App("development", image=image)
144
-
145
- if not self.registered_functions:
146
- print("Error: No @agent decorated function found.")
147
- return
148
-
149
- for i in self.registered_functions:
150
- i["config"][1] = True if prod else False
151
- self.app.function(serialized=True, name=i["name"])(
152
- modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
153
- (lambda: web(i["func"], *i["config"]))
154
- )
155
- if prod:
156
- for i in self.registered_functions:
157
- print(f"✅ Deployed to ⇒ https://{i['domain']}")
158
- self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
159
- return
160
- else:
161
- with modal.enable_output():
162
- run_app(app=self.app, client=self.client)
163
- print(" Modal development server is running. Press Ctrl+C to stop.")
164
- with modal.enable_output(), run_app(app=self.app, client=self.client):
165
- while True: time.sleep(10)
166
-
167
- # poetry config pypi-token.pypi <your-token>
168
- # poetry run python agent.py
169
- # poetry publish --build
95
+ return app
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cycls"
3
- version = "0.0.2.33"
3
+ version = "0.0.2.34"
4
4
 
5
5
  packages = [{ include = "cycls" }]
6
6
  include = ["cycls/theme/**/*"]
@@ -1,2 +0,0 @@
1
- from .cycls import Agent
2
- from .sdk import function
@@ -1,22 +0,0 @@
1
- from .runtime import Runtime
2
-
3
- def function(python_version="3.12", pip_install=None, apt_install=None, run_commands=None, copy=None, name=None, base_url=None, api_key=None):
4
- # """
5
- # A decorator factory that transforms a Python function into a containerized,
6
- # remotely executable object.
7
-
8
- # Args:
9
- # pip (list[str], optional): A list of pip packages to install.
10
- # apt (list[str], optional): A list of apt packages to install.
11
- # copy (list[str], optional): A list of local paths to copy to the
12
- # same path inside the image. For static dependencies.
13
- # name (str, optional): A name for this function. Defaults to the function's name.
14
-
15
- # Returns:
16
- # A decorator that replaces the decorated function with a Runtime instance.
17
- # """
18
- def decorator(func):
19
- Name = name or func.__name__ # should be moved to runtime... or default?
20
- copy_dict = {i:i for i in copy or []}
21
- return Runtime(func, Name.replace('_', '-'), python_version, pip_install, apt_install, run_commands, copy_dict, base_url, api_key)
22
- return decorator
File without changes