DrissionRecord 1.0.0__tar.gz
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.
- drissionrecord-1.0.0/DrissionRecord/__init__.py +7 -0
- drissionrecord-1.0.0/DrissionRecord/base.py +193 -0
- drissionrecord-1.0.0/DrissionRecord/base.pyi +143 -0
- drissionrecord-1.0.0/DrissionRecord/byte_recorder.py +42 -0
- drissionrecord-1.0.0/DrissionRecord/byte_recorder.pyi +33 -0
- drissionrecord-1.0.0/DrissionRecord/cell_style.py +589 -0
- drissionrecord-1.0.0/DrissionRecord/cell_style.pyi +667 -0
- drissionrecord-1.0.0/DrissionRecord/db_recorder.py +157 -0
- drissionrecord-1.0.0/DrissionRecord/db_recorder.pyi +81 -0
- drissionrecord-1.0.0/DrissionRecord/recorder.py +923 -0
- drissionrecord-1.0.0/DrissionRecord/recorder.pyi +579 -0
- drissionrecord-1.0.0/DrissionRecord/setter.py +293 -0
- drissionrecord-1.0.0/DrissionRecord/setter.pyi +334 -0
- drissionrecord-1.0.0/DrissionRecord/tools.py +927 -0
- drissionrecord-1.0.0/DrissionRecord/tools.pyi +517 -0
- drissionrecord-1.0.0/DrissionRecord.egg-info/PKG-INFO +125 -0
- drissionrecord-1.0.0/DrissionRecord.egg-info/SOURCES.txt +24 -0
- drissionrecord-1.0.0/DrissionRecord.egg-info/dependency_links.txt +1 -0
- drissionrecord-1.0.0/DrissionRecord.egg-info/requires.txt +1 -0
- drissionrecord-1.0.0/DrissionRecord.egg-info/top_level.txt +1 -0
- drissionrecord-1.0.0/LICENSE +21 -0
- drissionrecord-1.0.0/MANIFEST.in +1 -0
- drissionrecord-1.0.0/PKG-INFO +125 -0
- drissionrecord-1.0.0/README.md +95 -0
- drissionrecord-1.0.0/setup.cfg +4 -0
- drissionrecord-1.0.0/setup.py +31 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Lock
|
|
5
|
+
from time import sleep
|
|
6
|
+
|
|
7
|
+
from .setter import OriginalSetter, BaseSetter
|
|
8
|
+
from .tools import get_usable_path, make_valid_name, get_tables, make_final_data_simplify
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OriginalRecorder(object):
|
|
12
|
+
def __init__(self, path=None, cache_size=1000):
|
|
13
|
+
self._data = []
|
|
14
|
+
self._path = None
|
|
15
|
+
self._type = None
|
|
16
|
+
self._lock = Lock()
|
|
17
|
+
self._pause_add = False # 文件写入时暂停接收输入
|
|
18
|
+
self._pause_write = False # 标记文件正在被一个线程写入
|
|
19
|
+
self._setter = None
|
|
20
|
+
self._data_count = 0 # 已缓存数据的条数
|
|
21
|
+
self._file_exists = False
|
|
22
|
+
self._backup_path = 'backup'
|
|
23
|
+
self._backup_times = 0
|
|
24
|
+
self._backup_interval = 0 # 多少次就自动保存
|
|
25
|
+
self._backup_overwrite = False
|
|
26
|
+
self.show_msg = True
|
|
27
|
+
if path:
|
|
28
|
+
self.set.path(path)
|
|
29
|
+
self._cache = cache_size or 0
|
|
30
|
+
|
|
31
|
+
def __del__(self):
|
|
32
|
+
self.record()
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def set(self):
|
|
36
|
+
if self._setter is None:
|
|
37
|
+
self._setter = OriginalSetter(self)
|
|
38
|
+
return self._setter
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def cache_size(self):
|
|
42
|
+
return self._cache
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def path(self):
|
|
46
|
+
return self._path
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def type(self):
|
|
50
|
+
return self._type
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def data(self):
|
|
54
|
+
return self._data
|
|
55
|
+
|
|
56
|
+
def record(self):
|
|
57
|
+
if not self._data_count:
|
|
58
|
+
return self._path
|
|
59
|
+
if not self._path:
|
|
60
|
+
raise ValueError('保存路径为空。')
|
|
61
|
+
|
|
62
|
+
with self._lock:
|
|
63
|
+
if self._backup_interval and self._backup_times >= self._backup_interval:
|
|
64
|
+
self.backup(folder=self._backup_path, overwrite=self._backup_overwrite)
|
|
65
|
+
|
|
66
|
+
self._pause_add = True # 写入文件前暂缓接收数据
|
|
67
|
+
if self.show_msg:
|
|
68
|
+
print(f'{self.path} 开始写入文件,切勿关闭进程。')
|
|
69
|
+
|
|
70
|
+
Path(self.path).parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
while True:
|
|
72
|
+
try:
|
|
73
|
+
while self._pause_write: # 等待其它线程写入结束
|
|
74
|
+
sleep(.02)
|
|
75
|
+
|
|
76
|
+
self._pause_write = True
|
|
77
|
+
self._record()
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
except PermissionError:
|
|
81
|
+
if self.show_msg:
|
|
82
|
+
print('\r文件被打开,保存失败,请关闭,程序会自动重试。', end='')
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
try:
|
|
86
|
+
with open('failed_data.txt', 'a+', encoding='utf-8') as f:
|
|
87
|
+
f.write(str(self.data) + '\n')
|
|
88
|
+
print('保存失败的数据已保存到failed_data.txt。')
|
|
89
|
+
except:
|
|
90
|
+
raise e
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
finally:
|
|
94
|
+
self._pause_write = False
|
|
95
|
+
|
|
96
|
+
sleep(.3)
|
|
97
|
+
|
|
98
|
+
if self.show_msg:
|
|
99
|
+
print(f'{self.path} 写入文件结束。')
|
|
100
|
+
self.clear()
|
|
101
|
+
self._pause_add = False
|
|
102
|
+
|
|
103
|
+
if self._backup_interval:
|
|
104
|
+
self._backup_times += 1
|
|
105
|
+
self._file_exists = True
|
|
106
|
+
return self._path
|
|
107
|
+
|
|
108
|
+
def clear(self):
|
|
109
|
+
self._data.clear()
|
|
110
|
+
self._data_count = 0
|
|
111
|
+
|
|
112
|
+
def backup(self, folder=None, name=None, overwrite=None):
|
|
113
|
+
src_path = Path(self._path)
|
|
114
|
+
if not self._file_exists:
|
|
115
|
+
if not src_path.exists():
|
|
116
|
+
return ''
|
|
117
|
+
self._file_exists = True
|
|
118
|
+
|
|
119
|
+
if overwrite is None:
|
|
120
|
+
overwrite = self._backup_overwrite
|
|
121
|
+
folder = Path(folder if folder else self._backup_path)
|
|
122
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
if not name:
|
|
124
|
+
name = src_path.name
|
|
125
|
+
elif not name.endswith(src_path.suffix):
|
|
126
|
+
name = f'{name}{src_path.suffix}'
|
|
127
|
+
path = folder / make_valid_name(name)
|
|
128
|
+
if not overwrite and path.exists():
|
|
129
|
+
from datetime import datetime
|
|
130
|
+
name = f'{path.stem}_{datetime.now().strftime("%Y%m%d%H%M%S")}{path.suffix}'
|
|
131
|
+
path = get_usable_path(folder / name)
|
|
132
|
+
|
|
133
|
+
from shutil import copy
|
|
134
|
+
copy(self._path, path)
|
|
135
|
+
self._backup_times = 0
|
|
136
|
+
return str(path.absolute())
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def add_data(self, data):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def _record(self):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class BaseRecorder(OriginalRecorder):
|
|
148
|
+
def __init__(self, path=None, cache_size=None):
|
|
149
|
+
super().__init__(path, cache_size)
|
|
150
|
+
self._before = []
|
|
151
|
+
self._after = []
|
|
152
|
+
self._encoding = 'utf-8'
|
|
153
|
+
self._table = None
|
|
154
|
+
self._make_final_data = make_final_data_simplify
|
|
155
|
+
self._auto_new_header = False
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def set(self):
|
|
159
|
+
if self._setter is None:
|
|
160
|
+
self._setter = BaseSetter(self)
|
|
161
|
+
return self._setter
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def before(self):
|
|
165
|
+
return self._before
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def after(self):
|
|
169
|
+
return self._after
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def table(self):
|
|
173
|
+
return self._table
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def tables(self):
|
|
177
|
+
if self._type != 'xlsx':
|
|
178
|
+
raise TypeError('只有xlsx格式能使用tables属性。')
|
|
179
|
+
if not self._path:
|
|
180
|
+
raise RuntimeError('未指定文件路径。')
|
|
181
|
+
return get_tables(self._path)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def encoding(self):
|
|
185
|
+
return self._encoding
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
def add_data(self, data, table=None):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def _record(self):
|
|
193
|
+
pass
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Lock
|
|
5
|
+
from typing import Union, Optional, Callable
|
|
6
|
+
|
|
7
|
+
from .setter import OriginalSetter, BaseSetter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OriginalRecorder(object):
|
|
11
|
+
"""记录器的基类"""
|
|
12
|
+
_cache: int = ...
|
|
13
|
+
_path: Optional[str] = ...
|
|
14
|
+
_type: Optional[str] = ...
|
|
15
|
+
_data: list = ...
|
|
16
|
+
_lock: Lock = ...
|
|
17
|
+
_pause_add: bool = ...
|
|
18
|
+
_pause_write: bool = ...
|
|
19
|
+
_setter: Optional[OriginalSetter] = ...
|
|
20
|
+
_data_count: int = ...
|
|
21
|
+
_file_exists: bool = ...
|
|
22
|
+
_backup_path: str = ...
|
|
23
|
+
_backup_times: int = ...
|
|
24
|
+
_backup_interval: int = ...
|
|
25
|
+
_backup_overwrite: bool = ...
|
|
26
|
+
show_msg: bool = ...
|
|
27
|
+
|
|
28
|
+
def __init__(self,
|
|
29
|
+
path: Union[str, Path, None] = None,
|
|
30
|
+
cache_size: int = None) -> None:
|
|
31
|
+
"""
|
|
32
|
+
:param path: 保存的文件路径
|
|
33
|
+
:param cache_size: 每接收多少条记录写入文件,0为不自动写入
|
|
34
|
+
"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def __del__(self) -> None:
|
|
38
|
+
"""对象关闭时把剩下的数据写入文件"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def set(self) -> OriginalSetter:
|
|
43
|
+
"""返回用于设置属性的对象"""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def cache_size(self) -> int:
|
|
48
|
+
"""返回缓存大小"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def path(self) -> str:
|
|
53
|
+
"""返回文件路径"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def type(self) -> str:
|
|
58
|
+
"""返回文件类型"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def data(self) -> Union[dict, list]:
|
|
63
|
+
"""返回当前保存在缓存的数据"""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
def record(self) -> str:
|
|
67
|
+
"""记录数据,返回文件路径"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def clear(self) -> None:
|
|
71
|
+
"""清空缓存中的数据"""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def backup(self,
|
|
75
|
+
folder: Union[str, Path, None] = None,
|
|
76
|
+
name: str = None,
|
|
77
|
+
overwrite: bool = None) -> str:
|
|
78
|
+
"""把当前文件备份到指定路径
|
|
79
|
+
:param folder: 文件夹路径,为None使用内置路径(初始 'backup')
|
|
80
|
+
:param name: 保存的文件名,可不含后缀,为None使用内置路径文件名
|
|
81
|
+
:param overwrite: 是否覆盖同名文件,为False时每次备份文件名添加当前时间,为None使用内置设置
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def add_data(self, data):
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def _record(self):
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseRecorder(OriginalRecorder):
|
|
95
|
+
"""Recorder和DBRecorder的父类"""
|
|
96
|
+
_encoding: str = ...
|
|
97
|
+
_before: Optional[list] = ...
|
|
98
|
+
_after: Optional[list] = ...
|
|
99
|
+
_table: Optional[str] = ...
|
|
100
|
+
_setter: BaseSetter = ...
|
|
101
|
+
_make_final_data: Callable = ...
|
|
102
|
+
_auto_new_header: bool = ...
|
|
103
|
+
|
|
104
|
+
def __init__(self, path: Union[None, str, Path] = None, cache_size: int = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
:param path: 保存的文件路径
|
|
107
|
+
:param cache_size: 每接收多少条记录写入文件,0为不自动写入
|
|
108
|
+
"""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def set(self) -> BaseSetter:
|
|
113
|
+
"""返回用于设置属性的对象"""
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def before(self) -> Union[dict, list]:
|
|
118
|
+
"""返回当前before内容"""
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def after(self) -> Union[dict, list]:
|
|
123
|
+
"""返回当前after内容"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def table(self) -> Optional[str]:
|
|
128
|
+
"""返回当前使用的表名"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def tables(self) -> list:
|
|
133
|
+
"""返回所有表名"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def encoding(self) -> str:
|
|
138
|
+
"""返回csv文件使用的编码格式"""
|
|
139
|
+
...
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def _record(self):
|
|
143
|
+
...
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
from .base import OriginalRecorder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ByteRecorder(OriginalRecorder):
|
|
9
|
+
__END = (0, 2)
|
|
10
|
+
|
|
11
|
+
def __init__(self, path=None, cache_size=1000):
|
|
12
|
+
super().__init__(path, cache_size)
|
|
13
|
+
self._type = 'byte'
|
|
14
|
+
|
|
15
|
+
def add_data(self, data, seek=None):
|
|
16
|
+
while self._pause_add: # 等待其它线程写入结束
|
|
17
|
+
sleep(.02)
|
|
18
|
+
|
|
19
|
+
if not isinstance(data, bytes):
|
|
20
|
+
raise TypeError('只能接受bytes类型数据。')
|
|
21
|
+
if seek is not None and not (isinstance(seek, int) and seek >= 0):
|
|
22
|
+
raise ValueError('seek参数只能接受None或大于等于0的整数。')
|
|
23
|
+
|
|
24
|
+
self._data.append((data, seek))
|
|
25
|
+
self._data_count += 1
|
|
26
|
+
|
|
27
|
+
if 0 < self.cache_size <= self._data_count:
|
|
28
|
+
self.record()
|
|
29
|
+
|
|
30
|
+
def _record(self):
|
|
31
|
+
if not self._file_exists and not Path(self.path).exists():
|
|
32
|
+
with open(self.path, 'wb'):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
with open(self.path, 'rb+') as f:
|
|
36
|
+
previous = None
|
|
37
|
+
for i in self._data:
|
|
38
|
+
loc = ByteRecorder.__END if i[1] is None else (i[1], 0)
|
|
39
|
+
if not (previous == loc == ByteRecorder.__END):
|
|
40
|
+
f.seek(loc[0], loc[1])
|
|
41
|
+
previous = loc
|
|
42
|
+
f.write(i[0])
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union, Optional
|
|
4
|
+
|
|
5
|
+
from .base import OriginalRecorder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ByteRecorder(OriginalRecorder):
|
|
9
|
+
__END: tuple = ...
|
|
10
|
+
data: list = ...
|
|
11
|
+
|
|
12
|
+
def __init__(self,
|
|
13
|
+
path: Union[None, str, Path] = None,
|
|
14
|
+
cache_size: int = 1000):
|
|
15
|
+
"""用于记录字节数据的工具
|
|
16
|
+
:param path: 保存的文件路径
|
|
17
|
+
:param cache_size: 每接收多少条记录写入文件,0为不自动写入
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def add_data(self,
|
|
22
|
+
data: bytes,
|
|
23
|
+
seek: int = None) -> None:
|
|
24
|
+
"""添加一段二进制数据
|
|
25
|
+
:param data: bytes类型数据
|
|
26
|
+
:param seek: 在文件中的位置,None表示最后
|
|
27
|
+
:return: None
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def _record(self) -> None:
|
|
32
|
+
"""记录数据到文件"""
|
|
33
|
+
...
|