cycls 0.0.2.9__py3-none-any.whl → 0.0.2.14__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
@@ -1,24 +1,24 @@
1
1
  from fastapi import FastAPI, Request
2
- from fastapi.responses import StreamingResponse, PlainTextResponse
2
+ from fastapi.responses import StreamingResponse
3
3
  from pydantic import BaseModel
4
+ from typing import List, Dict, Optional
4
5
 
5
6
  from functools import wraps
6
7
  import uvicorn, socket, httpx
7
8
  import inspect
8
9
 
9
- from concurrent.futures import ThreadPoolExecutor
10
- import time, subprocess
10
+ import logging
11
+ logging.basicConfig(level=logging.ERROR)
11
12
 
12
13
  import os
13
14
  current_dir = os.path.dirname(os.path.abspath(__file__))
14
- key_path = os.path.join(current_dir, 'key.pub')
15
- # print(key_path)
15
+ key_path = os.path.join(current_dir, 'tuns')
16
16
 
17
- from typing import List, Dict
18
17
  class Message(BaseModel):
19
18
  handle: str
20
19
  content: str
21
- history: List[Dict[str, str]]
20
+ id: str
21
+ history: Optional[List[Dict[str, str]]] = None
22
22
 
23
23
  def find_available_port(start_port):
24
24
  port = start_port
@@ -28,86 +28,88 @@ def find_available_port(start_port):
28
28
  return port
29
29
  port += 1
30
30
 
31
+ import asyncssh, asyncio
32
+
33
+ async def create_ssh_tunnel(x,y,z='tuns.sh'):
34
+ try:
35
+ async with asyncssh.connect(z,client_keys=[key_path],known_hosts=None) as conn:
36
+ listener = await conn.forward_remote_port(x, 80, 'localhost', y)
37
+ print("✦/✧","tunnel\n")
38
+ await listener.wait_closed()
39
+ except (OSError, asyncssh.Error) as e:
40
+ print("✦/✧",f"tunnel disconnected: {e}")
41
+
42
+ def register(handles, network, url, mode):
43
+ try:
44
+ with httpx.Client() as client:
45
+ response = client.post(f"{network}/register", json={"handles":handles, "url":url, "mode":mode})
46
+ if response.status_code==200:
47
+ data = (response.json()).get("content")
48
+ for i in data:print(f"✦/✧ {i[0]} / {network}/{i[1]}")
49
+ else:
50
+ print("✦/✧ failed to register ⚠️")
51
+ except Exception as e:
52
+ print(f"An error occurred: {e}")
53
+
54
+ async def run_server(x,y):
55
+ config = uvicorn.Config(x, host="127.0.0.1", port=y, log_level="error")
56
+ server = uvicorn.Server(config)
57
+ await server.serve()
58
+
31
59
  class Cycls:
32
- def __init__(self,network="https://cycls.com", port=find_available_port(8001),url="",debug=False):
33
- self.handle = None
60
+ def __init__(self, url="", network="https://cycls.com", port=find_available_port(8001)):
61
+ import uuid
62
+ self.subdomain = str(uuid.uuid4())[:8]
34
63
  self.server = FastAPI()
35
64
  self.network = network
36
65
  self.port = port
37
66
  self.url = url
38
- self.debug = debug
67
+ self.apps = {}
39
68
 
40
69
  def __call__(self, handle):
41
- self.handle = handle
42
70
  def decorator(func):
43
- if inspect.iscoroutinefunction(func):
44
- @wraps(func)
45
- async def async_wrapper(*args, **kwargs):
46
- return await func(*args, **kwargs)
47
- self.server.post('/main')(async_wrapper)
48
- self.publish()
49
- return async_wrapper
50
- else:
51
- @wraps(func)
52
- def sync_wrapper(*args, **kwargs):
53
- return func(*args, **kwargs)
54
- self.server.post('/main')(sync_wrapper)
55
- self.publish()
56
- return sync_wrapper
71
+ @wraps(func)
72
+ async def async_wrapper(*args, **kwargs):
73
+ return await func(*args, **kwargs)
74
+ @wraps(func)
75
+ def sync_wrapper(*args, **kwargs):
76
+ return func(*args, **kwargs)
77
+ wrapper = async_wrapper if inspect.iscoroutinefunction(func) else sync_wrapper
78
+ self.apps["@"+handle] = wrapper
79
+ return wrapper
57
80
  return decorator
58
-
59
- def publish(self):
81
+
82
+ async def gateway(self, request: Request):
83
+ data = await request.json()
84
+ handle = data.get('handle')
85
+ if handle in self.apps:
86
+ func = self.apps[handle]
87
+ message = Message(**data)
88
+ return await func(message) if inspect.iscoroutinefunction(func) else func(message)
89
+ return {"error": "Handle not found"}
90
+
91
+ def push(self):
92
+ self.server.post("/gateway")(self.gateway)
93
+ asyncio.run(self.publish())
94
+
95
+ async def publish(self):
60
96
  prod=False
61
97
  if self.url != "":
62
98
  prod=True
63
99
 
64
- if self.debug: print("✦/✧ debug = True")
100
+ print(f"✦/✧ serving at port: {self.port}")
65
101
  if prod:
66
- print("✦/✧","production mode",f"(url: {self.url}, port: {self.port})")
102
+ print("✦/✧",f"production mode | url: {self.url}")
103
+ register(list(self.apps.keys()), self.network, self.url+"/gateway", "prod")
67
104
  else:
68
- print("✦/✧","development mode",f"(port: {self.port})")
69
- # self.url = f"https://{self.handle}-cycls.tuns.sh"
70
- self.url = f"https://{self.handle}-cycls.serveo.net"
71
-
72
- print("")
73
- print("✦/✧",f"https://cycls.com/@{self.handle}")
74
- print("")
75
-
76
- with ThreadPoolExecutor() as executor:
77
- if not prod:
78
- self.register('dev')
79
- executor.submit(self.tunnel)
80
- else:
81
- self.register('prod')
82
-
83
- if self.debug:
84
- executor.submit(uvicorn.run(self.server, host="127.0.0.1", port=self.port)) # perhaps keep traces?
85
- else:
86
- executor.submit(uvicorn.run(self.server, host="127.0.0.1", port=self.port, log_level="critical")) # perhaps keep traces?
87
-
88
- def register(self, mode):
89
- try:
90
- with httpx.Client() as client:
91
- response = client.post(f"{self.network}/register", json={"handle": f"@{self.handle}", "url": self.url, "mode": mode})
92
- if response.status_code==200:
93
- print(f"✦/✧ published 🎉")
94
- print("")
95
- else:
96
- print("✦/✧ failed to register ⚠️") # exit app
97
- except Exception as e:
98
- print(f"An error occurred: {e}")
99
-
100
- def tunnel(self):
101
- # ssh_command = ['ssh', '-q', '-i', 'tuns', '-o', 'StrictHostKeyChecking=no', '-R', f'{self.handle}-cycls:80:localhost:{self.port}', 'tuns.sh']
102
- # ssh_command = ['ssh', '-q', '-i', 'key.pub', '-o', 'StrictHostKeyChecking=no', '-R', f'{self.handle}-cycls:80:localhost:{self.port}', 'serveo.net']
103
- ssh_command = ['ssh', '-q', '-i', key_path, '-o', 'StrictHostKeyChecking=no', '-R', f'{self.handle}-cycls:80:localhost:{self.port}', 'serveo.net']
104
- try:
105
- if self.debug:
106
- process = subprocess.run(ssh_command,stdin=subprocess.DEVNULL) # very tricky! STDIN is what was messing with me
107
- else:
108
- process = subprocess.run(ssh_command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
109
- except Exception as e:
110
- print(f"An error occurred: {e}") # exit app
105
+ self.url = f"http://{self.subdomain}-cycls.tuns.sh"
106
+ print("✦/✧","development mode")
107
+ # print("✦/✧",f"url {self.url}")
108
+ register(list(self.apps.keys()), self.network, self.url+"/gateway", "dev")
109
+
110
+ t1 = asyncio.create_task(create_ssh_tunnel(f"{self.subdomain}-cycls", self.port))
111
+ t2 = asyncio.create_task(run_server(self.server,self.port))
112
+ await asyncio.gather(t1, t2) if not prod else asyncio.gather(t2)
111
113
 
112
114
  Text = StreamingResponse
113
115
 
cycls/tuns ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN OPENSSH PRIVATE KEY-----
2
+ b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
3
+ NhAAAAAwEAAQAAAQEAxq9z8nrMUnCRBSDhOcUeAZ//dolIg7RQoXM+Yo6aPACuqRMjo70b
4
+ wzXotyIqFRHe5eLntoa5FLj4n/3/uKVjy+f0xwPxquzTRESJehmUNN7WXaXQt2TX7BmMeo
5
+ vxWiQ69qzcQMQx7FNJNKSsA4Ynw9fIg/NDvEAtnYotmcF37finCJzj3OOxAaAB53bAzfY2
6
+ GD0JL+W8bzuMXpyZaqD+33selRNLqWVbcgNrJW6IDtZXvT1gBRsVsycbNrBrwFuhafnLW1
7
+ KAP/ZLLRP4w+BUgJVJVu9PqTfTrAcnDwspduNJqAtYPMbvuoToip1kYwws3wl8hHpsal67
8
+ U5xZeOn+5QAAA9i3Fmc8txZnPAAAAAdzc2gtcnNhAAABAQDGr3PyesxScJEFIOE5xR4Bn/
9
+ 92iUiDtFChcz5ijpo8AK6pEyOjvRvDNei3IioVEd7l4ue2hrkUuPif/f+4pWPL5/THA/Gq
10
+ 7NNERIl6GZQ03tZdpdC3ZNfsGYx6i/FaJDr2rNxAxDHsU0k0pKwDhifD18iD80O8QC2dii
11
+ 2ZwXft+KcInOPc47EBoAHndsDN9jYYPQkv5bxvO4xenJlqoP7fex6VE0upZVtyA2slbogO
12
+ 1le9PWAFGxWzJxs2sGvAW6Fp+ctbUoA/9kstE/jD4FSAlUlW70+pN9OsBycPCyl240moC1
13
+ g8xu+6hOiKnWRjDCzfCXyEemxqXrtTnFl46f7lAAAAAwEAAQAAAQBFcWl7JMRpRALL4hQW
14
+ VvkH5F4rlgwMTGeqJld1pxXtRufFHHVmc2BSuHLgH0bKGnbnrokCWNAzl/r+II7SgKwCxs
15
+ 3dCVncPe4RfEr4rBwK5p/SF3R9xPdbBAr/gg4XTXZ2ZTCOSoSQbwO1LKEakjcv0im5RLs1
16
+ /tBysasChIZgW9x4gGj7Kv4kz6Ojak/enlylwoz4q/dxaX9wV0op8YbnBubI3PS4gosYyz
17
+ fR9QAQbLrugo79xM9MpH1XjpQmk4UkuJX+OWqtZHq4Q2ZLuCoN4dv/mDcijAa/+lVtzerj
18
+ Ku6XtUREHRfdbKi88j+E7N4CfLW26UmV5xQhRjC8m+b5AAAAgQDoBpIUKoLEVVsWjc6n/e
19
+ TGip/qTlzcakODPELOzFK13GCAb/gj/sPxT6tUa382X9jcotgFSYBnO2TFA8gbBSXovWZv
20
+ zrXsNwfQBnvKih1Wngsq/bk7MYUvNPC0zomX0/dPi+riNb/5jLSEy5uICY4swMnsYb2Eyw
21
+ 5CO+eHC9yiWAAAAIEA6Rx6TvBlMvqt5YbmdOX6LdNi9KNbh4ZmimFJu95IuvYUDol0aDa+
22
+ qc85GPBXt/vU6+mD3A1somE2WI2P0sswdYn+g7pbBdItA3NcCuqBFl735bOU6CQ5mygY2P
23
+ B/AE43+jeQUFfjBM+lDJNB8adffyBOQBhgmGAqGCq/p6GtgvMAAACBANoxpAHC8Q4cm4No
24
+ vsCefODzNUqgsOtgrJXfp3aDVx2pL9Ua71EAh2e5eWvr8YdvmhgU7ophAeEezQqt0nrQYW
25
+ xheJQQd1hgm50DY9ssKNcFHd8BkLwGVRbgDfwhPLDektZZqRlbrVsqIBkDurVI764h2v8M
26
+ G/fyU3Xv57Ed1vzHAAAAHm1vaGFtbWVkYWxydWpheWlATW9oYW1tZWRzLUFpcgECAwQ=
27
+ -----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: cycls
3
+ Version: 0.0.2.14
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, Text
38
+
39
+ cycls = Cycls()
40
+
41
+ # sync app on https://cycls.com/@spark
42
+ @cycls("spark")
43
+ def spark_app(x):
44
+ print("history", x.history)
45
+ print("session id", x.id)
46
+ return Text(x.content+" from spark")
47
+
48
+ # async app on https://cycls.com/@cake
49
+ @cycls("cake")
50
+ async def cake_app(x):
51
+ print("history", x.history)
52
+ print("session id", x.id)
53
+ return Text(x.content+" from cake")
54
+
55
+ # publish to https://cycls.com
56
+ cycls.push()
57
+ ```
58
+
59
+ - `Text` renders markdown
60
+ - `Text` is both streaming/bulk based on input
61
+
62
+
63
+ Try it live https://cycls.com/@groq
64
+
@@ -0,0 +1,6 @@
1
+ cycls/__init__.py,sha256=mBBqS9LS550Uqluyxs7VdD1W-9sDkQfwqeX681PNiis,39
2
+ cycls/cycls.py,sha256=6LoGg4q5__AsTBHEfdctjoXXIzrwo3veRKaBdpwBd8M,4032
3
+ cycls/tuns,sha256=CT8olxDKOM0cY6r_bbSGtnWyEmEYFVDVHj9ug3gbYHk,1843
4
+ cycls-0.0.2.14.dist-info/METADATA,sha256=rZejL67T3wGlc3XRsjBedhIyDbsnOOg-zvEKE2gQNd0,1824
5
+ cycls-0.0.2.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
+ cycls-0.0.2.14.dist-info/RECORD,,
cycls/key.pub DELETED
@@ -1 +0,0 @@
1
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC022DIsZ11836rKBvsXcd1H7AMbirWyFSZczn3KkwaheLE90DigO/WkYhPMP5XJ9L5MIGM9nemiQhciZdN9f7z9o0xJh72ZI2+tl5Fa77a9r+IsuWbhn8fNGIkzvyYx8zwSTZV/f2nMYr4I7sjTVFVlH2Een6R8RCZpJPe9Q4ph+k2BCp4DeKzCuOrH6t//flm4vZnP8EXXXpSxic0TpbSL+Is7jeRsZdA4/7LrASCxzxnz4e0RCyy61B/TCkOHbSk06SPjoykA9amMMQ3T0OaO72bimiiCbRqEudgX0gNSVTxcRpmPARNJJDk72gAuOwAD3cpvIiW1nPu6Zhgb2VozQhsN+Rmh3ZjlYRvBVsHbwAkGb0iTBrtLA9ghiPLD2oXWdRI6P5XnNvSRyRXcrt59e1w9Z6XmKOmRl94NYciaMqH1qEEVnNCWUZyYKpMWRseL7v95FSNEoIL47+I4OPTF657sxZHlE85Oxei858kS6/b8GUT4yJgCq/PnhjN7UtGYCVRSv6UHGgXGvLMZAtHmbs3RlhEeLsluPvzgsDhmO6XzdKgh/MZIHKMIP/BEU11FT2lh/kB4EP48dqQPJMFNefB1ybEC509RCSdzaDATYIGRi7+FV4B1O15nC3EQTudFJl7io3chsLbkayAbGRInYVUJ0WDA5Et7ekIKRPdfw== m@m
@@ -1,131 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: cycls
3
- Version: 0.0.2.9
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: fastapi (>=0.111.0,<0.112.0)
15
- Requires-Dist: groq (>=0.9.0,<0.10.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
- # cycls.py
22
-
23
- ```sh
24
- pip install cycls
25
- ```
26
-
27
- ## sync app
28
- ```py
29
- from cycls import Cycls, Message, Text
30
-
31
- push = Cycls()
32
-
33
- @push("cake")
34
- def app(m: Message):
35
- return Text(m.content)
36
- ```
37
- `https://cycls.com/@cake`
38
-
39
- ## async app
40
- ```py
41
- from cycls import Cycls, Message, Text
42
-
43
- push = Cycls()
44
-
45
- @push("her")
46
- async def app(m: Message):
47
- return Text(m.content)
48
- ```
49
- `https://cycls.com/@her`
50
-
51
- ## debug
52
- ```py
53
- push = Cycls(debug=True)
54
- ```
55
-
56
- ## groq app
57
- ```py
58
- from cycls import Cycls, Message, Text
59
- from groq import AsyncGroq
60
-
61
- push = Cycls()
62
-
63
- groq = AsyncGroq(api_key="YOUR_KEY")
64
-
65
- async def llm(content):
66
- stream = await groq.chat.completions.create(
67
- messages=[
68
- {"role": "system", "content": "you are a helpful assistant."},
69
- {"role": "user", "content": content}
70
- ],
71
- model="llama3-70b-8192",
72
- temperature=0.5, max_tokens=1024, top_p=1, stop=None,
73
- stream=True,
74
- )
75
-
76
- async def event_stream():
77
- async for chunk in stream:
78
- yield f"{chunk.choices[0].delta.content}"
79
-
80
- return event_stream()
81
-
82
- @push("groq-app")
83
- async def app(x:Message):
84
- stream = await llm(x.content)
85
- return Text(stream)
86
- ```
87
- `https://cycls.com/@groq-app`
88
-
89
- ## history
90
- ```py
91
- @push("cake")
92
- def app(m:Message):
93
- print(m.history)
94
- return Text(m.content)
95
- ```
96
-
97
- ## groq app with history
98
- ```py
99
- from cycls import Cycls, Message, Text
100
- from groq import AsyncGroq
101
-
102
- push = Cycls()
103
-
104
- groq = AsyncGroq(api_key="YOUR_KEY")
105
-
106
- async def llm(messages):
107
- stream = await groq.chat.completions.create(
108
- messages=messages,
109
- model="llama3-70b-8192",
110
- temperature=0.5, max_tokens=1024, top_p=1, stop=None,
111
- stream=True,
112
- )
113
-
114
- async def event_stream():
115
- async for chunk in stream:
116
- yield f"{chunk.choices[0].delta.content}"
117
-
118
- return event_stream()
119
-
120
- @push("groq-app")
121
- async def app(m:Message):
122
- x = [{"role": "system", "content": "you are a helpful assistant."}]
123
- x += m.history
124
- x += [{"role": "user", "content": m.content}]
125
- stream = await llm(x)
126
- return Text(stream)
127
- ```
128
-
129
- # Known issues
130
- - Dev mode doesn't work on Windows machines
131
-
@@ -1,6 +0,0 @@
1
- cycls/__init__.py,sha256=mBBqS9LS550Uqluyxs7VdD1W-9sDkQfwqeX681PNiis,39
2
- cycls/cycls.py,sha256=3PSVlL17yoqIEvPxi_3zYqbV2e0PinkFecCKlC_Ip8k,4317
3
- cycls/key.pub,sha256=hWHddJ0TceUqDpm2xV4hDuNZKAz_wHpWox1yLaZ-JVw,729
4
- cycls-0.0.2.9.dist-info/METADATA,sha256=XbLo2g73VdpafV20l7AYzpyTuJVEWssU7DE1nPv5jIE,2845
5
- cycls-0.0.2.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
- cycls-0.0.2.9.dist-info/RECORD,,