webscout 7.0__py3-none-any.whl → 7.2__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 webscout might be problematic. Click here for more details.

Files changed (147) hide show
  1. webscout/AIauto.py +191 -191
  2. webscout/AIbase.py +122 -122
  3. webscout/AIutel.py +440 -440
  4. webscout/Bard.py +343 -161
  5. webscout/DWEBS.py +489 -492
  6. webscout/Extra/YTToolkit/YTdownloader.py +995 -995
  7. webscout/Extra/YTToolkit/__init__.py +2 -2
  8. webscout/Extra/YTToolkit/transcriber.py +476 -479
  9. webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
  10. webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
  11. webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
  12. webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
  13. webscout/Extra/YTToolkit/ytapi/video.py +103 -103
  14. webscout/Extra/autocoder/__init__.py +9 -9
  15. webscout/Extra/autocoder/autocoder_utiles.py +199 -199
  16. webscout/Extra/autocoder/rawdog.py +5 -7
  17. webscout/Extra/autollama.py +230 -230
  18. webscout/Extra/gguf.py +3 -3
  19. webscout/Extra/weather.py +171 -171
  20. webscout/LLM.py +442 -442
  21. webscout/Litlogger/__init__.py +67 -681
  22. webscout/Litlogger/core/__init__.py +6 -0
  23. webscout/Litlogger/core/level.py +20 -0
  24. webscout/Litlogger/core/logger.py +123 -0
  25. webscout/Litlogger/handlers/__init__.py +12 -0
  26. webscout/Litlogger/handlers/console.py +50 -0
  27. webscout/Litlogger/handlers/file.py +143 -0
  28. webscout/Litlogger/handlers/network.py +174 -0
  29. webscout/Litlogger/styles/__init__.py +7 -0
  30. webscout/Litlogger/styles/colors.py +231 -0
  31. webscout/Litlogger/styles/formats.py +377 -0
  32. webscout/Litlogger/styles/text.py +87 -0
  33. webscout/Litlogger/utils/__init__.py +6 -0
  34. webscout/Litlogger/utils/detectors.py +154 -0
  35. webscout/Litlogger/utils/formatters.py +200 -0
  36. webscout/Provider/AISEARCH/DeepFind.py +250 -250
  37. webscout/Provider/Blackboxai.py +136 -137
  38. webscout/Provider/ChatGPTGratis.py +226 -0
  39. webscout/Provider/Cloudflare.py +91 -78
  40. webscout/Provider/DeepSeek.py +218 -0
  41. webscout/Provider/Deepinfra.py +59 -35
  42. webscout/Provider/Free2GPT.py +131 -124
  43. webscout/Provider/Gemini.py +100 -115
  44. webscout/Provider/Glider.py +74 -59
  45. webscout/Provider/Groq.py +30 -18
  46. webscout/Provider/Jadve.py +108 -77
  47. webscout/Provider/Llama3.py +117 -94
  48. webscout/Provider/Marcus.py +191 -137
  49. webscout/Provider/Netwrck.py +62 -50
  50. webscout/Provider/PI.py +79 -124
  51. webscout/Provider/PizzaGPT.py +129 -83
  52. webscout/Provider/QwenLM.py +311 -0
  53. webscout/Provider/TTI/AiForce/__init__.py +22 -22
  54. webscout/Provider/TTI/AiForce/async_aiforce.py +257 -257
  55. webscout/Provider/TTI/AiForce/sync_aiforce.py +242 -242
  56. webscout/Provider/TTI/Nexra/__init__.py +22 -22
  57. webscout/Provider/TTI/Nexra/async_nexra.py +286 -286
  58. webscout/Provider/TTI/Nexra/sync_nexra.py +258 -258
  59. webscout/Provider/TTI/PollinationsAI/__init__.py +23 -23
  60. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +330 -330
  61. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +285 -285
  62. webscout/Provider/TTI/artbit/__init__.py +22 -22
  63. webscout/Provider/TTI/artbit/async_artbit.py +184 -184
  64. webscout/Provider/TTI/artbit/sync_artbit.py +176 -176
  65. webscout/Provider/TTI/blackbox/__init__.py +4 -4
  66. webscout/Provider/TTI/blackbox/async_blackbox.py +212 -212
  67. webscout/Provider/TTI/blackbox/sync_blackbox.py +199 -199
  68. webscout/Provider/TTI/deepinfra/__init__.py +4 -4
  69. webscout/Provider/TTI/deepinfra/async_deepinfra.py +227 -227
  70. webscout/Provider/TTI/deepinfra/sync_deepinfra.py +199 -199
  71. webscout/Provider/TTI/huggingface/__init__.py +22 -22
  72. webscout/Provider/TTI/huggingface/async_huggingface.py +199 -199
  73. webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -195
  74. webscout/Provider/TTI/imgninza/__init__.py +4 -4
  75. webscout/Provider/TTI/imgninza/async_ninza.py +214 -214
  76. webscout/Provider/TTI/imgninza/sync_ninza.py +209 -209
  77. webscout/Provider/TTI/talkai/__init__.py +4 -4
  78. webscout/Provider/TTI/talkai/async_talkai.py +229 -229
  79. webscout/Provider/TTI/talkai/sync_talkai.py +207 -207
  80. webscout/Provider/TTS/deepgram.py +182 -182
  81. webscout/Provider/TTS/elevenlabs.py +136 -136
  82. webscout/Provider/TTS/gesserit.py +150 -150
  83. webscout/Provider/TTS/murfai.py +138 -138
  84. webscout/Provider/TTS/parler.py +133 -134
  85. webscout/Provider/TTS/streamElements.py +360 -360
  86. webscout/Provider/TTS/utils.py +280 -280
  87. webscout/Provider/TTS/voicepod.py +116 -116
  88. webscout/Provider/TextPollinationsAI.py +74 -47
  89. webscout/Provider/WiseCat.py +193 -0
  90. webscout/Provider/__init__.py +144 -136
  91. webscout/Provider/cerebras.py +242 -227
  92. webscout/Provider/chatglm.py +204 -204
  93. webscout/Provider/dgaf.py +67 -39
  94. webscout/Provider/gaurish.py +105 -66
  95. webscout/Provider/geminiapi.py +208 -208
  96. webscout/Provider/granite.py +223 -0
  97. webscout/Provider/hermes.py +218 -218
  98. webscout/Provider/llama3mitril.py +179 -179
  99. webscout/Provider/llamatutor.py +72 -62
  100. webscout/Provider/llmchat.py +60 -35
  101. webscout/Provider/meta.py +794 -794
  102. webscout/Provider/multichat.py +331 -230
  103. webscout/Provider/typegpt.py +359 -356
  104. webscout/Provider/yep.py +5 -5
  105. webscout/__main__.py +5 -5
  106. webscout/cli.py +319 -319
  107. webscout/conversation.py +241 -242
  108. webscout/exceptions.py +328 -328
  109. webscout/litagent/__init__.py +28 -28
  110. webscout/litagent/agent.py +2 -3
  111. webscout/litprinter/__init__.py +0 -58
  112. webscout/scout/__init__.py +8 -8
  113. webscout/scout/core.py +884 -884
  114. webscout/scout/element.py +459 -459
  115. webscout/scout/parsers/__init__.py +69 -69
  116. webscout/scout/parsers/html5lib_parser.py +172 -172
  117. webscout/scout/parsers/html_parser.py +236 -236
  118. webscout/scout/parsers/lxml_parser.py +178 -178
  119. webscout/scout/utils.py +38 -38
  120. webscout/swiftcli/__init__.py +811 -811
  121. webscout/update_checker.py +2 -12
  122. webscout/version.py +1 -1
  123. webscout/webscout_search.py +1142 -1140
  124. webscout/webscout_search_async.py +635 -635
  125. webscout/zeroart/__init__.py +54 -54
  126. webscout/zeroart/base.py +60 -60
  127. webscout/zeroart/effects.py +99 -99
  128. webscout/zeroart/fonts.py +816 -816
  129. {webscout-7.0.dist-info → webscout-7.2.dist-info}/METADATA +21 -28
  130. webscout-7.2.dist-info/RECORD +217 -0
  131. webstoken/__init__.py +30 -30
  132. webstoken/classifier.py +189 -189
  133. webstoken/keywords.py +216 -216
  134. webstoken/language.py +128 -128
  135. webstoken/ner.py +164 -164
  136. webstoken/normalizer.py +35 -35
  137. webstoken/processor.py +77 -77
  138. webstoken/sentiment.py +206 -206
  139. webstoken/stemmer.py +73 -73
  140. webstoken/tagger.py +60 -60
  141. webstoken/tokenizer.py +158 -158
  142. webscout/Provider/RUBIKSAI.py +0 -272
  143. webscout-7.0.dist-info/RECORD +0 -199
  144. {webscout-7.0.dist-info → webscout-7.2.dist-info}/LICENSE.md +0 -0
  145. {webscout-7.0.dist-info → webscout-7.2.dist-info}/WHEEL +0 -0
  146. {webscout-7.0.dist-info → webscout-7.2.dist-info}/entry_points.txt +0 -0
  147. {webscout-7.0.dist-info → webscout-7.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,6 @@
1
+ """Core logging functionality."""
2
+
3
+ from .logger import Logger
4
+ from .level import LogLevel
5
+
6
+ __all__ = ["Logger", "LogLevel"]
@@ -0,0 +1,20 @@
1
+ from enum import Enum
2
+
3
+ class LogLevel(Enum):
4
+ DEBUG = 10
5
+ INFO = 20
6
+ WARNING = 30
7
+ ERROR = 40
8
+ CRITICAL = 50
9
+
10
+ @staticmethod
11
+ def get_level(level_str: str) -> 'LogLevel':
12
+ try:
13
+ return LogLevel[level_str.upper()]
14
+ except KeyError:
15
+ raise ValueError(f"Invalid log level: {level_str}")
16
+
17
+ def __lt__(self, other):
18
+ if isinstance(other, LogLevel):
19
+ return self.value < other.value
20
+ return NotImplemented
@@ -0,0 +1,123 @@
1
+
2
+ import asyncio
3
+ import json
4
+ from datetime import datetime
5
+ from typing import Any, Dict, List, Optional, Union
6
+
7
+ from ..styles.colors import LogColors
8
+ from ..styles.formats import LogFormat
9
+ from .level import LogLevel
10
+
11
+ class Logger:
12
+ # Emoji mappings for different log levels
13
+ LEVEL_EMOJIS = {
14
+ LogLevel.DEBUG: "🔍",
15
+ LogLevel.INFO: "ℹ️",
16
+ LogLevel.WARNING: "⚠️",
17
+ LogLevel.ERROR: "❌",
18
+ LogLevel.CRITICAL: "🔥"
19
+ }
20
+
21
+ def __init__(
22
+ self,
23
+ name: str = "LitLogger",
24
+ level: Union[str, LogLevel] = LogLevel.INFO,
25
+ format: str = LogFormat.MODERN,
26
+ handlers: List = None,
27
+ enable_colors: bool = True,
28
+ async_mode: bool = False
29
+ ):
30
+ self.name = name
31
+ self.level = LogLevel.get_level(level) if isinstance(level, str) else level
32
+ self.format = format
33
+ self.handlers = handlers or []
34
+ self.enable_colors = enable_colors
35
+ self.async_mode = async_mode
36
+ self._context_data = {}
37
+ self._metrics = {}
38
+
39
+ def _format_message(self, level: LogLevel, message: str, **kwargs) -> str:
40
+ now = datetime.now()
41
+ log_data = {
42
+ "timestamp": now.strftime("%Y-%m-%d %H:%M:%S"),
43
+ "time": now.strftime("%H:%M:%S"), # Add time field
44
+ "date": now.strftime("%Y-%m-%d"), # Add date field
45
+ "level": level.name,
46
+ "name": self.name,
47
+ "message": message,
48
+ "emoji": self.LEVEL_EMOJIS.get(level, ""),
49
+ **self._context_data,
50
+ **kwargs
51
+ }
52
+
53
+ if self.format == LogFormat.JSON:
54
+ return json.dumps(log_data)
55
+
56
+ try:
57
+ formatted = self.format.format(**log_data)
58
+ except KeyError as e:
59
+ # Fallback to a basic format if the specified format fails
60
+ basic_format = "[{time}] {level}: {message}"
61
+ formatted = basic_format.format(**log_data)
62
+
63
+ if self.enable_colors:
64
+ color = LogColors.LEVEL_COLORS.get(level, LogColors.RESET)
65
+ return f"{color}{formatted}{LogColors.RESET}"
66
+ return formatted
67
+
68
+ async def _async_log(self, level: LogLevel, message: str, **kwargs):
69
+ if level.value < self.level.value:
70
+ return
71
+
72
+ formatted_message = self._format_message(level, message, **kwargs)
73
+ tasks = []
74
+ for handler in self.handlers:
75
+ if hasattr(handler, 'async_emit'):
76
+ tasks.append(handler.async_emit(formatted_message, level))
77
+ else:
78
+ tasks.append(asyncio.to_thread(handler.emit, formatted_message, level))
79
+
80
+ await asyncio.gather(*tasks)
81
+
82
+ def _sync_log(self, level: LogLevel, message: str, **kwargs):
83
+ if level.value < self.level.value:
84
+ return
85
+
86
+ formatted_message = self._format_message(level, message, **kwargs)
87
+ for handler in self.handlers:
88
+ handler.emit(formatted_message, level)
89
+
90
+ def log(self, level: LogLevel, message: str, **kwargs):
91
+ if self.async_mode:
92
+ return asyncio.create_task(self._async_log(level, message, **kwargs))
93
+ return self._sync_log(level, message, **kwargs)
94
+
95
+ def debug(self, message: str, **kwargs):
96
+ self.log(LogLevel.DEBUG, message, **kwargs)
97
+
98
+ def info(self, message: str, **kwargs):
99
+ self.log(LogLevel.INFO, message, **kwargs)
100
+
101
+ def warning(self, message: str, **kwargs):
102
+ self.log(LogLevel.WARNING, message, **kwargs)
103
+
104
+ def error(self, message: str, **kwargs):
105
+ self.log(LogLevel.ERROR, message, **kwargs)
106
+
107
+ def critical(self, message: str, **kwargs):
108
+ self.log(LogLevel.CRITICAL, message, **kwargs)
109
+
110
+ def set_context(self, **kwargs):
111
+ self._context_data.update(kwargs)
112
+
113
+ def clear_context(self):
114
+ self._context_data.clear()
115
+
116
+ def __enter__(self):
117
+ return self
118
+
119
+ def __exit__(self, exc_type, exc_val, exc_tb):
120
+ if exc_type is not None:
121
+ self.error(f"Context exited with error: {exc_val}")
122
+ return False
123
+ return True
@@ -0,0 +1,12 @@
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
+ ]
@@ -0,0 +1,50 @@
1
+ import sys
2
+ from typing import Optional, TextIO
3
+ from ..core.level import LogLevel
4
+
5
+ class ConsoleHandler:
6
+ """Handler for outputting log messages to the console."""
7
+
8
+ def __init__(self,
9
+ stream: Optional[TextIO] = None,
10
+ level: LogLevel = LogLevel.DEBUG):
11
+ """
12
+ Initialize console handler.
13
+
14
+ Args:
15
+ stream: Output stream (defaults to sys.stdout)
16
+ level: Minimum log level to output
17
+ """
18
+ self.stream = stream or sys.stdout
19
+ self.level = level
20
+
21
+ def emit(self, message: str, level: LogLevel):
22
+ """
23
+ Write log message to console if level is sufficient.
24
+
25
+ Args:
26
+ message: Formatted log message
27
+ level: Message log level
28
+ """
29
+ if level.value >= self.level.value:
30
+ try:
31
+ self.stream.write(message + "\n")
32
+ self.stream.flush()
33
+ except Exception as e:
34
+ # Fallback to stderr on error
35
+ sys.stderr.write(f"Error in ConsoleHandler: {e}\n")
36
+ sys.stderr.write(message + "\n")
37
+ sys.stderr.flush()
38
+
39
+ async def async_emit(self, message: str, level: LogLevel):
40
+ """
41
+ Asynchronously write log message to console.
42
+ Just calls emit() since console output is generally fast enough.
43
+ """
44
+ self.emit(message, level)
45
+
46
+ class ErrorConsoleHandler(ConsoleHandler):
47
+ """Specialized handler that writes to stderr."""
48
+
49
+ def __init__(self, level: LogLevel = LogLevel.ERROR):
50
+ super().__init__(stream=sys.stderr, level=level)
@@ -0,0 +1,143 @@
1
+ import os
2
+ import time
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from typing import Optional, 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
@@ -0,0 +1,174 @@
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
+ if level.value < self.level.value:
129
+ return
130
+
131
+ log_data = {
132
+ "message": message,
133
+ "level": level.name,
134
+ **self.custom_fields
135
+ }
136
+
137
+ if self.batch_size > 0:
138
+ self._batch.append(log_data)
139
+ if len(self._batch) >= self.batch_size:
140
+ await self._send_batch()
141
+ else:
142
+ if self.protocol in ["http", "https"]:
143
+ await self._send_http(log_data)
144
+ else:
145
+ await self._send_tcp(log_data)
146
+
147
+ async def _send_batch(self):
148
+ """Send batched log messages."""
149
+ if not self._batch:
150
+ return
151
+
152
+ batch_data = {"logs": self._batch}
153
+ success = False
154
+
155
+ if self.protocol in ["http", "https"]:
156
+ success = await self._send_http(batch_data)
157
+ else:
158
+ success = await self._send_tcp(batch_data)
159
+
160
+ if success:
161
+ self._batch.clear()
162
+
163
+ async def close(self):
164
+ """Close network connections."""
165
+ if self._batch:
166
+ await self._send_batch()
167
+
168
+ if self._tcp_socket:
169
+ self._tcp_socket.close()
170
+ self._tcp_socket = None
171
+
172
+ if self._session:
173
+ await self._session.close()
174
+ self._session = None
@@ -0,0 +1,7 @@
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"]