webscout 8.2.8__py3-none-any.whl → 8.2.9__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.
Files changed (184) hide show
  1. webscout/AIauto.py +32 -14
  2. webscout/AIbase.py +96 -37
  3. webscout/AIutel.py +491 -87
  4. webscout/Bard.py +441 -323
  5. webscout/Extra/GitToolkit/__init__.py +10 -10
  6. webscout/Extra/YTToolkit/ytapi/video.py +232 -232
  7. webscout/Litlogger/README.md +10 -0
  8. webscout/Litlogger/__init__.py +7 -59
  9. webscout/Litlogger/formats.py +4 -0
  10. webscout/Litlogger/handlers.py +103 -0
  11. webscout/Litlogger/levels.py +13 -0
  12. webscout/Litlogger/logger.py +92 -0
  13. webscout/Provider/AISEARCH/Perplexity.py +332 -358
  14. webscout/Provider/AISEARCH/felo_search.py +9 -35
  15. webscout/Provider/AISEARCH/genspark_search.py +30 -56
  16. webscout/Provider/AISEARCH/hika_search.py +4 -16
  17. webscout/Provider/AISEARCH/iask_search.py +410 -436
  18. webscout/Provider/AISEARCH/monica_search.py +4 -30
  19. webscout/Provider/AISEARCH/scira_search.py +6 -32
  20. webscout/Provider/AISEARCH/webpilotai_search.py +38 -64
  21. webscout/Provider/Blackboxai.py +153 -35
  22. webscout/Provider/Deepinfra.py +339 -339
  23. webscout/Provider/ExaChat.py +358 -358
  24. webscout/Provider/Gemini.py +169 -169
  25. webscout/Provider/GithubChat.py +1 -2
  26. webscout/Provider/Glider.py +3 -3
  27. webscout/Provider/HeckAI.py +171 -81
  28. webscout/Provider/OPENAI/BLACKBOXAI.py +766 -735
  29. webscout/Provider/OPENAI/Cloudflare.py +7 -7
  30. webscout/Provider/OPENAI/FreeGemini.py +6 -5
  31. webscout/Provider/OPENAI/NEMOTRON.py +8 -20
  32. webscout/Provider/OPENAI/Qwen3.py +283 -0
  33. webscout/Provider/OPENAI/README.md +952 -1253
  34. webscout/Provider/OPENAI/TwoAI.py +357 -0
  35. webscout/Provider/OPENAI/__init__.py +5 -1
  36. webscout/Provider/OPENAI/ai4chat.py +40 -40
  37. webscout/Provider/OPENAI/api.py +808 -649
  38. webscout/Provider/OPENAI/c4ai.py +3 -3
  39. webscout/Provider/OPENAI/chatgpt.py +555 -555
  40. webscout/Provider/OPENAI/chatgptclone.py +493 -487
  41. webscout/Provider/OPENAI/chatsandbox.py +4 -3
  42. webscout/Provider/OPENAI/copilot.py +242 -0
  43. webscout/Provider/OPENAI/deepinfra.py +5 -2
  44. webscout/Provider/OPENAI/e2b.py +63 -5
  45. webscout/Provider/OPENAI/exaai.py +416 -410
  46. webscout/Provider/OPENAI/exachat.py +444 -443
  47. webscout/Provider/OPENAI/freeaichat.py +2 -2
  48. webscout/Provider/OPENAI/glider.py +5 -2
  49. webscout/Provider/OPENAI/groq.py +5 -2
  50. webscout/Provider/OPENAI/heckai.py +308 -307
  51. webscout/Provider/OPENAI/mcpcore.py +8 -2
  52. webscout/Provider/OPENAI/multichat.py +4 -4
  53. webscout/Provider/OPENAI/netwrck.py +6 -5
  54. webscout/Provider/OPENAI/oivscode.py +287 -0
  55. webscout/Provider/OPENAI/opkfc.py +496 -496
  56. webscout/Provider/OPENAI/pydantic_imports.py +172 -0
  57. webscout/Provider/OPENAI/scirachat.py +15 -9
  58. webscout/Provider/OPENAI/sonus.py +304 -303
  59. webscout/Provider/OPENAI/standardinput.py +433 -433
  60. webscout/Provider/OPENAI/textpollinations.py +4 -4
  61. webscout/Provider/OPENAI/toolbaz.py +413 -413
  62. webscout/Provider/OPENAI/typefully.py +3 -3
  63. webscout/Provider/OPENAI/typegpt.py +11 -5
  64. webscout/Provider/OPENAI/uncovrAI.py +463 -462
  65. webscout/Provider/OPENAI/utils.py +90 -79
  66. webscout/Provider/OPENAI/venice.py +431 -425
  67. webscout/Provider/OPENAI/wisecat.py +387 -381
  68. webscout/Provider/OPENAI/writecream.py +3 -3
  69. webscout/Provider/OPENAI/x0gpt.py +365 -378
  70. webscout/Provider/OPENAI/yep.py +39 -13
  71. webscout/Provider/TTI/README.md +55 -101
  72. webscout/Provider/TTI/__init__.py +4 -9
  73. webscout/Provider/TTI/aiarta.py +365 -0
  74. webscout/Provider/TTI/artbit.py +0 -0
  75. webscout/Provider/TTI/base.py +64 -0
  76. webscout/Provider/TTI/fastflux.py +200 -0
  77. webscout/Provider/TTI/magicstudio.py +201 -0
  78. webscout/Provider/TTI/piclumen.py +203 -0
  79. webscout/Provider/TTI/pixelmuse.py +225 -0
  80. webscout/Provider/TTI/pollinations.py +221 -0
  81. webscout/Provider/TTI/utils.py +11 -0
  82. webscout/Provider/TTS/__init__.py +2 -1
  83. webscout/Provider/TTS/base.py +159 -159
  84. webscout/Provider/TTS/openai_fm.py +129 -0
  85. webscout/Provider/TextPollinationsAI.py +308 -308
  86. webscout/Provider/TwoAI.py +239 -44
  87. webscout/Provider/UNFINISHED/Youchat.py +330 -330
  88. webscout/Provider/UNFINISHED/puterjs.py +635 -0
  89. webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
  90. webscout/Provider/Writecream.py +246 -246
  91. webscout/Provider/__init__.py +2 -0
  92. webscout/Provider/ai4chat.py +33 -8
  93. webscout/Provider/koala.py +169 -169
  94. webscout/Provider/oivscode.py +309 -0
  95. webscout/Provider/samurai.py +3 -2
  96. webscout/Provider/typegpt.py +3 -3
  97. webscout/Provider/uncovr.py +368 -368
  98. webscout/client.py +70 -0
  99. webscout/litprinter/__init__.py +58 -58
  100. webscout/optimizers.py +419 -419
  101. webscout/scout/README.md +3 -1
  102. webscout/scout/core/crawler.py +134 -64
  103. webscout/scout/core/scout.py +148 -109
  104. webscout/scout/element.py +106 -88
  105. webscout/swiftcli/Readme.md +323 -323
  106. webscout/swiftcli/plugins/manager.py +9 -2
  107. webscout/version.py +1 -1
  108. webscout/zeroart/__init__.py +134 -134
  109. webscout/zeroart/effects.py +100 -100
  110. webscout/zeroart/fonts.py +1238 -1238
  111. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/METADATA +159 -35
  112. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/RECORD +116 -161
  113. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
  114. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
  115. webscout/Litlogger/Readme.md +0 -175
  116. webscout/Litlogger/core/__init__.py +0 -6
  117. webscout/Litlogger/core/level.py +0 -23
  118. webscout/Litlogger/core/logger.py +0 -165
  119. webscout/Litlogger/handlers/__init__.py +0 -12
  120. webscout/Litlogger/handlers/console.py +0 -33
  121. webscout/Litlogger/handlers/file.py +0 -143
  122. webscout/Litlogger/handlers/network.py +0 -173
  123. webscout/Litlogger/styles/__init__.py +0 -7
  124. webscout/Litlogger/styles/colors.py +0 -249
  125. webscout/Litlogger/styles/formats.py +0 -458
  126. webscout/Litlogger/styles/text.py +0 -87
  127. webscout/Litlogger/utils/__init__.py +0 -6
  128. webscout/Litlogger/utils/detectors.py +0 -153
  129. webscout/Litlogger/utils/formatters.py +0 -200
  130. webscout/Provider/TTI/AiForce/README.md +0 -159
  131. webscout/Provider/TTI/AiForce/__init__.py +0 -22
  132. webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
  133. webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
  134. webscout/Provider/TTI/FreeAIPlayground/README.md +0 -99
  135. webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
  136. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
  137. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
  138. webscout/Provider/TTI/ImgSys/README.md +0 -174
  139. webscout/Provider/TTI/ImgSys/__init__.py +0 -23
  140. webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
  141. webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
  142. webscout/Provider/TTI/MagicStudio/README.md +0 -101
  143. webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
  144. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
  145. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
  146. webscout/Provider/TTI/Nexra/README.md +0 -155
  147. webscout/Provider/TTI/Nexra/__init__.py +0 -22
  148. webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
  149. webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
  150. webscout/Provider/TTI/PollinationsAI/README.md +0 -146
  151. webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
  152. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
  153. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
  154. webscout/Provider/TTI/aiarta/README.md +0 -134
  155. webscout/Provider/TTI/aiarta/__init__.py +0 -2
  156. webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
  157. webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
  158. webscout/Provider/TTI/artbit/README.md +0 -100
  159. webscout/Provider/TTI/artbit/__init__.py +0 -22
  160. webscout/Provider/TTI/artbit/async_artbit.py +0 -155
  161. webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
  162. webscout/Provider/TTI/fastflux/README.md +0 -129
  163. webscout/Provider/TTI/fastflux/__init__.py +0 -22
  164. webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
  165. webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
  166. webscout/Provider/TTI/huggingface/README.md +0 -114
  167. webscout/Provider/TTI/huggingface/__init__.py +0 -22
  168. webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
  169. webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
  170. webscout/Provider/TTI/piclumen/README.md +0 -161
  171. webscout/Provider/TTI/piclumen/__init__.py +0 -23
  172. webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
  173. webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
  174. webscout/Provider/TTI/pixelmuse/README.md +0 -79
  175. webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
  176. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
  177. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
  178. webscout/Provider/TTI/talkai/README.md +0 -139
  179. webscout/Provider/TTI/talkai/__init__.py +0 -4
  180. webscout/Provider/TTI/talkai/async_talkai.py +0 -229
  181. webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
  182. webscout/Provider/UNFINISHED/oivscode.py +0 -351
  183. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
  184. {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
@@ -1,23 +0,0 @@
1
- from enum import Enum
2
-
3
- class LogLevel(Enum):
4
- NOTSET = 0
5
- DEBUG = 10
6
- INFO = 20
7
- WARNING = 30
8
- ERROR = 40
9
- CRITICAL = 50
10
-
11
- @staticmethod
12
- def get_level(level_str: str) -> 'LogLevel':
13
- if not level_str:
14
- return LogLevel.NOTSET
15
- try:
16
- return LogLevel[level_str.upper()]
17
- except KeyError:
18
- raise ValueError(f"Invalid log level: {level_str}")
19
-
20
- def __lt__(self, other):
21
- if isinstance(other, LogLevel):
22
- return self.value < other.value
23
- return NotImplemented
@@ -1,165 +0,0 @@
1
-
2
- import asyncio
3
- import sys
4
- import threading
5
- import traceback
6
- from datetime import datetime
7
- from typing import List, Union
8
-
9
- from ..core.level import LogLevel
10
- from ..styles.formats import LogFormat
11
-
12
- class Logger:
13
- # Emoji mappings for different log levels
14
- LEVEL_EMOJIS = {
15
- LogLevel.DEBUG: "🔍",
16
- LogLevel.INFO: "ℹ️",
17
- LogLevel.WARNING: "⚠️",
18
- LogLevel.ERROR: "❌",
19
- LogLevel.CRITICAL: "🔥"
20
- }
21
-
22
- def __init__(
23
- self,
24
- name: str = "LitLogger",
25
- level: Union[str, LogLevel, None] = None,
26
- format: Union[str, LogFormat] = LogFormat.MODERN_EMOJI,
27
- handlers: List = None,
28
- enable_colors: bool = True,
29
- async_mode: bool = False,
30
- show_thread: bool = True,
31
- show_context: bool = True
32
- ):
33
- self.name = name
34
- self.level = LogLevel.NOTSET if level is None else (
35
- LogLevel.get_level(level) if isinstance(level, str) else level
36
- )
37
- self.format = format
38
- self.enable_colors = enable_colors
39
- self.async_mode = async_mode
40
- self.show_thread = show_thread
41
- self.show_context = show_context
42
- self._context_data = {}
43
-
44
- # Initialize with default console handler if none provided
45
- if handlers is None:
46
- from ..handlers.console import ConsoleHandler
47
- self.handlers = [ConsoleHandler(level=self.level)]
48
- else:
49
- self.handlers = handlers
50
-
51
- def _format_message(self, level: LogLevel, message: str, **kwargs) -> str:
52
- now = datetime.now()
53
- emoji = self.LEVEL_EMOJIS.get(level, "") if self.enable_colors else ""
54
-
55
- log_data = {
56
- "timestamp": now.strftime("%H:%M:%S"),
57
- "level": level.name,
58
- "name": self.name,
59
- "message": str(message),
60
- "emoji": emoji,
61
- "thread": threading.current_thread().name if self.show_thread else "",
62
- }
63
-
64
- # Add context data
65
- if self.show_context and self._context_data:
66
- log_data.update(self._context_data)
67
-
68
- # Add extra kwargs
69
- log_data.update(kwargs)
70
-
71
- # Format exception if present
72
- if 'exc_info' in kwargs:
73
- exc_info = kwargs['exc_info']
74
- if exc_info:
75
- exception_text = ''.join(traceback.format_exception(*exc_info))
76
- log_data['message'] = f"{message}\n{exception_text}"
77
-
78
- try:
79
- base_message = f"{emoji} [{log_data['timestamp']}] {level.name} {log_data['message']}"
80
- return base_message
81
- except Exception as e:
82
- return f"[{log_data['timestamp']}] {level.name}: {message}"
83
-
84
- def _log(self, level: LogLevel, message: str, **kwargs):
85
- if self.async_mode:
86
- loop = asyncio.get_event_loop()
87
- if loop.is_running():
88
- return asyncio.create_task(self._async_log(level, message, **kwargs))
89
- else:
90
- return loop.run_until_complete(self._async_log(level, message, **kwargs))
91
- return self._sync_log(level, message, **kwargs)
92
-
93
- def debug(self, message: str, **kwargs):
94
- self._log(LogLevel.DEBUG, message, **kwargs)
95
-
96
- def info(self, message: str, **kwargs):
97
- self._log(LogLevel.INFO, message, **kwargs)
98
-
99
- def warning(self, message: str, **kwargs):
100
- self._log(LogLevel.WARNING, message, **kwargs)
101
-
102
- def error(self, message: str, **kwargs):
103
- self._log(LogLevel.ERROR, message, **kwargs)
104
-
105
- def critical(self, message: str, **kwargs):
106
- self._log(LogLevel.CRITICAL, message, **kwargs)
107
-
108
- def exception(self, message: str, exc_info=True, **kwargs):
109
- """
110
- Log an exception with traceback.
111
-
112
- Args:
113
- message: The message to log
114
- exc_info: If True, adds exception info to the message. Can also be a tuple (type, value, traceback)
115
- **kwargs: Additional key-value pairs to log
116
- """
117
- if exc_info:
118
- if not isinstance(exc_info, tuple):
119
- exc_info = sys.exc_info()
120
- kwargs['exc_info'] = exc_info
121
- self.error(message, **kwargs)
122
-
123
- def _sync_log(self, level: LogLevel, message: str, **kwargs):
124
- if self._should_log(level):
125
- formatted_message = self._format_message(level, message, **kwargs)
126
- for handler in self.handlers:
127
- if handler.level == LogLevel.NOTSET or level.value >= handler.level.value:
128
- handler.emit(formatted_message, level)
129
-
130
- async def _async_log(self, level: LogLevel, message: str, **kwargs):
131
- if self._should_log(level):
132
- formatted_message = self._format_message(level, message, **kwargs)
133
- tasks = []
134
- for handler in self.handlers:
135
- if handler.level == LogLevel.NOTSET or level.value >= handler.level.value:
136
- if hasattr(handler, 'async_emit'):
137
- tasks.append(handler.async_emit(formatted_message, level))
138
- else:
139
- tasks.append(asyncio.to_thread(handler.emit, formatted_message, level))
140
- await asyncio.gather(*tasks)
141
-
142
- def _should_log(self, level: LogLevel) -> bool:
143
- return self.level == LogLevel.NOTSET or level.value >= self.level.value
144
-
145
- def set_context(self, **kwargs):
146
- self._context_data.update(kwargs)
147
-
148
- def clear_context(self):
149
- self._context_data.clear()
150
-
151
- def set_style(self, style: str):
152
- """Set logger style format."""
153
- if style in LogFormat.TEMPLATES:
154
- self.format = LogFormat.TEMPLATES[style]
155
- else:
156
- raise ValueError(f"Unknown style: {style}")
157
-
158
- def __enter__(self):
159
- return self
160
-
161
- def __exit__(self, exc_type, exc_val, exc_tb):
162
- if exc_type is not None:
163
- self.error(f"Context exited with error: {exc_val}")
164
- return False
165
- return True
@@ -1,12 +0,0 @@
1
- """Log output handlers for different destinations."""
2
-
3
- from .console import ConsoleHandler, ErrorConsoleHandler
4
- from .file import FileHandler
5
- from .network import NetworkHandler
6
-
7
- __all__ = [
8
- "ConsoleHandler",
9
- "ErrorConsoleHandler",
10
- "FileHandler",
11
- "NetworkHandler"
12
- ]
@@ -1,33 +0,0 @@
1
- import sys
2
- from ..core.level import LogLevel
3
- from ..styles.colors import LogColors
4
-
5
- class ConsoleHandler:
6
- def __init__(self, level: LogLevel = LogLevel.DEBUG, stream=sys.stdout):
7
- self.level = level
8
- self.stream = stream
9
- self.colors = LogColors()
10
-
11
- def emit(self, message: str, level: LogLevel):
12
- """Write the colored message to the console."""
13
- try:
14
- # Apply color based on log level
15
- color = LogColors.LEVEL_COLORS.get(level, LogColors.RESET)
16
- colored_message = f"{color}{message}{LogColors.RESET}"
17
- print(colored_message, file=self.stream, flush=True)
18
- except Exception as e:
19
- # Fallback to plain printing if coloring fails
20
- print(message, file=self.stream, flush=True)
21
-
22
- async def async_emit(self, message: str, level: LogLevel):
23
- """
24
- Asynchronously write log message to console.
25
- Just calls emit() since console output is generally fast enough.
26
- """
27
- self.emit(message, level)
28
-
29
- class ErrorConsoleHandler(ConsoleHandler):
30
- """Specialized handler that writes to stderr."""
31
-
32
- def __init__(self, level: LogLevel = LogLevel.ERROR):
33
- super().__init__(stream=sys.stderr, level=level)
@@ -1,143 +0,0 @@
1
- import os
2
- import time
3
- from datetime import datetime
4
- from pathlib import Path
5
- from typing import Union
6
- from ..core.level import LogLevel
7
-
8
- class FileHandler:
9
- """Handler for outputting log messages to a file with optional rotation."""
10
-
11
- def __init__(
12
- self,
13
- filename: Union[str, Path],
14
- mode: str = "a",
15
- encoding: str = "utf-8",
16
- level: LogLevel = LogLevel.DEBUG,
17
- max_bytes: int = 0,
18
- backup_count: int = 0,
19
- rotate_on_time: bool = False,
20
- time_interval: str = "D" # D=daily, H=hourly, M=monthly
21
- ):
22
- """
23
- Initialize file handler with rotation options.
24
-
25
- Args:
26
- filename: Log file path
27
- mode: File open mode ('a' for append, 'w' for write)
28
- encoding: File encoding
29
- level: Minimum log level to output
30
- max_bytes: Max file size before rotation (0 = no size limit)
31
- backup_count: Number of backup files to keep (0 = no backups)
32
- rotate_on_time: Enable time-based rotation
33
- time_interval: Rotation interval ('D'=daily, 'H'=hourly, 'M'=monthly)
34
- """
35
- self.filename = Path(filename)
36
- self.mode = mode
37
- self.encoding = encoding
38
- self.level = level
39
- self.max_bytes = max_bytes
40
- self.backup_count = backup_count
41
- self.rotate_on_time = rotate_on_time
42
- self.time_interval = time_interval.upper()
43
-
44
- if self.time_interval not in ["D", "H", "M"]:
45
- raise ValueError("time_interval must be 'D', 'H', or 'M'")
46
-
47
- self._file = None
48
- self._current_size = 0
49
- self._last_rollover_time = time.time()
50
-
51
- # Create directory if it doesn't exist
52
- self.filename.parent.mkdir(parents=True, exist_ok=True)
53
-
54
- # Open the file
55
- self._open()
56
-
57
- def _open(self):
58
- """Open or reopen the log file."""
59
- if self._file:
60
- self._file.close()
61
-
62
- self._file = open(
63
- self.filename,
64
- mode=self.mode,
65
- encoding=self.encoding
66
- )
67
-
68
- self._current_size = self._file.tell()
69
- if self.mode == "a":
70
- self._current_size = self.filename.stat().st_size
71
-
72
- def _should_rollover(self) -> bool:
73
- """Check if file should be rolled over based on size or time."""
74
- if self.max_bytes > 0 and self._current_size >= self.max_bytes:
75
- return True
76
-
77
- if self.rotate_on_time:
78
- current_time = time.time()
79
- if self.time_interval == "H":
80
- interval = 3600 # 1 hour
81
- elif self.time_interval == "D":
82
- interval = 86400 # 1 day
83
- else: # Monthly
84
- now = datetime.now()
85
- if now.month == datetime.fromtimestamp(self._last_rollover_time).month:
86
- return False
87
- return True
88
-
89
- if current_time - self._last_rollover_time >= interval:
90
- return True
91
-
92
- return False
93
-
94
- def _do_rollover(self):
95
- """Perform log file rotation."""
96
- if self._file:
97
- self._file.close()
98
- self._file = None
99
-
100
- if self.backup_count > 0:
101
- # Shift existing backup files
102
- for i in range(self.backup_count - 1, 0, -1):
103
- sfn = f"{self.filename}.{i}"
104
- dfn = f"{self.filename}.{i + 1}"
105
- if os.path.exists(sfn):
106
- if os.path.exists(dfn):
107
- os.remove(dfn)
108
- os.rename(sfn, dfn)
109
-
110
- dfn = f"{self.filename}.1"
111
- if os.path.exists(dfn):
112
- os.remove(dfn)
113
- os.rename(self.filename, dfn)
114
-
115
- self._open()
116
- self._last_rollover_time = time.time()
117
-
118
- def emit(self, message: str, level: LogLevel):
119
- """Write log message to file if level is sufficient."""
120
- if level.value >= self.level.value:
121
- try:
122
- if self._should_rollover():
123
- self._do_rollover()
124
-
125
- self._file.write(message + "\n")
126
- self._file.flush()
127
- self._current_size = self._file.tell()
128
-
129
- except Exception as e:
130
- # Fallback to console on error
131
- import sys
132
- sys.stderr.write(f"Error in FileHandler: {e}\n")
133
- sys.stderr.write(message + "\n")
134
-
135
- async def async_emit(self, message: str, level: LogLevel):
136
- """Asynchronously write log message to file."""
137
- self.emit(message, level)
138
-
139
- def close(self):
140
- """Close the log file."""
141
- if self._file:
142
- self._file.close()
143
- self._file = None
@@ -1,173 +0,0 @@
1
- import asyncio
2
- import json
3
- import socket
4
- from typing import Optional, Dict, Any
5
- import aiohttp
6
- from ..core.level import LogLevel
7
-
8
- class NetworkHandler:
9
- """Handler for sending log messages to a remote server."""
10
-
11
- def __init__(
12
- self,
13
- host: str,
14
- port: int,
15
- protocol: str = "http",
16
- endpoint: str = "/logs",
17
- method: str = "POST",
18
- headers: Optional[Dict[str, str]] = None,
19
- timeout: float = 5.0,
20
- level: LogLevel = LogLevel.DEBUG,
21
- batch_size: int = 0,
22
- retry_count: int = 3,
23
- retry_delay: float = 1.0,
24
- custom_fields: Optional[Dict[str, Any]] = None
25
- ):
26
- """
27
- Initialize network handler.
28
-
29
- Args:
30
- host: Remote server hostname/IP
31
- port: Remote server port
32
- protocol: 'http', 'https', or 'tcp'
33
- endpoint: Server endpoint for HTTP/HTTPS
34
- method: HTTP method to use
35
- headers: Optional HTTP headers
36
- timeout: Request timeout in seconds
37
- level: Minimum log level to send
38
- batch_size: Number of logs to batch (0 = no batching)
39
- retry_count: Number of retries on failure
40
- retry_delay: Delay between retries in seconds
41
- custom_fields: Additional fields to include in log data
42
- """
43
- self.host = host
44
- self.port = port
45
- self.protocol = protocol.lower()
46
- self.endpoint = endpoint
47
- self.method = method.upper()
48
- self.headers = headers or {}
49
- self.timeout = timeout
50
- self.level = level
51
- self.batch_size = batch_size
52
- self.retry_count = retry_count
53
- self.retry_delay = retry_delay
54
- self.custom_fields = custom_fields or {}
55
-
56
- if self.protocol not in ["http", "https", "tcp"]:
57
- raise ValueError("Protocol must be 'http', 'https' or 'tcp'")
58
-
59
- self._batch = []
60
- self._tcp_socket = None
61
- self._session = None
62
-
63
- async def _init_session(self):
64
- """Initialize HTTP session if needed."""
65
- if self.protocol in ["http", "https"] and not self._session:
66
- self._session = aiohttp.ClientSession(
67
- timeout=aiohttp.ClientTimeout(total=self.timeout)
68
- )
69
-
70
- async def _send_http(self, data: Dict[str, Any]) -> bool:
71
- """Send log data via HTTP/HTTPS."""
72
- await self._init_session()
73
-
74
- url = f"{self.protocol}://{self.host}:{self.port}{self.endpoint}"
75
-
76
- for attempt in range(self.retry_count + 1):
77
- try:
78
- async with self._session.request(
79
- method=self.method,
80
- url=url,
81
- json=data,
82
- headers=self.headers
83
- ) as response:
84
- return response.status < 400
85
-
86
- except Exception:
87
- if attempt == self.retry_count:
88
- return False
89
- await asyncio.sleep(self.retry_delay)
90
-
91
- async def _send_tcp(self, data: Dict[str, Any]) -> bool:
92
- """Send log data via TCP."""
93
- message = json.dumps(data).encode() + b"\n"
94
-
95
- for attempt in range(self.retry_count + 1):
96
- try:
97
- if not self._tcp_socket:
98
- self._tcp_socket = socket.socket(
99
- socket.AF_INET, socket.SOCK_STREAM
100
- )
101
- self._tcp_socket.settimeout(self.timeout)
102
- self._tcp_socket.connect((self.host, self.port))
103
-
104
- self._tcp_socket.sendall(message)
105
- return True
106
-
107
- except Exception:
108
- if self._tcp_socket:
109
- self._tcp_socket.close()
110
- self._tcp_socket = None
111
-
112
- if attempt == self.retry_count:
113
- return False
114
-
115
- await asyncio.sleep(self.retry_delay)
116
-
117
- def emit(self, message: str, level: LogLevel):
118
- """
119
- Synchronously send log message.
120
- Not recommended - use async_emit instead.
121
- """
122
- if level.value >= self.level.value:
123
- loop = asyncio.get_event_loop()
124
- loop.run_until_complete(self.async_emit(message, level))
125
-
126
- async def async_emit(self, message: str, level: LogLevel):
127
- """Asynchronously send log message to remote server."""
128
- # Fix: Allow all messages if level is NOTSET
129
- if self.level == LogLevel.NOTSET or level.value >= self.level.value:
130
- log_data = {
131
- "message": message,
132
- "level": level.name,
133
- **self.custom_fields
134
- }
135
-
136
- if self.batch_size > 0:
137
- self._batch.append(log_data)
138
- if len(self._batch) >= self.batch_size:
139
- await self._send_batch()
140
- else:
141
- if self.protocol in ["http", "https"]:
142
- await self._send_http(log_data)
143
- else:
144
- await self._send_tcp(log_data)
145
-
146
- async def _send_batch(self):
147
- """Send batched log messages."""
148
- if not self._batch:
149
- return
150
-
151
- batch_data = {"logs": self._batch}
152
- success = False
153
-
154
- if self.protocol in ["http", "https"]:
155
- success = await self._send_http(batch_data)
156
- else:
157
- success = await self._send_tcp(batch_data)
158
-
159
- if success:
160
- self._batch.clear()
161
-
162
- async def close(self):
163
- """Close network connections."""
164
- if self._batch:
165
- await self._send_batch()
166
-
167
- if self._tcp_socket:
168
- self._tcp_socket.close()
169
- self._tcp_socket = None
170
-
171
- if self._session:
172
- await self._session.close()
173
- self._session = None
@@ -1,7 +0,0 @@
1
- """Styling and formatting utilities."""
2
-
3
- from .colors import LogColors
4
- from .formats import LogFormat
5
- from .text import TextStyle
6
-
7
- __all__ = ["LogColors", "LogFormat", "TextStyle"]