webscout 2025.10.15__py3-none-any.whl → 2025.10.16__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 (60) hide show
  1. webscout/Extra/YTToolkit/README.md +1 -1
  2. webscout/Extra/tempmail/README.md +3 -3
  3. webscout/Provider/OPENAI/README.md +1 -1
  4. webscout/Provider/TTI/bing.py +4 -4
  5. webscout/__init__.py +1 -1
  6. webscout/client.py +4 -5
  7. webscout/litprinter/__init__.py +0 -42
  8. webscout/scout/README.md +59 -8
  9. webscout/scout/core/scout.py +62 -0
  10. webscout/scout/element.py +251 -45
  11. webscout/search/__init__.py +3 -4
  12. webscout/search/engines/bing/images.py +5 -2
  13. webscout/search/engines/bing/news.py +6 -4
  14. webscout/search/engines/bing/text.py +5 -2
  15. webscout/search/engines/yahoo/__init__.py +41 -0
  16. webscout/search/engines/yahoo/answers.py +16 -0
  17. webscout/search/engines/yahoo/base.py +34 -0
  18. webscout/search/engines/yahoo/images.py +324 -0
  19. webscout/search/engines/yahoo/maps.py +16 -0
  20. webscout/search/engines/yahoo/news.py +258 -0
  21. webscout/search/engines/yahoo/suggestions.py +140 -0
  22. webscout/search/engines/yahoo/text.py +273 -0
  23. webscout/search/engines/yahoo/translate.py +16 -0
  24. webscout/search/engines/yahoo/videos.py +302 -0
  25. webscout/search/engines/yahoo/weather.py +220 -0
  26. webscout/search/http_client.py +1 -1
  27. webscout/search/yahoo_main.py +54 -0
  28. webscout/{auth → server}/__init__.py +2 -23
  29. webscout/server/config.py +84 -0
  30. webscout/{auth → server}/request_processing.py +3 -28
  31. webscout/{auth → server}/routes.py +6 -148
  32. webscout/server/schemas.py +23 -0
  33. webscout/{auth → server}/server.py +11 -43
  34. webscout/server/simple_logger.py +84 -0
  35. webscout/version.py +1 -1
  36. webscout/version.py.bak +1 -1
  37. webscout/zeroart/README.md +17 -9
  38. webscout/zeroart/__init__.py +78 -6
  39. webscout/zeroart/effects.py +51 -1
  40. webscout/zeroart/fonts.py +559 -1
  41. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/METADATA +10 -52
  42. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/RECORD +49 -45
  43. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/entry_points.txt +1 -1
  44. webscout/auth/api_key_manager.py +0 -189
  45. webscout/auth/auth_system.py +0 -85
  46. webscout/auth/config.py +0 -175
  47. webscout/auth/database.py +0 -755
  48. webscout/auth/middleware.py +0 -248
  49. webscout/auth/models.py +0 -185
  50. webscout/auth/rate_limiter.py +0 -254
  51. webscout/auth/schemas.py +0 -103
  52. webscout/auth/simple_logger.py +0 -236
  53. webscout/search/engines/yahoo.py +0 -65
  54. webscout/search/engines/yahoo_news.py +0 -64
  55. /webscout/{auth → server}/exceptions.py +0 -0
  56. /webscout/{auth → server}/providers.py +0 -0
  57. /webscout/{auth → server}/request_models.py +0 -0
  58. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/WHEEL +0 -0
  59. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/licenses/LICENSE.md +0 -0
  60. {webscout-2025.10.15.dist-info → webscout-2025.10.16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
1
+ """
2
+ Configuration management for the Webscout API server.
3
+ """
4
+
5
+ import os
6
+ from typing import List, Dict, Optional, Any
7
+ from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
8
+ import sys
9
+
10
+ # Configuration constants
11
+ DEFAULT_PORT = 8000
12
+ DEFAULT_HOST = "0.0.0.0"
13
+
14
+ # Setup logger
15
+ logger = Logger(
16
+ name="webscout.api",
17
+ level=LogLevel.INFO,
18
+ handlers=[ConsoleHandler(stream=sys.stdout)],
19
+ fmt=LogFormat.DEFAULT
20
+ )
21
+
22
+
23
+ class ServerConfig:
24
+ """Centralized configuration management for the API server."""
25
+
26
+ def __init__(self):
27
+ self.provider_map: Dict[str, Any] = {}
28
+ self.default_provider: str = "ChatGPT"
29
+ self.base_url: Optional[str] = None
30
+ self.host: str = DEFAULT_HOST
31
+ self.port: int = DEFAULT_PORT
32
+ self.debug: bool = False
33
+ self.cors_origins: List[str] = ["*"]
34
+ self.max_request_size: int = 10 * 1024 * 1024 # 10MB
35
+ self.request_timeout: int = 300 # 5 minutes
36
+ self.auth_required: bool = False
37
+ self.rate_limit_enabled: bool = False
38
+ self.request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
39
+
40
+ def update(self, **kwargs) -> None:
41
+ """Update configuration with provided values."""
42
+ for key, value in kwargs.items():
43
+ if hasattr(self, key) and value is not None:
44
+ setattr(self, key, value)
45
+ logger.info(f"Config updated: {key} = {value}")
46
+
47
+ def validate(self) -> None:
48
+ """Validate configuration settings."""
49
+ if self.port < 1 or self.port > 65535:
50
+ raise ValueError(f"Invalid port number: {self.port}")
51
+
52
+ if self.default_provider not in self.provider_map and self.provider_map:
53
+ available_providers = list(set(v.__name__ for v in self.provider_map.values()))
54
+ logger.warning(f"Default provider '{self.default_provider}' not found. Available: {available_providers}")
55
+
56
+
57
+ class AppConfig:
58
+ """Legacy configuration class for backward compatibility."""
59
+ provider_map = {}
60
+ tti_provider_map = {} # Add TTI provider map
61
+ default_provider = "ChatGPT"
62
+ default_tti_provider = "PollinationsAI" # Add default TTI provider
63
+ base_url: Optional[str] = None
64
+ auth_required: bool = False
65
+ rate_limit_enabled: bool = False
66
+ request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
67
+
68
+ @classmethod
69
+ def set_config(cls, **data):
70
+ """Set configuration values."""
71
+ # Filter out auth-related keys
72
+ auth_keys = {'api_key'}
73
+ filtered_data = {k: v for k, v in data.items() if k not in auth_keys}
74
+
75
+ for key, value in filtered_data.items():
76
+ setattr(cls, key, value)
77
+ # Sync with new config system
78
+ try:
79
+ from .server import get_config
80
+ config = get_config()
81
+ config.update(**filtered_data)
82
+ except ImportError:
83
+ # Handle case where server module is not available
84
+ pass
@@ -16,8 +16,7 @@ import sys
16
16
 
17
17
  from .request_models import Message, ChatCompletionRequest
18
18
  from .exceptions import APIError, clean_text
19
- from .models import RequestLog
20
- from .auth_system import get_auth_components
19
+
21
20
  from .simple_logger import log_api_request, get_client_ip, generate_request_id
22
21
  from .config import AppConfig
23
22
 
@@ -33,9 +32,9 @@ logger = Logger(
33
32
  async def log_request(request_id: str, ip_address: str, model_used: str, question: str,
34
33
  answer: str, response_time_ms: int, status_code: int = 200,
35
34
  error_message: str = None, provider: str = None, request_obj=None):
36
- """Log API request to database."""
35
+ """Log API request."""
37
36
  try:
38
- # Use simple logger for no-auth mode if request logging is enabled
37
+ # Use simple logger if request logging is enabled
39
38
  if AppConfig.request_logging_enabled:
40
39
  user_agent = None
41
40
  if request_obj:
@@ -52,30 +51,6 @@ async def log_request(request_id: str, ip_address: str, model_used: str, questio
52
51
  error=error_message,
53
52
  user_agent=user_agent
54
53
  )
55
-
56
- # Also use the existing auth system logging if available
57
- auth_manager, db_manager, _ = get_auth_components()
58
-
59
- if db_manager:
60
- request_log = RequestLog(
61
- id=None, # Will be auto-generated
62
- request_id=request_id,
63
- ip_address=ip_address,
64
- model_used=model_used,
65
- question=question,
66
- answer=answer,
67
- user_id=None, # No auth mode
68
- api_key_id=None, # No auth mode
69
- created_at=datetime.now(timezone.utc),
70
- response_time_ms=response_time_ms,
71
- status_code=status_code,
72
- error_message=error_message,
73
- metadata={}
74
- )
75
-
76
- await db_manager.create_request_log(request_log)
77
- logger.debug(f"Logged request {request_id} to auth database")
78
-
79
54
  except Exception as e:
80
55
  logger.error(f"Failed to log request {request_id}: {e}")
81
56
  # Don't raise exception to avoid breaking the main request flow
@@ -12,7 +12,6 @@ from typing import Any
12
12
  from fastapi import FastAPI, Request, Body, Query
13
13
  from fastapi.responses import JSONResponse
14
14
  from fastapi.exceptions import RequestValidationError
15
- from fastapi.security import APIKeyHeader
16
15
  from starlette.exceptions import HTTPException as StarletteHTTPException
17
16
  from starlette.status import (
18
17
  HTTP_422_UNPROCESSABLE_ENTITY,
@@ -28,7 +27,6 @@ from .request_models import (
28
27
  ErrorResponse
29
28
  )
30
29
  from .schemas import (
31
- APIKeyCreateRequest, APIKeyCreateResponse, APIKeyValidationResponse,
32
30
  HealthCheckResponse
33
31
  )
34
32
  from .exceptions import APIError
@@ -40,10 +38,9 @@ from .request_processing import (
40
38
  process_messages, prepare_provider_params,
41
39
  handle_streaming_response, handle_non_streaming_response
42
40
  )
43
- from .auth_system import get_auth_components
44
41
  from .simple_logger import request_logger
45
42
  from ..search import DuckDuckGoSearch, YepSearch
46
- from webscout.Bing_search import BingSearch
43
+ from ..search import BingSearch
47
44
 
48
45
  # Setup logger
49
46
  logger = Logger(
@@ -59,29 +56,6 @@ class Api:
59
56
 
60
57
  def __init__(self, app: FastAPI) -> None:
61
58
  self.app = app
62
- self.get_api_key = APIKeyHeader(name="authorization", auto_error=False)
63
-
64
- def register_authorization(self):
65
- """Register legacy authorization middleware."""
66
- @self.app.middleware("http")
67
- async def authorization(request: Request, call_next):
68
- if AppConfig.api_key is not None:
69
- auth_header = await self.get_api_key(request)
70
- path = request.url.path
71
- if path.startswith("/v1"): # Only protect /v1 routes
72
- if auth_header is None:
73
- return JSONResponse(
74
- status_code=HTTP_401_UNAUTHORIZED,
75
- content={"error": {"message": "API key required", "type": "authentication_error"}}
76
- )
77
- if auth_header.startswith("Bearer "):
78
- auth_header = auth_header[7:]
79
- if not secrets.compare_digest(AppConfig.api_key, auth_header):
80
- return JSONResponse(
81
- status_code=HTTP_403_FORBIDDEN,
82
- content={"error": {"message": "Invalid API key", "type": "authentication_error"}}
83
- )
84
- return await call_next(request)
85
59
 
86
60
  def register_validation_exception_handler(self):
87
61
  """Register comprehensive exception handlers."""
@@ -162,7 +136,6 @@ class Api:
162
136
  """Register all API routes."""
163
137
  self._register_model_routes()
164
138
  self._register_chat_routes()
165
- self._register_auth_routes()
166
139
  self._register_websearch_routes()
167
140
  self._register_monitoring_routes()
168
141
 
@@ -407,105 +380,7 @@ class Api:
407
380
  "internal_error"
408
381
  )
409
382
 
410
- def _register_auth_routes(self):
411
- """Register authentication routes."""
412
- # Only register auth endpoints if authentication is required
413
- if not AppConfig.auth_required:
414
- logger.info("Auth endpoints are disabled (no-auth mode)")
415
- return
416
- auth_components = get_auth_components()
417
- api_key_manager = auth_components.get("api_key_manager")
418
383
 
419
- @self.app.post(
420
- "/v1/auth/generate-key",
421
- response_model=APIKeyCreateResponse,
422
- tags=["Authentication"],
423
- description="Generate a new API key for a user."
424
- )
425
- async def generate_api_key(request: APIKeyCreateRequest = Body(...)):
426
- """Generate a new API key."""
427
- if not api_key_manager:
428
- raise APIError("Authentication system not initialized", HTTP_500_INTERNAL_SERVER_ERROR)
429
-
430
- try:
431
- api_key, user = await api_key_manager.create_api_key(
432
- username=request.username,
433
- telegram_id=request.telegram_id,
434
- name=request.name,
435
- rate_limit=request.rate_limit or 10,
436
- expires_in_days=request.expires_in_days
437
- )
438
-
439
- return APIKeyCreateResponse(
440
- api_key=api_key.key,
441
- key_id=api_key.id,
442
- user_id=user.id,
443
- name=api_key.name,
444
- created_at=api_key.created_at,
445
- expires_at=api_key.expires_at,
446
- rate_limit=api_key.rate_limit
447
- )
448
- except Exception as e:
449
- logger.error(f"Error generating API key: {e}")
450
- raise APIError(f"Failed to generate API key: {str(e)}", HTTP_500_INTERNAL_SERVER_ERROR)
451
-
452
- @self.app.get(
453
- "/v1/auth/validate",
454
- response_model=APIKeyValidationResponse,
455
- tags=["Authentication"],
456
- description="Validate an API key and return its status."
457
- )
458
- async def validate_api_key(request: Request):
459
- """Validate an API key."""
460
- if not api_key_manager:
461
- raise APIError("Authentication system not initialized", HTTP_500_INTERNAL_SERVER_ERROR)
462
-
463
- auth_header = request.headers.get("authorization")
464
- if not auth_header:
465
- return APIKeyValidationResponse(valid=False, error="No authorization header provided")
466
-
467
- # Extract API key
468
- api_key = auth_header
469
- if auth_header.startswith("Bearer "):
470
- api_key = auth_header[7:]
471
-
472
- try:
473
- is_valid, api_key_obj, error_msg = await api_key_manager.validate_api_key(api_key)
474
-
475
- if is_valid and api_key_obj:
476
- return APIKeyValidationResponse(
477
- valid=True,
478
- user_id=api_key_obj.user_id,
479
- key_id=api_key_obj.id,
480
- rate_limit=api_key_obj.rate_limit,
481
- usage_count=api_key_obj.usage_count,
482
- last_used_at=api_key_obj.last_used_at
483
- )
484
- else:
485
- return APIKeyValidationResponse(valid=False, error=error_msg)
486
- except Exception as e:
487
- logger.error(f"Error validating API key: {e}")
488
- return APIKeyValidationResponse(valid=False, error="Internal validation error")
489
-
490
- @self.app.get(
491
- "/health",
492
- response_model=HealthCheckResponse,
493
- tags=["Health"],
494
- description="Health check endpoint for the API and database."
495
- )
496
- async def health_check():
497
- """Health check endpoint."""
498
- db_status = "unknown"
499
- db_manager = auth_components.get("db_manager")
500
- if db_manager:
501
- status_info = db_manager.get_status()
502
- db_status = f"{status_info['type']} - {status_info['status']}"
503
-
504
- return HealthCheckResponse(
505
- status="healthy",
506
- database=db_status,
507
- timestamp=datetime.now(timezone.utc)
508
- )
509
384
 
510
385
  def _register_websearch_routes(self):
511
386
  """Register web search endpoint."""
@@ -583,18 +458,15 @@ class Api:
583
458
  }
584
459
 
585
460
  def _register_monitoring_routes(self):
586
- """Register monitoring and analytics routes for no-auth mode."""
461
+ """Register monitoring and analytics routes."""
587
462
 
588
463
  @self.app.get(
589
464
  "/monitor/requests",
590
465
  tags=["Monitoring"],
591
- description="Get recent API requests (no-auth mode only)"
466
+ description="Get recent API requests"
592
467
  )
593
468
  async def get_recent_requests(limit: int = Query(10, description="Number of recent requests to fetch")):
594
469
  """Get recent API requests for monitoring."""
595
- if AppConfig.auth_required:
596
- return {"error": "Monitoring is only available in no-auth mode"}
597
-
598
470
  try:
599
471
  return await request_logger.get_recent_requests(limit)
600
472
  except Exception as e:
@@ -603,13 +475,10 @@ class Api:
603
475
  @self.app.get(
604
476
  "/monitor/stats",
605
477
  tags=["Monitoring"],
606
- description="Get API usage statistics (no-auth mode only)"
478
+ description="Get API usage statistics"
607
479
  )
608
480
  async def get_api_stats():
609
481
  """Get API usage statistics."""
610
- if AppConfig.auth_required:
611
- return {"error": "Monitoring is only available in no-auth mode"}
612
-
613
482
  try:
614
483
  return await request_logger.get_stats()
615
484
  except Exception as e:
@@ -618,24 +487,13 @@ class Api:
618
487
  @self.app.get(
619
488
  "/monitor/health",
620
489
  tags=["Monitoring"],
621
- description="Health check with database status"
490
+ description="Health check"
622
491
  )
623
492
  async def enhanced_health_check():
624
- """Enhanced health check including database connectivity."""
493
+ """Enhanced health check."""
625
494
  try:
626
- # Check database connectivity
627
- db_status = "disconnected"
628
- if request_logger.supabase_client:
629
- try:
630
- # Try a simple query to check connectivity
631
- result = request_logger.supabase_client.table("api_requests").select("id").limit(1).execute()
632
- db_status = "connected"
633
- except Exception as e:
634
- db_status = f"error: {str(e)[:100]}"
635
-
636
495
  return {
637
496
  "status": "healthy",
638
- "database": db_status,
639
497
  "auth_required": AppConfig.auth_required,
640
498
  "rate_limit_enabled": AppConfig.rate_limit_enabled,
641
499
  "request_logging_enabled": AppConfig.request_logging_enabled,
@@ -0,0 +1,23 @@
1
+ # webscout/server/schemas.py
2
+
3
+ from datetime import datetime
4
+ from typing import Optional, Dict, Any
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ErrorResponse(BaseModel):
9
+ """Standard error response model."""
10
+ error: str = Field(..., description="Error message")
11
+ code: str = Field(..., description="Error code")
12
+ details: Optional[Dict[str, Any]] = Field(None, description="Additional error details")
13
+
14
+
15
+ class HealthCheckResponse(BaseModel):
16
+ """Health check response model."""
17
+ status: str = Field(..., description="Service status")
18
+ timestamp: datetime = Field(..., description="Check timestamp")
19
+
20
+ class Config:
21
+ json_encoders = {
22
+ datetime: lambda v: v.isoformat()
23
+ }
@@ -25,7 +25,7 @@ from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
25
25
  from .config import ServerConfig, AppConfig
26
26
  from .routes import Api
27
27
  from .providers import initialize_provider_map, initialize_tti_provider_map
28
- from .auth_system import initialize_auth_system
28
+
29
29
 
30
30
  # Configuration constants
31
31
  DEFAULT_PORT = 8000
@@ -138,12 +138,7 @@ def create_app():
138
138
  allow_headers=["*"],
139
139
  )
140
140
 
141
- # Initialize authentication system with configuration
142
- initialize_auth_system(
143
- app,
144
- auth_required=AppConfig.auth_required,
145
- rate_limit_enabled=AppConfig.rate_limit_enabled
146
- )
141
+
147
142
  # Add startup event handler
148
143
  @app.on_event("startup")
149
144
  async def startup():
@@ -175,42 +170,33 @@ def create_app_debug():
175
170
  def start_server(
176
171
  port: int = DEFAULT_PORT,
177
172
  host: str = DEFAULT_HOST,
178
- api_key: str = None,
179
173
  default_provider: str = None,
180
174
  base_url: str = None,
181
175
  workers: int = 1,
182
176
  log_level: str = 'info',
183
177
  debug: bool = False,
184
- no_auth: bool = False,
185
- no_rate_limit: bool = False
186
178
  ):
187
179
  """Start the API server with the given configuration."""
188
180
  run_api(
189
181
  host=host,
190
182
  port=port,
191
- api_key=api_key,
192
183
  default_provider=default_provider,
193
184
  base_url=base_url,
194
185
  workers=workers,
195
186
  log_level=log_level,
196
187
  debug=debug,
197
- no_auth=no_auth,
198
- no_rate_limit=no_rate_limit,
199
188
  )
200
189
 
201
190
 
202
191
  def run_api(
203
192
  host: str = '0.0.0.0',
204
193
  port: int = None,
205
- api_key: str = None,
206
194
  default_provider: str = None,
207
195
  base_url: str = None,
208
196
  debug: bool = False,
209
197
  workers: int = 1,
210
198
  log_level: str = 'info',
211
199
  show_available_providers: bool = True,
212
- no_auth: bool = False,
213
- no_rate_limit: bool = False,
214
200
  ) -> None:
215
201
  """Run the API server with configuration."""
216
202
  print("Starting Webscout OpenAI API server...")
@@ -218,11 +204,11 @@ def run_api(
218
204
  port = DEFAULT_PORT
219
205
 
220
206
  AppConfig.set_config(
221
- api_key=api_key,
207
+ api_key=None,
222
208
  default_provider=default_provider or AppConfig.default_provider,
223
209
  base_url=base_url,
224
- auth_required=not no_auth,
225
- rate_limit_enabled=not no_rate_limit
210
+ auth_required=False,
211
+ rate_limit_enabled=False
226
212
  )
227
213
 
228
214
  if show_available_providers:
@@ -243,18 +229,10 @@ def run_api(
243
229
  print(f"Docs URL: {api_endpoint_base}/docs")
244
230
 
245
231
  # Show authentication status
246
- if no_auth:
247
- print(f"Authentication: 🔓 DISABLED (No-Auth Mode)")
248
- elif api_key:
249
- print(f"Authentication: 🔐 Legacy API Key")
250
- else:
251
- print(f"Authentication: 🔑 Enhanced API Keys")
232
+ print(f"Authentication: 🔓 DISABLED")
252
233
 
253
234
  # Show rate limiting status
254
- if no_rate_limit:
255
- print(f"Rate Limiting: ⚡ DISABLED")
256
- else:
257
- print(f"Rate Limiting: 🛡️ Enabled ({AppConfig.default_rate_limit}/min)")
235
+ print(f"Rate Limiting: ⚡ DISABLED")
258
236
 
259
237
  print(f"Default Provider: {AppConfig.default_provider}")
260
238
  print(f"Workers: {workers}")
@@ -291,7 +269,7 @@ def run_api(
291
269
  print("\nUse Ctrl+C to stop the server.")
292
270
  print("=" * 40 + "\n")
293
271
 
294
- uvicorn_app_str = "webscout.auth.server:create_app_debug" if debug else "webscout.auth.server:create_app"
272
+ uvicorn_app_str = "webscout.server.server:create_app_debug" if debug else "webscout.server.server:create_app"
295
273
 
296
274
  # Configure uvicorn settings
297
275
  uvicorn_config = {
@@ -322,24 +300,18 @@ def main():
322
300
  default_host = os.getenv('WEBSCOUT_HOST', DEFAULT_HOST)
323
301
  default_workers = int(os.getenv('WEBSCOUT_WORKERS', '1'))
324
302
  default_log_level = os.getenv('WEBSCOUT_LOG_LEVEL', 'info')
325
- default_api_key = os.getenv('WEBSCOUT_API_KEY', os.getenv('API_KEY'))
326
303
  default_provider = os.getenv('WEBSCOUT_DEFAULT_PROVIDER', os.getenv('DEFAULT_PROVIDER'))
327
304
  default_base_url = os.getenv('WEBSCOUT_BASE_URL', os.getenv('BASE_URL'))
328
305
  default_debug = os.getenv('WEBSCOUT_DEBUG', os.getenv('DEBUG', 'false')).lower() == 'true'
329
- default_no_auth = os.getenv('WEBSCOUT_NO_AUTH', 'false').lower() == 'true'
330
- default_no_rate_limit = os.getenv('WEBSCOUT_NO_RATE_LIMIT', 'false').lower() == 'true'
331
306
 
332
307
  parser = argparse.ArgumentParser(description='Start Webscout OpenAI-compatible API server')
333
308
  parser.add_argument('--port', type=int, default=default_port, help=f'Port to run the server on (default: {default_port})')
334
309
  parser.add_argument('--host', type=str, default=default_host, help=f'Host to bind the server to (default: {default_host})')
335
310
  parser.add_argument('--workers', type=int, default=default_workers, help=f'Number of worker processes (default: {default_workers})')
336
311
  parser.add_argument('--log-level', type=str, default=default_log_level, choices=['debug', 'info', 'warning', 'error', 'critical'], help=f'Log level (default: {default_log_level})')
337
- parser.add_argument('--api-key', type=str, default=default_api_key, help='API key for authentication (optional)')
338
312
  parser.add_argument('--default-provider', type=str, default=default_provider, help='Default provider to use (optional)')
339
313
  parser.add_argument('--base-url', type=str, default=default_base_url, help='Base URL for the API (optional, e.g., /api/v1)')
340
314
  parser.add_argument('--debug', action='store_true', default=default_debug, help='Run in debug mode')
341
- parser.add_argument('--no-auth', action='store_true', default=default_no_auth, help='Disable authentication (no API keys required)')
342
- parser.add_argument('--no-rate-limit', action='store_true', default=default_no_rate_limit, help='Disable rate limiting (unlimited requests)')
343
315
  args = parser.parse_args()
344
316
 
345
317
  # Print configuration summary
@@ -349,9 +321,8 @@ def main():
349
321
  print(f" Workers: {args.workers}")
350
322
  print(f" Log Level: {args.log_level}")
351
323
  print(f" Debug Mode: {args.debug}")
352
- print(f" No-Auth Mode: {'🔓 ENABLED' if args.no_auth else '🔐 Disabled'}")
353
- print(f" No Rate Limit: {'ENABLED' if args.no_rate_limit else '🛡️ Disabled'}")
354
- print(f" API Key: {'Set' if args.api_key else 'Not set'}")
324
+ print(f" Authentication: 🔓 DISABLED")
325
+ print(f" Rate Limiting: ⚡ DISABLED")
355
326
  print(f" Default Provider: {args.default_provider or 'Not set'}")
356
327
  print(f" Base URL: {args.base_url or 'Not set'}")
357
328
  print()
@@ -361,12 +332,9 @@ def main():
361
332
  port=args.port,
362
333
  workers=args.workers,
363
334
  log_level=args.log_level,
364
- api_key=args.api_key,
365
335
  default_provider=args.default_provider,
366
336
  base_url=args.base_url,
367
- debug=args.debug,
368
- no_auth=args.no_auth,
369
- no_rate_limit=args.no_rate_limit
337
+ debug=args.debug
370
338
  )
371
339
 
372
340
 
@@ -0,0 +1,84 @@
1
+ """
2
+ Simple logger for no-auth mode.
3
+ """
4
+
5
+ import asyncio
6
+ from datetime import datetime, timezone
7
+ from typing import Optional, Dict, Any
8
+ import sys
9
+ from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
10
+
11
+ # Setup logger
12
+ logger = Logger(
13
+ name="webscout.api.simple_db",
14
+ level=LogLevel.INFO,
15
+ handlers=[ConsoleHandler(stream=sys.stdout)],
16
+ fmt=LogFormat.DEFAULT
17
+ )
18
+
19
+ class SimpleRequestLogger:
20
+ """Simple request logger for no-auth mode."""
21
+
22
+ def __init__(self):
23
+ logger.info("Simple request logger initialized (no database).")
24
+
25
+ async def log_request(
26
+ self,
27
+ request_id: str,
28
+ ip_address: str,
29
+ model: str,
30
+ question: str,
31
+ answer: str,
32
+ **kwargs
33
+ ) -> bool:
34
+ """Logs request details to the console."""
35
+ logger.info(f"Request {request_id}: model={model}, ip={ip_address}")
36
+ return True
37
+
38
+ async def get_recent_requests(self, limit: int = 10) -> Dict[str, Any]:
39
+ """Returns empty list of recent requests."""
40
+ logger.info("get_recent_requests called, but no database is configured.")
41
+ return {"requests": [], "count": 0}
42
+
43
+ async def get_stats(self) -> Dict[str, Any]:
44
+ """Returns empty stats."""
45
+ logger.info("get_stats called, but no database is configured.")
46
+ return {"error": "No database configured.", "available": False}
47
+
48
+ # Global instance
49
+ request_logger = SimpleRequestLogger()
50
+
51
+ async def log_api_request(
52
+ request_id: str,
53
+ ip_address: str,
54
+ model: str,
55
+ question: str,
56
+ answer: str,
57
+ **kwargs
58
+ ) -> bool:
59
+ """Convenience function to log API requests."""
60
+ return await request_logger.log_request(
61
+ request_id=request_id,
62
+ ip_address=ip_address,
63
+ model=model,
64
+ question=question,
65
+ answer=answer,
66
+ **kwargs
67
+ )
68
+
69
+ def get_client_ip(request) -> str:
70
+ """Extract client IP address from request."""
71
+ forwarded_for = request.headers.get("X-Forwarded-For")
72
+ if forwarded_for:
73
+ return forwarded_for.split(",")[0].strip()
74
+
75
+ real_ip = request.headers.get("X-Real-IP")
76
+ if real_ip:
77
+ return real_ip.strip()
78
+
79
+ return getattr(request.client, "host", "unknown")
80
+
81
+ def generate_request_id() -> str:
82
+ """Generate a unique request ID."""
83
+ import uuid
84
+ return str(uuid.uuid4())
webscout/version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2025.10.15"
1
+ __version__ = "2025.10.16"
2
2
  __prog__ = "webscout"
webscout/version.py.bak CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = ""
1
+ __version__ = "2025.10.15"
2
2
  __prog__ = "webscout"