cycls 0.0.2.18__py3-none-any.whl → 0.0.2.21__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/cycls.py
CHANGED
|
@@ -10,6 +10,8 @@ import inspect
|
|
|
10
10
|
import logging
|
|
11
11
|
logging.basicConfig(level=logging.ERROR)
|
|
12
12
|
|
|
13
|
+
O = lambda x,y: print(f"✦/✧ {str(x).ljust(11)} | {y}")
|
|
14
|
+
|
|
13
15
|
import os
|
|
14
16
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
15
17
|
key_path = os.path.join(current_dir, 'tuns')
|
|
@@ -20,6 +22,7 @@ class Message(BaseModel):
|
|
|
20
22
|
id: str
|
|
21
23
|
history: Optional[List[Dict[str, str]]] = None
|
|
22
24
|
|
|
25
|
+
#!
|
|
23
26
|
def find_available_port(start_port):
|
|
24
27
|
port = start_port
|
|
25
28
|
while True:
|
|
@@ -29,42 +32,40 @@ def find_available_port(start_port):
|
|
|
29
32
|
port += 1
|
|
30
33
|
|
|
31
34
|
import asyncssh, asyncio
|
|
32
|
-
|
|
33
35
|
async def create_ssh_tunnel(x,y,z='tuns.sh'):
|
|
34
36
|
try:
|
|
35
37
|
async with asyncssh.connect(z,client_keys=[key_path],known_hosts=None) as conn:
|
|
36
38
|
listener = await conn.forward_remote_port(x, 80, 'localhost', y)
|
|
37
|
-
|
|
39
|
+
O("tunnel","open");print(" ")
|
|
38
40
|
await listener.wait_closed()
|
|
41
|
+
O("tunnel", "closed")
|
|
39
42
|
except (OSError, asyncssh.Error) as e:
|
|
40
|
-
|
|
43
|
+
O("tunnel",f"disconnected ({e})")
|
|
41
44
|
|
|
42
|
-
def register(handles,
|
|
45
|
+
def register(handles, net, url, email):
|
|
43
46
|
try:
|
|
44
47
|
with httpx.Client() as client:
|
|
45
|
-
response = client.post(f"{
|
|
48
|
+
response = client.post(f"{net}/register", json={"handles":handles, "url":url, "email":email})
|
|
46
49
|
if response.status_code==200:
|
|
47
50
|
data = (response.json()).get("content")
|
|
48
|
-
for i in data:
|
|
51
|
+
for i in data:
|
|
52
|
+
O(i[0],f"{net}/{i[1]}")
|
|
49
53
|
else:
|
|
50
54
|
print("✦/✧ failed to register ⚠️")
|
|
51
55
|
except Exception as e:
|
|
52
56
|
print(f"An error occurred: {e}")
|
|
53
57
|
|
|
54
|
-
async def run_server(x,y):
|
|
55
|
-
config = uvicorn.Config(x, host="127.0.0.1", port=y, log_level="error") # loop="asyncio"
|
|
56
|
-
server = uvicorn.Server(config)
|
|
57
|
-
await server.serve()
|
|
58
|
-
|
|
59
58
|
class Cycls:
|
|
60
|
-
def __init__(self, url="",
|
|
59
|
+
def __init__(self, url="", net="https://cycls.com", port=find_available_port(8001), email=None):
|
|
61
60
|
import uuid
|
|
62
61
|
self.subdomain = str(uuid.uuid4())[:8]
|
|
63
62
|
self.server = FastAPI()
|
|
64
|
-
self.
|
|
63
|
+
self.net = net
|
|
65
64
|
self.port = port
|
|
66
65
|
self.url = url
|
|
67
66
|
self.apps = {}
|
|
67
|
+
self.prod = False
|
|
68
|
+
self.email = email
|
|
68
69
|
|
|
69
70
|
def __call__(self, handle):
|
|
70
71
|
def decorator(func):
|
|
@@ -75,13 +76,20 @@ class Cycls:
|
|
|
75
76
|
def sync_wrapper(*args, **kwargs):
|
|
76
77
|
return StreamingResponse(func(*args, **kwargs))
|
|
77
78
|
wrapper = async_wrapper if inspect.iscoroutinefunction(func) else sync_wrapper
|
|
78
|
-
|
|
79
|
+
|
|
80
|
+
if self.url != "": self.prod=True #!
|
|
81
|
+
if not self.prod:
|
|
82
|
+
self.apps[handle + "-dev"] = wrapper
|
|
83
|
+
else:
|
|
84
|
+
self.apps[handle] = wrapper
|
|
85
|
+
|
|
79
86
|
return wrapper
|
|
80
87
|
return decorator
|
|
81
88
|
|
|
82
89
|
async def gateway(self, request: Request):
|
|
83
90
|
data = await request.json()
|
|
84
91
|
handle = data.get('handle')
|
|
92
|
+
print(handle,self.apps)
|
|
85
93
|
if handle in self.apps:
|
|
86
94
|
func = self.apps[handle]
|
|
87
95
|
message = Message(**data)
|
|
@@ -89,36 +97,40 @@ class Cycls:
|
|
|
89
97
|
return {"error": "Handle not found"}
|
|
90
98
|
|
|
91
99
|
def push(self):
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
prod=True
|
|
99
|
-
|
|
100
|
-
print(f"✦/✧ port | {self.port}")
|
|
101
|
-
if prod:
|
|
102
|
-
print("✦/✧",f"production mode | url: {self.url}")
|
|
103
|
-
register(list(self.apps.keys()), self.network, self.url+"/gateway", "prod")
|
|
100
|
+
if self.email:
|
|
101
|
+
O("email",self.email)
|
|
102
|
+
O("port",self.port)
|
|
103
|
+
if self.prod:
|
|
104
|
+
O("mode",f"production @ {self.url}")
|
|
105
|
+
register(list(self.apps.keys()), self.net, self.url+"/gateway", self.email)
|
|
104
106
|
else:
|
|
105
107
|
self.url = f"http://{self.subdomain}-cycls.tuns.sh"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
register(list(self.apps.keys()), self.
|
|
109
|
-
t1 = asyncio.create_task(create_ssh_tunnel(f"{self.subdomain}-cycls", self.port))
|
|
108
|
+
O("mode","development")
|
|
109
|
+
O("docs","for more information, visit https://github.com/Cycls/cycls-py")
|
|
110
|
+
register(list(self.apps.keys()), self.net, self.url+"/gateway", self.email)
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if
|
|
115
|
-
|
|
112
|
+
self.server.post("/gateway")(self.gateway)
|
|
113
|
+
@self.server.on_event("startup")
|
|
114
|
+
def startup_event():
|
|
115
|
+
if self.prod:
|
|
116
|
+
pass
|
|
116
117
|
else:
|
|
117
|
-
|
|
118
|
+
asyncio.create_task(create_ssh_tunnel(f"{self.subdomain}-cycls", self.port))
|
|
119
|
+
try:
|
|
120
|
+
uvicorn.run(self.server, host="127.0.0.1", port=self.port, log_level="error")
|
|
118
121
|
except KeyboardInterrupt:
|
|
119
|
-
|
|
120
|
-
tasks = asyncio.all_tasks()
|
|
121
|
-
for task in tasks:
|
|
122
|
-
task.cancel()
|
|
122
|
+
print(" ");O("exit","done")
|
|
123
123
|
|
|
124
|
+
async def call(self, handle, content):
|
|
125
|
+
data = {"handle":handle, "content":content, "session":{}, "agent":"yes"}
|
|
126
|
+
try:
|
|
127
|
+
url = f"{self.net}/stream/"
|
|
128
|
+
async with httpx.AsyncClient(timeout=20) as client, client.stream("POST", url, json=data) as response:
|
|
129
|
+
if response.status_code != 200:
|
|
130
|
+
print("http error")
|
|
131
|
+
async for token in response.aiter_text():
|
|
132
|
+
yield token
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print("Exception", e)
|
|
135
|
+
|
|
124
136
|
# poetry publish --build
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cycls
|
|
3
|
+
Version: 0.0.2.21
|
|
4
|
+
Summary: Cycls SDK
|
|
5
|
+
Author: Mohammed Jamal
|
|
6
|
+
Author-email: mj@cycls.com
|
|
7
|
+
Requires-Python: >=3.8,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: asyncssh (>=2.14.2,<3.0.0)
|
|
15
|
+
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
16
|
+
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
</br></br>
|
|
20
|
+
<p align="center">
|
|
21
|
+
<picture>
|
|
22
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cycls.com/static/assets/logo-gold.svg">
|
|
23
|
+
<source media="(prefers-color-scheme: light)" srcset="https://cycls.com/static/assets/logo.svg">
|
|
24
|
+
<img alt="Cycls" src="https://cycls.com/static/assets/logo.svg" width="150">
|
|
25
|
+
</picture>
|
|
26
|
+
</p>
|
|
27
|
+
</br></br>
|
|
28
|
+
|
|
29
|
+
<div align="center">
|
|
30
|
+
<a href="https://pypi.org/project/cycls/" target="_blank" rel="noopener noreferrer">
|
|
31
|
+
<img loading="lazy" src="https://img.shields.io/pypi/v/cycls.svg" alt="PyPI" class="img_ev3q" style="display: inline;">
|
|
32
|
+
</a>
|
|
33
|
+
<a href="https://discord.gg/BMnaMatDC7" target="_blank" rel="noopener noreferrer">
|
|
34
|
+
<img loading="lazy" src="https://img.shields.io/discord/1175782747164389466" alt="Discord" class="img_ev3q" style="display: inline;">
|
|
35
|
+
</a>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
</br>
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pip install cycls
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
# Apps ✦
|
|
45
|
+
Instantly publish and share AI apps
|
|
46
|
+
|
|
47
|
+
```py
|
|
48
|
+
from cycls import Cycls
|
|
49
|
+
|
|
50
|
+
cycls = Cycls()
|
|
51
|
+
|
|
52
|
+
@cycls("@spark")
|
|
53
|
+
def app(x):
|
|
54
|
+
return x.content + "from spark"
|
|
55
|
+
|
|
56
|
+
cycls.push()
|
|
57
|
+
```
|
|
58
|
+
`cycls.push()` will then publish the app `@spark` on [cycls.com/@spark](https://cycls.com/@spark)
|
|
59
|
+
## Async Apps
|
|
60
|
+
For performance, make the function asynchronous. The following is an async app with message `history` and session `id`
|
|
61
|
+
```py
|
|
62
|
+
from cycls import Cycls
|
|
63
|
+
|
|
64
|
+
cycls = Cycls()
|
|
65
|
+
|
|
66
|
+
@cycls("@spark")
|
|
67
|
+
async def app(x):
|
|
68
|
+
print(x.history, x.id)
|
|
69
|
+
return x.content + "from spark"
|
|
70
|
+
|
|
71
|
+
cycls.push()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
# Agents ✧
|
|
75
|
+
Call any public app as an agent, see [explore](https://explore.cycls.com)
|
|
76
|
+
```py
|
|
77
|
+
from cycls import Cycls
|
|
78
|
+
|
|
79
|
+
cycls = Cycls()
|
|
80
|
+
|
|
81
|
+
@cycls("@spark")
|
|
82
|
+
async def app(x):
|
|
83
|
+
return cycls.call("@groq",
|
|
84
|
+
x.content)
|
|
85
|
+
|
|
86
|
+
cycls.push()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Try it live
|
|
90
|
+
- [cycls.com/@groq](https://cycls.com/@groq) | [groq.py](https://github.com/Cycls/examples/blob/main/groq.py)
|
|
91
|
+
- [cycls.com/@openai](https://cycls.com/@openai) | [openai.py](https://github.com/Cycls/examples/blob/main/openai.py)
|
|
92
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
cycls/__init__.py,sha256=HPPQikt_Trbc4s8XyEOeOjB_JRWXlXIkJuh762nUM_g,24
|
|
2
|
+
cycls/cycls.py,sha256=Qz5O27nZrQjrwIwiBbr_VuiptUKUzVi-_oAwdGgVeyM,4826
|
|
3
|
+
cycls/tuns,sha256=CT8olxDKOM0cY6r_bbSGtnWyEmEYFVDVHj9ug3gbYHk,1843
|
|
4
|
+
cycls-0.0.2.21.dist-info/METADATA,sha256=DMY3hJaeDS8rOHOZ7Lj8s-ckDNt3mZ4lpmBI7oJyctw,2593
|
|
5
|
+
cycls-0.0.2.21.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
6
|
+
cycls-0.0.2.21.dist-info/RECORD,,
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: cycls
|
|
3
|
-
Version: 0.0.2.18
|
|
4
|
-
Summary: Cycls SDK
|
|
5
|
-
Author: Mohammed Jamal
|
|
6
|
-
Author-email: mj@cycls.com
|
|
7
|
-
Requires-Python: >=3.8,<4.0
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Requires-Dist: asyncssh (>=2.14.2,<3.0.0)
|
|
15
|
-
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
16
|
-
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
|
-
</br></br><p align="center"><img src="https://cycls.com/static/assets/favicon.svg" alt="Cycls"></p></br>
|
|
20
|
-
|
|
21
|
-
<div align="center">
|
|
22
|
-
<a href="https://pypi.org/project/cycls/" target="_blank" rel="noopener noreferrer">
|
|
23
|
-
<img loading="lazy" src="https://img.shields.io/pypi/v/cycls.svg" alt="PyPI" class="img_ev3q" style="display: inline;">
|
|
24
|
-
</a>
|
|
25
|
-
<a href="https://discord.gg/BMnaMatDC7" target="_blank" rel="noopener noreferrer">
|
|
26
|
-
<img loading="lazy" src="https://img.shields.io/discord/1175782747164389466" alt="Discord" class="img_ev3q" style="display: inline;">
|
|
27
|
-
</a>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
</br>
|
|
31
|
-
|
|
32
|
-
```sh
|
|
33
|
-
pip install cycls
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
```py
|
|
37
|
-
from cycls import Cycls
|
|
38
|
-
|
|
39
|
-
cycls = Cycls()
|
|
40
|
-
|
|
41
|
-
# sync app on https://cycls.com/@spark
|
|
42
|
-
@cycls("spark")
|
|
43
|
-
def spark_app(message):
|
|
44
|
-
print("history", message.history)
|
|
45
|
-
print("session id", message.id)
|
|
46
|
-
return message.content + "from spark"
|
|
47
|
-
|
|
48
|
-
# async app on https://cycls.com/@cake
|
|
49
|
-
@cycls("cake")
|
|
50
|
-
async def cake_app(message):
|
|
51
|
-
print("history", message.history)
|
|
52
|
-
print("session id", message.id)
|
|
53
|
-
return message.content + "from cake"
|
|
54
|
-
|
|
55
|
-
# publish to https://cycls.com
|
|
56
|
-
cycls.push()
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Return a string. Supports markdown. Supports generators for streaming responses.
|
|
60
|
-
|
|
61
|
-
try it live
|
|
62
|
-
- https://cycls.com/@groq
|
|
63
|
-
- https://cycls.com/@openai
|
|
64
|
-
|
|
65
|
-
code examples
|
|
66
|
-
- https://github.com/Cycls/examples/blob/main/groq.py
|
|
67
|
-
- https://github.com/Cycls/examples/blob/main/openai.py
|
|
68
|
-
|
cycls-0.0.2.18.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
cycls/__init__.py,sha256=HPPQikt_Trbc4s8XyEOeOjB_JRWXlXIkJuh762nUM_g,24
|
|
2
|
-
cycls/cycls.py,sha256=-jm2IM_yNlZoUYmcDhqTcz_Umiwf7jQLBq_s6pbpAQM,4304
|
|
3
|
-
cycls/tuns,sha256=CT8olxDKOM0cY6r_bbSGtnWyEmEYFVDVHj9ug3gbYHk,1843
|
|
4
|
-
cycls-0.0.2.18.dist-info/METADATA,sha256=bpAtiAC5ACoddUPX7EYzzikKgT-IZf0f_CyJCGqFasA,2018
|
|
5
|
-
cycls-0.0.2.18.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
6
|
-
cycls-0.0.2.18.dist-info/RECORD,,
|
|
File without changes
|