staran 0.6.1__py3-none-any.whl → 1.0.1__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.
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 日志系统测试
6
+ ==========
7
+
8
+ 测试Date类的日志功能,包括:
9
+ - 日志级别设置
10
+ - 日志消息记录
11
+ - 日志输出控制
12
+ - 性能日志
13
+ """
14
+
15
+ import unittest
16
+ import sys
17
+ import os
18
+ import logging
19
+ import io
20
+ from contextlib import redirect_stderr
21
+
22
+ # 添加项目根目录到路径
23
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
24
+
25
+ from staran.tools.date import Date
26
+
27
+
28
+ class TestLoggingSystem(unittest.TestCase):
29
+ """测试日志系统基本功能"""
30
+
31
+ def setUp(self):
32
+ """设置测试环境"""
33
+ # 重置日志级别
34
+ Date.set_log_level(logging.DEBUG)
35
+
36
+ # 创建字符串流来捕获日志输出
37
+ self.log_stream = io.StringIO()
38
+
39
+ # 获取Date的日志记录器
40
+ self.logger = logging.getLogger('staran.tools.date')
41
+
42
+ # 清除现有的处理器
43
+ for handler in self.logger.handlers[:]:
44
+ self.logger.removeHandler(handler)
45
+
46
+ # 添加流处理器
47
+ self.handler = logging.StreamHandler(self.log_stream)
48
+ self.handler.setLevel(logging.DEBUG)
49
+ formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
50
+ self.handler.setFormatter(formatter)
51
+ self.logger.addHandler(self.handler)
52
+ self.logger.setLevel(logging.DEBUG)
53
+
54
+ def tearDown(self):
55
+ """清理测试环境"""
56
+ # 移除处理器
57
+ self.logger.removeHandler(self.handler)
58
+ self.handler.close()
59
+
60
+ # 重置日志级别为ERROR,避免干扰其他测试
61
+ Date.set_log_level(logging.ERROR)
62
+
63
+ def get_log_output(self):
64
+ """获取日志输出"""
65
+ return self.log_stream.getvalue()
66
+
67
+ def test_log_level_setting(self):
68
+ """测试日志级别设置"""
69
+ # 测试设置不同的日志级别
70
+ Date.set_log_level(logging.INFO)
71
+ Date.set_log_level(logging.WARNING)
72
+ Date.set_log_level(logging.ERROR)
73
+ Date.set_log_level(logging.DEBUG)
74
+
75
+ # 验证级别设置成功(通过创建对象来触发日志)
76
+ self.log_stream.truncate(0)
77
+ self.log_stream.seek(0)
78
+
79
+ Date('20250415')
80
+ log_output = self.get_log_output()
81
+
82
+ # 应该有DEBUG级别的日志
83
+ self.assertIn('DEBUG', log_output)
84
+
85
+ def test_object_creation_logging(self):
86
+ """测试对象创建日志"""
87
+ self.log_stream.truncate(0)
88
+ self.log_stream.seek(0)
89
+
90
+ # 创建不同类型的Date对象
91
+ Date('20250415')
92
+ Date('202504')
93
+ Date('2025')
94
+ Date(2025, 4, 15)
95
+
96
+ log_output = self.get_log_output()
97
+
98
+ # 验证有创建日志
99
+ self.assertIn('创建Date对象', log_output)
100
+ self.assertIn('20250415', log_output)
101
+
102
+ def test_method_call_logging(self):
103
+ """测试方法调用日志"""
104
+ self.log_stream.truncate(0)
105
+ self.log_stream.seek(0)
106
+
107
+ date = Date('20250415')
108
+
109
+ # 调用各种方法
110
+ date.format_iso()
111
+ date.add_days(10)
112
+ date.get_weekday()
113
+ date.is_weekend()
114
+
115
+ log_output = self.get_log_output()
116
+
117
+ # 验证有方法调用日志
118
+ self.assertIn('调用方法', log_output)
119
+
120
+ def test_error_logging(self):
121
+ """测试错误日志"""
122
+ self.log_stream.truncate(0)
123
+ self.log_stream.seek(0)
124
+
125
+ # 触发错误
126
+ try:
127
+ Date('invalid_date')
128
+ except ValueError:
129
+ pass
130
+
131
+ log_output = self.get_log_output()
132
+
133
+ # 验证有错误日志
134
+ self.assertIn('ERROR', log_output)
135
+
136
+ def test_performance_logging(self):
137
+ """测试性能日志"""
138
+ self.log_stream.truncate(0)
139
+ self.log_stream.seek(0)
140
+
141
+ date = Date('20250415')
142
+
143
+ # 执行一些计算密集的操作
144
+ for i in range(10):
145
+ date.add_days(i)
146
+
147
+ log_output = self.get_log_output()
148
+
149
+ # 性能日志可能不会每次都出现,但应该有调用日志
150
+ self.assertIn('调用方法', log_output)
151
+
152
+
153
+ class TestLoggingConfiguration(unittest.TestCase):
154
+ """测试日志配置"""
155
+
156
+ def setUp(self):
157
+ """设置测试环境"""
158
+ Date.set_log_level(logging.ERROR) # 默认高级别
159
+
160
+ def test_log_level_effects(self):
161
+ """测试日志级别效果"""
162
+ # 创建测试流
163
+ log_stream = io.StringIO()
164
+ logger = logging.getLogger('staran.tools.date')
165
+
166
+ # 清除现有处理器
167
+ for handler in logger.handlers[:]:
168
+ logger.removeHandler(handler)
169
+
170
+ # 添加新处理器
171
+ handler = logging.StreamHandler(log_stream)
172
+ handler.setLevel(logging.DEBUG)
173
+ formatter = logging.Formatter('%(levelname)s:%(message)s')
174
+ handler.setFormatter(formatter)
175
+ logger.addHandler(handler)
176
+
177
+ try:
178
+ # 测试ERROR级别
179
+ Date.set_log_level(logging.ERROR)
180
+ logger.setLevel(logging.ERROR)
181
+
182
+ log_stream.truncate(0)
183
+ log_stream.seek(0)
184
+
185
+ Date('20250415')
186
+ error_output = log_stream.getvalue()
187
+
188
+ # 测试DEBUG级别
189
+ Date.set_log_level(logging.DEBUG)
190
+ logger.setLevel(logging.DEBUG)
191
+
192
+ log_stream.truncate(0)
193
+ log_stream.seek(0)
194
+
195
+ Date('20250415')
196
+ debug_output = log_stream.getvalue()
197
+
198
+ # DEBUG级别应该有更多输出
199
+ self.assertGreaterEqual(len(debug_output), len(error_output))
200
+
201
+ finally:
202
+ logger.removeHandler(handler)
203
+ handler.close()
204
+
205
+ def test_log_message_format(self):
206
+ """测试日志消息格式"""
207
+ log_stream = io.StringIO()
208
+ logger = logging.getLogger('staran.tools.date')
209
+
210
+ # 清除现有处理器
211
+ for handler in logger.handlers[:]:
212
+ logger.removeHandler(handler)
213
+
214
+ handler = logging.StreamHandler(log_stream)
215
+ handler.setLevel(logging.DEBUG)
216
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
217
+ handler.setFormatter(formatter)
218
+ logger.addHandler(handler)
219
+ logger.setLevel(logging.DEBUG)
220
+
221
+ try:
222
+ Date.set_log_level(logging.DEBUG)
223
+
224
+ Date('20250415')
225
+ output = log_stream.getvalue()
226
+
227
+ # 验证日志格式包含时间戳、名称、级别、消息
228
+ if output: # 如果有输出
229
+ lines = output.strip().split('\n')
230
+ for line in lines:
231
+ parts = line.split(' - ')
232
+ if len(parts) >= 4:
233
+ self.assertIn('staran.tools.date', parts[1])
234
+ self.assertIn(parts[2], ['DEBUG', 'INFO', 'WARNING', 'ERROR'])
235
+
236
+ finally:
237
+ logger.removeHandler(handler)
238
+ handler.close()
239
+
240
+
241
+ class TestLoggingIntegration(unittest.TestCase):
242
+ """测试日志集成"""
243
+
244
+ def setUp(self):
245
+ """设置测试环境"""
246
+ Date.set_log_level(logging.WARNING)
247
+
248
+ def test_logging_with_various_operations(self):
249
+ """测试各种操作的日志集成"""
250
+ log_stream = io.StringIO()
251
+ logger = logging.getLogger('staran.tools.date')
252
+
253
+ # 清除现有处理器
254
+ for handler in logger.handlers[:]:
255
+ logger.removeHandler(handler)
256
+
257
+ handler = logging.StreamHandler(log_stream)
258
+ handler.setLevel(logging.DEBUG)
259
+ formatter = logging.Formatter('%(levelname)s:%(funcName)s:%(message)s')
260
+ handler.setFormatter(formatter)
261
+ logger.addHandler(handler)
262
+ logger.setLevel(logging.DEBUG)
263
+
264
+ try:
265
+ Date.set_log_level(logging.DEBUG)
266
+
267
+ # 执行各种操作
268
+ date = Date('20250415')
269
+ date.format_iso()
270
+ date.add_days(10)
271
+ date.get_weekday()
272
+ result = date.is_weekend()
273
+
274
+ output = log_stream.getvalue()
275
+
276
+ # 验证不同操作都有日志记录
277
+ if output:
278
+ self.assertIsInstance(output, str)
279
+ # 基本验证日志不为空
280
+ self.assertGreater(len(output.strip()), 0)
281
+
282
+ finally:
283
+ logger.removeHandler(handler)
284
+ handler.close()
285
+
286
+ def test_logging_performance_impact(self):
287
+ """测试日志对性能的影响"""
288
+ import time
289
+
290
+ # 测试无日志性能
291
+ Date.set_log_level(logging.CRITICAL)
292
+
293
+ start_time = time.time()
294
+ for i in range(100):
295
+ date = Date('20250415')
296
+ date.add_days(i % 10)
297
+ no_log_time = time.time() - start_time
298
+
299
+ # 测试有日志性能
300
+ Date.set_log_level(logging.DEBUG)
301
+
302
+ start_time = time.time()
303
+ for i in range(100):
304
+ date = Date('20250415')
305
+ date.add_days(i % 10)
306
+ with_log_time = time.time() - start_time
307
+
308
+ # 日志不应该显著影响性能(这里允许10倍的性能差异)
309
+ self.assertLess(with_log_time, no_log_time * 10)
310
+
311
+ # 重置日志级别
312
+ Date.set_log_level(logging.ERROR)
313
+
314
+
315
+ class TestLoggingEdgeCases(unittest.TestCase):
316
+ """测试日志边缘情况"""
317
+
318
+ def setUp(self):
319
+ """设置测试环境"""
320
+ Date.set_log_level(logging.ERROR)
321
+
322
+ def test_logging_with_invalid_operations(self):
323
+ """测试无效操作的日志"""
324
+ log_stream = io.StringIO()
325
+ logger = logging.getLogger('staran.tools.date')
326
+
327
+ # 清除现有处理器
328
+ for handler in logger.handlers[:]:
329
+ logger.removeHandler(handler)
330
+
331
+ handler = logging.StreamHandler(log_stream)
332
+ handler.setLevel(logging.DEBUG)
333
+ formatter = logging.Formatter('%(levelname)s:%(message)s')
334
+ handler.setFormatter(formatter)
335
+ logger.addHandler(handler)
336
+ logger.setLevel(logging.DEBUG)
337
+
338
+ try:
339
+ Date.set_log_level(logging.DEBUG)
340
+
341
+ # 尝试无效操作
342
+ try:
343
+ Date('invalid')
344
+ except ValueError:
345
+ pass
346
+
347
+ try:
348
+ Date(2025, 13, 1) # 无效月份
349
+ except ValueError:
350
+ pass
351
+
352
+ output = log_stream.getvalue()
353
+
354
+ # 应该有错误日志
355
+ if output:
356
+ self.assertIn('ERROR', output)
357
+
358
+ finally:
359
+ logger.removeHandler(handler)
360
+ handler.close()
361
+
362
+ def test_concurrent_logging(self):
363
+ """测试并发日志"""
364
+ import threading
365
+ import time
366
+
367
+ Date.set_log_level(logging.DEBUG)
368
+
369
+ results = []
370
+
371
+ def create_dates():
372
+ for i in range(10):
373
+ try:
374
+ date = Date(f'2025041{i % 10}')
375
+ results.append(str(date))
376
+ except:
377
+ pass
378
+
379
+ # 创建多个线程
380
+ threads = []
381
+ for i in range(5):
382
+ thread = threading.Thread(target=create_dates)
383
+ threads.append(thread)
384
+
385
+ # 启动所有线程
386
+ for thread in threads:
387
+ thread.start()
388
+
389
+ # 等待所有线程完成
390
+ for thread in threads:
391
+ thread.join()
392
+
393
+ # 验证结果
394
+ self.assertGreater(len(results), 0)
395
+
396
+ # 重置日志级别
397
+ Date.set_log_level(logging.ERROR)
398
+
399
+
400
+ if __name__ == '__main__':
401
+ # 运行所有日志测试
402
+ unittest.main(verbosity=2)
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: staran
3
+ Version: 1.0.1
4
+ Summary: staran - 轻量级Python日期工具库
5
+ Home-page: https://github.com/starlxa/staran
6
+ Author: StarAn
7
+ Author-email: starlxa@icloud.com
8
+ License: MIT
9
+ Project-URL: Bug Reports, https://github.com/starlxa/staran/issues
10
+ Project-URL: Source, https://github.com/starlxa/staran
11
+ Keywords: date datetime utilities time-processing
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: Utilities
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Operating System :: OS Independent
24
+ Requires-Python: >=3.7
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: keywords
33
+ Dynamic: license
34
+ Dynamic: license-file
35
+ Dynamic: project-url
36
+ Dynamic: requires-python
37
+ Dynamic: summary
@@ -0,0 +1,13 @@
1
+ staran/__init__.py,sha256=KBMMJN15kcGQLCOVUqfGf7UnuJs3_HFOXZq7SLlSF_U,208
2
+ staran/tools/__init__.py,sha256=lSEZeU6pp9smeiuVS3eYci203IFYBode0vUU5GeYb8w,1061
3
+ staran/tools/date.py,sha256=zZz9W94jed_KYgRnnPTaG8zyDD99UJdqwa0A5C_EyOw,14208
4
+ staran/tools/tests/__init__.py,sha256=s8WYc5XhtK7saVOeYyY85lmeLNoB8qpfbxZYSVEMfxs,3068
5
+ staran/tools/tests/run_tests.py,sha256=i-IjimWMOLDD4XZY-Tr1GNXV6yX01-7TjKiYKjB186A,7904
6
+ staran/tools/tests/test_api_compatibility.py,sha256=wmmM6f7nrS9J-b70xIltmtaIbiLALYCmaCsV4_wTxbE,11942
7
+ staran/tools/tests/test_date.py,sha256=8vVe0QyRSfPqvGK0WBhmOAs03MGjDlnw-fxYV5Wjv6Q,18429
8
+ staran/tools/tests/test_logging.py,sha256=VdoD8PJoKob01yQ93asQHz9Zo69jNfyq6jKtNSEi8UM,12041
9
+ staran-1.0.1.dist-info/licenses/LICENSE,sha256=2EmsBIyDCono4iVXNpv5_px9qt2b7hfPq1WuyGVMNP4,1361
10
+ staran-1.0.1.dist-info/METADATA,sha256=gx3O-5bO2L3hdJ1JgVDJuOwIh2X9VC_xL58g_pIbmfc,1295
11
+ staran-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ staran-1.0.1.dist-info/top_level.txt,sha256=NOUZtXSh5oSIEjHrC0lQ9WmoKtD010Q00dghWyag-Zs,7
13
+ staran-1.0.1.dist-info/RECORD,,
staran/banks/__init__.py DELETED
@@ -1,30 +0,0 @@
1
- """
2
- staran.banks - 银行配置模块
3
-
4
- 该模块包含不同银行的特定配置,包括:
5
- - 数据库连接配置
6
- - 表结构定义
7
- - 业务规则设置
8
- - 模型配置
9
-
10
- 支持的银行:
11
- - xinjiang_icbc: 新疆工行配置
12
-
13
- 版本: 0.6.0
14
- """
15
-
16
- from .xinjiang_icbc import (
17
- XinjiangICBCConfig,
18
- get_xinjiang_icbc_tables,
19
- get_xinjiang_icbc_models,
20
- xinjiang_icbc_config
21
- )
22
-
23
- __all__ = [
24
- 'XinjiangICBCConfig',
25
- 'xinjiang_icbc_config',
26
- 'get_xinjiang_icbc_tables',
27
- 'get_xinjiang_icbc_models'
28
- ]
29
-
30
- __version__ = "0.6.0"
@@ -1,90 +0,0 @@
1
- """
2
- 新疆工行银行配置模块
3
-
4
- 专门针对新疆工行代发长尾客户的配置:
5
- - 数据库表结构定义(代发长尾客户专用)
6
- - 业务规则配置
7
- - 模型配置(提升模型和防流失模型)
8
-
9
- 数据库: xinjiang_icbc_daifa_longtail
10
- 业务范围: 代发长尾客户
11
- """
12
-
13
- from dataclasses import dataclass
14
- from typing import Dict, List, Optional
15
- from datetime import datetime
16
-
17
-
18
- @dataclass
19
- class XinjiangICBCConfig:
20
- """新疆工行配置类"""
21
-
22
- # 数据库配置
23
- database_name: str = "xinjiang_icbc_daifa_longtail"
24
- schema_name: str = "daifa_longtail"
25
-
26
- # 业务配置
27
- business_domain: str = "代发长尾客户"
28
- customer_segment: str = "代发长尾"
29
-
30
- # 模型配置
31
- available_models: List[str] = None
32
-
33
- # 业务规则
34
- longtail_asset_min: float = 10000 # 长尾客户最小资产
35
- longtail_asset_max: float = 100000 # 长尾客户最大资产
36
- upgrade_target: float = 3000 # 提升目标金额
37
- churn_threshold: float = 1500 # 流失阈值金额
38
-
39
- def __post_init__(self):
40
- if self.available_models is None:
41
- self.available_models = [
42
- "daifa_longtail_upgrade_3k", # 代发长尾提升3k模型
43
- "daifa_longtail_churn_1_5k" # 代发长尾防流失1.5k模型
44
- ]
45
-
46
-
47
- def get_xinjiang_icbc_tables() -> Dict[str, str]:
48
- """获取新疆工行代发长尾客户表配置"""
49
- return {
50
- # 代发长尾客户行为表
51
- "daifa_longtail_behavior": "xinjiang_icbc_daifa_hlwj_dfcw_f1_f4_wy",
52
-
53
- # 代发长尾客户资产平均表
54
- "daifa_longtail_asset_avg": "xinjiang_icbc_daifa_hlwj_zi_chan_avg_wy",
55
-
56
- # 代发长尾客户资产配置表
57
- "daifa_longtail_asset_config": "xinjiang_icbc_daifa_hlwj_zi_chan_config_wy",
58
-
59
- # 代发长尾客户月度统计表
60
- "daifa_longtail_monthly_stat": "xinjiang_icbc_daifa_hlwj_monthly_stat_wy"
61
- }
62
-
63
-
64
- def get_xinjiang_icbc_models() -> Dict[str, Dict]:
65
- """获取新疆工行代发长尾客户模型配置"""
66
- return {
67
- "daifa_longtail_upgrade_3k": {
68
- "name": "代发长尾客户提升3k预测模型",
69
- "description": "预测下个月代发长尾客户资产提升3000元的概率",
70
- "target": "upgrade_3k_next_month",
71
- "model_type": "binary_classification",
72
- "business_objective": "识别有潜力提升资产的代发长尾客户",
73
- "target_threshold": 3000,
74
- "prediction_window": "1_month"
75
- },
76
-
77
- "daifa_longtail_churn_1_5k": {
78
- "name": "代发长尾客户防流失1.5k预测模型",
79
- "description": "预测下个月代发长尾客户流失1500元资产的风险",
80
- "target": "churn_1_5k_next_month",
81
- "model_type": "binary_classification",
82
- "business_objective": "识别有流失风险的代发长尾客户",
83
- "target_threshold": 1500,
84
- "prediction_window": "1_month"
85
- }
86
- }
87
-
88
-
89
- # 创建默认配置实例
90
- xinjiang_icbc_config = XinjiangICBCConfig()
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- 数据库引擎模块
6
- 提供统一的数据库引擎接口
7
- """
8
-
9
- # 基础组件
10
- from .base import BaseEngine, DatabaseType
11
-
12
- # 具体引擎实现
13
- from .spark import SparkEngine
14
- from .hive import HiveEngine
15
-
16
- # 图灵平台引擎 (可选导入)
17
- try:
18
- from .turing import TuringEngine, create_turing_engine
19
- _TURING_AVAILABLE = True
20
- except ImportError:
21
- TuringEngine = None
22
- create_turing_engine = None
23
- _TURING_AVAILABLE = False
24
-
25
- # 便捷创建函数
26
- def create_engine(engine_type: str, database_name: str, **kwargs) -> BaseEngine:
27
- """
28
- 创建数据库引擎的便捷函数
29
-
30
- Args:
31
- engine_type: 引擎类型 ('spark', 'hive', 'turing')
32
- database_name: 数据库名称
33
- **kwargs: 其他参数
34
-
35
- Returns:
36
- 数据库引擎实例
37
- """
38
- engine_type = engine_type.lower()
39
-
40
- if engine_type == 'spark':
41
- return SparkEngine(database_name, **kwargs)
42
- elif engine_type == 'hive':
43
- return HiveEngine(database_name, **kwargs)
44
- elif engine_type == 'turing':
45
- if not _TURING_AVAILABLE:
46
- raise ImportError("TuringEngine不可用,请确保turingPythonLib已安装")
47
- return TuringEngine(database_name, **kwargs)
48
- else:
49
- raise ValueError(f"不支持的引擎类型: {engine_type}")
50
-
51
- # 主要导出
52
- __all__ = [
53
- 'BaseEngine',
54
- 'DatabaseType',
55
- 'SparkEngine',
56
- 'HiveEngine',
57
- 'create_engine'
58
- ]
59
-
60
- # 如果图灵引擎可用,添加到导出
61
- if _TURING_AVAILABLE:
62
- __all__.extend([
63
- 'TuringEngine',
64
- 'create_turing_engine'
65
- ])