First commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Async Worker Module
|
||||
|
||||
Provides a persistent async worker thread for executing coroutines.
|
||||
Solves "Event loop is closed" errors by maintaining a single event loop.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Any, Optional
|
||||
from concurrent.futures import Future
|
||||
import logging
|
||||
|
||||
|
||||
class AsyncWorker:
|
||||
"""
|
||||
Persistent async worker thread for executing coroutines.
|
||||
|
||||
This worker maintains a single event loop for the application's lifetime,
|
||||
solving the "Event loop is closed" error by ensuring all async operations
|
||||
use the same loop and credentials remain valid.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
self.thread: Optional[threading.Thread] = None
|
||||
self.running = False
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def start(self):
|
||||
"""Start the async worker thread."""
|
||||
if self.running:
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.thread = threading.Thread(
|
||||
target=self._run_loop,
|
||||
daemon=True,
|
||||
name="AsyncWorker"
|
||||
)
|
||||
self.thread.start()
|
||||
|
||||
# Wait for loop to be ready
|
||||
import time
|
||||
while self.loop is None:
|
||||
time.sleep(0.01)
|
||||
|
||||
def _run_loop(self):
|
||||
"""Run the event loop in the worker thread."""
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
|
||||
self.logger.info("AsyncWorker event loop started")
|
||||
|
||||
try:
|
||||
self.loop.run_forever()
|
||||
finally:
|
||||
self.loop.close()
|
||||
self.logger.info("AsyncWorker event loop closed")
|
||||
|
||||
def submit(self, coro) -> Future:
|
||||
"""
|
||||
Submit a coroutine to be executed in the worker loop.
|
||||
|
||||
Args:
|
||||
coro: Coroutine to execute
|
||||
|
||||
Returns:
|
||||
Future that will contain the result
|
||||
"""
|
||||
if not self.running:
|
||||
raise RuntimeError("AsyncWorker not started")
|
||||
|
||||
result_future = Future()
|
||||
|
||||
def callback():
|
||||
"""Execute coroutine and set result in future."""
|
||||
try:
|
||||
task = asyncio.ensure_future(coro, loop=self.loop)
|
||||
|
||||
def done_callback(task_future):
|
||||
try:
|
||||
result = task_future.result()
|
||||
result_future.set_result(result)
|
||||
except Exception as e:
|
||||
result_future.set_exception(e)
|
||||
|
||||
task.add_done_callback(done_callback)
|
||||
except Exception as e:
|
||||
result_future.set_exception(e)
|
||||
|
||||
self.loop.call_soon_threadsafe(callback)
|
||||
return result_future
|
||||
|
||||
def stop(self):
|
||||
"""Stop the async worker thread."""
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.running = False
|
||||
|
||||
if self.loop:
|
||||
self.loop.call_soon_threadsafe(self.loop.stop)
|
||||
|
||||
if self.thread:
|
||||
self.thread.join(timeout=5.0)
|
||||
|
||||
self.logger.info("AsyncWorker stopped")
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Logging Utility
|
||||
|
||||
Configures logging for the application.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def setup_logger(name: str = 'AzureKeyVaultManager', log_file: str = None, level=logging.INFO):
|
||||
"""
|
||||
Set up and configure a logger.
|
||||
|
||||
Args:
|
||||
name: Logger name
|
||||
log_file: Optional log file path (defaults to logs/app_{date}.log)
|
||||
level: Logging level (default: INFO)
|
||||
|
||||
Returns:
|
||||
logging.Logger: Configured logger instance
|
||||
"""
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
|
||||
# Remove existing handlers
|
||||
logger.handlers = []
|
||||
|
||||
# Create formatter
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(level)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler (optional)
|
||||
if log_file:
|
||||
# Create logs directory if it doesn't exist
|
||||
log_dir = os.path.dirname(log_file)
|
||||
if log_dir and not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(level)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
else:
|
||||
# Default log file in logs directory
|
||||
log_dir = 'logs'
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
log_file = os.path.join(log_dir, f'app_{datetime.now().strftime("%Y%m%d")}.log')
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(level)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
# Create default logger instance
|
||||
logger = setup_logger()
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Name Sanitization Utility
|
||||
|
||||
Sanitizes names for use in Azure Key Vault.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def sanitize_name(name: str) -> str:
|
||||
"""
|
||||
Sanitize a name for use in Azure Key Vault.
|
||||
Key Vault secret names can only contain alphanumeric characters and hyphens.
|
||||
|
||||
Args:
|
||||
name: The name to sanitize
|
||||
|
||||
Returns:
|
||||
str: Sanitized name
|
||||
|
||||
Example:
|
||||
>>> sanitize_name("My App (Production) #2024")
|
||||
'My-App-Production-2024'
|
||||
"""
|
||||
if not name:
|
||||
return name
|
||||
|
||||
# Replace any non-alphanumeric character (except hyphens) with hyphen
|
||||
sanitized = re.sub(r'[^0-9a-zA-Z-]', '-', name)
|
||||
|
||||
# Remove consecutive hyphens
|
||||
sanitized = re.sub(r'-+', '-', sanitized)
|
||||
|
||||
# Remove leading/trailing hyphens
|
||||
sanitized = sanitized.strip('-')
|
||||
|
||||
return sanitized
|
||||
Reference in New Issue
Block a user