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.
Files changed (37) hide show
  1. fudster-0.1.0/.flake8 +11 -0
  2. fudster-0.1.0/.python-version +1 -0
  3. fudster-0.1.0/PKG-INFO +195 -0
  4. fudster-0.1.0/README.md +19 -0
  5. fudster-0.1.0/fudster/__init__.py +41 -0
  6. fudster-0.1.0/fudster/api/__init__.py +4 -0
  7. fudster-0.1.0/fudster/api/api_connector.py +99 -0
  8. fudster-0.1.0/fudster/api/clients/__init__.py +5 -0
  9. fudster-0.1.0/fudster/api/clients/coindesk_client.py +32 -0
  10. fudster-0.1.0/fudster/api/clients/groq_client.py +59 -0
  11. fudster-0.1.0/fudster/api/clients/poetry_db_client.py +23 -0
  12. fudster-0.1.0/fudster/api/clients/websocket_echo_client.py +32 -0
  13. fudster-0.1.0/fudster/api/cors.py +38 -0
  14. fudster-0.1.0/fudster/api/routes.py +51 -0
  15. fudster-0.1.0/fudster/api/utils/__init__.py +13 -0
  16. fudster-0.1.0/fudster/api/utils/dynamic_endpoint_utils.py +25 -0
  17. fudster-0.1.0/fudster/api/utils/image_utils.py +68 -0
  18. fudster-0.1.0/fudster/api/utils/kr_decorator.py +29 -0
  19. fudster-0.1.0/fudster/api/utils/rss_utils.py +86 -0
  20. fudster-0.1.0/fudster/api/websockets.py +164 -0
  21. fudster-0.1.0/fudster/apps/__init__.py +25 -0
  22. fudster-0.1.0/fudster/apps/chrome_client.py +167 -0
  23. fudster-0.1.0/fudster/apps/discord_client.py +98 -0
  24. fudster-0.1.0/fudster/apps/novnc_client.py +85 -0
  25. fudster-0.1.0/fudster/apps/runelite.py +142 -0
  26. fudster-0.1.0/fudster/apps/screen_client.py +132 -0
  27. fudster-0.1.0/fudster/ml/__init__.py +1 -0
  28. fudster-0.1.0/fudster/models/__init__.py +16 -0
  29. fudster-0.1.0/fudster/models/broadcast_models.py +46 -0
  30. fudster-0.1.0/fudster/models/coindesk.py +28 -0
  31. fudster-0.1.0/fudster/models/groq.py +36 -0
  32. fudster-0.1.0/fudster/models/poem.py +9 -0
  33. fudster-0.1.0/fudster/models/rsps.py +67 -0
  34. fudster-0.1.0/fudster/models/rss.py +16 -0
  35. fudster-0.1.0/project.json +72 -0
  36. fudster-0.1.0/pyproject.toml +203 -0
  37. fudster-0.1.0/uv.lock +1997 -0
fudster-0.1.0/.flake8 ADDED
@@ -0,0 +1,11 @@
1
+ [flake8]
2
+ exclude =
3
+ .git,
4
+ __pycache__,
5
+ build,
6
+ dist,
7
+ .tox,
8
+ venv,
9
+ .venv,
10
+ .pytest_cache
11
+ max-line-length = 120
@@ -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
+ ```
@@ -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,4 @@
1
+ from .routes import Routes # noqa: F401
2
+ from .cors import CORS # noqa: F401
3
+ from .websockets import WS # noqa: F401
4
+ from .api_connector import APIConnector # noqa: F401
@@ -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,5 @@
1
+ """API Clients"""
2
+ from .coindesk_client import CoinDeskClient # noqa: F401
3
+ from .poetry_db_client import PoetryDBClient # noqa: F401
4
+ from .groq_client import GroqClient # noqa: F401
5
+ from .websocket_echo_client import WebsocketEchoClient # noqa: F401
@@ -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"])