fudster 0.1.0__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.
- fudster-0.1.0/.flake8 +11 -0
- fudster-0.1.0/.python-version +1 -0
- fudster-0.1.0/PKG-INFO +195 -0
- fudster-0.1.0/README.md +19 -0
- fudster-0.1.0/fudster/__init__.py +41 -0
- fudster-0.1.0/fudster/api/__init__.py +4 -0
- fudster-0.1.0/fudster/api/api_connector.py +99 -0
- fudster-0.1.0/fudster/api/clients/__init__.py +5 -0
- fudster-0.1.0/fudster/api/clients/coindesk_client.py +32 -0
- fudster-0.1.0/fudster/api/clients/groq_client.py +59 -0
- fudster-0.1.0/fudster/api/clients/poetry_db_client.py +23 -0
- fudster-0.1.0/fudster/api/clients/websocket_echo_client.py +32 -0
- fudster-0.1.0/fudster/api/cors.py +38 -0
- fudster-0.1.0/fudster/api/routes.py +51 -0
- fudster-0.1.0/fudster/api/utils/__init__.py +13 -0
- fudster-0.1.0/fudster/api/utils/dynamic_endpoint_utils.py +25 -0
- fudster-0.1.0/fudster/api/utils/image_utils.py +68 -0
- fudster-0.1.0/fudster/api/utils/kr_decorator.py +29 -0
- fudster-0.1.0/fudster/api/utils/rss_utils.py +86 -0
- fudster-0.1.0/fudster/api/websockets.py +164 -0
- fudster-0.1.0/fudster/apps/__init__.py +25 -0
- fudster-0.1.0/fudster/apps/chrome_client.py +167 -0
- fudster-0.1.0/fudster/apps/discord_client.py +98 -0
- fudster-0.1.0/fudster/apps/novnc_client.py +85 -0
- fudster-0.1.0/fudster/apps/runelite.py +142 -0
- fudster-0.1.0/fudster/apps/screen_client.py +132 -0
- fudster-0.1.0/fudster/ml/__init__.py +1 -0
- fudster-0.1.0/fudster/models/__init__.py +16 -0
- fudster-0.1.0/fudster/models/broadcast_models.py +46 -0
- fudster-0.1.0/fudster/models/coindesk.py +28 -0
- fudster-0.1.0/fudster/models/groq.py +36 -0
- fudster-0.1.0/fudster/models/poem.py +9 -0
- fudster-0.1.0/fudster/models/rsps.py +67 -0
- fudster-0.1.0/fudster/models/rss.py +16 -0
- fudster-0.1.0/project.json +72 -0
- fudster-0.1.0/pyproject.toml +203 -0
- fudster-0.1.0/uv.lock +1997 -0
fudster-0.1.0/.flake8
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
fudster-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fudster
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fudster - A composable ML library for haystack-ai and pgvector integrations
|
|
5
|
+
License-Expression: LicenseRef-Proprietary
|
|
6
|
+
Requires-Python: <3.14,>=3.12
|
|
7
|
+
Requires-Dist: aiohappyeyeballs==2.6.1
|
|
8
|
+
Requires-Dist: aiohttp==3.13.3
|
|
9
|
+
Requires-Dist: aiosignal==1.4.0
|
|
10
|
+
Requires-Dist: annotated-doc==0.0.4
|
|
11
|
+
Requires-Dist: annotated-types==0.7.0
|
|
12
|
+
Requires-Dist: anyio==4.12.1
|
|
13
|
+
Requires-Dist: attrs==25.4.0
|
|
14
|
+
Requires-Dist: beautifulsoup4==4.14.3
|
|
15
|
+
Requires-Dist: broadcaster==0.3.1
|
|
16
|
+
Requires-Dist: certifi==2026.1.4
|
|
17
|
+
Requires-Dist: charset-normalizer==3.4.4
|
|
18
|
+
Requires-Dist: fastapi==0.128.7
|
|
19
|
+
Requires-Dist: frozenlist==1.8.0
|
|
20
|
+
Requires-Dist: idna==3.11
|
|
21
|
+
Requires-Dist: jinja2==3.1.6
|
|
22
|
+
Requires-Dist: lxml==5.4.0
|
|
23
|
+
Requires-Dist: markupsafe==3.0.3
|
|
24
|
+
Requires-Dist: multidict==6.7.1
|
|
25
|
+
Requires-Dist: propcache==0.4.1
|
|
26
|
+
Requires-Dist: pydantic-core==2.41.5
|
|
27
|
+
Requires-Dist: pydantic==2.12.5
|
|
28
|
+
Requires-Dist: requests==2.32.5
|
|
29
|
+
Requires-Dist: soupsieve==2.8.3
|
|
30
|
+
Requires-Dist: starlette==0.52.1
|
|
31
|
+
Requires-Dist: typing-extensions==4.15.0
|
|
32
|
+
Requires-Dist: typing-inspection==0.4.2
|
|
33
|
+
Requires-Dist: urllib3==2.6.3
|
|
34
|
+
Requires-Dist: yarl==1.22.0
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: behave==1.2.6; extra == 'all'
|
|
37
|
+
Requires-Dist: chardet==5.2.0; extra == 'all'
|
|
38
|
+
Requires-Dist: colorama==0.4.6; extra == 'all'
|
|
39
|
+
Requires-Dist: cssselect==1.4.0; extra == 'all'
|
|
40
|
+
Requires-Dist: exceptiongroup==1.3.1; extra == 'all'
|
|
41
|
+
Requires-Dist: execnet==2.1.2; extra == 'all'
|
|
42
|
+
Requires-Dist: fasteners==0.20; extra == 'all'
|
|
43
|
+
Requires-Dist: filelock==3.20.3; extra == 'all'
|
|
44
|
+
Requires-Dist: h11==0.16.0; extra == 'all'
|
|
45
|
+
Requires-Dist: humancursor==1.1.5; extra == 'all'
|
|
46
|
+
Requires-Dist: iniconfig==2.3.0; extra == 'all'
|
|
47
|
+
Requires-Dist: jwcrypto==1.5.6; extra == 'all'
|
|
48
|
+
Requires-Dist: markdown-it-py==4.0.0; extra == 'all'
|
|
49
|
+
Requires-Dist: mdurl==0.1.2; extra == 'all'
|
|
50
|
+
Requires-Dist: mouseinfo==0.1.3; extra == 'all'
|
|
51
|
+
Requires-Dist: mycdp==1.3.2; extra == 'all'
|
|
52
|
+
Requires-Dist: nest-asyncio==1.6.0; extra == 'all'
|
|
53
|
+
Requires-Dist: numpy==2.4.2; extra == 'all'
|
|
54
|
+
Requires-Dist: opencv-python==4.13.0.92; extra == 'all'
|
|
55
|
+
Requires-Dist: outcome==1.3.0.post0; extra == 'all'
|
|
56
|
+
Requires-Dist: packaging==26.0; extra == 'all'
|
|
57
|
+
Requires-Dist: parameterized==0.9.0; extra == 'all'
|
|
58
|
+
Requires-Dist: parse-type==0.6.6; extra == 'all'
|
|
59
|
+
Requires-Dist: parse==1.21.0; extra == 'all'
|
|
60
|
+
Requires-Dist: pdbp==1.8.2; extra == 'all'
|
|
61
|
+
Requires-Dist: pillow==11.3.0; extra == 'all'
|
|
62
|
+
Requires-Dist: pip==26.0.1; extra == 'all'
|
|
63
|
+
Requires-Dist: platformdirs==4.5.1; extra == 'all'
|
|
64
|
+
Requires-Dist: pluggy==1.6.0; extra == 'all'
|
|
65
|
+
Requires-Dist: pyautogui==0.9.54; extra == 'all'
|
|
66
|
+
Requires-Dist: pygetwindow==0.0.9; extra == 'all'
|
|
67
|
+
Requires-Dist: pygments==2.19.2; extra == 'all'
|
|
68
|
+
Requires-Dist: pymsgbox==2.0.1; extra == 'all'
|
|
69
|
+
Requires-Dist: pynose==1.5.5; extra == 'all'
|
|
70
|
+
Requires-Dist: pyobjc-core==12.1; extra == 'all'
|
|
71
|
+
Requires-Dist: pyobjc-framework-quartz==12.1; extra == 'all'
|
|
72
|
+
Requires-Dist: pyotp==2.9.0; extra == 'all'
|
|
73
|
+
Requires-Dist: pyreadline3==3.5.4; extra == 'all'
|
|
74
|
+
Requires-Dist: pyscreeze==1.0.1; extra == 'all'
|
|
75
|
+
Requires-Dist: pytest-html==4.0.2; extra == 'all'
|
|
76
|
+
Requires-Dist: pytest-metadata==3.1.1; extra == 'all'
|
|
77
|
+
Requires-Dist: pytest-ordering==0.6; extra == 'all'
|
|
78
|
+
Requires-Dist: pytest-rerunfailures==16.1; extra == 'all'
|
|
79
|
+
Requires-Dist: pytest-xdist==3.8.0; extra == 'all'
|
|
80
|
+
Requires-Dist: pytest==9.0.2; extra == 'all'
|
|
81
|
+
Requires-Dist: python-xlib==0.33; extra == 'all'
|
|
82
|
+
Requires-Dist: python3-xlib==0.15; extra == 'all'
|
|
83
|
+
Requires-Dist: pytweening==1.2.0; extra == 'all'
|
|
84
|
+
Requires-Dist: pyyaml==6.0.3; extra == 'all'
|
|
85
|
+
Requires-Dist: redis==7.1.1; extra == 'all'
|
|
86
|
+
Requires-Dist: rich==14.3.2; extra == 'all'
|
|
87
|
+
Requires-Dist: sbvirtualdisplay==1.4.0; extra == 'all'
|
|
88
|
+
Requires-Dist: selenium==4.40.0; extra == 'all'
|
|
89
|
+
Requires-Dist: seleniumbase==4.46.5; extra == 'all'
|
|
90
|
+
Requires-Dist: setuptools==82.0.0; extra == 'all'
|
|
91
|
+
Requires-Dist: six==1.17.0; extra == 'all'
|
|
92
|
+
Requires-Dist: sniffio==1.3.1; extra == 'all'
|
|
93
|
+
Requires-Dist: sortedcontainers==2.4.0; extra == 'all'
|
|
94
|
+
Requires-Dist: tabcompleter==1.4.0; extra == 'all'
|
|
95
|
+
Requires-Dist: trio-websocket==0.12.2; extra == 'all'
|
|
96
|
+
Requires-Dist: trio==0.32.0; extra == 'all'
|
|
97
|
+
Requires-Dist: websocket-client==1.9.0; extra == 'all'
|
|
98
|
+
Requires-Dist: websockets==16.0; extra == 'all'
|
|
99
|
+
Requires-Dist: websockify==0.13.0; extra == 'all'
|
|
100
|
+
Requires-Dist: wheel==0.46.3; extra == 'all'
|
|
101
|
+
Requires-Dist: wsproto==1.3.2; extra == 'all'
|
|
102
|
+
Provides-Extra: automation
|
|
103
|
+
Requires-Dist: humancursor==1.1.5; extra == 'automation'
|
|
104
|
+
Requires-Dist: mouseinfo==0.1.3; extra == 'automation'
|
|
105
|
+
Requires-Dist: numpy==2.4.2; extra == 'automation'
|
|
106
|
+
Requires-Dist: opencv-python==4.13.0.92; extra == 'automation'
|
|
107
|
+
Requires-Dist: pillow==11.3.0; extra == 'automation'
|
|
108
|
+
Requires-Dist: pyautogui==0.9.54; extra == 'automation'
|
|
109
|
+
Requires-Dist: pygetwindow==0.0.9; extra == 'automation'
|
|
110
|
+
Requires-Dist: pymsgbox==2.0.1; extra == 'automation'
|
|
111
|
+
Requires-Dist: pyobjc-core==12.1; extra == 'automation'
|
|
112
|
+
Requires-Dist: pyobjc-framework-quartz==12.1; extra == 'automation'
|
|
113
|
+
Requires-Dist: pyscreeze==1.0.1; extra == 'automation'
|
|
114
|
+
Requires-Dist: python3-xlib==0.15; extra == 'automation'
|
|
115
|
+
Requires-Dist: pytweening==1.2.0; extra == 'automation'
|
|
116
|
+
Requires-Dist: selenium==4.40.0; extra == 'automation'
|
|
117
|
+
Provides-Extra: browser
|
|
118
|
+
Requires-Dist: behave==1.2.6; extra == 'browser'
|
|
119
|
+
Requires-Dist: chardet==5.2.0; extra == 'browser'
|
|
120
|
+
Requires-Dist: colorama==0.4.6; extra == 'browser'
|
|
121
|
+
Requires-Dist: cssselect==1.4.0; extra == 'browser'
|
|
122
|
+
Requires-Dist: exceptiongroup==1.3.1; extra == 'browser'
|
|
123
|
+
Requires-Dist: execnet==2.1.2; extra == 'browser'
|
|
124
|
+
Requires-Dist: fasteners==0.20; extra == 'browser'
|
|
125
|
+
Requires-Dist: filelock==3.20.3; extra == 'browser'
|
|
126
|
+
Requires-Dist: h11==0.16.0; extra == 'browser'
|
|
127
|
+
Requires-Dist: iniconfig==2.3.0; extra == 'browser'
|
|
128
|
+
Requires-Dist: markdown-it-py==4.0.0; extra == 'browser'
|
|
129
|
+
Requires-Dist: mdurl==0.1.2; extra == 'browser'
|
|
130
|
+
Requires-Dist: mycdp==1.3.2; extra == 'browser'
|
|
131
|
+
Requires-Dist: nest-asyncio==1.6.0; extra == 'browser'
|
|
132
|
+
Requires-Dist: outcome==1.3.0.post0; extra == 'browser'
|
|
133
|
+
Requires-Dist: packaging==26.0; extra == 'browser'
|
|
134
|
+
Requires-Dist: parameterized==0.9.0; extra == 'browser'
|
|
135
|
+
Requires-Dist: parse-type==0.6.6; extra == 'browser'
|
|
136
|
+
Requires-Dist: parse==1.21.0; extra == 'browser'
|
|
137
|
+
Requires-Dist: pdbp==1.8.2; extra == 'browser'
|
|
138
|
+
Requires-Dist: pip==26.0.1; extra == 'browser'
|
|
139
|
+
Requires-Dist: platformdirs==4.5.1; extra == 'browser'
|
|
140
|
+
Requires-Dist: pluggy==1.6.0; extra == 'browser'
|
|
141
|
+
Requires-Dist: pyautogui==0.9.54; extra == 'browser'
|
|
142
|
+
Requires-Dist: pygments==2.19.2; extra == 'browser'
|
|
143
|
+
Requires-Dist: pynose==1.5.5; extra == 'browser'
|
|
144
|
+
Requires-Dist: pyotp==2.9.0; extra == 'browser'
|
|
145
|
+
Requires-Dist: pyreadline3==3.5.4; extra == 'browser'
|
|
146
|
+
Requires-Dist: pytest-html==4.0.2; extra == 'browser'
|
|
147
|
+
Requires-Dist: pytest-metadata==3.1.1; extra == 'browser'
|
|
148
|
+
Requires-Dist: pytest-ordering==0.6; extra == 'browser'
|
|
149
|
+
Requires-Dist: pytest-rerunfailures==16.1; extra == 'browser'
|
|
150
|
+
Requires-Dist: pytest-xdist==3.8.0; extra == 'browser'
|
|
151
|
+
Requires-Dist: pytest==9.0.2; extra == 'browser'
|
|
152
|
+
Requires-Dist: python-xlib==0.33; extra == 'browser'
|
|
153
|
+
Requires-Dist: pyyaml==6.0.3; extra == 'browser'
|
|
154
|
+
Requires-Dist: rich==14.3.2; extra == 'browser'
|
|
155
|
+
Requires-Dist: sbvirtualdisplay==1.4.0; extra == 'browser'
|
|
156
|
+
Requires-Dist: selenium==4.40.0; extra == 'browser'
|
|
157
|
+
Requires-Dist: seleniumbase==4.46.5; extra == 'browser'
|
|
158
|
+
Requires-Dist: setuptools==82.0.0; extra == 'browser'
|
|
159
|
+
Requires-Dist: six==1.17.0; extra == 'browser'
|
|
160
|
+
Requires-Dist: sniffio==1.3.1; extra == 'browser'
|
|
161
|
+
Requires-Dist: sortedcontainers==2.4.0; extra == 'browser'
|
|
162
|
+
Requires-Dist: tabcompleter==1.4.0; extra == 'browser'
|
|
163
|
+
Requires-Dist: trio-websocket==0.12.2; extra == 'browser'
|
|
164
|
+
Requires-Dist: trio==0.32.0; extra == 'browser'
|
|
165
|
+
Requires-Dist: websocket-client==1.9.0; extra == 'browser'
|
|
166
|
+
Requires-Dist: websockets==16.0; extra == 'browser'
|
|
167
|
+
Requires-Dist: wheel==0.46.3; extra == 'browser'
|
|
168
|
+
Requires-Dist: wsproto==1.3.2; extra == 'browser'
|
|
169
|
+
Provides-Extra: vnc
|
|
170
|
+
Requires-Dist: jwcrypto==1.5.6; extra == 'vnc'
|
|
171
|
+
Requires-Dist: numpy==2.4.2; extra == 'vnc'
|
|
172
|
+
Requires-Dist: redis==7.1.1; extra == 'vnc'
|
|
173
|
+
Requires-Dist: websockets==16.0; extra == 'vnc'
|
|
174
|
+
Requires-Dist: websockify==0.13.0; extra == 'vnc'
|
|
175
|
+
Description-Content-Type: text/markdown
|
|
176
|
+
|
|
177
|
+
# fudster
|
|
178
|
+
|
|
179
|
+
A composable ML library for haystack-ai and pgvector integrations.
|
|
180
|
+
|
|
181
|
+
## Installation
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pip install fudster
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
This package is part of the [KBVE](https://github.com/kbve/kbve) monorepo, managed with Nx and Poetry.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pnpm nx test python-fudster
|
|
193
|
+
pnpm nx lint python-fudster
|
|
194
|
+
pnpm nx build python-fudster
|
|
195
|
+
```
|
fudster-0.1.0/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# fudster
|
|
2
|
+
|
|
3
|
+
A composable ML library for haystack-ai and pgvector integrations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install fudster
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
|
|
13
|
+
This package is part of the [KBVE](https://github.com/kbve/kbve) monorepo, managed with Nx and Poetry.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm nx test python-fudster
|
|
17
|
+
pnpm nx lint python-fudster
|
|
18
|
+
pnpm nx build python-fudster
|
|
19
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Fudster - A composable ML library for haystack-ai and pgvector integrations."""
|
|
2
|
+
|
|
3
|
+
import logging as _logging
|
|
4
|
+
|
|
5
|
+
_logger = _logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
# Broadcast models
|
|
8
|
+
from .models import ( # noqa: F401, E402
|
|
9
|
+
CommandModel, LoggerModel, BroadcastModel, KBVELoginModel, HandshakeModel, model_map,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# API models
|
|
13
|
+
from .models.rss import RssItem, RssFeed # noqa: F401, E402
|
|
14
|
+
from .models.poem import PoemDB # noqa: F401, E402
|
|
15
|
+
from .models.coindesk import CoinDeskAPIResponse # noqa: F401, E402
|
|
16
|
+
from .models.groq import AiGroqPayload, GroqResponse # noqa: F401, E402
|
|
17
|
+
from .models.rsps import GameEvent, GameStat, GameInventory # noqa: F401, E402
|
|
18
|
+
|
|
19
|
+
# Core API
|
|
20
|
+
from .api import Routes, CORS, WS, APIConnector # noqa: F401, E402
|
|
21
|
+
|
|
22
|
+
# API clients
|
|
23
|
+
from .api.clients import CoinDeskClient, PoetryDBClient, GroqClient, WebsocketEchoClient # noqa: F401, E402
|
|
24
|
+
|
|
25
|
+
# API utils
|
|
26
|
+
from .api.utils import RSSUtility, KRDecorator, DynamicEndpoint # noqa: F401, E402
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from .api.utils import ImageUtility # noqa: F401, E402
|
|
30
|
+
except ImportError:
|
|
31
|
+
_logger.debug("ImageUtility unavailable — install fudster[image] for image processing support")
|
|
32
|
+
|
|
33
|
+
# Apps
|
|
34
|
+
from .apps import RuneLiteClient # noqa: F401, E402
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from .apps import ScreenClient, ChromeClient, DiscordClient, NoVNCClient # noqa: F401, E402
|
|
38
|
+
except ImportError:
|
|
39
|
+
_logger.debug("Optional app clients unavailable — install fudster[browser,automation,vnc] as needed")
|
|
40
|
+
|
|
41
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
from aiohttp import ClientTimeout
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class APIConnector:
|
|
9
|
+
def __init__(self, base_url: str, key: Optional[str] = None, websocket=None, timeout: int = 30):
|
|
10
|
+
self.base_url = base_url
|
|
11
|
+
self.key = key
|
|
12
|
+
self.session = aiohttp.ClientSession(timeout=ClientTimeout(total=timeout))
|
|
13
|
+
self.websocket = websocket
|
|
14
|
+
|
|
15
|
+
async def __aenter__(self):
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
19
|
+
await self.close()
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
async def _request(self, method: str, endpoint: str, **kwargs) -> Any:
|
|
23
|
+
url = f"{self.base_url}/{endpoint}"
|
|
24
|
+
headers = self._prepare_headers(kwargs.pop('auth', None))
|
|
25
|
+
async with self.session.request(method, url, headers=headers, **kwargs) as response:
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
return await response.json()
|
|
28
|
+
|
|
29
|
+
async def _get_session(self):
|
|
30
|
+
if not self.session:
|
|
31
|
+
self.session = aiohttp.ClientSession()
|
|
32
|
+
return self.session
|
|
33
|
+
|
|
34
|
+
async def get(self, endpoint: str, **kwargs) -> Any:
|
|
35
|
+
return await self._request('GET', endpoint, **kwargs)
|
|
36
|
+
|
|
37
|
+
async def post(self, endpoint: str, **kwargs) -> Any:
|
|
38
|
+
return await self._request('POST', endpoint, **kwargs)
|
|
39
|
+
|
|
40
|
+
async def delete(self, endpoint: str, **kwargs) -> Any:
|
|
41
|
+
return await self._request('DELETE', endpoint, **kwargs)
|
|
42
|
+
|
|
43
|
+
async def close(self):
|
|
44
|
+
if self.websocket is not None and not self.websocket.closed:
|
|
45
|
+
await self.websocket.close()
|
|
46
|
+
await self.session.close()
|
|
47
|
+
|
|
48
|
+
async def connect_websocket(self, endpoint: Optional[str] = None) -> aiohttp.ClientWebSocketResponse:
|
|
49
|
+
headers = {}
|
|
50
|
+
if self.key:
|
|
51
|
+
headers['Authorization'] = f"Bearer {self.key}"
|
|
52
|
+
if endpoint:
|
|
53
|
+
websocket_url = f"{self.base_url}/{endpoint}"
|
|
54
|
+
else:
|
|
55
|
+
websocket_url = self.base_url
|
|
56
|
+
|
|
57
|
+
self.websocket = await self.session.ws_connect(websocket_url, headers=headers)
|
|
58
|
+
return self.websocket
|
|
59
|
+
|
|
60
|
+
async def ensure_websocket_connected(self):
|
|
61
|
+
"""Ensure that the WebSocket connection is established."""
|
|
62
|
+
if self.websocket is None or self.websocket.closed:
|
|
63
|
+
self.websocket = await self.session.ws_connect(f"{self.base_url}", headers=self._prepare_headers())
|
|
64
|
+
return self.websocket
|
|
65
|
+
|
|
66
|
+
async def send_websocket_message(self, message: str, timeout: float = 30.0):
|
|
67
|
+
"""Send a message through the WebSocket connection."""
|
|
68
|
+
await self.ensure_websocket_connected()
|
|
69
|
+
await asyncio.wait_for(self.websocket.send_str(message), timeout=timeout)
|
|
70
|
+
|
|
71
|
+
async def receive_websocket_message(self, timeout: float = 30.0) -> str:
|
|
72
|
+
"""Receive a message from the WebSocket connection."""
|
|
73
|
+
await self.ensure_websocket_connected()
|
|
74
|
+
msg = await asyncio.wait_for(self.websocket.receive(), timeout=timeout)
|
|
75
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
76
|
+
return msg.data
|
|
77
|
+
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR):
|
|
78
|
+
raise Exception("WebSocket connection closed or encountered an error.")
|
|
79
|
+
raise Exception(f"Unexpected WebSocket message type: {msg.type}")
|
|
80
|
+
|
|
81
|
+
def _prepare_headers(self, auth: Optional[str] = None) -> dict:
|
|
82
|
+
"""Prepare headers for HTTP or WebSocket connection."""
|
|
83
|
+
headers = {}
|
|
84
|
+
if self.key and auth == 'header':
|
|
85
|
+
headers['Authorization'] = f"Bearer {self.key}"
|
|
86
|
+
return headers
|
|
87
|
+
|
|
88
|
+
async def get_raw_content(self, endpoint: str) -> bytes:
|
|
89
|
+
"""
|
|
90
|
+
Performs a GET request to the specified endpoint and returns the raw response content.
|
|
91
|
+
|
|
92
|
+
:param endpoint: The API endpoint to fetch.
|
|
93
|
+
:return: Raw content of the response as bytes.
|
|
94
|
+
"""
|
|
95
|
+
session = await self._get_session()
|
|
96
|
+
url = f"{self.base_url}/{endpoint}"
|
|
97
|
+
async with session.get(url) as response:
|
|
98
|
+
response.raise_for_status()
|
|
99
|
+
return await response.read()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from pydantic import TypeAdapter, ValidationError
|
|
2
|
+
from ..api_connector import APIConnector
|
|
3
|
+
from ...models.coindesk import CoinDeskAPIResponse
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger("uvicorn")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CoinDeskClient(APIConnector):
|
|
10
|
+
"""
|
|
11
|
+
A client for fetching Bitcoin price data from the CoinDesk API.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
super().__init__("https://api.coindesk.com/v1")
|
|
16
|
+
|
|
17
|
+
async def get_current_bitcoin_price(self):
|
|
18
|
+
"""
|
|
19
|
+
Fetches the current Bitcoin price in USD from the CoinDesk API.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
str: A string that states the current USD price of Bitcoin.
|
|
23
|
+
"""
|
|
24
|
+
response = await self.get("bpi/currentprice.json")
|
|
25
|
+
adapter = TypeAdapter(CoinDeskAPIResponse)
|
|
26
|
+
try:
|
|
27
|
+
price_info = adapter.validate_python(response)
|
|
28
|
+
except ValidationError as ve:
|
|
29
|
+
logger.error(f"Validation error while parsing the API response: {ve}")
|
|
30
|
+
raise
|
|
31
|
+
usd_price = price_info.bpi.USD.rate
|
|
32
|
+
return f"The current USD price for Bitcoin is: ${usd_price}"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
from pydantic import TypeAdapter, ValidationError
|
|
4
|
+
from ..api_connector import APIConnector
|
|
5
|
+
from ...models.groq import GroqResponse, AiGroqPayload
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("uvicorn")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GroqClient(APIConnector):
|
|
12
|
+
def __init__(self, api_key: str = None):
|
|
13
|
+
super().__init__("https://rust.kbve.com/api/v1")
|
|
14
|
+
self.api_key = api_key
|
|
15
|
+
|
|
16
|
+
def escape_message(self, message: str) -> str:
|
|
17
|
+
return json.dumps(message)[1:-1]
|
|
18
|
+
|
|
19
|
+
async def call_groq(self, payload: AiGroqPayload) -> GroqResponse:
|
|
20
|
+
headers = {"Content-Type": "application/json"}
|
|
21
|
+
if self.api_key:
|
|
22
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
23
|
+
|
|
24
|
+
payload.message = self.escape_message(payload.message)
|
|
25
|
+
|
|
26
|
+
response = await self.post("call_groq", json=payload.dict(), headers=headers)
|
|
27
|
+
adapter = TypeAdapter(GroqResponse)
|
|
28
|
+
try:
|
|
29
|
+
groq_response = adapter.validate_python(response)
|
|
30
|
+
except ValidationError as ve:
|
|
31
|
+
logger.error(f"Validation error while parsing the API response: {ve}")
|
|
32
|
+
raise
|
|
33
|
+
return groq_response
|
|
34
|
+
|
|
35
|
+
async def groq_process_pathways(
|
|
36
|
+
self, pathways: dict, pathway: str,
|
|
37
|
+
payload: AiGroqPayload, max_calls: int = 5
|
|
38
|
+
) -> GroqResponse:
|
|
39
|
+
current_pathway = pathway
|
|
40
|
+
for _ in range(max_calls):
|
|
41
|
+
if current_pathway not in pathways:
|
|
42
|
+
logger.error(f"Pathway {current_pathway} not defined")
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
current_config = pathways[current_pathway]
|
|
46
|
+
payload.message = current_config["prompt"]
|
|
47
|
+
response = await self.call_groq(payload)
|
|
48
|
+
|
|
49
|
+
next_pathway = None
|
|
50
|
+
for condition in current_config["next"]:
|
|
51
|
+
if re.search(condition["condition"], response.message):
|
|
52
|
+
next_pathway = condition["action"]
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
if next_pathway:
|
|
56
|
+
current_pathway = next_pathway
|
|
57
|
+
else:
|
|
58
|
+
return response
|
|
59
|
+
return response
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
|
|
3
|
+
from ..api_connector import APIConnector
|
|
4
|
+
from ...models.poem import PoemDB
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PoetryDBClient(APIConnector):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
"""
|
|
10
|
+
Initializes the PoetryDBClient with the base URL for the PoetryDB API.
|
|
11
|
+
"""
|
|
12
|
+
super().__init__("https://poetrydb.org", key=None)
|
|
13
|
+
|
|
14
|
+
async def get_random_poem(self) -> PoemDB:
|
|
15
|
+
"""
|
|
16
|
+
Fetches a random poem from the PoetryDB API and returns it as a PoemDB model instance.
|
|
17
|
+
"""
|
|
18
|
+
response = await self.get("random", auth=None)
|
|
19
|
+
if response:
|
|
20
|
+
poem = PoemDB.model_validate(response[0])
|
|
21
|
+
return poem
|
|
22
|
+
else:
|
|
23
|
+
raise aiohttp.ClientResponseError("Failed to fetch a random poem.")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from ..api_connector import APIConnector
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WebsocketEchoClient(APIConnector):
|
|
5
|
+
"""
|
|
6
|
+
A client for interacting with a WebSocket echo server.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__("wss://echo.websocket.org", key=None, websocket=None)
|
|
11
|
+
|
|
12
|
+
async def example(self):
|
|
13
|
+
"""
|
|
14
|
+
Demonstrates the use of the WebSocket echo client.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
self.websocket = await self.connect_websocket()
|
|
18
|
+
|
|
19
|
+
initial_message = await self.receive_websocket_message()
|
|
20
|
+
print("Initial server response:", initial_message)
|
|
21
|
+
|
|
22
|
+
await self.send_websocket_message(message="Hello, WebSocket!")
|
|
23
|
+
echo1 = await self.receive_websocket_message()
|
|
24
|
+
print("Echo of first message:", echo1)
|
|
25
|
+
|
|
26
|
+
await self.send_websocket_message(message="This is a second message.")
|
|
27
|
+
echo2 = await self.receive_websocket_message()
|
|
28
|
+
print("Echo of second message:", echo2)
|
|
29
|
+
|
|
30
|
+
finally:
|
|
31
|
+
if self.websocket:
|
|
32
|
+
await self.websocket.close()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CORS:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
app: FastAPI,
|
|
10
|
+
origins: List[str] = [
|
|
11
|
+
"http://localhost:8086",
|
|
12
|
+
"http://localhost:4321",
|
|
13
|
+
"http://localhost:1337",
|
|
14
|
+
"http://localhost",
|
|
15
|
+
"http://localhost:8080",
|
|
16
|
+
"https://automation.kbve.com",
|
|
17
|
+
"https://rust.kbve.com",
|
|
18
|
+
"https://kbve.com",
|
|
19
|
+
],
|
|
20
|
+
allow_credentials: bool = True,
|
|
21
|
+
allow_methods: List[str] = ["*"],
|
|
22
|
+
allow_headers: List[str] = ["*"],
|
|
23
|
+
):
|
|
24
|
+
self.app = app
|
|
25
|
+
self.origins = origins
|
|
26
|
+
self.allow_credentials = allow_credentials
|
|
27
|
+
self.allow_methods = allow_methods
|
|
28
|
+
self.allow_headers = allow_headers
|
|
29
|
+
self.add_cors_middleware()
|
|
30
|
+
|
|
31
|
+
def add_cors_middleware(self):
|
|
32
|
+
self.app.add_middleware(
|
|
33
|
+
CORSMiddleware,
|
|
34
|
+
allow_origins=self.origins,
|
|
35
|
+
allow_credentials=self.allow_credentials,
|
|
36
|
+
allow_methods=self.allow_methods,
|
|
37
|
+
allow_headers=self.allow_headers,
|
|
38
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
2
|
+
from fastapi.templating import Jinja2Templates
|
|
3
|
+
from typing import Type
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Routes:
|
|
8
|
+
def __init__(self, app: FastAPI, templates_dir: str = "templates"):
|
|
9
|
+
self.app = app
|
|
10
|
+
self.templates = Jinja2Templates(directory=templates_dir)
|
|
11
|
+
|
|
12
|
+
async def parse_json_body(self, request: Request):
|
|
13
|
+
try:
|
|
14
|
+
return await request.json()
|
|
15
|
+
except (ValueError, ValidationError):
|
|
16
|
+
raise HTTPException(status_code=400, detail="Invalid JSON payload")
|
|
17
|
+
|
|
18
|
+
def get_client_method(self, client, method_name: str):
|
|
19
|
+
method = getattr(client, method_name, None)
|
|
20
|
+
if not callable(method):
|
|
21
|
+
raise HTTPException(status_code=500, detail="Method not callable")
|
|
22
|
+
return method
|
|
23
|
+
|
|
24
|
+
def add_route(self, path: str, client_class: Type, method_name: str, methods=["GET"]):
|
|
25
|
+
async def wrapper(request: Request = None):
|
|
26
|
+
client = client_class()
|
|
27
|
+
try:
|
|
28
|
+
method = self.get_client_method(client, method_name)
|
|
29
|
+
if request and request.method == "POST":
|
|
30
|
+
body = await self.parse_json_body(request)
|
|
31
|
+
result = await method(body)
|
|
32
|
+
else:
|
|
33
|
+
result = await method()
|
|
34
|
+
|
|
35
|
+
return result if isinstance(result, (dict, list)) else {"data": str(result)}
|
|
36
|
+
finally:
|
|
37
|
+
await client.close()
|
|
38
|
+
|
|
39
|
+
self.app.add_api_route(path, wrapper, methods=methods)
|
|
40
|
+
|
|
41
|
+
def render(self, path: str, template_name: str):
|
|
42
|
+
async def wrapper(request: Request):
|
|
43
|
+
return self.templates.TemplateResponse(template_name, {"request": request})
|
|
44
|
+
|
|
45
|
+
self.app.add_api_route(path, wrapper, methods=["GET"])
|
|
46
|
+
|
|
47
|
+
def get(self, path: str, client_class: Type, method_name: str):
|
|
48
|
+
self.add_route(path, client_class, method_name, methods=["GET"])
|
|
49
|
+
|
|
50
|
+
def post(self, path: str, client_class: Type, method_name: str):
|
|
51
|
+
self.add_route(path, client_class, method_name, methods=["POST"])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""API Utilities"""
|
|
2
|
+
import logging as _logging
|
|
3
|
+
|
|
4
|
+
_logger = _logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
from .rss_utils import RSSUtility # noqa: F401, E402
|
|
7
|
+
from .kr_decorator import KRDecorator # noqa: F401, E402
|
|
8
|
+
from .dynamic_endpoint_utils import DynamicEndpoint # noqa: F401, E402
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from .image_utils import ImageUtility # noqa: F401, E402
|
|
12
|
+
except ImportError:
|
|
13
|
+
_logger.debug("ImageUtility unavailable — install Pillow for image processing support")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from fastapi import FastAPI, HTTPException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DynamicEndpoint:
|
|
6
|
+
def __init__(self, app: FastAPI):
|
|
7
|
+
self.app = app
|
|
8
|
+
|
|
9
|
+
def add_dynamic_route(self, path: str):
|
|
10
|
+
async def dynamic_handler(module_name: str, function_name: str):
|
|
11
|
+
try:
|
|
12
|
+
module = importlib.import_module(f"plugins.{module_name}")
|
|
13
|
+
method = getattr(module, function_name)
|
|
14
|
+
if not callable(method):
|
|
15
|
+
raise HTTPException(status_code=500, detail="Function not callable")
|
|
16
|
+
result = await method()
|
|
17
|
+
return {"result": result}
|
|
18
|
+
except ModuleNotFoundError:
|
|
19
|
+
raise HTTPException(status_code=404, detail=f"Module '{module_name}' not found")
|
|
20
|
+
except AttributeError:
|
|
21
|
+
raise HTTPException(status_code=404, detail=f"Function '{function_name}' not found")
|
|
22
|
+
except Exception as e:
|
|
23
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
24
|
+
|
|
25
|
+
self.app.add_api_route(path, dynamic_handler, methods=["GET"])
|