educommon 3.13.0__py3-none-any.whl → 3.13.2__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.
- educommon/__init__.py +0 -1
- educommon/about/ui/actions.py +16 -30
- educommon/about/ui/ui.py +3 -12
- educommon/about/utils.py +6 -5
- educommon/async_task/__init__.py +0 -1
- educommon/async_task/actions.py +18 -13
- educommon/async_task/apps.py +4 -0
- educommon/async_task/locker.py +2 -5
- educommon/async_task/migrations/0001_initial.py +55 -9
- educommon/async_task/migrations/0002_task_type_and_status_data.py +94 -89
- educommon/async_task/migrations/0003_alter_runningtask_options.py +0 -1
- educommon/async_task/models.py +9 -6
- educommon/async_task/tasks.py +11 -7
- educommon/async_task/ui.py +16 -35
- educommon/async_tasks/__init__.py +0 -1
- educommon/async_tasks/apps.py +4 -0
- educommon/async_tasks/locks.py +11 -21
- educommon/async_tasks/migrations/0001_initial.py +68 -8
- educommon/async_tasks/migrations/0002_load_initial_data.py +0 -1
- educommon/async_tasks/models.py +9 -29
- educommon/async_tasks/tasks.py +25 -54
- educommon/audit_log/__init__.py +1 -0
- educommon/audit_log/actions.py +27 -36
- educommon/audit_log/app_meta.py +7 -4
- educommon/audit_log/apps.py +44 -29
- educommon/audit_log/constants.py +7 -4
- educommon/audit_log/error_log/actions.py +1 -3
- educommon/audit_log/helpers.py +2 -4
- educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
- educommon/audit_log/migrations/0001_initial.py +91 -16
- educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
- educommon/audit_log/migrations/0003_logproxy.py +1 -3
- educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
- educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
- educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
- educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
- educommon/audit_log/migrations/0008_table_logged.py +0 -1
- educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
- educommon/audit_log/models.py +36 -42
- educommon/audit_log/permissions.py +11 -9
- educommon/audit_log/proxies.py +12 -23
- educommon/audit_log/ui.py +18 -15
- educommon/audit_log/utils/__init__.py +28 -60
- educommon/audit_log/utils/operations.py +16 -2
- educommon/auth/__init__.py +0 -3
- educommon/auth/rbac/__init__.py +2 -4
- educommon/auth/rbac/actions.py +148 -145
- educommon/auth/rbac/app_meta.py +9 -6
- educommon/auth/rbac/backends/base.py +2 -8
- educommon/auth/rbac/backends/caching.py +27 -37
- educommon/auth/rbac/backends/simple.py +1 -4
- educommon/auth/rbac/checker.py +1 -3
- educommon/auth/rbac/management/commands/rbac.py +6 -11
- educommon/auth/rbac/manager.py +18 -47
- educommon/auth/rbac/migrations/0001_initial.py +73 -12
- educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
- educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
- educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
- educommon/auth/rbac/models.py +63 -68
- educommon/auth/rbac/permissions.py +6 -7
- educommon/auth/rbac/ui.py +83 -84
- educommon/auth/rbac/utils.py +10 -11
- educommon/auth/rbac/validators.py +4 -5
- educommon/auth/simple_auth/__init__.py +1 -5
- educommon/auth/simple_auth/actions.py +79 -92
- educommon/auth/simple_auth/app_meta.py +2 -9
- educommon/auth/simple_auth/checkers.py +3 -3
- educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
- educommon/auth/simple_auth/validators.py +0 -1
- educommon/contingent/actions.py +7 -7
- educommon/contingent/app_meta.py +1 -4
- educommon/contingent/base.py +10 -15
- educommon/contingent/catalogs.py +424 -540
- educommon/contingent/contingent_plugin/actions.py +4 -15
- educommon/contingent/contingent_plugin/apps.py +10 -4
- educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
- educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
- educommon/contingent/contingent_plugin/model_views.py +2 -12
- educommon/contingent/contingent_plugin/models.py +2 -7
- educommon/contingent/contingent_plugin/observer.py +14 -13
- educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
- educommon/contingent/contingent_plugin/storage.py +8 -7
- educommon/contingent/contingent_plugin/utils.py +6 -6
- educommon/django/db/fields.py +72 -86
- educommon/django/db/migration/__init__.py +3 -7
- educommon/django/db/migration/operations.py +29 -51
- educommon/django/db/mixins/__init__.py +16 -10
- educommon/django/db/mixins/date_interval.py +47 -75
- educommon/django/db/mixins/validation.py +26 -26
- educommon/django/db/model_view/__init__.py +18 -22
- educommon/django/db/models.py +9 -8
- educommon/django/db/observer.py +9 -27
- educommon/django/db/partitioning/__init__.py +66 -92
- educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
- educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
- educommon/django/db/partitioning/management/commands/split_table.py +18 -13
- educommon/django/db/routers.py +6 -15
- educommon/django/db/signals.py +4 -2
- educommon/django/db/utils.py +14 -19
- educommon/django/db/validators/__init__.py +1 -0
- educommon/django/db/validators/simple.py +72 -100
- educommon/django/storages/atcfs/api.py +39 -53
- educommon/django/storages/atcfs/app_meta.py +1 -1
- educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
- educommon/django/storages/atcfs/models.py +0 -3
- educommon/django/storages/atcfs/monkey_patching.py +18 -12
- educommon/django/storages/atcfs/storage.py +14 -23
- educommon/extjs/fields/input_params.py +15 -45
- educommon/importer/XLSReader.py +143 -241
- educommon/importer/__init__.py +86 -4
- educommon/importer/api.py +53 -84
- educommon/importer/constants.py +4 -14
- educommon/importer/loggers.py +16 -26
- educommon/importer/proxy.py +131 -176
- educommon/importer/proxy_import.py +11 -12
- educommon/importer/report.py +4 -6
- educommon/importer/ui.py +32 -26
- educommon/importer/validators.py +4 -7
- educommon/integration_entities/helpers.py +14 -18
- educommon/ioc/__init__.py +3 -6
- educommon/logger/loggers.py +10 -14
- educommon/m3/__init__.py +20 -38
- educommon/m3/extensions/__init__.py +1 -0
- educommon/m3/extensions/listeners/__init__.py +22 -38
- educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
- educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
- educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
- educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
- educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
- educommon/m3/extensions/ui.py +15 -33
- educommon/m3/transaction_context.py +17 -19
- educommon/objectpack/actions.py +70 -88
- educommon/objectpack/apps.py +5 -0
- educommon/objectpack/filters.py +9 -15
- educommon/objectpack/ui.py +59 -77
- educommon/report/__init__.py +9 -5
- educommon/report/actions.py +29 -32
- educommon/report/constructor/__init__.py +5 -8
- educommon/report/constructor/app_meta.py +1 -3
- educommon/report/constructor/apps.py +1 -0
- educommon/report/constructor/base.py +33 -80
- educommon/report/constructor/builders/excel/_base.py +138 -286
- educommon/report/constructor/builders/excel/_header.py +2 -9
- educommon/report/constructor/builders/excel/product.py +13 -34
- educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
- educommon/report/constructor/config.py +2 -0
- educommon/report/constructor/editor/actions.py +101 -215
- educommon/report/constructor/editor/ui.py +71 -93
- educommon/report/constructor/exceptions.py +6 -12
- educommon/report/constructor/migrations/0001_initial.py +36 -44
- educommon/report/constructor/migrations/0002_report_filters.py +86 -72
- educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
- educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
- educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
- educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
- educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
- educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
- educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
- educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
- educommon/report/constructor/mixins.py +14 -15
- educommon/report/constructor/models.py +76 -124
- educommon/report/constructor/utils.py +3 -8
- educommon/report/constructor/validators.py +1 -3
- educommon/report/reporter.py +25 -43
- educommon/report/utils.py +14 -40
- educommon/rest/actions.py +7 -11
- educommon/rest/context.py +6 -16
- educommon/rest/controllers.py +10 -10
- educommon/rest/mixins.py +29 -27
- educommon/secure_media/app_meta.py +9 -9
- educommon/utils/__init__.py +3 -2
- educommon/utils/caching.py +1 -3
- educommon/utils/conversion.py +1 -3
- educommon/utils/crypto.py +1 -2
- educommon/utils/date.py +13 -26
- educommon/utils/db/__init__.py +17 -26
- educommon/utils/db/postgresql.py +1 -4
- educommon/utils/fonts/__init__.py +3 -4
- educommon/utils/licence/__init__.py +5 -16
- educommon/utils/misc.py +9 -18
- educommon/utils/object_grid.py +55 -62
- educommon/utils/phone_number/modelfields.py +1 -3
- educommon/utils/phone_number/phone_number.py +5 -8
- educommon/utils/phone_number/validators.py +8 -23
- educommon/utils/plugins.py +15 -28
- educommon/utils/registry.py +2 -1
- educommon/utils/seqtools.py +1 -3
- educommon/utils/serializer.py +9 -16
- educommon/utils/storage.py +3 -2
- educommon/utils/system.py +1 -3
- educommon/utils/system_app/management/commands/delete_objects.py +17 -34
- educommon/utils/ui.py +87 -84
- educommon/utils/xml/__init__.py +2 -7
- educommon/utils/xml/resolver.py +1 -0
- educommon/ws_log/actions.py +31 -76
- educommon/ws_log/base.py +6 -20
- educommon/ws_log/migrations/0001_initial.py +25 -8
- educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
- educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
- educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
- educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
- educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
- educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
- educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
- educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
- educommon/ws_log/models.py +21 -35
- educommon/ws_log/provider.py +2 -1
- educommon/ws_log/report.py +8 -13
- educommon/ws_log/smev/applications.py +12 -27
- educommon/ws_log/smev/exceptions.py +2 -3
- educommon/ws_log/ui.py +32 -32
- educommon/ws_log/utils.py +1 -3
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/METADATA +26 -14
- educommon-3.13.2.dist-info/RECORD +354 -0
- educommon/utils/patches.py +0 -27
- educommon/version.conf +0 -11
- educommon-3.13.0.dist-info/RECORD +0 -357
- educommon-3.13.0.dist-info/dependency_links.txt +0 -1
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +0 -0
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
educommon/importer/XLSReader.py
CHANGED
@@ -30,10 +30,12 @@ _SPECIAL_KEYS = (
|
|
30
30
|
|
31
31
|
|
32
32
|
def _fold(tree, path='', group=None):
|
33
|
-
"""
|
33
|
+
"""Свертка словаря-дерева.
|
34
|
+
|
34
35
|
Свертка словаря-дерева в список пар (путь, значение, группа|None)
|
35
|
-
("группа", это uuid, для группировки значений одного поддерева)
|
36
|
+
("группа", это uuid, для группировки значений одного поддерева).
|
36
37
|
"""
|
38
|
+
|
37
39
|
if not isinstance(tree, dict):
|
38
40
|
return [(path, tree, group)]
|
39
41
|
|
@@ -41,20 +43,21 @@ def _fold(tree, path='', group=None):
|
|
41
43
|
if tree.get(SUBTREE_CAN_BE_EMPTY, False):
|
42
44
|
group = uuid.uuid4()
|
43
45
|
|
44
|
-
path =
|
46
|
+
path = f'{path}/{{0}}' if path else '{0}'
|
47
|
+
|
45
48
|
res = []
|
46
49
|
for key, val in tree.items():
|
47
50
|
if key in _SPECIAL_KEYS:
|
48
51
|
continue
|
49
|
-
|
52
|
+
|
53
|
+
key = path.format(key)
|
50
54
|
res.extend(_fold(val, key, group))
|
55
|
+
|
51
56
|
return res
|
52
57
|
|
53
58
|
|
54
59
|
def _unfold(lst):
|
55
|
-
"""
|
56
|
-
Развертка списка пар (путь, значение) в словарь-дерево
|
57
|
-
"""
|
60
|
+
"""Развертка списка пар (путь, значение) в словарь-дерево."""
|
58
61
|
res = {}
|
59
62
|
|
60
63
|
def set_val(res, path, val):
|
@@ -79,9 +82,7 @@ MATCH_IF_CONTAINS = 3
|
|
79
82
|
|
80
83
|
|
81
84
|
def _is_match(value, pattern, match_type=MATCH_IF_EXACT):
|
82
|
-
"""
|
83
|
-
Проверка на совпадение значения с образцом
|
84
|
-
"""
|
85
|
+
"""Проверка на совпадение значения с образцом."""
|
85
86
|
pattern = pattern.upper()
|
86
87
|
value = value.upper()
|
87
88
|
|
@@ -89,29 +90,25 @@ def _is_match(value, pattern, match_type=MATCH_IF_EXACT):
|
|
89
90
|
return value.find(pattern) >= 0
|
90
91
|
elif match_type == MATCH_IF_STARTS_WITH:
|
91
92
|
return value.startswith(pattern)
|
93
|
+
|
92
94
|
return value == pattern
|
93
95
|
|
94
96
|
|
95
97
|
class CellValueError(Exception):
|
96
|
-
"""
|
97
|
-
|
98
|
-
"""
|
98
|
+
"""Исключение для неверного значения ячейки."""
|
99
|
+
|
99
100
|
pass
|
100
101
|
|
101
102
|
|
102
103
|
class EmptyObligatoryCellError(CellValueError):
|
103
|
-
"""
|
104
|
-
|
105
|
-
"""
|
104
|
+
"""Исключение для неверного значения ячейки."""
|
105
|
+
|
106
106
|
def __init__(self, message=None):
|
107
|
-
super(
|
108
|
-
message or 'Ячейка не может быть пустой!')
|
107
|
+
super().__init__(message or 'Ячейка не может быть пустой!')
|
109
108
|
|
110
109
|
|
111
110
|
class BaseCell:
|
112
|
-
"""
|
113
|
-
Прототип парсера ячейки
|
114
|
-
"""
|
111
|
+
"""Прототип парсера ячейки."""
|
115
112
|
|
116
113
|
_default = _NO_DEFAULT
|
117
114
|
|
@@ -125,15 +122,11 @@ class BaseCell:
|
|
125
122
|
return (value, False)
|
126
123
|
|
127
124
|
def _parse(self, value):
|
128
|
-
"""
|
129
|
-
Разбор значения
|
130
|
-
"""
|
125
|
+
"""Разбор значения."""
|
131
126
|
raise NotImplementedError('Прототип не используется напрямую!')
|
132
127
|
|
133
128
|
def from_cell(self, sheet, row, col):
|
134
|
-
"""
|
135
|
-
Получение значения из ячейки в виде (значение, is_default)
|
136
|
-
"""
|
129
|
+
"""Получение значения из ячейки в виде (значение, is_default)."""
|
137
130
|
try:
|
138
131
|
value = sheet.cell(row, col).value
|
139
132
|
except IndexError:
|
@@ -144,7 +137,7 @@ class BaseCell:
|
|
144
137
|
if isinstance(value, str):
|
145
138
|
value = value.strip()
|
146
139
|
|
147
|
-
if value is None or value ==
|
140
|
+
if value is None or value == '':
|
148
141
|
if self.is_obligatory():
|
149
142
|
raise EmptyObligatoryCellError()
|
150
143
|
return self.default()
|
@@ -156,19 +149,15 @@ class BaseCell:
|
|
156
149
|
|
157
150
|
|
158
151
|
class StringCell(BaseCell):
|
159
|
-
"""
|
160
|
-
Строковая ячейка
|
161
|
-
"""
|
152
|
+
"""Строковая ячейка."""
|
162
153
|
|
163
|
-
MSG_RECORD_TRUNCATED =
|
164
|
-
'Превышена максимально допустимая длина записи. Запись сокращена'
|
165
|
-
)
|
154
|
+
MSG_RECORD_TRUNCATED = 'Превышена максимально допустимая длина записи. Запись сокращена'
|
166
155
|
|
167
156
|
DEFAULT_RE_MESSAGE = 'Неправильный формат записи'
|
168
157
|
|
169
158
|
def __init__(
|
170
|
-
|
171
|
-
|
159
|
+
self, default=_NO_DEFAULT, max_length=None, regex=None, validator=None, error_message=None, verbose=True
|
160
|
+
):
|
172
161
|
"""Строковая ячейка.
|
173
162
|
|
174
163
|
:param int max_length: Максимальная длина строки.
|
@@ -179,7 +168,7 @@ class StringCell(BaseCell):
|
|
179
168
|
"""
|
180
169
|
assert max_length is None or isinstance(max_length, int)
|
181
170
|
|
182
|
-
super(
|
171
|
+
super().__init__(default)
|
183
172
|
|
184
173
|
self._max_length = max_length
|
185
174
|
self._verbose = verbose
|
@@ -218,9 +207,7 @@ class StringCell(BaseCell):
|
|
218
207
|
except ValidationError as err:
|
219
208
|
if self._default is None:
|
220
209
|
if self._verbose:
|
221
|
-
self.set_mgs(
|
222
|
-
', '.join(err.messages)
|
223
|
-
)
|
210
|
+
self.set_mgs(', '.join(err.messages))
|
224
211
|
value = ''
|
225
212
|
|
226
213
|
elif self._default is _NO_DEFAULT:
|
@@ -230,13 +217,13 @@ class StringCell(BaseCell):
|
|
230
217
|
|
231
218
|
def _truncate(self, value):
|
232
219
|
"""Обрезает строку до указанной в self._max_length длины."""
|
233
|
-
new_value = value[:self._max_length]
|
220
|
+
new_value = value[: self._max_length]
|
234
221
|
if self._verbose and new_value != value:
|
235
222
|
self.set_mgs(self.MSG_RECORD_TRUNCATED)
|
223
|
+
|
236
224
|
return new_value
|
237
225
|
|
238
226
|
def _parse(self, value):
|
239
|
-
|
240
227
|
self.message = None
|
241
228
|
|
242
229
|
if value.endswith('.0'):
|
@@ -255,12 +242,9 @@ class StringCell(BaseCell):
|
|
255
242
|
|
256
243
|
|
257
244
|
class MaybeStringCell(StringCell):
|
258
|
-
"""
|
259
|
-
|
260
|
-
|
261
|
-
def __init__(
|
262
|
-
self, default=None, max_length=None, regex=None,
|
263
|
-
validator=None, error_message=None, verbose=True):
|
245
|
+
"""Строка или None."""
|
246
|
+
|
247
|
+
def __init__(self, default=None, max_length=None, regex=None, validator=None, error_message=None, verbose=True):
|
264
248
|
"""Строка или None.
|
265
249
|
|
266
250
|
:param int max_length: Максимальная длина строки.
|
@@ -269,33 +253,36 @@ class MaybeStringCell(StringCell):
|
|
269
253
|
:param error_message: Сообщение в случае провала проверки.
|
270
254
|
:param bool verbose: Записывать ли сообщения в лог.
|
271
255
|
"""
|
272
|
-
super(
|
273
|
-
default=None,
|
274
|
-
|
256
|
+
super().__init__(
|
257
|
+
default=None,
|
258
|
+
max_length=max_length,
|
259
|
+
regex=regex,
|
260
|
+
validator=validator,
|
261
|
+
error_message=error_message,
|
262
|
+
verbose=verbose,
|
263
|
+
)
|
275
264
|
|
276
265
|
|
277
266
|
class RawCell(BaseCell):
|
267
|
+
"""Сырая строковая ячейка "как есть".
|
268
|
+
|
269
|
+
В том числе с добавками Excel в зависимости от типа ячейки.
|
278
270
|
"""
|
279
|
-
|
280
|
-
в том числе с добавками Excel в зависимости
|
281
|
-
от типа ячейки
|
282
|
-
"""
|
271
|
+
|
283
272
|
def _parse(self, value):
|
284
273
|
return self.result(str(value))
|
285
274
|
|
286
275
|
|
287
276
|
class MaybeRawCell(RawCell):
|
288
|
-
"""
|
289
|
-
|
290
|
-
"""
|
277
|
+
"""Сырая строковая ячейка "как есть" или None."""
|
278
|
+
|
291
279
|
def __init__(self):
|
292
|
-
super(
|
280
|
+
super().__init__(default=None)
|
293
281
|
|
294
282
|
|
295
283
|
class IntCell(BaseCell):
|
296
|
-
"""
|
297
|
-
|
298
|
-
"""
|
284
|
+
"""Integer ячейка."""
|
285
|
+
|
299
286
|
def _parse(self, value):
|
300
287
|
try:
|
301
288
|
result = int(value)
|
@@ -303,73 +290,62 @@ class IntCell(BaseCell):
|
|
303
290
|
try:
|
304
291
|
result = int(float(value))
|
305
292
|
except (TypeError, ValueError):
|
306
|
-
raise CellValueError(
|
307
|
-
'Число имеет неверный формат! "%s"' % value)
|
293
|
+
raise CellValueError(f'Число имеет неверный формат! "{value}"')
|
308
294
|
return self.result(result)
|
309
295
|
|
310
296
|
|
311
297
|
class MaybeIntCell(IntCell):
|
312
|
-
"""
|
313
|
-
|
314
|
-
"""
|
298
|
+
"""Int или None."""
|
299
|
+
|
315
300
|
def __init__(self):
|
316
|
-
super(
|
301
|
+
super().__init__(default=None)
|
317
302
|
|
318
303
|
|
319
304
|
class MaybeNonNegativeIntCell(MaybeIntCell):
|
305
|
+
"""Не отрицательное целое число или None.
|
306
|
+
|
307
|
+
Не отбрасывает дробную часть (если она больше 0) и райзит ошибку.
|
320
308
|
"""
|
321
|
-
|
322
|
-
Не отбрасывает дробную часть (если она больше 0) и райзит ошибку
|
323
|
-
"""
|
309
|
+
|
324
310
|
def _parse(self, value):
|
325
|
-
result, _ = super(
|
311
|
+
result, _ = super()._parse(value)
|
312
|
+
|
326
313
|
if result < 0:
|
327
|
-
raise CellValueError(
|
328
|
-
'Число должно быть не меньше нуля! "%s"' % value)
|
314
|
+
raise CellValueError(f'Число должно быть не меньше нуля! "{value}"')
|
329
315
|
if float(value) != float(int(float(value))):
|
330
|
-
raise CellValueError(
|
331
|
-
'Число должно быть целым! "%s"' % value)
|
316
|
+
raise CellValueError(f'Число должно быть целым! "{value}"')
|
332
317
|
return self.result(result)
|
333
318
|
|
334
319
|
|
335
320
|
class BooleanCell(BaseCell):
|
336
|
-
"""
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
"""
|
342
|
-
Если паттерн опущен (или None), то результат будет True при любой
|
343
|
-
значащей строке в ячейке.
|
344
|
-
"""
|
321
|
+
"""Bool-ячейка, получает True при совпадении с паттерном."""
|
322
|
+
|
323
|
+
def __init__(self, default=_NO_DEFAULT, pattern=None, match_type=MATCH_IF_EXACT):
|
324
|
+
"""Если паттерн опущен (или None), то результат будет True при любой значащей строке в ячейке."""
|
325
|
+
super().__init__(default=default)
|
345
326
|
|
346
|
-
super(BooleanCell, self).__init__(default=default)
|
347
327
|
self.pattern = pattern
|
348
328
|
self.match_type = match_type
|
349
329
|
|
350
330
|
def _parse(self, value):
|
351
331
|
if self.pattern is None:
|
352
|
-
return self.result(
|
353
|
-
bool(value.strip()))
|
332
|
+
return self.result(bool(value.strip()))
|
354
333
|
return self.result(_is_match(value, self.pattern, self.match_type))
|
355
334
|
|
356
335
|
|
357
336
|
class MaybeTrueCell(BooleanCell):
|
358
|
-
"""
|
359
|
-
|
360
|
-
паттерна
|
361
|
-
"""
|
337
|
+
"""Bool-ячейка, которая получает значение True при успешном совпадении паттерна."""
|
338
|
+
|
362
339
|
def __init__(self, default=None, pattern=None, match_type=MATCH_IF_EXACT):
|
340
|
+
"""Если паттерн опущен (или None), то результат будет True при любой значащей строке в ячейке.
|
341
|
+
|
342
|
+
Параметр default игнорируется (всегда равен False).
|
363
343
|
"""
|
364
|
-
|
365
|
-
значащей строке в ячейке.
|
366
|
-
Параметр default игнорируется (всегда равет False)
|
367
|
-
"""
|
368
|
-
super(MaybeTrueCell, self).__init__(
|
369
|
-
default=False, pattern=pattern, match_type=match_type)
|
344
|
+
super().__init__(default=False, pattern=pattern, match_type=match_type)
|
370
345
|
|
371
346
|
def _parse(self, value):
|
372
|
-
result, _ = super(
|
347
|
+
result, _ = super()._parse(value)
|
348
|
+
|
373
349
|
if result:
|
374
350
|
return self.result(True)
|
375
351
|
else:
|
@@ -377,21 +353,18 @@ class MaybeTrueCell(BooleanCell):
|
|
377
353
|
|
378
354
|
|
379
355
|
class MaybeFalseCell(BooleanCell):
|
380
|
-
"""
|
381
|
-
|
382
|
-
паттерна
|
383
|
-
"""
|
356
|
+
"""Bool-ячейка, которая получает значение False при успешном совпадении паттерна."""
|
357
|
+
|
384
358
|
def __init__(self, default=None, pattern=None, match_type=MATCH_IF_EXACT):
|
359
|
+
"""Если паттерн опущен (или None), то результат будет False при любой значащей строке в ячейке.
|
360
|
+
|
361
|
+
Параметр default игнорируется (всегда равен True).
|
385
362
|
"""
|
386
|
-
|
387
|
-
значащей строке в ячейке.
|
388
|
-
Параметр default игнорируется (всегда равет True)
|
389
|
-
"""
|
390
|
-
super(MaybeFalseCell, self).__init__(
|
391
|
-
default=True, pattern=pattern, match_type=match_type)
|
363
|
+
super().__init__(default=True, pattern=pattern, match_type=match_type)
|
392
364
|
|
393
365
|
def _parse(self, value):
|
394
|
-
result, _ = super(
|
366
|
+
result, _ = super()._parse(value)
|
367
|
+
|
395
368
|
if result:
|
396
369
|
return self.result(False)
|
397
370
|
else:
|
@@ -399,16 +372,14 @@ class MaybeFalseCell(BooleanCell):
|
|
399
372
|
|
400
373
|
|
401
374
|
class EnumCell(BaseCell):
|
402
|
-
"""
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
match_type=MATCH_IF_EXACT, blank_values=None):
|
407
|
-
"""
|
408
|
-
choices - список вариантов вида (pattern, value)
|
409
|
-
"""
|
375
|
+
"""Ячейка, которая может принимать одно из заданных значений."""
|
376
|
+
|
377
|
+
def __init__(self, default=_NO_DEFAULT, choices=None, match_type=MATCH_IF_EXACT, blank_values=None):
|
378
|
+
"""choices - список вариантов вида (pattern, value)."""
|
410
379
|
assert len(choices) >= 2, 'Должно быть указано хотя бы 2 варианта!'
|
411
|
-
|
380
|
+
|
381
|
+
super().__init__(default=default)
|
382
|
+
|
412
383
|
self.choices = choices
|
413
384
|
self.match_type = match_type
|
414
385
|
self.blank_values = blank_values
|
@@ -442,8 +413,7 @@ class DateCell(BaseCell):
|
|
442
413
|
result = None
|
443
414
|
try:
|
444
415
|
result = datetime(*xlrd.xldate_as_tuple(float(value), 0))
|
445
|
-
except (XLDateNegative, XLDateAmbiguous, XLDateTooLarge,
|
446
|
-
XLDateBadDatemode):
|
416
|
+
except (XLDateNegative, XLDateAmbiguous, XLDateTooLarge, XLDateBadDatemode):
|
447
417
|
raise CellValueError(WRONG_DATE_FORMAT_MSG)
|
448
418
|
except ValueError:
|
449
419
|
value = str(value).strip()[:10]
|
@@ -464,22 +434,16 @@ class DateCell(BaseCell):
|
|
464
434
|
|
465
435
|
|
466
436
|
class MaybeDateCell(DateCell):
|
467
|
-
"""
|
468
|
-
|
469
|
-
"""
|
437
|
+
"""Date или None."""
|
438
|
+
|
470
439
|
def __init__(self):
|
471
|
-
super(
|
440
|
+
super().__init__(default=None)
|
472
441
|
|
473
442
|
|
474
443
|
class DynamicStartRow:
|
475
|
-
"""
|
476
|
-
Автоматический поиск начала таблицы по ее заголовкам ее столбцов
|
477
|
-
"""
|
444
|
+
"""Автоматический поиск начала таблицы по ее заголовкам ее столбцов."""
|
478
445
|
|
479
|
-
def __init__(self, table_headers,
|
480
|
-
order_sensetive=True,
|
481
|
-
case_sensitive=False,
|
482
|
-
search_area=[(0, 0), (50, 50)]):
|
446
|
+
def __init__(self, table_headers, order_sensetive=True, case_sensitive=False, search_area=[(0, 0), (50, 50)]):
|
483
447
|
"""
|
484
448
|
:param list table_headers: Список заголовков: ['Фамилия' 'Имя']
|
485
449
|
:param bool order_sensetive: Учитывать порядок списка table_headers
|
@@ -501,7 +465,6 @@ class DynamicStartRow:
|
|
501
465
|
self.table_headers = [s.upper() for s in table_headers]
|
502
466
|
|
503
467
|
def find_pos(self, sheet):
|
504
|
-
|
505
468
|
max_row = min(self.max_row, sheet.nrows)
|
506
469
|
max_col = min(self.max_col, sheet.ncols)
|
507
470
|
|
@@ -517,7 +480,6 @@ class DynamicStartRow:
|
|
517
480
|
matched.setdefault(val, col)
|
518
481
|
|
519
482
|
if len(matched) == len(self.table_headers):
|
520
|
-
|
521
483
|
cols_nums = [matched[h] for h in self.table_headers]
|
522
484
|
if self.order_sensetive and sorted(cols_nums) != cols_nums:
|
523
485
|
continue
|
@@ -525,7 +487,6 @@ class DynamicStartRow:
|
|
525
487
|
return row, min(cols_nums)
|
526
488
|
|
527
489
|
def find_row(self, sheet):
|
528
|
-
|
529
490
|
pos = self.find_pos(sheet)
|
530
491
|
if pos:
|
531
492
|
row, col = pos
|
@@ -533,12 +494,9 @@ class DynamicStartRow:
|
|
533
494
|
|
534
495
|
|
535
496
|
class SimpleColumnMatcher:
|
536
|
-
"""
|
537
|
-
Класс сопоставления столбцов таблицы
|
538
|
-
"""
|
497
|
+
"""Класс сопоставления столбцов таблицы."""
|
539
498
|
|
540
|
-
def __init__(self, column_name, unique_check=False,
|
541
|
-
strip=True, case_sensitive=False, replacer=None):
|
499
|
+
def __init__(self, column_name, unique_check=False, strip=True, case_sensitive=False, replacer=None):
|
542
500
|
"""
|
543
501
|
:param str column_name: Название столбца
|
544
502
|
:param bool unique_check: Нужно ли проверять на то, чтобы данный
|
@@ -559,7 +517,7 @@ class SimpleColumnMatcher:
|
|
559
517
|
self.replacer = replacer
|
560
518
|
|
561
519
|
def get_column_name(self):
|
562
|
-
"""Возвращает название
|
520
|
+
"""Возвращает название столбца."""
|
563
521
|
return self.column_name
|
564
522
|
|
565
523
|
@property
|
@@ -567,7 +525,7 @@ class SimpleColumnMatcher:
|
|
567
525
|
return self._unique_check
|
568
526
|
|
569
527
|
def _prepare_cell_value(self, cell_value):
|
570
|
-
"""Подготовка значения с учетом флагов
|
528
|
+
"""Подготовка значения с учетом флагов настроек."""
|
571
529
|
cell_value = str(cell_value)
|
572
530
|
if self.strip:
|
573
531
|
cell_value = cell_value.strip()
|
@@ -581,26 +539,21 @@ class SimpleColumnMatcher:
|
|
581
539
|
return cell_value
|
582
540
|
|
583
541
|
def match(self, cell_value):
|
584
|
-
"""
|
585
|
-
Проверка: отвечает ли значение в cell_value
|
586
|
-
требуемому названию столбца
|
587
|
-
"""
|
542
|
+
"""Проверка: отвечает ли значение в cell_value требуемому названию столбца."""
|
588
543
|
|
589
544
|
column_name = self.column_name
|
590
545
|
if not self.case_sensitive:
|
591
546
|
column_name = column_name.upper()
|
592
547
|
|
593
548
|
cell_value = self._prepare_cell_value(cell_value)
|
549
|
+
|
594
550
|
return cell_value == column_name
|
595
551
|
|
596
552
|
|
597
553
|
class RegexColumnMatcher(SimpleColumnMatcher):
|
598
|
-
"""
|
599
|
-
Сопоставление столбцов по регулярному выражению
|
600
|
-
"""
|
554
|
+
"""Сопоставление столбцов по регулярному выражению."""
|
601
555
|
|
602
556
|
def __init__(self, column_name, regex, **kwargs):
|
603
|
-
|
604
557
|
if isinstance(regex, str):
|
605
558
|
flags = re.UNICODE
|
606
559
|
if not kwargs.get('case_sensitive'):
|
@@ -608,10 +561,12 @@ class RegexColumnMatcher(SimpleColumnMatcher):
|
|
608
561
|
|
609
562
|
regex = re.compile(regex, flags)
|
610
563
|
self.regex = regex
|
611
|
-
|
564
|
+
|
565
|
+
super().__init__(column_name, **kwargs)
|
612
566
|
|
613
567
|
def match(self, cell_value):
|
614
568
|
cell_value = self._prepare_cell_value(cell_value)
|
569
|
+
|
615
570
|
return bool(self.regex.search(cell_value))
|
616
571
|
|
617
572
|
|
@@ -619,10 +574,10 @@ class RegexColumnMatcher(SimpleColumnMatcher):
|
|
619
574
|
# XLSLoader
|
620
575
|
# =============================================================================
|
621
576
|
|
577
|
+
|
622
578
|
class XLSLoader:
|
623
|
-
"""
|
624
|
-
|
625
|
-
"""
|
579
|
+
"""Загрузчик xls-файла с разбором."""
|
580
|
+
|
626
581
|
config = {}
|
627
582
|
|
628
583
|
"""
|
@@ -665,14 +620,10 @@ class XLSLoader:
|
|
665
620
|
self._row_errors_log = {}
|
666
621
|
self._log_change_flag = False
|
667
622
|
|
668
|
-
self._book = xlrd.open_workbook(
|
669
|
-
file_contents=memory_mapped_file.read())
|
623
|
+
self._book = xlrd.open_workbook(file_contents=memory_mapped_file.read())
|
670
624
|
|
671
|
-
if not all((
|
672
|
-
self.
|
673
|
-
self._book.nsheets > 0
|
674
|
-
)):
|
675
|
-
self._log("Не удалось загрузить файл!")
|
625
|
+
if not all((self._file, self._book.nsheets > 0)):
|
626
|
+
self._log('Не удалось загрузить файл!')
|
676
627
|
self._book = None
|
677
628
|
raise ValueError('See loader log')
|
678
629
|
|
@@ -694,30 +645,23 @@ class XLSLoader:
|
|
694
645
|
HEADER_PARSER,
|
695
646
|
# парсер строки шапки листа по-умолчанию:
|
696
647
|
# накапливает в список непустые ячейки
|
697
|
-
lambda x: list(filter(bool, x))
|
648
|
+
lambda x: list(filter(bool, x)),
|
698
649
|
)
|
699
650
|
self._flat_config[k] = _fold(v)
|
700
651
|
|
701
652
|
if self._XLSLoader__RESERVED_SHEET in self._flat_config:
|
702
|
-
raise AssertionError(
|
703
|
-
'Название листа "%s" зарезервировано!' %
|
704
|
-
self._XLSLoader__RESERVED_SHEET
|
705
|
-
)
|
653
|
+
raise AssertionError(f'Название листа "{self._XLSLoader__RESERVED_SHEET}" зарезервировано!')
|
706
654
|
|
707
655
|
self._loaded_data = {}
|
708
656
|
self._headers = {}
|
709
657
|
|
710
658
|
def _log_common_error(self, *items):
|
711
|
-
"""
|
712
|
-
Запись в основной лог ошибок
|
713
|
-
"""
|
659
|
+
"""Запись в основной лог ошибок."""
|
714
660
|
self._log_change_flag = True
|
715
661
|
self._common_log.extend(items)
|
716
662
|
|
717
663
|
def _log_row_error(self, xls_pos, *items):
|
718
|
-
"""
|
719
|
-
Добавление ошибки в строке
|
720
|
-
"""
|
664
|
+
"""Добавление ошибки в строке."""
|
721
665
|
self._log_change_flag = True
|
722
666
|
self._row_errors_log.setdefault(xls_pos, []).extend(items)
|
723
667
|
|
@@ -744,9 +688,7 @@ class XLSLoader:
|
|
744
688
|
return self._headers
|
745
689
|
|
746
690
|
def load(self):
|
747
|
-
"""
|
748
|
-
Загрузка листа по дескриптору колонок строки
|
749
|
-
"""
|
691
|
+
"""Загрузка листа по дескриптору колонок строки."""
|
750
692
|
if self._book is None:
|
751
693
|
return False
|
752
694
|
|
@@ -769,8 +711,7 @@ class XLSLoader:
|
|
769
711
|
if sheet_name == self._XLSLoader__RESERVED_SHEET:
|
770
712
|
continue
|
771
713
|
for k, v in self._flat_config.items():
|
772
|
-
if (isinstance(k, int) and (sheet_num + 1) == k) or (
|
773
|
-
isinstance(k, tuple) and (sheet_num + 1) in k):
|
714
|
+
if (isinstance(k, int) and (sheet_num + 1) == k) or (isinstance(k, tuple) and (sheet_num + 1) in k):
|
774
715
|
parsers = v
|
775
716
|
parser_key = k
|
776
717
|
break
|
@@ -780,22 +721,14 @@ class XLSLoader:
|
|
780
721
|
else:
|
781
722
|
expected_sheets = (
|
782
723
|
key
|
783
|
-
for key in map(
|
784
|
-
lambda x: str(x).capitalize(),
|
785
|
-
self._flat_config
|
786
|
-
)
|
724
|
+
for key in map(lambda x: str(x).capitalize(), self._flat_config)
|
787
725
|
if key != self.ANY_SHEET
|
788
726
|
)
|
789
727
|
|
790
728
|
self._log_row_error(
|
791
729
|
(sheet.name, sheet_num + 1, 0),
|
792
|
-
'Неверное название листа
|
793
|
-
'Ожидаемые названия листов:
|
794
|
-
(
|
795
|
-
sheet_num + 1,
|
796
|
-
sheet.name,
|
797
|
-
', '.join(expected_sheets)
|
798
|
-
),
|
730
|
+
f'Неверное название листа №{sheet_num + 1}: {sheet.name}.\n'
|
731
|
+
f'Ожидаемые названия листов: {", ".join(expected_sheets)}',
|
799
732
|
)
|
800
733
|
continue
|
801
734
|
|
@@ -816,18 +749,13 @@ class XLSLoader:
|
|
816
749
|
header_data = self._headers.setdefault(parser_key, [])
|
817
750
|
for row in range(0, start_row):
|
818
751
|
# парсеру заголовка передаётся итератор ячеек по строкам
|
819
|
-
header_data.append(
|
820
|
-
header_parser((
|
821
|
-
sheet.cell(row, i).value for i in range(sheet.ncols)
|
822
|
-
))
|
823
|
-
)
|
752
|
+
header_data.append(header_parser((sheet.cell(row, i).value for i in range(sheet.ncols))))
|
824
753
|
|
825
754
|
# разбор шапки листа
|
826
755
|
col_parsers = []
|
827
756
|
errors = []
|
828
757
|
|
829
758
|
for path, (col_mather, parser), grp in parsers:
|
830
|
-
|
831
759
|
if isinstance(col_mather, str):
|
832
760
|
col_mather = SimpleColumnMatcher(col_mather)
|
833
761
|
# в шапке могут быть целочисленные ячейки
|
@@ -847,39 +775,28 @@ class XLSLoader:
|
|
847
775
|
break
|
848
776
|
|
849
777
|
if col_mather.match(cell_value):
|
850
|
-
matched_cols.append(
|
851
|
-
(col, str(cell_value).strip())
|
852
|
-
)
|
778
|
+
matched_cols.append((col, str(cell_value).strip()))
|
853
779
|
|
854
780
|
if not matched_cols:
|
855
781
|
if parser.is_obligatory():
|
856
782
|
errors.append(
|
857
|
-
'На листе "
|
858
|
-
'необходимый для импорта'
|
859
|
-
sheet_name, sheet_num + 1, column_name
|
860
|
-
)
|
783
|
+
f'На листе "{sheet_name}" ({sheet_num + 1}) отсутствует столбец "{column_name}", '
|
784
|
+
'необходимый для импорта'
|
861
785
|
)
|
862
786
|
|
863
787
|
elif col_mather.unique_check and len(matched_cols) > 1:
|
788
|
+
column_names = ', '.join(f'"{x[1]}"' for x in matched_cols)
|
864
789
|
errors.append(
|
865
|
-
'На листе "
|
866
|
-
'столбцы:
|
867
|
-
sheet_name, sheet_num + 1,
|
868
|
-
', '.join('"%s"' % x[1] for x in matched_cols)
|
869
|
-
)
|
790
|
+
f'На листе "{sheet_name}" ({sheet_num + 1}) присутствуют взаимоисключающие '
|
791
|
+
f'столбцы: {column_names}.'
|
870
792
|
)
|
871
793
|
else:
|
872
794
|
col_pos, real_column_name = matched_cols[0]
|
873
|
-
col_parsers.append(
|
874
|
-
(col_pos, real_column_name, path, parser, grp)
|
875
|
-
)
|
795
|
+
col_parsers.append((col_pos, real_column_name, path, parser, grp))
|
876
796
|
|
877
797
|
if errors:
|
878
798
|
for error in errors:
|
879
|
-
self._log_row_error(
|
880
|
-
(sheet.name, sheet_num + 1, start_row + 1),
|
881
|
-
error
|
882
|
-
)
|
799
|
+
self._log_row_error((sheet.name, sheet_num + 1, start_row + 1), error)
|
883
800
|
continue
|
884
801
|
|
885
802
|
# список результатов связанный с именем листа
|
@@ -900,19 +817,15 @@ class XLSLoader:
|
|
900
817
|
filled_cells_counts = {}
|
901
818
|
|
902
819
|
def inc_counter(grp):
|
903
|
-
filled_cells_counts[grp] = (
|
904
|
-
filled_cells_counts.get(grp, 0) + 1
|
905
|
-
)
|
820
|
+
filled_cells_counts[grp] = filled_cells_counts.get(grp, 0) + 1
|
906
821
|
|
907
822
|
row_errors = {}
|
908
823
|
|
909
824
|
def add_err(grp, title, err):
|
910
|
-
row_errors.setdefault(grp, set()).add(
|
911
|
-
'Cтолбец "%s": %s' % (title, err)
|
912
|
-
)
|
825
|
+
row_errors.setdefault(grp, set()).add(f'Cтолбец "{title}": {err}')
|
913
826
|
|
914
827
|
def add_warn(grp, title, msg):
|
915
|
-
message = 'Cтолбец "
|
828
|
+
message = f'Cтолбец "{title}": {msg}'
|
916
829
|
self._log_row_error(xls_pos, *[message])
|
917
830
|
|
918
831
|
groups = set()
|
@@ -978,15 +891,14 @@ class XLSLoader:
|
|
978
891
|
Преобразует словарь-лог ошибок строк в отсортированный список строк
|
979
892
|
"""
|
980
893
|
result = []
|
981
|
-
for pos, lines in sorted(
|
982
|
-
|
983
|
-
|
984
|
-
result.append('Лист "%s" (%s), строка %s:' % pos)
|
985
|
-
result.extend((' %s' % line) for line in sorted(lines))
|
894
|
+
for pos, lines in sorted(log.items(), key=lambda x: (x[0][1], str(x[0][2]))):
|
895
|
+
result.append(f'Лист "{pos[0]}" ({pos[1]}), строка {pos[2]}:')
|
896
|
+
result.extend(f' {line}' for line in sorted(lines))
|
986
897
|
result.append('')
|
987
898
|
|
988
899
|
return result
|
989
900
|
|
901
|
+
|
990
902
|
if __name__ == '__main__':
|
991
903
|
|
992
904
|
def cells_to_pair(cells):
|
@@ -1002,14 +914,8 @@ if __name__ == '__main__':
|
|
1002
914
|
'date': ('date', DateCell()),
|
1003
915
|
'int': ('int', IntCell(default=-100)),
|
1004
916
|
},
|
1005
|
-
'mb_true': (
|
1006
|
-
|
1007
|
-
MaybeTrueCell(pattern='y', match_type=MATCH_IF_STARTS_WITH)
|
1008
|
-
),
|
1009
|
-
'enum': (
|
1010
|
-
'enum',
|
1011
|
-
EnumCell(choices=((1, 'aaa'), (2, 'bbb')), default=3)
|
1012
|
-
),
|
917
|
+
'mb_true': ('mb_true', MaybeTrueCell(pattern='y', match_type=MATCH_IF_STARTS_WITH)),
|
918
|
+
'enum': ('enum', EnumCell(choices=((1, 'aaa'), (2, 'bbb')), default=3)),
|
1013
919
|
'opt_strs': {
|
1014
920
|
# SUBTREE_CAN_BE_EMPTY: False,
|
1015
921
|
'date': ('date', DateCell()),
|
@@ -1018,8 +924,7 @@ if __name__ == '__main__':
|
|
1018
924
|
'mb_int': ('mb_int', MaybeIntCell()),
|
1019
925
|
},
|
1020
926
|
'pasport': (
|
1021
|
-
RegexColumnMatcher(
|
1022
|
-
'Номер паспорта', '^(№|Номер) паспорта$'),
|
927
|
+
RegexColumnMatcher('Номер паспорта', '^(№|Номер) паспорта$'),
|
1023
928
|
StringCell(),
|
1024
929
|
),
|
1025
930
|
# Авто определение начала таблицы
|
@@ -1032,17 +937,14 @@ if __name__ == '__main__':
|
|
1032
937
|
with open('test_file.xls') as xls_file:
|
1033
938
|
ldr = XLSLoader(xls_file, config=config)
|
1034
939
|
|
1035
|
-
print(
|
940
|
+
print(f'Loaded: {ldr.load()}')
|
1036
941
|
|
1037
942
|
print('---- header ----')
|
1038
|
-
print('\n'.join(
|
1039
|
-
'%s -> %r' % i
|
1040
|
-
for i in ldr.headers['TEST']
|
1041
|
-
if i
|
1042
|
-
))
|
943
|
+
print('\n'.join(f'{col} -> {val!r}' for col, val in ldr.headers['TEST'] if (col, val)))
|
1043
944
|
|
1044
945
|
print('---- data ----')
|
1045
946
|
import pprint
|
947
|
+
|
1046
948
|
pprint.pprint(ldr.data)
|
1047
949
|
|
1048
950
|
print('---- rows_log ----')
|