jettask 0.2.19__py3-none-any.whl → 0.2.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. jettask/__init__.py +10 -3
  2. jettask/cli.py +314 -228
  3. jettask/config/__init__.py +9 -1
  4. jettask/config/config.py +245 -0
  5. jettask/config/env_loader.py +381 -0
  6. jettask/config/lua_scripts.py +158 -0
  7. jettask/config/nacos_config.py +132 -5
  8. jettask/core/__init__.py +1 -1
  9. jettask/core/app.py +1573 -666
  10. jettask/core/app_importer.py +33 -16
  11. jettask/core/container.py +532 -0
  12. jettask/core/task.py +1 -4
  13. jettask/core/unified_manager_base.py +2 -2
  14. jettask/executor/__init__.py +38 -0
  15. jettask/executor/core.py +625 -0
  16. jettask/executor/executor.py +338 -0
  17. jettask/executor/orchestrator.py +290 -0
  18. jettask/executor/process_entry.py +638 -0
  19. jettask/executor/task_executor.py +317 -0
  20. jettask/messaging/__init__.py +68 -0
  21. jettask/messaging/event_pool.py +2188 -0
  22. jettask/messaging/reader.py +519 -0
  23. jettask/messaging/registry.py +266 -0
  24. jettask/messaging/scanner.py +369 -0
  25. jettask/messaging/sender.py +312 -0
  26. jettask/persistence/__init__.py +118 -0
  27. jettask/persistence/backlog_monitor.py +567 -0
  28. jettask/{backend/data_access.py → persistence/base.py} +58 -57
  29. jettask/persistence/consumer.py +315 -0
  30. jettask/{core → persistence}/db_manager.py +23 -22
  31. jettask/persistence/maintenance.py +81 -0
  32. jettask/persistence/message_consumer.py +259 -0
  33. jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
  34. jettask/persistence/offline_recovery.py +196 -0
  35. jettask/persistence/queue_discovery.py +215 -0
  36. jettask/persistence/task_persistence.py +218 -0
  37. jettask/persistence/task_updater.py +583 -0
  38. jettask/scheduler/__init__.py +2 -2
  39. jettask/scheduler/loader.py +6 -5
  40. jettask/scheduler/run_scheduler.py +1 -1
  41. jettask/scheduler/scheduler.py +7 -7
  42. jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
  43. jettask/task/__init__.py +16 -0
  44. jettask/{router.py → task/router.py} +26 -8
  45. jettask/task/task_center/__init__.py +9 -0
  46. jettask/task/task_executor.py +318 -0
  47. jettask/task/task_registry.py +291 -0
  48. jettask/test_connection_monitor.py +73 -0
  49. jettask/utils/__init__.py +31 -1
  50. jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
  51. jettask/utils/db_connector.py +1629 -0
  52. jettask/{db_init.py → utils/db_init.py} +1 -1
  53. jettask/utils/rate_limit/__init__.py +30 -0
  54. jettask/utils/rate_limit/concurrency_limiter.py +665 -0
  55. jettask/utils/rate_limit/config.py +145 -0
  56. jettask/utils/rate_limit/limiter.py +41 -0
  57. jettask/utils/rate_limit/manager.py +269 -0
  58. jettask/utils/rate_limit/qps_limiter.py +154 -0
  59. jettask/utils/rate_limit/task_limiter.py +384 -0
  60. jettask/utils/serializer.py +3 -0
  61. jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
  62. jettask/utils/time_sync.py +173 -0
  63. jettask/webui/__init__.py +27 -0
  64. jettask/{api/v1 → webui/api}/alerts.py +1 -1
  65. jettask/{api/v1 → webui/api}/analytics.py +2 -2
  66. jettask/{api/v1 → webui/api}/namespaces.py +1 -1
  67. jettask/{api/v1 → webui/api}/overview.py +1 -1
  68. jettask/{api/v1 → webui/api}/queues.py +3 -3
  69. jettask/{api/v1 → webui/api}/scheduled.py +1 -1
  70. jettask/{api/v1 → webui/api}/settings.py +1 -1
  71. jettask/{api.py → webui/app.py} +253 -145
  72. jettask/webui/namespace_manager/__init__.py +10 -0
  73. jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
  74. jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
  75. jettask/{run.py → webui/run.py} +2 -2
  76. jettask/{services → webui/services}/__init__.py +1 -3
  77. jettask/{services → webui/services}/overview_service.py +34 -16
  78. jettask/{services → webui/services}/queue_service.py +1 -1
  79. jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
  80. jettask/{services → webui/services}/settings_service.py +1 -1
  81. jettask/worker/__init__.py +53 -0
  82. jettask/worker/lifecycle.py +1507 -0
  83. jettask/worker/manager.py +583 -0
  84. jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
  85. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/METADATA +2 -71
  86. jettask-0.2.20.dist-info/RECORD +145 -0
  87. jettask/__main__.py +0 -140
  88. jettask/api/__init__.py +0 -103
  89. jettask/backend/__init__.py +0 -1
  90. jettask/backend/api/__init__.py +0 -3
  91. jettask/backend/api/v1/__init__.py +0 -17
  92. jettask/backend/api/v1/monitoring.py +0 -431
  93. jettask/backend/api/v1/namespaces.py +0 -504
  94. jettask/backend/api/v1/queues.py +0 -342
  95. jettask/backend/api/v1/tasks.py +0 -367
  96. jettask/backend/core/__init__.py +0 -3
  97. jettask/backend/core/cache.py +0 -221
  98. jettask/backend/core/database.py +0 -200
  99. jettask/backend/core/exceptions.py +0 -102
  100. jettask/backend/dependencies.py +0 -261
  101. jettask/backend/init_meta_db.py +0 -158
  102. jettask/backend/main.py +0 -1426
  103. jettask/backend/main_unified.py +0 -78
  104. jettask/backend/main_v2.py +0 -394
  105. jettask/backend/models/__init__.py +0 -3
  106. jettask/backend/models/requests.py +0 -236
  107. jettask/backend/models/responses.py +0 -230
  108. jettask/backend/namespace_api_old.py +0 -267
  109. jettask/backend/services/__init__.py +0 -3
  110. jettask/backend/start.py +0 -42
  111. jettask/backend/unified_api_router.py +0 -1541
  112. jettask/cleanup_deprecated_tables.sql +0 -16
  113. jettask/core/consumer_manager.py +0 -1695
  114. jettask/core/delay_scanner.py +0 -256
  115. jettask/core/event_pool.py +0 -1700
  116. jettask/core/heartbeat_process.py +0 -222
  117. jettask/core/task_batch.py +0 -153
  118. jettask/core/worker_scanner.py +0 -271
  119. jettask/executors/__init__.py +0 -5
  120. jettask/executors/asyncio.py +0 -876
  121. jettask/executors/base.py +0 -30
  122. jettask/executors/common.py +0 -148
  123. jettask/executors/multi_asyncio.py +0 -309
  124. jettask/gradio_app.py +0 -570
  125. jettask/integrated_gradio_app.py +0 -1088
  126. jettask/main.py +0 -0
  127. jettask/monitoring/__init__.py +0 -3
  128. jettask/pg_consumer.py +0 -1896
  129. jettask/run_monitor.py +0 -22
  130. jettask/run_webui.py +0 -148
  131. jettask/scheduler/multi_namespace_scheduler.py +0 -294
  132. jettask/scheduler/unified_manager.py +0 -450
  133. jettask/task_center_client.py +0 -150
  134. jettask/utils/serializer_optimized.py +0 -33
  135. jettask/webui_exceptions.py +0 -67
  136. jettask-0.2.19.dist-info/RECORD +0 -150
  137. /jettask/{constants.py → config/constants.py} +0 -0
  138. /jettask/{backend/config.py → config/task_center.py} +0 -0
  139. /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
  140. /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
  141. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
  142. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
  143. /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
  144. /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
  145. /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
  146. /jettask/{models.py → persistence/models.py} +0 -0
  147. /jettask/scheduler/{manager.py → task_crud.py} +0 -0
  148. /jettask/{schema.sql → schemas/schema.sql} +0 -0
  149. /jettask/{task_center.py → task/task_center/client.py} +0 -0
  150. /jettask/{monitoring → utils}/file_watcher.py +0 -0
  151. /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
  152. /jettask/{api/v1 → webui/api}/__init__.py +0 -0
  153. /jettask/{webui_config.py → webui/config.py} +0 -0
  154. /jettask/{webui_models → webui/models}/__init__.py +0 -0
  155. /jettask/{webui_models → webui/models}/namespace.py +0 -0
  156. /jettask/{services → webui/services}/alert_service.py +0 -0
  157. /jettask/{services → webui/services}/analytics_service.py +0 -0
  158. /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
  159. /jettask/{services → webui/services}/task_service.py +0 -0
  160. /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
  161. /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
  162. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/WHEEL +0 -0
  163. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/entry_points.txt +0 -0
  164. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/licenses/LICENSE +0 -0
  165. {jettask-0.2.19.dist-info → jettask-0.2.20.dist-info}/top_level.txt +0 -0
@@ -3,5 +3,13 @@ Jettask配置模块
3
3
  """
4
4
 
5
5
  from .performance import PerformanceConfig, perf_config, env_config
6
+ from .config import JetTaskConfig
7
+ from . import lua_scripts
6
8
 
7
- __all__ = ['PerformanceConfig', 'perf_config', 'env_config']
9
+ __all__ = [
10
+ 'PerformanceConfig',
11
+ 'perf_config',
12
+ 'env_config',
13
+ 'JetTaskConfig',
14
+ 'lua_scripts',
15
+ ]
@@ -0,0 +1,245 @@
1
+ """
2
+ 配置管理模块 - 统一的配置定义
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional, Dict, Any
7
+
8
+
9
+ @dataclass
10
+ class RedisConfig:
11
+ """Redis 配置"""
12
+ url: str = "redis://localhost:6379/0"
13
+ prefix: str = "jettask"
14
+
15
+ # 连接池配置
16
+ max_connections: int = 50
17
+ decode_responses: bool = True # 文本模式客户端
18
+
19
+ def __post_init__(self):
20
+ """验证配置"""
21
+ if not self.url:
22
+ raise ValueError("Redis URL is required")
23
+ if not self.prefix:
24
+ raise ValueError("Redis prefix is required")
25
+
26
+
27
+ @dataclass
28
+ class ExecutorConfig:
29
+ """执行器配置"""
30
+ type: str = "asyncio" # asyncio, multi_asyncio, process, thread
31
+ concurrency: int = 10
32
+ prefetch_multiplier: int = 1 # 预取倍数
33
+
34
+ # Worker配置
35
+ worker_heartbeat_interval: float = 1.0 # 心跳间隔(秒)
36
+ worker_heartbeat_timeout: float = 3.0 # 心跳超时(秒)
37
+
38
+ def __post_init__(self):
39
+ """验证配置"""
40
+ if self.concurrency < 1:
41
+ raise ValueError("Concurrency must be at least 1")
42
+ if self.prefetch_multiplier < 1:
43
+ raise ValueError("Prefetch multiplier must be at least 1")
44
+
45
+
46
+ @dataclass
47
+ class RateLimitConfig:
48
+ """限流配置"""
49
+ enabled: bool = False
50
+ strategy: str = "local" # local, ondemand
51
+
52
+ # Local sliding window配置
53
+ qps_limit: Optional[int] = None
54
+ window_size: float = 1.0
55
+ sync_interval: float = 5.0 # 配额同步间隔(秒)
56
+
57
+ def __post_init__(self):
58
+ """验证配置"""
59
+ if self.enabled and not self.qps_limit:
60
+ raise ValueError("QPS limit is required when rate limit is enabled")
61
+
62
+
63
+ @dataclass
64
+ class MessageConfig:
65
+ """消息配置"""
66
+ # 延迟队列扫描
67
+ delayed_scan_interval: float = 0.05 # 扫描间隔(秒)
68
+ delayed_batch_size: int = 100 # 批量处理大小
69
+
70
+ # 消息读取
71
+ read_block_time: int = 1000 # 阻塞读取时间(毫秒)
72
+ read_batch_size: int = 1 # 每次读取消息数
73
+
74
+ # 消息重试
75
+ max_retries: int = 3
76
+ retry_backoff: float = 1.0 # 重试退避时间(秒)
77
+
78
+
79
+ @dataclass
80
+ class ConsumerConfig:
81
+ """消费者配置"""
82
+ strategy: str = "heartbeat" # heartbeat, reuse
83
+
84
+ # 心跳策略配置
85
+ heartbeat_interval: float = 1.0
86
+ heartbeat_timeout: float = 3.0
87
+
88
+ # 复用策略配置
89
+ reuse_timeout: float = 60.0
90
+
91
+ def __post_init__(self):
92
+ """验证配置"""
93
+ valid_strategies = ["heartbeat", "reuse"]
94
+ if self.strategy not in valid_strategies:
95
+ raise ValueError(f"Consumer strategy must be one of {valid_strategies}")
96
+
97
+
98
+ @dataclass
99
+ class WorkerConfig:
100
+ """Worker 配置"""
101
+ worker_id: Optional[str] = None # 如果不指定,会自动生成
102
+ hostname: Optional[str] = None # 如果不指定,会自动获取
103
+
104
+ # Worker状态管理
105
+ state_sync_enabled: bool = True
106
+ state_pubsub_enabled: bool = True # 是否启用状态变化的Pub/Sub通知
107
+
108
+ # Worker扫描配置
109
+ scanner_enabled: bool = True
110
+ scanner_interval: float = 1.0 # 扫描间隔(秒)
111
+
112
+
113
+ @dataclass
114
+ class JetTaskConfig:
115
+ """
116
+ JetTask 统一配置
117
+
118
+ 示例:
119
+ config = JetTaskConfig(
120
+ redis=RedisConfig(
121
+ url="redis://localhost:6379/0",
122
+ prefix="myapp"
123
+ ),
124
+ executor=ExecutorConfig(
125
+ type="asyncio",
126
+ concurrency=20
127
+ ),
128
+ rate_limit=RateLimitConfig(
129
+ enabled=True,
130
+ qps_limit=100
131
+ )
132
+ )
133
+ """
134
+ redis: RedisConfig = field(default_factory=RedisConfig)
135
+ executor: ExecutorConfig = field(default_factory=ExecutorConfig)
136
+ rate_limit: RateLimitConfig = field(default_factory=RateLimitConfig)
137
+ message: MessageConfig = field(default_factory=MessageConfig)
138
+ consumer: ConsumerConfig = field(default_factory=ConsumerConfig)
139
+ worker: WorkerConfig = field(default_factory=WorkerConfig)
140
+
141
+ # 其他全局配置
142
+ debug: bool = False
143
+ extra: Dict[str, Any] = field(default_factory=dict)
144
+
145
+ @classmethod
146
+ def from_dict(cls, config_dict: Dict[str, Any]) -> 'JetTaskConfig':
147
+ """
148
+ 从字典创建配置对象
149
+
150
+ Args:
151
+ config_dict: 配置字典
152
+
153
+ Returns:
154
+ JetTaskConfig实例
155
+
156
+ 示例:
157
+ config = JetTaskConfig.from_dict({
158
+ 'redis': {
159
+ 'url': 'redis://localhost:6379/0',
160
+ 'prefix': 'myapp'
161
+ },
162
+ 'executor': {
163
+ 'concurrency': 20
164
+ }
165
+ })
166
+ """
167
+ redis_config = RedisConfig(**config_dict.get('redis', {}))
168
+ executor_config = ExecutorConfig(**config_dict.get('executor', {}))
169
+ rate_limit_config = RateLimitConfig(**config_dict.get('rate_limit', {}))
170
+ message_config = MessageConfig(**config_dict.get('message', {}))
171
+ consumer_config = ConsumerConfig(**config_dict.get('consumer', {}))
172
+ worker_config = WorkerConfig(**config_dict.get('worker', {}))
173
+
174
+ return cls(
175
+ redis=redis_config,
176
+ executor=executor_config,
177
+ rate_limit=rate_limit_config,
178
+ message=message_config,
179
+ consumer=consumer_config,
180
+ worker=worker_config,
181
+ debug=config_dict.get('debug', False),
182
+ extra=config_dict.get('extra', {})
183
+ )
184
+
185
+ def to_dict(self) -> Dict[str, Any]:
186
+ """
187
+ 转换为字典
188
+
189
+ Returns:
190
+ 配置字典
191
+ """
192
+ from dataclasses import asdict
193
+ return asdict(self)
194
+
195
+ def validate(self):
196
+ """验证所有配置"""
197
+ # 各个子配置在__post_init__中已经验证
198
+ # 这里可以添加跨配置的验证逻辑
199
+ pass
200
+
201
+
202
+ # 便捷函数:创建默认配置
203
+ def create_default_config(redis_url: str = "redis://localhost:6379/0",
204
+ redis_prefix: str = "jettask") -> JetTaskConfig:
205
+ """
206
+ 创建默认配置
207
+
208
+ Args:
209
+ redis_url: Redis连接URL
210
+ redis_prefix: Redis键前缀
211
+
212
+ Returns:
213
+ 默认配置对象
214
+ """
215
+ return JetTaskConfig(
216
+ redis=RedisConfig(url=redis_url, prefix=redis_prefix)
217
+ )
218
+
219
+
220
+ # 便捷函数:从环境变量创建配置
221
+ def create_config_from_env() -> JetTaskConfig:
222
+ """
223
+ 从环境变量创建配置
224
+
225
+ 环境变量:
226
+ JETTASK_REDIS_URL: Redis连接URL
227
+ JETTASK_REDIS_PREFIX: Redis键前缀
228
+ JETTASK_EXECUTOR_CONCURRENCY: 并发数
229
+ JETTASK_DEBUG: 调试模式
230
+
231
+ Returns:
232
+ 配置对象
233
+ """
234
+ import os
235
+
236
+ redis_url = os.getenv('JETTASK_REDIS_URL', 'redis://localhost:6379/0')
237
+ redis_prefix = os.getenv('JETTASK_REDIS_PREFIX', 'jettask')
238
+ concurrency = int(os.getenv('JETTASK_EXECUTOR_CONCURRENCY', '10'))
239
+ debug = os.getenv('JETTASK_DEBUG', 'false').lower() == 'true'
240
+
241
+ return JetTaskConfig(
242
+ redis=RedisConfig(url=redis_url, prefix=redis_prefix),
243
+ executor=ExecutorConfig(concurrency=concurrency),
244
+ debug=debug
245
+ )
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 环境变量加载器
4
+
5
+ 提供统一的环境变量加载和管理功能
6
+ """
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Optional, Dict, Any
10
+ from dotenv import load_dotenv
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class EnvLoader:
17
+ """环境变量加载器
18
+
19
+ 功能:
20
+ 1. 从.env文件加载环境变量
21
+ 2. 支持多个.env文件按优先级加载
22
+ 3. 提供类型安全的环境变量获取方法
23
+ 4. 支持默认值
24
+
25
+ 使用示例:
26
+ # 基本使用
27
+ loader = EnvLoader()
28
+ loader.load_env_file('.env')
29
+ redis_url = loader.get('JETTASK_REDIS_URL')
30
+
31
+ # 加载多个文件(后加载的覆盖先加载的)
32
+ loader = EnvLoader()
33
+ loader.load_env_file('.env')
34
+ loader.load_env_file('.env.local', override=True)
35
+
36
+ # 类型转换
37
+ max_conn = loader.get_int('JETTASK_MAX_CONNECTIONS', default=200)
38
+ debug = loader.get_bool('DEBUG', default=False)
39
+
40
+ # 批量获取配置
41
+ config = loader.get_config_dict('JETTASK_')
42
+ """
43
+
44
+ def __init__(self, auto_load: bool = False):
45
+ """
46
+ 初始化环境变量加载器
47
+
48
+ Args:
49
+ auto_load: 是否自动加载当前目录的.env文件
50
+ """
51
+ self._loaded_files = []
52
+
53
+ if auto_load:
54
+ self.auto_load()
55
+
56
+ def auto_load(self, search_paths: list = None) -> bool:
57
+ """
58
+ 自动查找并加载.env文件
59
+
60
+ 查找顺序:
61
+ 1. .env.local (本地开发配置,优先级最高)
62
+ 2. .env.{ENVIRONMENT} (如 .env.production, .env.development)
63
+ 3. .env (默认配置)
64
+
65
+ Args:
66
+ search_paths: 搜索路径列表,默认为当前目录
67
+
68
+ Returns:
69
+ 是否成功加载至少一个文件
70
+ """
71
+ if search_paths is None:
72
+ search_paths = [Path.cwd()]
73
+
74
+ env = os.environ.get('ENVIRONMENT', os.environ.get('ENV', 'development'))
75
+
76
+ # 按优先级查找文件
77
+ env_files = [
78
+ '.env',
79
+ f'.env.{env}',
80
+ '.env.local',
81
+ ]
82
+
83
+ loaded_any = False
84
+ for search_path in search_paths:
85
+ search_path = Path(search_path)
86
+ for env_file in env_files:
87
+ file_path = search_path / env_file
88
+ if file_path.exists():
89
+ self.load_env_file(str(file_path), override=True)
90
+ loaded_any = True
91
+
92
+ return loaded_any
93
+
94
+ def load_env_file(self, file_path: str, override: bool = True) -> bool:
95
+ """
96
+ 从文件加载环境变量
97
+
98
+ Args:
99
+ file_path: .env文件路径
100
+ override: 是否覆盖已存在的环境变量
101
+
102
+ Returns:
103
+ 是否成功加载
104
+
105
+ Raises:
106
+ FileNotFoundError: 文件不存在
107
+ """
108
+ path = Path(file_path).resolve()
109
+
110
+ if not path.exists():
111
+ raise FileNotFoundError(f"Environment file not found: {file_path}")
112
+
113
+ if not path.is_file():
114
+ raise ValueError(f"Not a file: {file_path}")
115
+
116
+ logger.info(f"Loading environment variables from: {path}")
117
+
118
+ # 加载环境变量
119
+ success = load_dotenv(str(path), override=override)
120
+
121
+ if success:
122
+ self._loaded_files.append(str(path))
123
+ logger.info(f"✓ Loaded environment variables from: {path}")
124
+ else:
125
+ logger.warning(f"Failed to load environment variables from: {path}")
126
+
127
+ return success
128
+
129
+ def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
130
+ """
131
+ 获取字符串类型的环境变量
132
+
133
+ Args:
134
+ key: 环境变量名
135
+ default: 默认值
136
+
137
+ Returns:
138
+ 环境变量值或默认值
139
+ """
140
+ return os.environ.get(key, default)
141
+
142
+ def get_int(self, key: str, default: Optional[int] = None) -> Optional[int]:
143
+ """
144
+ 获取整数类型的环境变量
145
+
146
+ Args:
147
+ key: 环境变量名
148
+ default: 默认值
149
+
150
+ Returns:
151
+ 整数值或默认值
152
+
153
+ Raises:
154
+ ValueError: 环境变量值无法转换为整数
155
+ """
156
+ value = os.environ.get(key)
157
+ if value is None:
158
+ return default
159
+
160
+ try:
161
+ return int(value)
162
+ except ValueError:
163
+ raise ValueError(f"Environment variable {key}='{value}' cannot be converted to int")
164
+
165
+ def get_float(self, key: str, default: Optional[float] = None) -> Optional[float]:
166
+ """
167
+ 获取浮点数类型的环境变量
168
+
169
+ Args:
170
+ key: 环境变量名
171
+ default: 默认值
172
+
173
+ Returns:
174
+ 浮点数值或默认值
175
+
176
+ Raises:
177
+ ValueError: 环境变量值无法转换为浮点数
178
+ """
179
+ value = os.environ.get(key)
180
+ if value is None:
181
+ return default
182
+
183
+ try:
184
+ return float(value)
185
+ except ValueError:
186
+ raise ValueError(f"Environment variable {key}='{value}' cannot be converted to float")
187
+
188
+ def get_bool(self, key: str, default: Optional[bool] = None) -> Optional[bool]:
189
+ """
190
+ 获取布尔类型的环境变量
191
+
192
+ 支持的true值: true, 1, yes, on, enabled
193
+ 支持的false值: false, 0, no, off, disabled
194
+
195
+ Args:
196
+ key: 环境变量名
197
+ default: 默认值
198
+
199
+ Returns:
200
+ 布尔值或默认值
201
+
202
+ Raises:
203
+ ValueError: 环境变量值无法转换为布尔值
204
+ """
205
+ value = os.environ.get(key)
206
+ if value is None:
207
+ return default
208
+
209
+ value_lower = value.lower().strip()
210
+
211
+ if value_lower in ('true', '1', 'yes', 'on', 'enabled'):
212
+ return True
213
+ elif value_lower in ('false', '0', 'no', 'off', 'disabled'):
214
+ return False
215
+ else:
216
+ raise ValueError(
217
+ f"Environment variable {key}='{value}' cannot be converted to bool. "
218
+ f"Valid values: true/false, 1/0, yes/no, on/off, enabled/disabled"
219
+ )
220
+
221
+ def get_list(self, key: str, separator: str = ',', default: Optional[list] = None) -> Optional[list]:
222
+ """
223
+ 获取列表类型的环境变量(逗号分隔)
224
+
225
+ Args:
226
+ key: 环境变量名
227
+ separator: 分隔符,默认为逗号
228
+ default: 默认值
229
+
230
+ Returns:
231
+ 列表或默认值
232
+ """
233
+ value = os.environ.get(key)
234
+ if value is None:
235
+ return default or []
236
+
237
+ # 分割并去除空白
238
+ return [item.strip() for item in value.split(separator) if item.strip()]
239
+
240
+ def get_dict(self, key: str, default: Optional[dict] = None) -> Optional[dict]:
241
+ """
242
+ 获取字典类型的环境变量(JSON格式)
243
+
244
+ Args:
245
+ key: 环境变量名
246
+ default: 默认值
247
+
248
+ Returns:
249
+ 字典或默认值
250
+
251
+ Raises:
252
+ ValueError: JSON解析失败
253
+ """
254
+ value = os.environ.get(key)
255
+ if value is None:
256
+ return default or {}
257
+
258
+ try:
259
+ import json
260
+ return json.loads(value)
261
+ except json.JSONDecodeError as e:
262
+ raise ValueError(f"Environment variable {key}='{value}' is not valid JSON: {e}")
263
+
264
+ def get_config_dict(self, prefix: str = '') -> Dict[str, str]:
265
+ """
266
+ 获取所有指定前缀的环境变量(返回字典)
267
+
268
+ Args:
269
+ prefix: 环境变量前缀(如 'JETTASK_')
270
+
271
+ Returns:
272
+ 环境变量字典(键不包含前缀)
273
+
274
+ 示例:
275
+ # 假设环境变量: JETTASK_REDIS_URL=redis://localhost, JETTASK_PG_URL=postgresql://...
276
+ config = loader.get_config_dict('JETTASK_')
277
+ # 返回: {'REDIS_URL': 'redis://localhost', 'PG_URL': 'postgresql://...'}
278
+ """
279
+ config = {}
280
+ for key, value in os.environ.items():
281
+ if key.startswith(prefix):
282
+ # 去除前缀
283
+ config_key = key[len(prefix):]
284
+ config[config_key] = value
285
+
286
+ return config
287
+
288
+ def require(self, key: str) -> str:
289
+ """
290
+ 获取必需的环境变量(如果不存在则抛出异常)
291
+
292
+ Args:
293
+ key: 环境变量名
294
+
295
+ Returns:
296
+ 环境变量值
297
+
298
+ Raises:
299
+ ValueError: 环境变量未设置
300
+ """
301
+ value = os.environ.get(key)
302
+ if value is None:
303
+ raise ValueError(
304
+ f"Required environment variable '{key}' is not set. "
305
+ f"Please set it in your environment or .env file."
306
+ )
307
+ return value
308
+
309
+ def set(self, key: str, value: Any, override: bool = True) -> None:
310
+ """
311
+ 设置环境变量
312
+
313
+ Args:
314
+ key: 环境变量名
315
+ value: 值
316
+ override: 是否覆盖已存在的值
317
+ """
318
+ if override or key not in os.environ:
319
+ os.environ[key] = str(value)
320
+
321
+ def clear_all(self, prefix: str = '') -> int:
322
+ """
323
+ 清除所有指定前缀的环境变量
324
+
325
+ Args:
326
+ prefix: 环境变量前缀(如 'JETTASK_')
327
+
328
+ Returns:
329
+ 清除的环境变量数量
330
+ """
331
+ keys_to_remove = [key for key in os.environ.keys() if key.startswith(prefix)]
332
+ for key in keys_to_remove:
333
+ del os.environ[key]
334
+
335
+ return len(keys_to_remove)
336
+
337
+ def get_loaded_files(self) -> list:
338
+ """
339
+ 获取已加载的环境变量文件列表
340
+
341
+ Returns:
342
+ 文件路径列表
343
+ """
344
+ return self._loaded_files.copy()
345
+
346
+ def __repr__(self) -> str:
347
+ return f"EnvLoader(loaded_files={len(self._loaded_files)})"
348
+
349
+
350
+ # 全局单例实例
351
+ _global_loader = None
352
+
353
+
354
+ def get_env_loader() -> EnvLoader:
355
+ """获取全局环境变量加载器单例"""
356
+ global _global_loader
357
+ if _global_loader is None:
358
+ _global_loader = EnvLoader()
359
+ return _global_loader
360
+
361
+
362
+ # 便捷函数
363
+ def load_env(file_path: str = None, override: bool = True) -> EnvLoader:
364
+ """
365
+ 加载环境变量(便捷函数)
366
+
367
+ Args:
368
+ file_path: .env文件路径,如果为None则自动查找
369
+ override: 是否覆盖已存在的环境变量
370
+
371
+ Returns:
372
+ 环境变量加载器实例
373
+ """
374
+ loader = get_env_loader()
375
+
376
+ if file_path:
377
+ loader.load_env_file(file_path, override=override)
378
+ else:
379
+ loader.auto_load()
380
+
381
+ return loader