webscout 7.1__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 (144) 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 +3 -3
  38. webscout/Provider/ChatGPTGratis.py +226 -0
  39. webscout/Provider/Cloudflare.py +3 -4
  40. webscout/Provider/DeepSeek.py +218 -0
  41. webscout/Provider/Deepinfra.py +3 -3
  42. webscout/Provider/Free2GPT.py +131 -124
  43. webscout/Provider/Gemini.py +100 -115
  44. webscout/Provider/Glider.py +3 -3
  45. webscout/Provider/Groq.py +5 -1
  46. webscout/Provider/Jadve.py +3 -3
  47. webscout/Provider/Marcus.py +191 -192
  48. webscout/Provider/Netwrck.py +3 -3
  49. webscout/Provider/PI.py +2 -2
  50. webscout/Provider/PizzaGPT.py +2 -3
  51. webscout/Provider/QwenLM.py +311 -0
  52. webscout/Provider/TTI/AiForce/__init__.py +22 -22
  53. webscout/Provider/TTI/AiForce/async_aiforce.py +257 -257
  54. webscout/Provider/TTI/AiForce/sync_aiforce.py +242 -242
  55. webscout/Provider/TTI/Nexra/__init__.py +22 -22
  56. webscout/Provider/TTI/Nexra/async_nexra.py +286 -286
  57. webscout/Provider/TTI/Nexra/sync_nexra.py +258 -258
  58. webscout/Provider/TTI/PollinationsAI/__init__.py +23 -23
  59. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +330 -330
  60. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +285 -285
  61. webscout/Provider/TTI/artbit/__init__.py +22 -22
  62. webscout/Provider/TTI/artbit/async_artbit.py +184 -184
  63. webscout/Provider/TTI/artbit/sync_artbit.py +176 -176
  64. webscout/Provider/TTI/blackbox/__init__.py +4 -4
  65. webscout/Provider/TTI/blackbox/async_blackbox.py +212 -212
  66. webscout/Provider/TTI/blackbox/sync_blackbox.py +199 -199
  67. webscout/Provider/TTI/deepinfra/__init__.py +4 -4
  68. webscout/Provider/TTI/deepinfra/async_deepinfra.py +227 -227
  69. webscout/Provider/TTI/deepinfra/sync_deepinfra.py +199 -199
  70. webscout/Provider/TTI/huggingface/__init__.py +22 -22
  71. webscout/Provider/TTI/huggingface/async_huggingface.py +199 -199
  72. webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -195
  73. webscout/Provider/TTI/imgninza/__init__.py +4 -4
  74. webscout/Provider/TTI/imgninza/async_ninza.py +214 -214
  75. webscout/Provider/TTI/imgninza/sync_ninza.py +209 -209
  76. webscout/Provider/TTI/talkai/__init__.py +4 -4
  77. webscout/Provider/TTI/talkai/async_talkai.py +229 -229
  78. webscout/Provider/TTI/talkai/sync_talkai.py +207 -207
  79. webscout/Provider/TTS/deepgram.py +182 -182
  80. webscout/Provider/TTS/elevenlabs.py +136 -136
  81. webscout/Provider/TTS/gesserit.py +150 -150
  82. webscout/Provider/TTS/murfai.py +138 -138
  83. webscout/Provider/TTS/parler.py +133 -134
  84. webscout/Provider/TTS/streamElements.py +360 -360
  85. webscout/Provider/TTS/utils.py +280 -280
  86. webscout/Provider/TTS/voicepod.py +116 -116
  87. webscout/Provider/TextPollinationsAI.py +2 -3
  88. webscout/Provider/WiseCat.py +193 -0
  89. webscout/Provider/__init__.py +144 -134
  90. webscout/Provider/cerebras.py +242 -227
  91. webscout/Provider/chatglm.py +204 -204
  92. webscout/Provider/dgaf.py +2 -3
  93. webscout/Provider/gaurish.py +2 -3
  94. webscout/Provider/geminiapi.py +208 -208
  95. webscout/Provider/granite.py +223 -0
  96. webscout/Provider/hermes.py +218 -218
  97. webscout/Provider/llama3mitril.py +179 -179
  98. webscout/Provider/llamatutor.py +3 -3
  99. webscout/Provider/llmchat.py +2 -3
  100. webscout/Provider/meta.py +794 -794
  101. webscout/Provider/multichat.py +331 -331
  102. webscout/Provider/typegpt.py +359 -359
  103. webscout/Provider/yep.py +2 -2
  104. webscout/__main__.py +5 -5
  105. webscout/cli.py +319 -319
  106. webscout/conversation.py +241 -242
  107. webscout/exceptions.py +328 -328
  108. webscout/litagent/__init__.py +28 -28
  109. webscout/litagent/agent.py +2 -3
  110. webscout/litprinter/__init__.py +0 -58
  111. webscout/scout/__init__.py +8 -8
  112. webscout/scout/core.py +884 -884
  113. webscout/scout/element.py +459 -459
  114. webscout/scout/parsers/__init__.py +69 -69
  115. webscout/scout/parsers/html5lib_parser.py +172 -172
  116. webscout/scout/parsers/html_parser.py +236 -236
  117. webscout/scout/parsers/lxml_parser.py +178 -178
  118. webscout/scout/utils.py +38 -38
  119. webscout/swiftcli/__init__.py +811 -811
  120. webscout/update_checker.py +2 -12
  121. webscout/version.py +1 -1
  122. webscout/webscout_search.py +5 -4
  123. webscout/zeroart/__init__.py +54 -54
  124. webscout/zeroart/base.py +60 -60
  125. webscout/zeroart/effects.py +99 -99
  126. webscout/zeroart/fonts.py +816 -816
  127. {webscout-7.1.dist-info → webscout-7.2.dist-info}/METADATA +4 -3
  128. webscout-7.2.dist-info/RECORD +217 -0
  129. webstoken/__init__.py +30 -30
  130. webstoken/classifier.py +189 -189
  131. webstoken/keywords.py +216 -216
  132. webstoken/language.py +128 -128
  133. webstoken/ner.py +164 -164
  134. webstoken/normalizer.py +35 -35
  135. webstoken/processor.py +77 -77
  136. webstoken/sentiment.py +206 -206
  137. webstoken/stemmer.py +73 -73
  138. webstoken/tagger.py +60 -60
  139. webstoken/tokenizer.py +158 -158
  140. webscout-7.1.dist-info/RECORD +0 -198
  141. {webscout-7.1.dist-info → webscout-7.2.dist-info}/LICENSE.md +0 -0
  142. {webscout-7.1.dist-info → webscout-7.2.dist-info}/WHEEL +0 -0
  143. {webscout-7.1.dist-info → webscout-7.2.dist-info}/entry_points.txt +0 -0
  144. {webscout-7.1.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"]