grasp-sdk 0.1.0__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.
Potentially problematic release.
This version of grasp-sdk might be problematic. Click here for more details.
- grasp_sdk/__init__.py +262 -0
- grasp_sdk/models/__init__.py +78 -0
- grasp_sdk/sandbox/chrome-stable.mjs +381 -0
- grasp_sdk/sandbox/chromium.mjs +378 -0
- grasp_sdk/sandbox/jsconfig.json +22 -0
- grasp_sdk/services/__init__.py +8 -0
- grasp_sdk/services/browser.py +414 -0
- grasp_sdk/services/sandbox.py +583 -0
- grasp_sdk/utils/__init__.py +31 -0
- grasp_sdk/utils/auth.py +227 -0
- grasp_sdk/utils/config.py +150 -0
- grasp_sdk/utils/logger.py +233 -0
- grasp_sdk-0.1.0.dist-info/METADATA +201 -0
- grasp_sdk-0.1.0.dist-info/RECORD +17 -0
- grasp_sdk-0.1.0.dist-info/WHEEL +5 -0
- grasp_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- grasp_sdk-0.1.0.dist-info/top_level.txt +1 -0
grasp_sdk/utils/auth.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Authentication utilities for Grasp SDK Python implementation.
|
|
2
|
+
|
|
3
|
+
This module provides authentication and key verification functionality
|
|
4
|
+
for the Grasp platform.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Dict, Any, Optional, TYPE_CHECKING
|
|
9
|
+
from urllib.parse import quote
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import aiohttp
|
|
13
|
+
except ImportError:
|
|
14
|
+
aiohttp = None
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from aiohttp import ClientSession
|
|
18
|
+
|
|
19
|
+
from ..models import ISandboxConfig
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthError(Exception):
|
|
23
|
+
"""Exception raised for authentication errors."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class KeyVerificationError(AuthError):
|
|
28
|
+
"""Exception raised when key verification fails."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def login(token: str) -> Dict[str, Any]:
|
|
33
|
+
"""Authenticates with the Grasp platform using a token.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
token: Authentication token to verify
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict[str, Any]: Response from the authentication API
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
AuthError: If authentication fails
|
|
43
|
+
ImportError: If aiohttp is not installed
|
|
44
|
+
"""
|
|
45
|
+
if aiohttp is None:
|
|
46
|
+
raise ImportError("aiohttp is required for authentication. Install with: pip install aiohttp")
|
|
47
|
+
|
|
48
|
+
url = f"https://d1toyru2btfpfr.cloudfront.net/api/key/verify?token={quote(token)}"
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
if aiohttp is None:
|
|
52
|
+
raise ImportError("aiohttp is required for authentication. Install with: pip install aiohttp")
|
|
53
|
+
|
|
54
|
+
async with aiohttp.ClientSession() as session:
|
|
55
|
+
async with session.get(url) as response:
|
|
56
|
+
if response.status == 200:
|
|
57
|
+
return await response.json()
|
|
58
|
+
elif response.status == 401:
|
|
59
|
+
raise AuthError("Invalid authentication token")
|
|
60
|
+
elif response.status == 403:
|
|
61
|
+
raise AuthError("Access forbidden - token may be expired")
|
|
62
|
+
else:
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
return await response.json()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
if aiohttp is not None and isinstance(e, aiohttp.ClientError):
|
|
67
|
+
raise AuthError(f"Network error during authentication: {str(e)}")
|
|
68
|
+
else:
|
|
69
|
+
raise AuthError(f"Unexpected error during authentication: {str(e)}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def verify(config: ISandboxConfig) -> Dict[str, Any]:
|
|
73
|
+
"""Verifies the sandbox configuration and authenticates.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
config: Sandbox configuration containing the API key
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dict[str, Any]: Authentication response
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
KeyVerificationError: If the key is missing or invalid
|
|
83
|
+
AuthError: If authentication fails
|
|
84
|
+
"""
|
|
85
|
+
if not config['key']:
|
|
86
|
+
raise KeyVerificationError('Grasp key is required')
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
return await login(config['key'])
|
|
90
|
+
except AuthError:
|
|
91
|
+
raise
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise KeyVerificationError(f"Key verification failed: {str(e)}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def verify_sync(config: ISandboxConfig) -> Dict[str, Any]:
|
|
97
|
+
"""Synchronous wrapper for the verify function.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
config: Sandbox configuration containing the API key
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dict[str, Any]: Authentication response
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
KeyVerificationError: If the key is missing or invalid
|
|
107
|
+
AuthError: If authentication fails
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
loop = asyncio.get_event_loop()
|
|
111
|
+
except RuntimeError:
|
|
112
|
+
loop = asyncio.new_event_loop()
|
|
113
|
+
asyncio.set_event_loop(loop)
|
|
114
|
+
|
|
115
|
+
return loop.run_until_complete(verify(config))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def validate_token(token: str) -> bool:
|
|
119
|
+
"""Validates a token without raising exceptions.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
token: Token to validate
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool: True if token is valid, False otherwise
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
await login(token)
|
|
129
|
+
return True
|
|
130
|
+
except (AuthError, Exception):
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def validate_token_sync(token: str) -> bool:
|
|
135
|
+
"""Synchronous wrapper for token validation.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
token: Token to validate
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
bool: True if token is valid, False otherwise
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
loop = asyncio.get_event_loop()
|
|
145
|
+
except RuntimeError:
|
|
146
|
+
loop = asyncio.new_event_loop()
|
|
147
|
+
asyncio.set_event_loop(loop)
|
|
148
|
+
|
|
149
|
+
return loop.run_until_complete(validate_token(token))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class AuthManager:
|
|
153
|
+
"""Authentication manager for handling multiple tokens and sessions."""
|
|
154
|
+
|
|
155
|
+
def __init__(self):
|
|
156
|
+
"""Initialize the authentication manager."""
|
|
157
|
+
if aiohttp is None:
|
|
158
|
+
raise ImportError("aiohttp is required for AuthManager. Install with: pip install aiohttp")
|
|
159
|
+
self._verified_tokens: Dict[str, Dict[str, Any]] = {}
|
|
160
|
+
self._session: Optional['ClientSession'] = None
|
|
161
|
+
|
|
162
|
+
async def __aenter__(self):
|
|
163
|
+
"""Async context manager entry."""
|
|
164
|
+
if aiohttp is None:
|
|
165
|
+
raise ImportError("aiohttp is required for AuthManager. Install with: pip install aiohttp")
|
|
166
|
+
self._session = aiohttp.ClientSession()
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
170
|
+
"""Async context manager exit."""
|
|
171
|
+
if self._session:
|
|
172
|
+
await self._session.close()
|
|
173
|
+
|
|
174
|
+
async def verify_token(self, token: str, force_refresh: bool = False) -> Dict[str, Any]:
|
|
175
|
+
"""Verify a token with caching support.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
token: Token to verify
|
|
179
|
+
force_refresh: Whether to force a new verification
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dict[str, Any]: Verification response
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
AuthError: If verification fails
|
|
186
|
+
"""
|
|
187
|
+
if not force_refresh and token in self._verified_tokens:
|
|
188
|
+
return self._verified_tokens[token]
|
|
189
|
+
|
|
190
|
+
url = f"https://d1toyru2btfpfr.cloudfront.net/api/key/verify?token={quote(token)}"
|
|
191
|
+
|
|
192
|
+
if not self._session:
|
|
193
|
+
raise AuthError("AuthManager not properly initialized. Use as async context manager.")
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
async with self._session.get(url) as response:
|
|
197
|
+
if response.status == 200:
|
|
198
|
+
result = await response.json()
|
|
199
|
+
self._verified_tokens[token] = result
|
|
200
|
+
return result
|
|
201
|
+
elif response.status == 401:
|
|
202
|
+
raise AuthError("Invalid authentication token")
|
|
203
|
+
elif response.status == 403:
|
|
204
|
+
raise AuthError("Access forbidden - token may be expired")
|
|
205
|
+
else:
|
|
206
|
+
response.raise_for_status()
|
|
207
|
+
return await response.json()
|
|
208
|
+
except Exception as e:
|
|
209
|
+
if aiohttp is not None and isinstance(e, aiohttp.ClientError):
|
|
210
|
+
raise AuthError(f"Network error during authentication: {str(e)}")
|
|
211
|
+
else:
|
|
212
|
+
raise AuthError(f"Unexpected error during authentication: {str(e)}")
|
|
213
|
+
|
|
214
|
+
def clear_cache(self) -> None:
|
|
215
|
+
"""Clear the token verification cache."""
|
|
216
|
+
self._verified_tokens.clear()
|
|
217
|
+
|
|
218
|
+
def is_token_cached(self, token: str) -> bool:
|
|
219
|
+
"""Check if a token is cached.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
token: Token to check
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
bool: True if token is cached
|
|
226
|
+
"""
|
|
227
|
+
return token in self._verified_tokens
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Configuration management for Grasp SDK Python implementation.
|
|
2
|
+
|
|
3
|
+
This module provides configuration loading from environment variables
|
|
4
|
+
and default configuration constants.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
from ..models import ISandboxConfig, IBrowserConfig
|
|
11
|
+
|
|
12
|
+
# Load environment variables from .env.grasp file
|
|
13
|
+
load_dotenv('.env.grasp')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_config() -> Dict[str, Any]:
|
|
17
|
+
"""Gets application configuration from environment variables.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Dict[str, Any]: Application configuration object containing
|
|
21
|
+
sandbox and logger configurations.
|
|
22
|
+
"""
|
|
23
|
+
return {
|
|
24
|
+
'sandbox': {
|
|
25
|
+
'key': os.getenv('GRASP_KEY', ''),
|
|
26
|
+
'templateId': 'playwright-pnpm-template',
|
|
27
|
+
'timeout': int(os.getenv('GRASP_SERVICE_TIMEOUT', '900000')),
|
|
28
|
+
'debug': os.getenv('GRASP_DEBUG', 'false').lower() == 'true',
|
|
29
|
+
},
|
|
30
|
+
'logger': {
|
|
31
|
+
'level': os.getenv('GRASP_LOG_LEVEL', 'info'),
|
|
32
|
+
'console': True,
|
|
33
|
+
'file': os.getenv('GRASP_LOG_FILE'),
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_sandbox_config() -> ISandboxConfig:
|
|
39
|
+
"""Gets sandbox-specific configuration.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ISandboxConfig: Sandbox configuration object.
|
|
43
|
+
"""
|
|
44
|
+
config = get_config()
|
|
45
|
+
sandbox_config = ISandboxConfig(
|
|
46
|
+
key=config['sandbox']['key'],
|
|
47
|
+
templateId=config['sandbox']['templateId'],
|
|
48
|
+
timeout=config['sandbox']['timeout'],
|
|
49
|
+
debug=config['sandbox'].get('debug', False),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Only add workspace if it exists
|
|
53
|
+
workspace = os.getenv('GRASP_WORKSPACE')
|
|
54
|
+
if workspace:
|
|
55
|
+
sandbox_config['workspace'] = workspace
|
|
56
|
+
|
|
57
|
+
return sandbox_config
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_browser_config() -> IBrowserConfig:
|
|
61
|
+
"""Gets browser-specific configuration.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
IBrowserConfig: Browser configuration object.
|
|
65
|
+
"""
|
|
66
|
+
return IBrowserConfig(
|
|
67
|
+
cdpPort=int(os.getenv('GRASP_CDP_PORT', '9222')),
|
|
68
|
+
args=[
|
|
69
|
+
'--no-sandbox',
|
|
70
|
+
'--disable-setuid-sandbox',
|
|
71
|
+
'--disable-dev-shm-usage',
|
|
72
|
+
'--disable-gpu',
|
|
73
|
+
'--remote-debugging-port=9222',
|
|
74
|
+
'--remote-debugging-address=0.0.0.0',
|
|
75
|
+
],
|
|
76
|
+
headless=os.getenv('GRASP_HEADLESS', 'true').lower() == 'true',
|
|
77
|
+
launchTimeout=int(os.getenv('GRASP_LAUNCH_TIMEOUT', '30000')),
|
|
78
|
+
envs={
|
|
79
|
+
'PLAYWRIGHT_BROWSERS_PATH': '0',
|
|
80
|
+
'DISPLAY': ':99',
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Default configuration constants
|
|
86
|
+
DEFAULT_CONFIG = {
|
|
87
|
+
'PLAYWRIGHT_BROWSERS_PATH': '0',
|
|
88
|
+
'WORKING_DIRECTORY': '/home/user',
|
|
89
|
+
'SCREENSHOT_PATH': '/home/user',
|
|
90
|
+
'DEFAULT_VIEWPORT': {
|
|
91
|
+
'width': 1280,
|
|
92
|
+
'height': 720,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Environment variable names for easy reference
|
|
98
|
+
ENV_VARS = {
|
|
99
|
+
'GRASP_KEY': 'GRASP_KEY',
|
|
100
|
+
'GRASP_WORKSPACE': 'GRASP_WORKSPACE',
|
|
101
|
+
'GRASP_SERVICE_TIMEOUT': 'GRASP_SERVICE_TIMEOUT',
|
|
102
|
+
'GRASP_DEBUG': 'GRASP_DEBUG',
|
|
103
|
+
'GRASP_LOG_LEVEL': 'GRASP_LOG_LEVEL',
|
|
104
|
+
'GRASP_LOG_FILE': 'GRASP_LOG_FILE',
|
|
105
|
+
'GRASP_CDP_PORT': 'GRASP_CDP_PORT',
|
|
106
|
+
'GRASP_HEADLESS': 'GRASP_HEADLESS',
|
|
107
|
+
'GRASP_LAUNCH_TIMEOUT': 'GRASP_LAUNCH_TIMEOUT',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_config() -> bool:
|
|
112
|
+
"""Validates that required configuration is present.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
bool: True if configuration is valid, False otherwise.
|
|
116
|
+
"""
|
|
117
|
+
config = get_config()
|
|
118
|
+
|
|
119
|
+
# Check required fields
|
|
120
|
+
if not config['sandbox']['key']:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
# Validate timeout values
|
|
124
|
+
if config['sandbox']['timeout'] <= 0:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_env_var(key: str, default: Optional[str] = None) -> Optional[str]:
|
|
131
|
+
"""Gets an environment variable with optional default.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
key: Environment variable name
|
|
135
|
+
default: Default value if not found
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
str or None: Environment variable value or default
|
|
139
|
+
"""
|
|
140
|
+
return os.getenv(key, default)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def set_env_var(key: str, value: str) -> None:
|
|
144
|
+
"""Sets an environment variable.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
key: Environment variable name
|
|
148
|
+
value: Environment variable value
|
|
149
|
+
"""
|
|
150
|
+
os.environ[key] = value
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Logging utilities for Grasp SDK Python implementation.
|
|
2
|
+
|
|
3
|
+
This module provides a structured logging system with support for
|
|
4
|
+
different log levels, console output, and file logging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Dict, Optional, Union
|
|
12
|
+
from enum import IntEnum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LogLevel(IntEnum):
|
|
16
|
+
"""Log levels with numeric values for comparison."""
|
|
17
|
+
DEBUG = 0
|
|
18
|
+
INFO = 1
|
|
19
|
+
WARN = 2
|
|
20
|
+
ERROR = 3
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Logger:
|
|
24
|
+
"""Logger class for structured logging."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: Dict[str, Any]):
|
|
27
|
+
"""Initialize logger with configuration.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config: Logger configuration dictionary containing:
|
|
31
|
+
- level: Log level ('debug', 'info', 'warn', 'error')
|
|
32
|
+
- console: Whether to output to console
|
|
33
|
+
- file: Optional file path for logging
|
|
34
|
+
"""
|
|
35
|
+
self.config = config
|
|
36
|
+
self.current_level = LogLevel[config['level'].upper()]
|
|
37
|
+
self._setup_file_logger()
|
|
38
|
+
|
|
39
|
+
def _setup_file_logger(self) -> None:
|
|
40
|
+
"""Setup file logging if configured."""
|
|
41
|
+
if self.config.get('file'):
|
|
42
|
+
# Configure Python's built-in logging for file output
|
|
43
|
+
logging.basicConfig(
|
|
44
|
+
filename=self.config['file'],
|
|
45
|
+
level=getattr(logging, self.config['level'].upper()),
|
|
46
|
+
format='%(asctime)s [%(levelname)s] %(message)s',
|
|
47
|
+
datefmt='%Y-%m-%dT%H:%M:%S.%fZ'
|
|
48
|
+
)
|
|
49
|
+
self.file_logger = logging.getLogger('grasp_file')
|
|
50
|
+
else:
|
|
51
|
+
self.file_logger = None
|
|
52
|
+
|
|
53
|
+
def _format_message(self, level: str, message: str, data: Optional[Any] = None) -> str:
|
|
54
|
+
"""Formats log message with timestamp and level.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
level: Log level string
|
|
58
|
+
message: Log message
|
|
59
|
+
data: Additional data to log
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
str: Formatted log string
|
|
63
|
+
"""
|
|
64
|
+
timestamp = datetime.utcnow().isoformat() + 'Z'
|
|
65
|
+
data_str = f' {json.dumps(data)}' if data is not None else ''
|
|
66
|
+
return f'[{timestamp}] [{level.upper()}] {message}{data_str}'
|
|
67
|
+
|
|
68
|
+
def _log(self, level: str, message: str, data: Optional[Any] = None) -> None:
|
|
69
|
+
"""Logs a message if the level is enabled.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
level: Log level string
|
|
73
|
+
message: Log message
|
|
74
|
+
data: Additional data to log
|
|
75
|
+
"""
|
|
76
|
+
level_enum = LogLevel[level.upper()]
|
|
77
|
+
if level_enum < self.current_level:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
formatted_message = self._format_message(level, message, data)
|
|
81
|
+
|
|
82
|
+
# Console output
|
|
83
|
+
if self.config['console']:
|
|
84
|
+
if level == 'debug':
|
|
85
|
+
print(formatted_message, file=sys.stdout)
|
|
86
|
+
elif level == 'info':
|
|
87
|
+
print(formatted_message, file=sys.stdout)
|
|
88
|
+
elif level == 'warn':
|
|
89
|
+
print(formatted_message, file=sys.stderr)
|
|
90
|
+
elif level == 'error':
|
|
91
|
+
print(formatted_message, file=sys.stderr)
|
|
92
|
+
|
|
93
|
+
# File output
|
|
94
|
+
if self.file_logger:
|
|
95
|
+
if level == 'debug':
|
|
96
|
+
self.file_logger.debug(message, extra={'data': data} if data else None)
|
|
97
|
+
elif level == 'info':
|
|
98
|
+
self.file_logger.info(message, extra={'data': data} if data else None)
|
|
99
|
+
elif level == 'warn':
|
|
100
|
+
self.file_logger.warning(message, extra={'data': data} if data else None)
|
|
101
|
+
elif level == 'error':
|
|
102
|
+
self.file_logger.error(message, extra={'data': data} if data else None)
|
|
103
|
+
|
|
104
|
+
def debug(self, message: str, data: Optional[Any] = None) -> None:
|
|
105
|
+
"""Logs debug message.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
message: Debug message
|
|
109
|
+
data: Additional data
|
|
110
|
+
"""
|
|
111
|
+
self._log('debug', message, data)
|
|
112
|
+
|
|
113
|
+
def info(self, message: str, data: Optional[Any] = None) -> None:
|
|
114
|
+
"""Logs info message.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
message: Info message
|
|
118
|
+
data: Additional data
|
|
119
|
+
"""
|
|
120
|
+
self._log('info', message, data)
|
|
121
|
+
|
|
122
|
+
def warn(self, message: str, data: Optional[Any] = None) -> None:
|
|
123
|
+
"""Logs warning message.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
message: Warning message
|
|
127
|
+
data: Additional data
|
|
128
|
+
"""
|
|
129
|
+
self._log('warn', message, data)
|
|
130
|
+
|
|
131
|
+
def error(self, message: str, data: Optional[Any] = None) -> None:
|
|
132
|
+
"""Logs error message.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
message: Error message
|
|
136
|
+
data: Additional data
|
|
137
|
+
"""
|
|
138
|
+
self._log('error', message, data)
|
|
139
|
+
|
|
140
|
+
def child(self, context: str) -> 'Logger':
|
|
141
|
+
"""Creates a child logger with additional context.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
context: Context to add to all log messages
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Logger: New logger instance with context
|
|
148
|
+
"""
|
|
149
|
+
child_logger = Logger(self.config)
|
|
150
|
+
|
|
151
|
+
# Override the _log method to add context
|
|
152
|
+
original_log = child_logger._log
|
|
153
|
+
|
|
154
|
+
def contextual_log(level: str, message: str, data: Optional[Any] = None) -> None:
|
|
155
|
+
original_log(level, f'[{context}] {message}', data)
|
|
156
|
+
|
|
157
|
+
child_logger._log = contextual_log
|
|
158
|
+
return child_logger
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Global logger instance
|
|
162
|
+
_default_logger: Optional[Logger] = None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def init_logger(config: Dict[str, Any]) -> None:
|
|
166
|
+
"""Initializes the default logger.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
config: Logger configuration dictionary
|
|
170
|
+
"""
|
|
171
|
+
global _default_logger
|
|
172
|
+
_default_logger = Logger(config)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_logger() -> Logger:
|
|
176
|
+
"""Gets the default logger instance.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Logger: Default logger instance
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
RuntimeError: If logger is not initialized
|
|
183
|
+
"""
|
|
184
|
+
if _default_logger is None:
|
|
185
|
+
raise RuntimeError('Logger not initialized. Call init_logger() first.')
|
|
186
|
+
return _default_logger
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_default_logger() -> Logger:
|
|
190
|
+
"""Gets a default logger with basic configuration.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Logger: Logger with default configuration
|
|
194
|
+
"""
|
|
195
|
+
default_config = {
|
|
196
|
+
'level': 'info',
|
|
197
|
+
'console': True,
|
|
198
|
+
'file': None,
|
|
199
|
+
}
|
|
200
|
+
return Logger(default_config)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Convenience functions for quick logging
|
|
204
|
+
def debug(message: str, data: Optional[Any] = None) -> None:
|
|
205
|
+
"""Quick debug logging function."""
|
|
206
|
+
try:
|
|
207
|
+
get_logger().debug(message, data)
|
|
208
|
+
except RuntimeError:
|
|
209
|
+
get_default_logger().debug(message, data)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def info(message: str, data: Optional[Any] = None) -> None:
|
|
213
|
+
"""Quick info logging function."""
|
|
214
|
+
try:
|
|
215
|
+
get_logger().info(message, data)
|
|
216
|
+
except RuntimeError:
|
|
217
|
+
get_default_logger().info(message, data)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def warn(message: str, data: Optional[Any] = None) -> None:
|
|
221
|
+
"""Quick warning logging function."""
|
|
222
|
+
try:
|
|
223
|
+
get_logger().warn(message, data)
|
|
224
|
+
except RuntimeError:
|
|
225
|
+
get_default_logger().warn(message, data)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def error(message: str, data: Optional[Any] = None) -> None:
|
|
229
|
+
"""Quick error logging function."""
|
|
230
|
+
try:
|
|
231
|
+
get_logger().error(message, data)
|
|
232
|
+
except RuntimeError:
|
|
233
|
+
get_default_logger().error(message, data)
|