Skip to content

Commit 9544b7c

Browse files
committed
fix: pyright issues and lint errors
1 parent f7405a4 commit 9544b7c

File tree

3 files changed

+458
-379
lines changed

3 files changed

+458
-379
lines changed

src/flare_ai_social/bot_manager.py

Lines changed: 112 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44

55
import google.generativeai as genai
66
import structlog
7+
from anyio import Event
78
from google.api_core.exceptions import InvalidArgument, NotFound
89

910
from flare_ai_social.ai import BaseAIProvider, GeminiProvider
1011
from flare_ai_social.prompts import FEW_SHOT_PROMPT
1112
from flare_ai_social.settings import settings
1213
from flare_ai_social.telegram import TelegramBot
13-
from flare_ai_social.twitter import TwitterBot
14+
from flare_ai_social.twitter import TwitterBot, TwitterConfig
1415

1516
logger = structlog.get_logger(__name__)
1617

18+
# Error messages
19+
ERR_AI_PROVIDER_NOT_INITIALIZED = "AI provider must be initialized"
20+
1721

1822
class BotManager:
1923
"""Manager class for handling multiple social media bots."""
@@ -25,6 +29,7 @@ def __init__(self) -> None:
2529
self.twitter_thread: threading.Thread | None = None
2630
self.active_bots: list[str] = []
2731
self.running = False
32+
self._telegram_polling_task: asyncio.Task | None = None
2833

2934
def initialize_ai_provider(self) -> None:
3035
"""Initialize the AI provider with either tuned model or default model."""
@@ -42,41 +47,46 @@ def initialize_ai_provider(self) -> None:
4247
model_info = genai.get_tuned_model(
4348
name=f"tunedModels/{tuned_model_id}"
4449
)
45-
logger.info("Tuned model info", model_info=model_info)
46-
4750
# Initialize AI provider with tuned model
4851
self.ai_provider = GeminiProvider(
4952
settings.gemini_api_key,
5053
model_name=f"tunedModels/{tuned_model_id}",
5154
system_instruction=FEW_SHOT_PROMPT,
5255
)
53-
logger.info(f"Using tuned model: tunedModels/{tuned_model_id}")
54-
return
55-
except (InvalidArgument, NotFound) as e:
56-
logger.warning(f"Failed to load tuned model: {e}")
56+
logger.info("Tuned model info", model_info=model_info)
57+
except (InvalidArgument, NotFound):
58+
logger.warning("Failed to load tuned model.")
59+
self._initialize_default_model()
5760
else:
5861
logger.warning(
59-
f"Tuned model '{tuned_model_id}' not found in available models. Using default model."
62+
"Tuned model not found in available models. Using default model."
6063
)
61-
except Exception as e:
62-
logger.exception(f"Error accessing tuned models: {e}")
64+
self._initialize_default_model()
65+
except Exception:
66+
logger.exception("Error accessing tuned models")
67+
self._initialize_default_model()
6368

64-
# Fall back to default model
69+
def _initialize_default_model(self) -> None:
70+
"""Initialize the default Gemini model."""
6571
logger.info("Using default Gemini Flash model with few-shot prompting")
6672
self.ai_provider = GeminiProvider(
6773
settings.gemini_api_key,
6874
model_name="gemini-1.5-flash",
6975
system_instruction=FEW_SHOT_PROMPT,
7076
)
7177

78+
def _check_ai_provider_initialized(self) -> BaseAIProvider:
79+
"""Check if AI provider is initialized and raise error if not."""
80+
if self.ai_provider is None:
81+
raise RuntimeError(ERR_AI_PROVIDER_NOT_INITIALIZED)
82+
return self.ai_provider
83+
7284
def start_twitter_bot(self) -> bool:
7385
"""Initialize and start the Twitter bot in a separate thread."""
74-
# Check if Twitter is enabled in settings
7586
if not settings.enable_twitter:
7687
logger.info("Twitter bot disabled in settings")
7788
return False
7889

79-
# Check if required Twitter credentials are configured
8090
if not all(
8191
[
8292
settings.x_api_key,
@@ -92,10 +102,9 @@ def start_twitter_bot(self) -> bool:
92102
return False
93103

94104
try:
95-
# Ensure AI provider is initialized
96-
assert self.ai_provider is not None, "AI provider must be initialized"
97-
twitter_bot = TwitterBot(
98-
ai_provider=self.ai_provider,
105+
ai_provider = self._check_ai_provider_initialized()
106+
107+
config = TwitterConfig(
99108
bearer_token=settings.x_bearer_token,
100109
api_key=settings.x_api_key,
101110
api_secret=settings.x_api_key_secret,
@@ -107,25 +116,29 @@ def start_twitter_bot(self) -> bool:
107116
polling_interval=settings.twitter_polling_interval,
108117
)
109118

110-
# Start the Twitter bot in a separate thread
119+
twitter_bot = TwitterBot(
120+
ai_provider=ai_provider,
121+
config=config,
122+
)
123+
111124
self.twitter_thread = threading.Thread(
112125
target=twitter_bot.start, daemon=True, name="TwitterBotThread"
113126
)
114127
self.twitter_thread.start()
115128
logger.info("Twitter bot started in background thread")
116129
self.active_bots.append("Twitter")
117-
return True
118130

119-
except ValueError as e:
120-
logger.exception(f"Failed to start Twitter bot: {e}")
131+
except ValueError:
132+
logger.exception("Failed to start Twitter bot")
121133
return False
122-
except Exception as e:
123-
logger.error(f"Unexpected error starting Twitter bot: {e}", exc_info=True)
134+
except Exception:
135+
logger.exception("Unexpected error starting Twitter bot")
124136
return False
137+
else:
138+
return True
125139

126140
async def start_telegram_bot(self) -> bool:
127141
"""Initialize and start the Telegram bot."""
128-
# Check if Telegram is enabled in settings
129142
if not settings.enable_telegram:
130143
logger.info("Telegram bot disabled in settings")
131144
return False
@@ -135,79 +148,108 @@ async def start_telegram_bot(self) -> bool:
135148
return False
136149

137150
try:
138-
# Parse allowed users if provided
139-
allowed_users: list[int] = []
140-
if settings.telegram_allowed_users:
141-
try:
142-
# Convert comma-separated string to list of integers
143-
allowed_users = [
144-
int(user_id.strip())
145-
for user_id in settings.telegram_allowed_users.split(",")
146-
if user_id.strip().isdigit()
147-
]
148-
except Exception as e:
149-
logger.warning(f"Error parsing telegram_allowed_users: {e}")
150-
151-
# Ensure AI provider is initialized
152-
assert self.ai_provider is not None, "AI provider must be initialized"
153-
154-
# Create and start Telegram bot
151+
allowed_users = self._parse_allowed_users()
152+
ai_provider = self._check_ai_provider_initialized()
153+
155154
self.telegram_bot = TelegramBot(
156-
ai_provider=self.ai_provider,
155+
ai_provider=ai_provider,
157156
api_token=settings.telegram_api_token,
158157
allowed_user_ids=allowed_users,
159158
polling_interval=settings.telegram_polling_interval,
160159
)
161160

162-
# Properly initialize and start polling
163161
await self.telegram_bot.initialize()
164-
await self.telegram_bot.start_polling()
162+
self._telegram_polling_task = asyncio.create_task(
163+
self.telegram_bot.start_polling()
164+
)
165165
self.active_bots.append("Telegram")
166-
return True
167166

168-
except Exception as e:
169-
logger.error(f"Failed to start Telegram bot: {e}", exc_info=True)
167+
except Exception:
168+
logger.exception("Failed to start Telegram bot")
169+
if self.telegram_bot:
170+
await self.telegram_bot.shutdown()
170171
return False
172+
else:
173+
return True
174+
175+
def _parse_allowed_users(self) -> list[int]:
176+
"""Parse the allowed users from settings."""
177+
allowed_users: list[int] = []
178+
if settings.telegram_allowed_users:
179+
try:
180+
allowed_users = [
181+
int(user_id.strip())
182+
for user_id in settings.telegram_allowed_users.split(",")
183+
if user_id.strip().isdigit()
184+
]
185+
except ValueError:
186+
logger.warning("Error parsing telegram_allowed_users")
187+
return allowed_users
188+
189+
async def _check_telegram_status(self) -> None:
190+
"""Check and handle Telegram bot status."""
191+
if not (
192+
self.telegram_bot
193+
and self.telegram_bot.application
194+
and self.telegram_bot.application.updater
195+
and self.telegram_bot.application.updater.running
196+
):
197+
logger.error("Telegram bot stopped responding")
198+
try:
199+
# Store telegram_bot in a local variable to help type checker
200+
telegram_bot = self.telegram_bot
201+
if telegram_bot is not None: # Add explicit None check
202+
await telegram_bot.shutdown()
203+
if await self.start_telegram_bot():
204+
logger.info("Telegram bot restarted successfully")
205+
else:
206+
logger.error("Failed to restart Telegram bot")
207+
self.active_bots.remove("Telegram")
208+
except Exception:
209+
logger.exception("Error restarting Telegram bot")
210+
self.active_bots.remove("Telegram")
211+
212+
def _check_twitter_status(self) -> None:
213+
"""Check and handle Twitter bot status."""
214+
if self.twitter_thread and not self.twitter_thread.is_alive():
215+
logger.error("Twitter bot thread terminated unexpectedly")
216+
self.active_bots.remove("Twitter")
217+
if self.start_twitter_bot():
218+
logger.info("Twitter bot restarted successfully")
171219

172220
async def monitor_bots(self) -> None:
173221
"""Monitor active bots and handle unexpected terminations."""
174222
self.running = True
223+
175224
try:
176225
while self.running and self.active_bots:
177-
# Check Twitter bot status
178-
if "Twitter" in self.active_bots and self.twitter_thread:
179-
if not self.twitter_thread.is_alive():
180-
logger.error("Twitter bot thread terminated unexpectedly")
181-
self.active_bots.remove("Twitter")
182-
# Attempt to restart Twitter if auto-restart is enabled
183-
if getattr(settings, "auto_restart_bots", False):
184-
logger.info("Attempting to restart Twitter bot")
185-
if self.start_twitter_bot():
186-
logger.info("Twitter bot restarted successfully")
187-
188-
# Exit if no bots are active anymore
226+
if "Telegram" in self.active_bots and self.telegram_bot:
227+
await self._check_telegram_status()
228+
229+
if "Twitter" in self.active_bots:
230+
self._check_twitter_status()
231+
189232
if not self.active_bots:
190233
logger.error("No active bots remaining")
191234
break
192235

193236
await asyncio.sleep(5)
194237

195-
except Exception as e:
196-
logger.error(f"Error in bot monitoring loop: {e}", exc_info=True)
238+
except Exception:
239+
logger.exception("Error in bot monitoring loop")
197240
finally:
198241
self.running = False
199242

200243
async def shutdown(self) -> None:
201244
"""Gracefully shutdown all active bots."""
202245
self.running = False
203246

204-
# Shutdown Telegram bot if active
205247
if self.telegram_bot:
206248
try:
207249
logger.info("Shutting down Telegram bot")
208250
await self.telegram_bot.shutdown()
209-
except Exception as e:
210-
logger.exception(f"Error shutting down Telegram bot: {e}")
251+
except Exception:
252+
logger.exception("Error shutting down Telegram bot")
211253

212254
if "Twitter" in self.active_bots:
213255
logger.info("Twitter bot daemon thread will terminate with main process")
@@ -220,32 +262,26 @@ async def async_start() -> None:
220262
bot_manager = BotManager()
221263

222264
try:
223-
# Initialize AI provider
224265
bot_manager.initialize_ai_provider()
225266
if not bot_manager.ai_provider:
226267
logger.error("Failed to initialize AI provider")
227268
return
228269

229-
# Start Twitter bot (if enabled and configured)
230270
bot_manager.start_twitter_bot()
231-
232-
# Start Telegram bot (if enabled and configured)
233271
await bot_manager.start_telegram_bot()
234272

235273
if bot_manager.active_bots:
236-
logger.info(f"Active bots: {', '.join(bot_manager.active_bots)}")
274+
logger.info("Active bots: %s", ", ".join(bot_manager.active_bots))
237275
monitor_task = asyncio.create_task(bot_manager.monitor_bots())
238276

239277
try:
240-
while bot_manager.active_bots:
241-
await asyncio.sleep(1)
278+
await Event().wait()
242279
except asyncio.CancelledError:
243280
logger.info("Main task cancelled")
244281
finally:
245282
monitor_task.cancel()
246283
with contextlib.suppress(asyncio.CancelledError):
247284
await monitor_task
248-
249285
await bot_manager.shutdown()
250286
else:
251287
logger.info(
@@ -255,8 +291,8 @@ async def async_start() -> None:
255291
except KeyboardInterrupt:
256292
logger.info("Application stopped by user")
257293
await bot_manager.shutdown()
258-
except Exception as e:
259-
logger.error(f"Fatal error in async_start: {e}", exc_info=True)
294+
except Exception:
295+
logger.exception("Fatal error in async_start")
260296
await bot_manager.shutdown()
261297

262298

@@ -266,5 +302,5 @@ def start_bot_manager() -> None:
266302
asyncio.run(async_start())
267303
except KeyboardInterrupt:
268304
logger.info("Application stopped by user")
269-
except Exception as e:
270-
logger.error(f"Fatal error in start: {e}", exc_info=True)
305+
except Exception:
306+
logger.exception("Fatal error in start")

0 commit comments

Comments
 (0)