unrealon 1.0.9__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unrealon/__init__.py +23 -21
- unrealon-1.1.0.dist-info/METADATA +164 -0
- unrealon-1.1.0.dist-info/RECORD +82 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.0.dist-info}/WHEEL +1 -1
- unrealon-1.1.0.dist-info/entry_points.txt +9 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.0.dist-info/licenses}/LICENSE +1 -1
- unrealon_bridge/__init__.py +114 -0
- unrealon_bridge/cli.py +316 -0
- unrealon_bridge/client/__init__.py +93 -0
- unrealon_bridge/client/base.py +78 -0
- unrealon_bridge/client/commands.py +89 -0
- unrealon_bridge/client/connection.py +90 -0
- unrealon_bridge/client/events.py +65 -0
- unrealon_bridge/client/health.py +38 -0
- unrealon_bridge/client/html_parser.py +146 -0
- unrealon_bridge/client/logging.py +139 -0
- unrealon_bridge/client/proxy.py +70 -0
- unrealon_bridge/client/scheduler.py +450 -0
- unrealon_bridge/client/session.py +70 -0
- unrealon_bridge/configs/__init__.py +14 -0
- unrealon_bridge/configs/bridge_config.py +212 -0
- unrealon_bridge/configs/bridge_config.yaml +39 -0
- unrealon_bridge/models/__init__.py +138 -0
- unrealon_bridge/models/base.py +28 -0
- unrealon_bridge/models/command.py +41 -0
- unrealon_bridge/models/events.py +40 -0
- unrealon_bridge/models/html_parser.py +79 -0
- unrealon_bridge/models/logging.py +55 -0
- unrealon_bridge/models/parser.py +63 -0
- unrealon_bridge/models/proxy.py +41 -0
- unrealon_bridge/models/requests.py +95 -0
- unrealon_bridge/models/responses.py +88 -0
- unrealon_bridge/models/scheduler.py +592 -0
- unrealon_bridge/models/session.py +28 -0
- unrealon_bridge/server/__init__.py +91 -0
- unrealon_bridge/server/base.py +171 -0
- unrealon_bridge/server/handlers/__init__.py +23 -0
- unrealon_bridge/server/handlers/command.py +110 -0
- unrealon_bridge/server/handlers/html_parser.py +139 -0
- unrealon_bridge/server/handlers/logging.py +95 -0
- unrealon_bridge/server/handlers/parser.py +95 -0
- unrealon_bridge/server/handlers/proxy.py +75 -0
- unrealon_bridge/server/handlers/scheduler.py +545 -0
- unrealon_bridge/server/handlers/session.py +66 -0
- unrealon_browser/__init__.py +61 -18
- unrealon_browser/{src/cli → cli}/browser_cli.py +6 -13
- unrealon_browser/{src/cli → cli}/cookies_cli.py +5 -1
- unrealon_browser/{src/core → core}/browser_manager.py +2 -2
- unrealon_browser/{src/managers → managers}/captcha.py +1 -1
- unrealon_browser/{src/managers → managers}/cookies.py +1 -1
- unrealon_browser/managers/logger_bridge.py +231 -0
- unrealon_browser/{src/managers → managers}/profile.py +1 -1
- unrealon_driver/__init__.py +73 -19
- unrealon_driver/browser/__init__.py +8 -0
- unrealon_driver/browser/config.py +74 -0
- unrealon_driver/browser/manager.py +416 -0
- unrealon_driver/exceptions.py +28 -0
- unrealon_driver/parser/__init__.py +55 -0
- unrealon_driver/parser/cli_manager.py +141 -0
- unrealon_driver/parser/daemon_manager.py +227 -0
- unrealon_driver/parser/managers/__init__.py +46 -0
- unrealon_driver/parser/managers/browser.py +51 -0
- unrealon_driver/parser/managers/config.py +281 -0
- unrealon_driver/parser/managers/error.py +412 -0
- unrealon_driver/parser/managers/html.py +732 -0
- unrealon_driver/parser/managers/logging.py +609 -0
- unrealon_driver/parser/managers/result.py +321 -0
- unrealon_driver/parser/parser_manager.py +628 -0
- unrealon/sdk_config.py +0 -88
- unrealon-1.0.9.dist-info/METADATA +0 -810
- unrealon-1.0.9.dist-info/RECORD +0 -246
- unrealon_browser/pyproject.toml +0 -182
- unrealon_browser/src/__init__.py +0 -62
- unrealon_browser/src/managers/logger_bridge.py +0 -395
- unrealon_driver/README.md +0 -204
- unrealon_driver/pyproject.toml +0 -187
- unrealon_driver/src/__init__.py +0 -90
- unrealon_driver/src/cli/__init__.py +0 -10
- unrealon_driver/src/cli/main.py +0 -66
- unrealon_driver/src/cli/simple.py +0 -510
- unrealon_driver/src/config/__init__.py +0 -11
- unrealon_driver/src/config/auto_config.py +0 -478
- unrealon_driver/src/core/__init__.py +0 -18
- unrealon_driver/src/core/exceptions.py +0 -289
- unrealon_driver/src/core/parser.py +0 -638
- unrealon_driver/src/dto/__init__.py +0 -66
- unrealon_driver/src/dto/cli.py +0 -119
- unrealon_driver/src/dto/config.py +0 -18
- unrealon_driver/src/dto/events.py +0 -237
- unrealon_driver/src/dto/execution.py +0 -313
- unrealon_driver/src/dto/services.py +0 -311
- unrealon_driver/src/execution/__init__.py +0 -23
- unrealon_driver/src/execution/daemon_mode.py +0 -317
- unrealon_driver/src/execution/interactive_mode.py +0 -88
- unrealon_driver/src/execution/modes.py +0 -45
- unrealon_driver/src/execution/scheduled_mode.py +0 -209
- unrealon_driver/src/execution/test_mode.py +0 -250
- unrealon_driver/src/logging/__init__.py +0 -24
- unrealon_driver/src/logging/driver_logger.py +0 -512
- unrealon_driver/src/services/__init__.py +0 -24
- unrealon_driver/src/services/browser_service.py +0 -726
- unrealon_driver/src/services/llm/__init__.py +0 -15
- unrealon_driver/src/services/llm/browser_llm_service.py +0 -363
- unrealon_driver/src/services/llm/llm.py +0 -195
- unrealon_driver/src/services/logger_service.py +0 -232
- unrealon_driver/src/services/metrics_service.py +0 -185
- unrealon_driver/src/services/scheduler_service.py +0 -489
- unrealon_driver/src/services/websocket_service.py +0 -362
- unrealon_driver/src/utils/__init__.py +0 -16
- unrealon_driver/src/utils/service_factory.py +0 -317
- unrealon_driver/src/utils/time_formatter.py +0 -338
- unrealon_llm/README.md +0 -44
- unrealon_llm/__init__.py +0 -26
- unrealon_llm/pyproject.toml +0 -154
- unrealon_llm/src/__init__.py +0 -228
- unrealon_llm/src/cli/__init__.py +0 -0
- unrealon_llm/src/core/__init__.py +0 -11
- unrealon_llm/src/core/smart_client.py +0 -438
- unrealon_llm/src/dto/__init__.py +0 -155
- unrealon_llm/src/dto/models/__init__.py +0 -0
- unrealon_llm/src/dto/models/config.py +0 -343
- unrealon_llm/src/dto/models/core.py +0 -328
- unrealon_llm/src/dto/models/enums.py +0 -123
- unrealon_llm/src/dto/models/html_analysis.py +0 -345
- unrealon_llm/src/dto/models/statistics.py +0 -473
- unrealon_llm/src/dto/models/translation.py +0 -383
- unrealon_llm/src/dto/models/type_conversion.py +0 -462
- unrealon_llm/src/dto/schemas/__init__.py +0 -0
- unrealon_llm/src/exceptions.py +0 -392
- unrealon_llm/src/llm_config/__init__.py +0 -20
- unrealon_llm/src/llm_config/logging_config.py +0 -178
- unrealon_llm/src/llm_logging/__init__.py +0 -42
- unrealon_llm/src/llm_logging/llm_events.py +0 -107
- unrealon_llm/src/llm_logging/llm_logger.py +0 -466
- unrealon_llm/src/managers/__init__.py +0 -15
- unrealon_llm/src/managers/cache_manager.py +0 -67
- unrealon_llm/src/managers/cost_manager.py +0 -107
- unrealon_llm/src/managers/request_manager.py +0 -298
- unrealon_llm/src/modules/__init__.py +0 -0
- unrealon_llm/src/modules/html_processor/__init__.py +0 -25
- unrealon_llm/src/modules/html_processor/base_processor.py +0 -415
- unrealon_llm/src/modules/html_processor/details_processor.py +0 -85
- unrealon_llm/src/modules/html_processor/listing_processor.py +0 -91
- unrealon_llm/src/modules/html_processor/models/__init__.py +0 -20
- unrealon_llm/src/modules/html_processor/models/processing_models.py +0 -40
- unrealon_llm/src/modules/html_processor/models/universal_model.py +0 -56
- unrealon_llm/src/modules/html_processor/processor.py +0 -102
- unrealon_llm/src/modules/llm/__init__.py +0 -0
- unrealon_llm/src/modules/translator/__init__.py +0 -0
- unrealon_llm/src/provider.py +0 -116
- unrealon_llm/src/utils/__init__.py +0 -95
- unrealon_llm/src/utils/common.py +0 -64
- unrealon_llm/src/utils/data_extractor.py +0 -188
- unrealon_llm/src/utils/html_cleaner.py +0 -767
- unrealon_llm/src/utils/language_detector.py +0 -308
- unrealon_llm/src/utils/models_cache.py +0 -592
- unrealon_llm/src/utils/smart_counter.py +0 -229
- unrealon_llm/src/utils/token_counter.py +0 -189
- unrealon_sdk/README.md +0 -25
- unrealon_sdk/__init__.py +0 -30
- unrealon_sdk/pyproject.toml +0 -231
- unrealon_sdk/src/__init__.py +0 -150
- unrealon_sdk/src/cli/__init__.py +0 -12
- unrealon_sdk/src/cli/commands/__init__.py +0 -22
- unrealon_sdk/src/cli/commands/benchmark.py +0 -42
- unrealon_sdk/src/cli/commands/diagnostics.py +0 -573
- unrealon_sdk/src/cli/commands/health.py +0 -46
- unrealon_sdk/src/cli/commands/integration.py +0 -498
- unrealon_sdk/src/cli/commands/reports.py +0 -43
- unrealon_sdk/src/cli/commands/security.py +0 -36
- unrealon_sdk/src/cli/commands/server.py +0 -483
- unrealon_sdk/src/cli/commands/servers.py +0 -56
- unrealon_sdk/src/cli/commands/tests.py +0 -55
- unrealon_sdk/src/cli/main.py +0 -126
- unrealon_sdk/src/cli/utils/reporter.py +0 -519
- unrealon_sdk/src/clients/openapi.yaml +0 -3347
- unrealon_sdk/src/clients/python_http/__init__.py +0 -3
- unrealon_sdk/src/clients/python_http/api_config.py +0 -228
- unrealon_sdk/src/clients/python_http/models/BaseModel.py +0 -12
- unrealon_sdk/src/clients/python_http/models/BroadcastDeliveryStats.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastMessage.py +0 -17
- unrealon_sdk/src/clients/python_http/models/BroadcastMessageRequest.py +0 -35
- unrealon_sdk/src/clients/python_http/models/BroadcastPriority.py +0 -10
- unrealon_sdk/src/clients/python_http/models/BroadcastResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/BroadcastResultResponse.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastTarget.py +0 -11
- unrealon_sdk/src/clients/python_http/models/ConnectionStats.py +0 -27
- unrealon_sdk/src/clients/python_http/models/ConnectionsResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/DeveloperMessageResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ErrorResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/HTTPValidationError.py +0 -16
- unrealon_sdk/src/clients/python_http/models/HealthResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/HealthStatus.py +0 -33
- unrealon_sdk/src/clients/python_http/models/LogLevel.py +0 -10
- unrealon_sdk/src/clients/python_http/models/LoggingRequest.py +0 -27
- unrealon_sdk/src/clients/python_http/models/LoggingResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/MaintenanceMode.py +0 -9
- unrealon_sdk/src/clients/python_http/models/MaintenanceModeRequest.py +0 -33
- unrealon_sdk/src/clients/python_http/models/MaintenanceStatusResponse.py +0 -39
- unrealon_sdk/src/clients/python_http/models/ParserCommandRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserMessageResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationRequest.py +0 -28
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserType.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyBlockRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyEndpointResponse.py +0 -20
- unrealon_sdk/src/clients/python_http/models/ProxyListResponse.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyProvider.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyPurchaseRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ProxyResponse.py +0 -47
- unrealon_sdk/src/clients/python_http/models/ProxyRotationRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ProxyStatus.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyUsageRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyUsageStatsResponse.py +0 -26
- unrealon_sdk/src/clients/python_http/models/ServiceRegistrationDto.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ServiceStatsResponse.py +0 -31
- unrealon_sdk/src/clients/python_http/models/SessionStartRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/SuccessResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/SystemNotificationResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ValidationError.py +0 -18
- unrealon_sdk/src/clients/python_http/models/ValidationErrorResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/WebSocketMetrics.py +0 -21
- unrealon_sdk/src/clients/python_http/models/__init__.py +0 -44
- unrealon_sdk/src/clients/python_http/services/None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/SocketLogging_service.py +0 -187
- unrealon_sdk/src/clients/python_http/services/SystemHealth_service.py +0 -119
- unrealon_sdk/src/clients/python_http/services/WebSocketAPI_service.py +0 -198
- unrealon_sdk/src/clients/python_http/services/__init__.py +0 -0
- unrealon_sdk/src/clients/python_http/services/admin_service.py +0 -125
- unrealon_sdk/src/clients/python_http/services/async_None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/async_ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/async_ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/async_SocketLogging_service.py +0 -189
- unrealon_sdk/src/clients/python_http/services/async_SystemHealth_service.py +0 -123
- unrealon_sdk/src/clients/python_http/services/async_WebSocketAPI_service.py +0 -200
- unrealon_sdk/src/clients/python_http/services/async_admin_service.py +0 -125
- unrealon_sdk/src/clients/python_websocket/__init__.py +0 -28
- unrealon_sdk/src/clients/python_websocket/client.py +0 -490
- unrealon_sdk/src/clients/python_websocket/events.py +0 -732
- unrealon_sdk/src/clients/python_websocket/example.py +0 -136
- unrealon_sdk/src/clients/python_websocket/types.py +0 -871
- unrealon_sdk/src/core/__init__.py +0 -64
- unrealon_sdk/src/core/client.py +0 -556
- unrealon_sdk/src/core/config.py +0 -465
- unrealon_sdk/src/core/exceptions.py +0 -239
- unrealon_sdk/src/core/metadata.py +0 -191
- unrealon_sdk/src/core/models.py +0 -142
- unrealon_sdk/src/core/types.py +0 -68
- unrealon_sdk/src/dto/__init__.py +0 -268
- unrealon_sdk/src/dto/authentication.py +0 -108
- unrealon_sdk/src/dto/cache.py +0 -208
- unrealon_sdk/src/dto/common.py +0 -19
- unrealon_sdk/src/dto/concurrency.py +0 -393
- unrealon_sdk/src/dto/events.py +0 -108
- unrealon_sdk/src/dto/health.py +0 -339
- unrealon_sdk/src/dto/load_balancing.py +0 -336
- unrealon_sdk/src/dto/logging.py +0 -230
- unrealon_sdk/src/dto/performance.py +0 -165
- unrealon_sdk/src/dto/rate_limiting.py +0 -295
- unrealon_sdk/src/dto/resource_pooling.py +0 -128
- unrealon_sdk/src/dto/structured_logging.py +0 -112
- unrealon_sdk/src/dto/task_scheduling.py +0 -121
- unrealon_sdk/src/dto/websocket.py +0 -55
- unrealon_sdk/src/enterprise/__init__.py +0 -59
- unrealon_sdk/src/enterprise/authentication.py +0 -401
- unrealon_sdk/src/enterprise/cache_manager.py +0 -578
- unrealon_sdk/src/enterprise/error_recovery.py +0 -494
- unrealon_sdk/src/enterprise/event_system.py +0 -549
- unrealon_sdk/src/enterprise/health_monitor.py +0 -747
- unrealon_sdk/src/enterprise/load_balancer.py +0 -964
- unrealon_sdk/src/enterprise/logging/__init__.py +0 -68
- unrealon_sdk/src/enterprise/logging/cleanup.py +0 -156
- unrealon_sdk/src/enterprise/logging/development.py +0 -744
- unrealon_sdk/src/enterprise/logging/service.py +0 -410
- unrealon_sdk/src/enterprise/multithreading_manager.py +0 -853
- unrealon_sdk/src/enterprise/performance_monitor.py +0 -539
- unrealon_sdk/src/enterprise/proxy_manager.py +0 -696
- unrealon_sdk/src/enterprise/rate_limiter.py +0 -652
- unrealon_sdk/src/enterprise/resource_pool.py +0 -763
- unrealon_sdk/src/enterprise/task_scheduler.py +0 -709
- unrealon_sdk/src/internal/__init__.py +0 -10
- unrealon_sdk/src/internal/command_router.py +0 -497
- unrealon_sdk/src/internal/connection_manager.py +0 -397
- unrealon_sdk/src/internal/http_client.py +0 -446
- unrealon_sdk/src/internal/websocket_client.py +0 -420
- unrealon_sdk/src/provider.py +0 -471
- unrealon_sdk/src/utils.py +0 -234
- /unrealon_browser/{src/cli → cli}/__init__.py +0 -0
- /unrealon_browser/{src/cli → cli}/interactive_mode.py +0 -0
- /unrealon_browser/{src/cli → cli}/main.py +0 -0
- /unrealon_browser/{src/core → core}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/config.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/core.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/dataclasses.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/detection.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/enums.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/statistics.py +0 -0
- /unrealon_browser/{src/managers → managers}/__init__.py +0 -0
- /unrealon_browser/{src/managers → managers}/stealth.py +0 -0
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Scheduler Service for UnrealOn Driver v3.0
|
|
3
|
-
|
|
4
|
-
Full scheduler implementation with human-readable intervals and enterprise monitoring.
|
|
5
|
-
Based on the proven architecture from v2.0 with modern enhancements.
|
|
6
|
-
|
|
7
|
-
CRITICAL REQUIREMENTS COMPLIANCE:
|
|
8
|
-
- ✅ Absolute imports only
|
|
9
|
-
- ✅ Pydantic v2 models everywhere
|
|
10
|
-
- ✅ Complete type annotations
|
|
11
|
-
- ✅ Full unrealon_sdk integration
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import asyncio
|
|
15
|
-
import re
|
|
16
|
-
import time
|
|
17
|
-
from datetime import datetime, timedelta
|
|
18
|
-
from typing import Any, Dict, List, Optional, Callable, Union
|
|
19
|
-
from enum import Enum
|
|
20
|
-
|
|
21
|
-
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
|
22
|
-
|
|
23
|
-
from unrealon_sdk.src.dto.task_scheduling import TaskSchedulerConfig, ScheduleInfo, TaskProgress
|
|
24
|
-
from unrealon_sdk.src.enterprise.logging.development import get_development_logger
|
|
25
|
-
from unrealon_sdk.src.dto.logging import SDKContext, SDKEventType
|
|
26
|
-
|
|
27
|
-
from unrealon_driver.src.core.exceptions import SchedulingError
|
|
28
|
-
from unrealon_driver.src.dto.services import SchedulerConfig
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class ScheduledTaskStatus(str, Enum):
|
|
32
|
-
"""Task status enumeration."""
|
|
33
|
-
|
|
34
|
-
PENDING = "pending"
|
|
35
|
-
RUNNING = "running"
|
|
36
|
-
COMPLETED = "completed"
|
|
37
|
-
FAILED = "failed"
|
|
38
|
-
CANCELLED = "cancelled"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class ScheduleExpression(BaseModel):
|
|
42
|
-
"""Pydantic model for schedule expression parsing."""
|
|
43
|
-
|
|
44
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
45
|
-
|
|
46
|
-
expression: str = Field(..., description="Human-readable schedule expression")
|
|
47
|
-
interval_seconds: int = Field(..., ge=1, description="Parsed interval in seconds")
|
|
48
|
-
|
|
49
|
-
@field_validator("expression")
|
|
50
|
-
@classmethod
|
|
51
|
-
def validate_expression(cls, v: str) -> str:
|
|
52
|
-
"""Validate schedule expression format."""
|
|
53
|
-
v = v.lower().strip()
|
|
54
|
-
|
|
55
|
-
# Allowed patterns
|
|
56
|
-
patterns = [
|
|
57
|
-
r"^\d+[smhd]$", # 30s, 5m, 1h, 2d
|
|
58
|
-
r"^(minutely|hourly|daily|weekly|monthly)$", # Named intervals
|
|
59
|
-
r"^(weekdays|weekends|business_hours)$", # Special intervals
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
if not any(re.match(pattern, v) for pattern in patterns):
|
|
63
|
-
raise ValueError(f"Invalid schedule expression: {v}")
|
|
64
|
-
|
|
65
|
-
return v
|
|
66
|
-
|
|
67
|
-
@classmethod
|
|
68
|
-
def parse(cls, expression: str) -> "ScheduleExpression":
|
|
69
|
-
"""Parse human-readable expression to seconds."""
|
|
70
|
-
expr = expression.lower().strip()
|
|
71
|
-
|
|
72
|
-
# Parse time units (30s, 5m, 1h, 2d)
|
|
73
|
-
if re.match(r"^\d+[smhd]$", expr):
|
|
74
|
-
number = int(expr[:-1])
|
|
75
|
-
unit = expr[-1]
|
|
76
|
-
|
|
77
|
-
multipliers = {"s": 1, "m": 60, "h": 3600, "d": 86400}
|
|
78
|
-
|
|
79
|
-
seconds = number * multipliers[unit]
|
|
80
|
-
return cls(expression=expression, interval_seconds=seconds)
|
|
81
|
-
|
|
82
|
-
# Named intervals
|
|
83
|
-
named_intervals = {
|
|
84
|
-
"minutely": 60,
|
|
85
|
-
"hourly": 3600,
|
|
86
|
-
"daily": 86400,
|
|
87
|
-
"weekly": 604800,
|
|
88
|
-
"monthly": 2592000, # 30 days
|
|
89
|
-
"weekdays": 86400, # Daily but only weekdays
|
|
90
|
-
"weekends": 86400, # Daily but only weekends
|
|
91
|
-
"business_hours": 3600, # Hourly during business hours
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if expr in named_intervals:
|
|
95
|
-
return cls(expression=expression, interval_seconds=named_intervals[expr])
|
|
96
|
-
|
|
97
|
-
raise ValueError(f"Unsupported schedule expression: {expression}")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class ScheduledTask(BaseModel):
|
|
101
|
-
"""Pydantic model for scheduled task configuration."""
|
|
102
|
-
|
|
103
|
-
model_config = ConfigDict(
|
|
104
|
-
validate_assignment=True, extra="forbid", arbitrary_types_allowed=True
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
task_id: str = Field(..., description="Unique task identifier")
|
|
108
|
-
name: str = Field(..., description="Human-readable task name")
|
|
109
|
-
func: Callable = Field(..., description="Async function to execute")
|
|
110
|
-
schedule: ScheduleExpression = Field(..., description="Schedule expression")
|
|
111
|
-
|
|
112
|
-
# Optional configuration
|
|
113
|
-
enabled: bool = Field(default=True, description="Whether task is enabled")
|
|
114
|
-
max_runs: Optional[int] = Field(default=None, ge=1, description="Maximum number of runs")
|
|
115
|
-
timeout: int = Field(default=300, ge=1, description="Task timeout in seconds")
|
|
116
|
-
retry_attempts: int = Field(default=3, ge=0, description="Number of retry attempts")
|
|
117
|
-
retry_delay: float = Field(default=1.0, ge=0, description="Delay between retries")
|
|
118
|
-
|
|
119
|
-
# Runtime tracking
|
|
120
|
-
status: ScheduledTaskStatus = Field(default=ScheduledTaskStatus.PENDING)
|
|
121
|
-
runs_count: int = Field(default=0, ge=0, description="Number of completed runs")
|
|
122
|
-
last_run: Optional[datetime] = Field(default=None, description="Last execution time")
|
|
123
|
-
next_run: Optional[datetime] = Field(default=None, description="Next scheduled execution")
|
|
124
|
-
last_duration: Optional[float] = Field(default=None, description="Last execution duration")
|
|
125
|
-
last_error: Optional[str] = Field(default=None, description="Last error message")
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class SchedulerService:
|
|
129
|
-
"""
|
|
130
|
-
📅 Scheduler Service - Enterprise Task Scheduling
|
|
131
|
-
|
|
132
|
-
Human-readable scheduling with monitoring and enterprise features:
|
|
133
|
-
- Natural language intervals ("30m", "1h", "daily")
|
|
134
|
-
- Smart load balancing with jitter
|
|
135
|
-
- Error recovery and retries
|
|
136
|
-
- Health monitoring and alerting
|
|
137
|
-
- Production-ready reliability
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
def __init__(
|
|
141
|
-
self, config: SchedulerConfig, logger: Optional[Any] = None, metrics: Optional[Any] = None
|
|
142
|
-
):
|
|
143
|
-
"""Initialize scheduler service."""
|
|
144
|
-
self.config = config
|
|
145
|
-
self.logger = logger
|
|
146
|
-
self.metrics = metrics
|
|
147
|
-
|
|
148
|
-
# ✅ DEVELOPMENT LOGGER INTEGRATION (CRITICAL REQUIREMENT)
|
|
149
|
-
self.dev_logger = get_development_logger()
|
|
150
|
-
|
|
151
|
-
# Scheduler state
|
|
152
|
-
self._tasks: Dict[str, ScheduledTask] = {}
|
|
153
|
-
self._running_tasks: Dict[str, asyncio.Task] = {}
|
|
154
|
-
self._is_running = False
|
|
155
|
-
self._shutdown_event = asyncio.Event()
|
|
156
|
-
|
|
157
|
-
# Configuration - SchedulerConfig is a Pydantic object
|
|
158
|
-
self.max_concurrent = config.max_concurrent_tasks
|
|
159
|
-
self.enable_jitter = config.enable_jitter
|
|
160
|
-
self.jitter_range = config.jitter_range
|
|
161
|
-
|
|
162
|
-
# Log initialization with development logger
|
|
163
|
-
if self.dev_logger:
|
|
164
|
-
self.dev_logger.log_info(
|
|
165
|
-
SDKEventType.COMPONENT_CREATED,
|
|
166
|
-
"Scheduler service initialized",
|
|
167
|
-
context=SDKContext(
|
|
168
|
-
parser_id=self.config.parser_id,
|
|
169
|
-
component_name="Scheduler",
|
|
170
|
-
layer_name="UnrealOn_Driver",
|
|
171
|
-
metadata={
|
|
172
|
-
"max_concurrent": self.max_concurrent,
|
|
173
|
-
"enable_jitter": self.enable_jitter,
|
|
174
|
-
},
|
|
175
|
-
),
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
def add_task(
|
|
179
|
-
self, task_id: str, name: str, func: Callable, every: str, **kwargs
|
|
180
|
-
) -> ScheduledTask:
|
|
181
|
-
"""
|
|
182
|
-
Add a new scheduled task.
|
|
183
|
-
|
|
184
|
-
Args:
|
|
185
|
-
task_id: Unique task identifier
|
|
186
|
-
name: Human-readable task name
|
|
187
|
-
func: Async function to execute
|
|
188
|
-
every: Schedule expression ("30m", "1h", "daily", etc.)
|
|
189
|
-
**kwargs: Additional task configuration
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
Created ScheduledTask
|
|
193
|
-
|
|
194
|
-
Example:
|
|
195
|
-
scheduler.add_task(
|
|
196
|
-
"parse_news",
|
|
197
|
-
"Parse News Data",
|
|
198
|
-
my_parse_function,
|
|
199
|
-
every="30m",
|
|
200
|
-
max_runs=100,
|
|
201
|
-
timeout=60
|
|
202
|
-
)
|
|
203
|
-
"""
|
|
204
|
-
try:
|
|
205
|
-
# Parse schedule expression
|
|
206
|
-
schedule = ScheduleExpression.parse(every)
|
|
207
|
-
|
|
208
|
-
# Create task
|
|
209
|
-
task = ScheduledTask(task_id=task_id, name=name, func=func, schedule=schedule, **kwargs)
|
|
210
|
-
|
|
211
|
-
# Calculate next run time
|
|
212
|
-
task.next_run = self._calculate_next_run(task)
|
|
213
|
-
|
|
214
|
-
# Store task
|
|
215
|
-
self._tasks[task_id] = task
|
|
216
|
-
|
|
217
|
-
if self.logger:
|
|
218
|
-
self.logger.info(f"📅 Added scheduled task: {name} (every {every})")
|
|
219
|
-
|
|
220
|
-
return task
|
|
221
|
-
|
|
222
|
-
except Exception as e:
|
|
223
|
-
raise SchedulingError(f"Failed to add task {task_id}: {e}", schedule_expression=every)
|
|
224
|
-
|
|
225
|
-
def remove_task(self, task_id: str) -> bool:
|
|
226
|
-
"""Remove a scheduled task."""
|
|
227
|
-
if task_id in self._tasks:
|
|
228
|
-
# Cancel running task if any
|
|
229
|
-
if task_id in self._running_tasks:
|
|
230
|
-
self._running_tasks[task_id].cancel()
|
|
231
|
-
del self._running_tasks[task_id]
|
|
232
|
-
|
|
233
|
-
# Remove from schedule
|
|
234
|
-
del self._tasks[task_id]
|
|
235
|
-
|
|
236
|
-
if self.logger:
|
|
237
|
-
self.logger.info(f"📅 Removed scheduled task: {task_id}")
|
|
238
|
-
|
|
239
|
-
return True
|
|
240
|
-
|
|
241
|
-
return False
|
|
242
|
-
|
|
243
|
-
async def start(self):
|
|
244
|
-
"""Start the scheduler."""
|
|
245
|
-
if self._is_running:
|
|
246
|
-
if self.logger:
|
|
247
|
-
self.logger.warning("Scheduler already running")
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
self._is_running = True
|
|
251
|
-
self._shutdown_event.clear()
|
|
252
|
-
|
|
253
|
-
if self.logger:
|
|
254
|
-
self.logger.info(f"📅 Starting scheduler with {len(self._tasks)} tasks")
|
|
255
|
-
|
|
256
|
-
try:
|
|
257
|
-
# Main scheduler loop
|
|
258
|
-
while self._is_running:
|
|
259
|
-
await self._scheduler_tick()
|
|
260
|
-
await asyncio.sleep(1) # Check every second
|
|
261
|
-
|
|
262
|
-
except asyncio.CancelledError:
|
|
263
|
-
if self.logger:
|
|
264
|
-
self.logger.info("📅 Scheduler cancelled")
|
|
265
|
-
except Exception as e:
|
|
266
|
-
if self.logger:
|
|
267
|
-
self.logger.error(f"❌ Scheduler error: {e}")
|
|
268
|
-
raise
|
|
269
|
-
finally:
|
|
270
|
-
await self._cleanup_running_tasks()
|
|
271
|
-
|
|
272
|
-
async def stop(self):
|
|
273
|
-
"""Stop the scheduler gracefully."""
|
|
274
|
-
if not self._is_running:
|
|
275
|
-
return
|
|
276
|
-
|
|
277
|
-
if self.logger:
|
|
278
|
-
self.logger.info("📅 Stopping scheduler...")
|
|
279
|
-
|
|
280
|
-
self._is_running = False
|
|
281
|
-
self._shutdown_event.set()
|
|
282
|
-
|
|
283
|
-
# Wait for running tasks to complete
|
|
284
|
-
await self._cleanup_running_tasks()
|
|
285
|
-
|
|
286
|
-
if self.logger:
|
|
287
|
-
self.logger.info("✅ Scheduler stopped")
|
|
288
|
-
|
|
289
|
-
async def _scheduler_tick(self):
|
|
290
|
-
"""Single scheduler tick - check and execute due tasks."""
|
|
291
|
-
now = datetime.now()
|
|
292
|
-
|
|
293
|
-
# Find tasks that are due
|
|
294
|
-
due_tasks = [
|
|
295
|
-
task
|
|
296
|
-
for task in self._tasks.values()
|
|
297
|
-
if (
|
|
298
|
-
task.enabled
|
|
299
|
-
and task.next_run
|
|
300
|
-
and task.next_run <= now
|
|
301
|
-
and task.task_id not in self._running_tasks
|
|
302
|
-
and (task.max_runs is None or task.runs_count < task.max_runs)
|
|
303
|
-
)
|
|
304
|
-
]
|
|
305
|
-
|
|
306
|
-
# Check concurrent task limit
|
|
307
|
-
available_slots = self.max_concurrent - len(self._running_tasks)
|
|
308
|
-
due_tasks = due_tasks[:available_slots]
|
|
309
|
-
|
|
310
|
-
# Execute due tasks
|
|
311
|
-
for task in due_tasks:
|
|
312
|
-
await self._execute_task(task)
|
|
313
|
-
|
|
314
|
-
async def _execute_task(self, task: ScheduledTask):
|
|
315
|
-
"""Execute a scheduled task."""
|
|
316
|
-
if self.logger:
|
|
317
|
-
self.logger.info(f"🚀 Executing task: {task.name}")
|
|
318
|
-
|
|
319
|
-
# Create execution coroutine
|
|
320
|
-
execution_coro = self._run_task_with_monitoring(task)
|
|
321
|
-
|
|
322
|
-
# Start task
|
|
323
|
-
task_handle = asyncio.create_task(execution_coro)
|
|
324
|
-
self._running_tasks[task.task_id] = task_handle
|
|
325
|
-
|
|
326
|
-
# Update task status
|
|
327
|
-
task.status = ScheduledTaskStatus.RUNNING
|
|
328
|
-
task.last_run = datetime.now()
|
|
329
|
-
|
|
330
|
-
async def _run_task_with_monitoring(self, task: ScheduledTask):
|
|
331
|
-
"""Run task with full monitoring and error handling."""
|
|
332
|
-
start_time = time.time()
|
|
333
|
-
|
|
334
|
-
try:
|
|
335
|
-
# Execute with timeout
|
|
336
|
-
await asyncio.wait_for(task.func(), timeout=task.timeout)
|
|
337
|
-
|
|
338
|
-
# Task completed successfully
|
|
339
|
-
duration = time.time() - start_time
|
|
340
|
-
task.status = ScheduledTaskStatus.COMPLETED
|
|
341
|
-
task.runs_count += 1
|
|
342
|
-
task.last_duration = duration
|
|
343
|
-
task.last_error = None
|
|
344
|
-
|
|
345
|
-
# Calculate next run
|
|
346
|
-
task.next_run = self._calculate_next_run(task)
|
|
347
|
-
|
|
348
|
-
if self.logger:
|
|
349
|
-
self.logger.info(f"✅ Task completed: {task.name} ({duration:.2f}s)")
|
|
350
|
-
|
|
351
|
-
# Record metrics
|
|
352
|
-
if self.metrics:
|
|
353
|
-
self.metrics.record_operation(
|
|
354
|
-
service="scheduler",
|
|
355
|
-
operation="task_execution",
|
|
356
|
-
duration=duration,
|
|
357
|
-
result_count=1,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
except asyncio.TimeoutError:
|
|
361
|
-
duration = time.time() - start_time
|
|
362
|
-
error_msg = f"Task timeout after {duration:.2f}s"
|
|
363
|
-
|
|
364
|
-
task.status = ScheduledTaskStatus.FAILED
|
|
365
|
-
task.last_duration = duration
|
|
366
|
-
task.last_error = error_msg
|
|
367
|
-
|
|
368
|
-
if self.logger:
|
|
369
|
-
self.logger.error(f"⏰ Task timeout: {task.name}")
|
|
370
|
-
|
|
371
|
-
await self._handle_task_failure(task, error_msg)
|
|
372
|
-
|
|
373
|
-
except Exception as e:
|
|
374
|
-
duration = time.time() - start_time
|
|
375
|
-
error_msg = str(e)
|
|
376
|
-
|
|
377
|
-
task.status = ScheduledTaskStatus.FAILED
|
|
378
|
-
task.last_duration = duration
|
|
379
|
-
task.last_error = error_msg
|
|
380
|
-
|
|
381
|
-
if self.logger:
|
|
382
|
-
self.logger.error(f"❌ Task failed: {task.name} - {error_msg}")
|
|
383
|
-
|
|
384
|
-
await self._handle_task_failure(task, error_msg)
|
|
385
|
-
|
|
386
|
-
finally:
|
|
387
|
-
# Remove from running tasks
|
|
388
|
-
if task.task_id in self._running_tasks:
|
|
389
|
-
del self._running_tasks[task.task_id]
|
|
390
|
-
|
|
391
|
-
async def _handle_task_failure(self, task: ScheduledTask, error_msg: str):
|
|
392
|
-
"""Handle task failure with retry logic."""
|
|
393
|
-
# TODO: Implement retry logic if needed
|
|
394
|
-
# Calculate next run even for failed tasks
|
|
395
|
-
task.next_run = self._calculate_next_run(task)
|
|
396
|
-
|
|
397
|
-
# Record error metrics
|
|
398
|
-
if self.metrics:
|
|
399
|
-
self.metrics.record_operation(
|
|
400
|
-
service="scheduler",
|
|
401
|
-
operation="task_execution",
|
|
402
|
-
duration=task.last_duration or 0,
|
|
403
|
-
result_count=0,
|
|
404
|
-
error=error_msg,
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
def _calculate_next_run(self, task: ScheduledTask) -> datetime:
|
|
408
|
-
"""Calculate next run time for a task."""
|
|
409
|
-
now = datetime.now()
|
|
410
|
-
base_next = now + timedelta(seconds=task.schedule.interval_seconds)
|
|
411
|
-
|
|
412
|
-
# Apply jitter if enabled
|
|
413
|
-
if self.enable_jitter:
|
|
414
|
-
jitter_seconds = task.schedule.interval_seconds * self.jitter_range
|
|
415
|
-
import random
|
|
416
|
-
|
|
417
|
-
jitter = random.uniform(-jitter_seconds, jitter_seconds)
|
|
418
|
-
base_next += timedelta(seconds=jitter)
|
|
419
|
-
|
|
420
|
-
return base_next
|
|
421
|
-
|
|
422
|
-
async def _cleanup_running_tasks(self):
|
|
423
|
-
"""Clean up running tasks during shutdown."""
|
|
424
|
-
if not self._running_tasks:
|
|
425
|
-
return
|
|
426
|
-
|
|
427
|
-
if self.logger:
|
|
428
|
-
self.logger.info(f"🧹 Cleaning up {len(self._running_tasks)} running tasks...")
|
|
429
|
-
|
|
430
|
-
# Cancel all running tasks
|
|
431
|
-
for task_id, task_handle in self._running_tasks.items():
|
|
432
|
-
task_handle.cancel()
|
|
433
|
-
|
|
434
|
-
# Wait for cancellation
|
|
435
|
-
if self._running_tasks:
|
|
436
|
-
await asyncio.gather(*self._running_tasks.values(), return_exceptions=True)
|
|
437
|
-
|
|
438
|
-
self._running_tasks.clear()
|
|
439
|
-
|
|
440
|
-
# ==========================================
|
|
441
|
-
# STATUS AND MONITORING
|
|
442
|
-
# ==========================================
|
|
443
|
-
|
|
444
|
-
def get_task_status(self, task_id: str) -> Optional[dict]:
|
|
445
|
-
"""Get status of a specific task."""
|
|
446
|
-
if task_id not in self._tasks:
|
|
447
|
-
return None
|
|
448
|
-
|
|
449
|
-
task = self._tasks[task_id]
|
|
450
|
-
return {
|
|
451
|
-
"task_id": task.task_id,
|
|
452
|
-
"name": task.name,
|
|
453
|
-
"status": task.status.value,
|
|
454
|
-
"enabled": task.enabled,
|
|
455
|
-
"runs_count": task.runs_count,
|
|
456
|
-
"last_run": task.last_run.isoformat() if task.last_run else None,
|
|
457
|
-
"next_run": task.next_run.isoformat() if task.next_run else None,
|
|
458
|
-
"last_duration": task.last_duration,
|
|
459
|
-
"last_error": task.last_error,
|
|
460
|
-
"schedule": task.schedule.expression,
|
|
461
|
-
"interval_seconds": task.schedule.interval_seconds,
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
def get_all_tasks_status(self) -> dict:
|
|
465
|
-
"""Get status of all tasks."""
|
|
466
|
-
return {task_id: self.get_task_status(task_id) for task_id in self._tasks}
|
|
467
|
-
|
|
468
|
-
async def health_check(self) -> dict:
|
|
469
|
-
"""Check scheduler service health."""
|
|
470
|
-
return {
|
|
471
|
-
"status": "healthy" if self._is_running else "stopped",
|
|
472
|
-
"is_running": self._is_running,
|
|
473
|
-
"total_tasks": len(self._tasks),
|
|
474
|
-
"enabled_tasks": sum(1 for t in self._tasks.values() if t.enabled),
|
|
475
|
-
"running_tasks": len(self._running_tasks),
|
|
476
|
-
"max_concurrent": self.max_concurrent,
|
|
477
|
-
"tasks": self.get_all_tasks_status(),
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async def cleanup(self):
|
|
481
|
-
"""Clean up scheduler resources."""
|
|
482
|
-
await self.stop()
|
|
483
|
-
self._tasks.clear()
|
|
484
|
-
|
|
485
|
-
if self.logger:
|
|
486
|
-
self.logger.info("Scheduler service cleanup completed")
|
|
487
|
-
|
|
488
|
-
def __repr__(self) -> str:
|
|
489
|
-
return f"<SchedulerService(running={self._is_running}, tasks={len(self._tasks)})>"
|