edu-rdm-integration 3.4.2__py3-none-any.whl → 3.4.4__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.
- edu_rdm_integration/management/commands/async_fix_attachment_size.py +254 -0
- edu_rdm_integration/migrations/0013_set_attachment_size.py +23 -14
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/METADATA +19 -1
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/RECORD +8 -7
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/LICENSE +0 -0
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/WHEEL +0 -0
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/namespace_packages.txt +0 -0
- {edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,254 @@
|
|
1
|
+
"""
|
2
|
+
Команда для проставления расчетов файла . По умолчанию собирается за месяц. Обновление работает асинхронно.
|
3
|
+
|
4
|
+
Выполнение:
|
5
|
+
django-admin async_fix_attachment_size --chunk_size=50 --count_store_size --connection_pool_size=5
|
6
|
+
--async_workers_count=10 --date_begin='01-01-2023'
|
7
|
+
|
8
|
+
Параметры:
|
9
|
+
--date_begin: дата начала сбора данных по сгенерированным файлам в формате ДД-ММ-ГГГГ (по умолчанию месяц назад).
|
10
|
+
--date_end: дата окончания сбора данных по сгенерированным файлам в формате ДД-ММ-ГГГГ (по умолчанию текущая дата).
|
11
|
+
--chunk_size: количество файлов в одной итерации, по умолчанию 500
|
12
|
+
--count_store_size: при указании этого флага, будет произведен расчет размера файла (
|
13
|
+
запрос через оs и вычисление размера) - не рекомендуется при большом количестве данных и на дампах с прода
|
14
|
+
--connection_pool_size: количество одновременных коннектов к базе по умолчанию 10
|
15
|
+
--async_workers_count количество одновременных асинхронных воркеров-исполнителей по умолчанию 20
|
16
|
+
"""
|
17
|
+
|
18
|
+
|
19
|
+
import asyncio
|
20
|
+
from datetime import (
|
21
|
+
date,
|
22
|
+
datetime,
|
23
|
+
timedelta,
|
24
|
+
)
|
25
|
+
from typing import (
|
26
|
+
List,
|
27
|
+
Tuple,
|
28
|
+
)
|
29
|
+
|
30
|
+
import asyncpg
|
31
|
+
from django.conf import (
|
32
|
+
settings,
|
33
|
+
)
|
34
|
+
from django.core.management import (
|
35
|
+
BaseCommand,
|
36
|
+
CommandError,
|
37
|
+
)
|
38
|
+
|
39
|
+
from educommon import (
|
40
|
+
logger,
|
41
|
+
)
|
42
|
+
from educommon.utils.seqtools import (
|
43
|
+
make_chunks,
|
44
|
+
)
|
45
|
+
|
46
|
+
from edu_rdm_integration.enums import (
|
47
|
+
FileUploadStatusEnum,
|
48
|
+
)
|
49
|
+
from edu_rdm_integration.models import (
|
50
|
+
ExportingDataSubStageAttachment,
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
DEFAULT_QUERYSET_CHUNK_SIZE = 500
|
55
|
+
DEFAULT_FILE_SIZE = 10_485_760
|
56
|
+
DEFAULT_ASYNC_WORKERS_COUNT = 20
|
57
|
+
DEFAULT_POOL_SIZE = 10
|
58
|
+
DB_SETTINGS = settings.DATABASES['default']
|
59
|
+
|
60
|
+
|
61
|
+
class Command(BaseCommand):
|
62
|
+
"""Команда для проставления размера по умолчанию (в байтах) для отправленных файлов."""
|
63
|
+
|
64
|
+
help = ( # noqa: A003
|
65
|
+
'Команда для проставления размера по умолчанию (в байтах) для отправленных файлов.'
|
66
|
+
)
|
67
|
+
|
68
|
+
def __init__(self, *args, **kwargs):
|
69
|
+
super().__init__(*args, **kwargs)
|
70
|
+
|
71
|
+
self.result = {
|
72
|
+
'initial_count_rows_to_fix': 0,
|
73
|
+
'errors': [],
|
74
|
+
'fixed_rows_count': 0,
|
75
|
+
}
|
76
|
+
|
77
|
+
def add_arguments(self, parser):
|
78
|
+
"""Добавление аргументов запуска команды."""
|
79
|
+
parser.add_argument(
|
80
|
+
'--chunk_size',
|
81
|
+
type=int,
|
82
|
+
default=DEFAULT_QUERYSET_CHUNK_SIZE,
|
83
|
+
help='Кол-во единовременно изменяемых элементов в запросе'
|
84
|
+
)
|
85
|
+
parser.add_argument(
|
86
|
+
'--count_store_size',
|
87
|
+
action='store_true',
|
88
|
+
default=False,
|
89
|
+
help='Рассчитать размер файлов'
|
90
|
+
),
|
91
|
+
parser.add_argument(
|
92
|
+
'--connection_pool_size',
|
93
|
+
type=int,
|
94
|
+
default=DEFAULT_POOL_SIZE,
|
95
|
+
help='Кол-во одновременных коннектов к базе'
|
96
|
+
)
|
97
|
+
parser.add_argument(
|
98
|
+
'--async_workers_count',
|
99
|
+
type=int,
|
100
|
+
default=DEFAULT_ASYNC_WORKERS_COUNT,
|
101
|
+
help='Кол-во одновременных асинхронных воркеров-исполнителей'
|
102
|
+
)
|
103
|
+
parser.add_argument(
|
104
|
+
'--date_begin',
|
105
|
+
type=lambda d: datetime.strptime(d, '%d-%m-%Y').date(),
|
106
|
+
help='Дата начала поиска сгенерированных файлов в формате %%d-%%m-%%Y.',
|
107
|
+
)
|
108
|
+
parser.add_argument(
|
109
|
+
'--date_end',
|
110
|
+
type=lambda d: datetime.strptime(d, '%d-%m-%Y').date(),
|
111
|
+
help='Дата окончания поиска сгенерированных файлов в формате %%d-%%m-%%Y.',
|
112
|
+
)
|
113
|
+
|
114
|
+
def get_attachments_fix_query(
|
115
|
+
self,
|
116
|
+
chunk_size: int,
|
117
|
+
date_begin: date,
|
118
|
+
date_end: date,
|
119
|
+
count_store_size: bool
|
120
|
+
) -> List[Tuple[int, int]]:
|
121
|
+
"""Формирование id файлов, которым надо обновить размер."""
|
122
|
+
logger.info('Определение файлов, которым надо обновить размер.')
|
123
|
+
attachment_query = ExportingDataSubStageAttachment.objects.filter(
|
124
|
+
exportingdatasubstageuploaderclientlog__file_upload_status=FileUploadStatusEnum.IN_PROGRESS,
|
125
|
+
exportingdatasubstageuploaderclientlog__is_emulation=False,
|
126
|
+
attachment_size__isnull=True,
|
127
|
+
modified__gte=date_begin,
|
128
|
+
modified__lte=date_end,
|
129
|
+
)
|
130
|
+
update_attachments = []
|
131
|
+
|
132
|
+
for chunk in make_chunks(attachment_query.iterator(), chunk_size):
|
133
|
+
if count_store_size:
|
134
|
+
update_attachments.append(
|
135
|
+
[
|
136
|
+
(
|
137
|
+
attachment.id, attachment.attachment.size
|
138
|
+
if attachment.attachment.field.storage.exists(attachment.attachment.name)
|
139
|
+
else DEFAULT_FILE_SIZE
|
140
|
+
)
|
141
|
+
for attachment in chunk
|
142
|
+
]
|
143
|
+
)
|
144
|
+
else:
|
145
|
+
update_attachments.append([(attachment.id, DEFAULT_FILE_SIZE) for attachment in chunk])
|
146
|
+
|
147
|
+
return update_attachments
|
148
|
+
|
149
|
+
async def execute_update_sql(
|
150
|
+
self,
|
151
|
+
pool: asyncpg.Pool,
|
152
|
+
data: List[Tuple[int, int]]
|
153
|
+
) -> None:
|
154
|
+
"""Метод для исполнения сгенерированного UPDATE sql запроса."""
|
155
|
+
self.result['initial_count_rows_to_fix'] += len(data)
|
156
|
+
logger.info('Обновление размера файлов')
|
157
|
+
async with pool.acquire() as connection:
|
158
|
+
try:
|
159
|
+
await connection.executemany(
|
160
|
+
"""
|
161
|
+
UPDATE rdm_exporting_data_sub_stage_attachment
|
162
|
+
set attachment_size = $2
|
163
|
+
where id = $1;
|
164
|
+
""", data,
|
165
|
+
)
|
166
|
+
self.result['fixed_rows_count'] += len(data)
|
167
|
+
except Exception as ex:
|
168
|
+
self.result['errors'].append(str(ex))
|
169
|
+
|
170
|
+
async def fix_attachment_size(
|
171
|
+
self,
|
172
|
+
fix_queries: List,
|
173
|
+
pool_size: int = None,
|
174
|
+
workers_count: int = None
|
175
|
+
) -> None:
|
176
|
+
"""Асинхронное обновление размера файлов."""
|
177
|
+
pool = await asyncpg.create_pool(
|
178
|
+
max_size=pool_size,
|
179
|
+
min_size=pool_size,
|
180
|
+
host=DB_SETTINGS['HOST'],
|
181
|
+
port=DB_SETTINGS['PORT'],
|
182
|
+
user=DB_SETTINGS['USER'],
|
183
|
+
password=DB_SETTINGS['PASSWORD'],
|
184
|
+
database=DB_SETTINGS['NAME']
|
185
|
+
)
|
186
|
+
if len(fix_queries) > workers_count:
|
187
|
+
for chunk in make_chunks(fix_queries, workers_count):
|
188
|
+
tasks = [self.execute_update_sql(pool, query) for query in chunk]
|
189
|
+
await asyncio.gather(*tasks)
|
190
|
+
else:
|
191
|
+
tasks = [self.execute_update_sql(pool, query) for query in fix_queries]
|
192
|
+
await asyncio.gather(*tasks)
|
193
|
+
|
194
|
+
def update_attachment_size(
|
195
|
+
self,
|
196
|
+
connection_pool_size: int,
|
197
|
+
chunk_size: int,
|
198
|
+
date_begin: date,
|
199
|
+
date_end:date,
|
200
|
+
async_workers_count: int,
|
201
|
+
count_store_size: bool
|
202
|
+
):
|
203
|
+
"""Обновление размеров файла."""
|
204
|
+
fix_queries = self.get_attachments_fix_query(chunk_size, date_begin, date_end, count_store_size)
|
205
|
+
if fix_queries:
|
206
|
+
event_loop = asyncio.get_event_loop()
|
207
|
+
try:
|
208
|
+
event_loop.run_until_complete(
|
209
|
+
self.fix_attachment_size(fix_queries, connection_pool_size, async_workers_count)
|
210
|
+
)
|
211
|
+
finally:
|
212
|
+
event_loop.close()
|
213
|
+
|
214
|
+
def handle(self, *args, **options):
|
215
|
+
"""Выполнение команды."""
|
216
|
+
time_start = datetime.now()
|
217
|
+
|
218
|
+
chunk_size = options['chunk_size']
|
219
|
+
connection_pool_size = options['connection_pool_size']
|
220
|
+
async_workers_count = options['async_workers_count']
|
221
|
+
count_store_size = options['count_store_size']
|
222
|
+
|
223
|
+
date_begin = options.get('date_begin')
|
224
|
+
date_end = options.get('date_end') or date.today()
|
225
|
+
|
226
|
+
if not date_begin:
|
227
|
+
date_begin = date_end - timedelta(days=30)
|
228
|
+
|
229
|
+
elif date_begin > date_end:
|
230
|
+
raise CommandError('Дата начала сборки больше, чем дата окончания')
|
231
|
+
|
232
|
+
logger.info('Обновление размеров файла\n')
|
233
|
+
|
234
|
+
self.update_attachment_size(
|
235
|
+
connection_pool_size,
|
236
|
+
chunk_size,
|
237
|
+
date_begin,
|
238
|
+
date_end,
|
239
|
+
async_workers_count,
|
240
|
+
count_store_size
|
241
|
+
)
|
242
|
+
|
243
|
+
delta = datetime.now() - time_start
|
244
|
+
|
245
|
+
logger.info(f'Время выполнения: {delta}\n')
|
246
|
+
|
247
|
+
logger.info(
|
248
|
+
f'Начальное кол-во записей для исправления: {self.result["initial_count_rows_to_fix"]}\n'
|
249
|
+
f'Исправлено: {self.result["fixed_rows_count"]}\n'
|
250
|
+
)
|
251
|
+
|
252
|
+
errors = self.result['errors']
|
253
|
+
for error in errors:
|
254
|
+
logger.error(error)
|
@@ -7,33 +7,42 @@ from django.db import (
|
|
7
7
|
from edu_rdm_integration.enums import (
|
8
8
|
FileUploadStatusEnum,
|
9
9
|
)
|
10
|
+
from educommon.utils.seqtools import (
|
11
|
+
make_chunks,
|
12
|
+
)
|
10
13
|
|
11
14
|
|
12
15
|
# Значение размера файла по умолчанию (если файл не найден)
|
13
16
|
ATTACHMENT_SIZES = 10_485_760
|
17
|
+
CHUNK_SIZE = 500
|
14
18
|
|
15
19
|
|
16
20
|
def set_attachment_size(apps, schema_editor):
|
17
21
|
"""Установка размера файла по умолчанию."""
|
18
|
-
ExportingDataSubStageAttachment = apps.get_model(
|
19
|
-
|
22
|
+
ExportingDataSubStageAttachment = apps.get_model(
|
23
|
+
'edu_rdm_integration', 'ExportingDataSubStageAttachment'
|
24
|
+
) # noqa: N806
|
25
|
+
ExportingDataSubStageUploaderClientLog = apps.get_model(
|
26
|
+
'edu_rdm_integration', 'ExportingDataSubStageUploaderClientLog'
|
27
|
+
) # noqa: N806
|
20
28
|
|
21
29
|
attachment_ids = ExportingDataSubStageUploaderClientLog.objects.filter(
|
22
30
|
file_upload_status=FileUploadStatusEnum.IN_PROGRESS
|
23
|
-
).values_list('attachment_id', flat=True)
|
31
|
+
).values_list('attachment_id', flat=True).iterator()
|
24
32
|
|
25
33
|
sub_stage_with_deleted_attachments = []
|
26
|
-
for
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
sub_stage_attachment.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
for chunked_attachment_ids in make_chunks(attachment_ids, CHUNK_SIZE):
|
35
|
+
for sub_stage_attachment in ExportingDataSubStageAttachment.objects.filter(
|
36
|
+
attachment_size__isnull=True,
|
37
|
+
id__in=chunked_attachment_ids,
|
38
|
+
):
|
39
|
+
if sub_stage_attachment.attachment.field.storage.exists(sub_stage_attachment.attachment.name):
|
40
|
+
sub_stage_attachment.attachment_size = sub_stage_attachment.attachment.size
|
41
|
+
else:
|
42
|
+
sub_stage_attachment.attachment_size = ATTACHMENT_SIZES
|
43
|
+
sub_stage_with_deleted_attachments.append(sub_stage_attachment)
|
44
|
+
|
45
|
+
ExportingDataSubStageAttachment.objects.bulk_update(sub_stage_with_deleted_attachments, ['attachment_size'])
|
37
46
|
|
38
47
|
|
39
48
|
class Migration(migrations.Migration):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: edu-rdm-integration
|
3
|
-
Version: 3.4.
|
3
|
+
Version: 3.4.4
|
4
4
|
Summary: Интеграция с Региональной витриной данных
|
5
5
|
Home-page:
|
6
6
|
Download-URL:
|
@@ -28,6 +28,7 @@ Requires-Dist: wheel <0.42,>=0.37.1
|
|
28
28
|
Requires-Dist: transliterate <2
|
29
29
|
Requires-Dist: Django <3.3,>=2.2
|
30
30
|
Requires-Dist: celery <5.3,>=4.4.7
|
31
|
+
Requires-Dist: asyncpg ==0.23.0
|
31
32
|
Requires-Dist: educommon <4,>=3.11.0
|
32
33
|
Requires-Dist: function-tools <1,>=0.9.0
|
33
34
|
Requires-Dist: m3-db-utils <1,>=0.3.10
|
@@ -302,6 +303,23 @@ Requires-Dist: uploader-client <1,>=0.2.1
|
|
302
303
|
### Удалено
|
303
304
|
|
304
305
|
|
306
|
+
## 3.4.4 - 2024-10-23
|
307
|
+
Добавлена ассинхронная менедж-команда простановки размеров файлов выгрузки в модель ExportingDataSubStageAttachment
|
308
|
+
|
309
|
+
## Исправлено
|
310
|
+
- [EDUSCHL-22651](https://jira.bars.group/browse/EDUSCHL-22651)
|
311
|
+
Добавлена ассинхронная менедж-команда простановки размеров файлов выгрузки в модель ExportingDataSubStageAttachment
|
312
|
+
|
313
|
+
|
314
|
+
## 3.4.3 - 2024-10-17
|
315
|
+
Исправлена миграция простановки размеров файлов выгрузки в модель ExportingDataSubStageAttachment
|
316
|
+
|
317
|
+
|
318
|
+
## Исправлено
|
319
|
+
- [EDUSCHL-22651](https://jira.bars.group/browse/EDUSCHL-22651)
|
320
|
+
Исправлена миграция простановки размеров файлов выгрузки в модель ExportingDataSubStageAttachment
|
321
|
+
|
322
|
+
|
305
323
|
## 3.4.2 - 2024-10-15
|
306
324
|
Добавлены отдельные параметры по управлению временем запуска UploadDataAsyncTask
|
307
325
|
|
@@ -143,6 +143,7 @@ edu_rdm_integration/function_templates/function_export_data_template/validators.
|
|
143
143
|
edu_rdm_integration/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
144
144
|
edu_rdm_integration/management/general.py,sha256=hmZGp9EKpSAm-VuhXNpW_ETA5TWi3Y-caSj7XB2vtuM,12957
|
145
145
|
edu_rdm_integration/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
146
|
+
edu_rdm_integration/management/commands/async_fix_attachment_size.py,sha256=4bit6jKuTfw-wF9QfatAcQg29hyGpJzYQ4RHXuAaA9U,9882
|
146
147
|
edu_rdm_integration/management/commands/check_upload_status.py,sha256=jGqFMLMgrirG_H2OxzxNG3Z3d1xvORIzWqzKBUtr22Y,3843
|
147
148
|
edu_rdm_integration/management/commands/collect_latest_models_data.py,sha256=4ObBFqYMkX5v1saV9V7PivYBm0RMcDCGNlMGx77UAjs,750
|
148
149
|
edu_rdm_integration/management/commands/collect_models_data.py,sha256=11RshcQiXk6Flz-8oiFuM5KSZ_8_c1111qkqmyiu5sw,1022
|
@@ -162,7 +163,7 @@ edu_rdm_integration/migrations/0009_auto_20240522_1619.py,sha256=hz8cBq6m18XK5Pb
|
|
162
163
|
edu_rdm_integration/migrations/0010_transferredentity_export_enabled.py,sha256=LJH7QY_1GLWSHLRPB--9PPypPiQd4pAT2-oIOEF84DY,507
|
163
164
|
edu_rdm_integration/migrations/0011_exportingdatasubstageentity.py,sha256=2BfIif_hkFv1h6VEfe0Ys4J_uk6LR9YtO711ocDYPso,1263
|
164
165
|
edu_rdm_integration/migrations/0012_exportingdatasubstageattachment_attachment_size.py,sha256=y_JQO69k9pEfrJyimaRiAOBmhaJmssIyepCGd-Sy9hs,511
|
165
|
-
edu_rdm_integration/migrations/0013_set_attachment_size.py,sha256=
|
166
|
+
edu_rdm_integration/migrations/0013_set_attachment_size.py,sha256=8-AJpnIViGUh9Q2aMegUPqyw-fthcL-7Ppbm7IvlRms,2047
|
166
167
|
edu_rdm_integration/migrations/0014_uploaddatacommand.py,sha256=Hh0vKKiGgKOvY1kBAcmway4dSYUXwVArHAc9YrsjCIU,2079
|
167
168
|
edu_rdm_integration/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
168
169
|
edu_rdm_integration/registry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -176,9 +177,9 @@ edu_rdm_integration/uploader_log/enums.py,sha256=rgSO3BL2rh2xpfm0Pt4waQW8fB1VMJL
|
|
176
177
|
edu_rdm_integration/uploader_log/managers.py,sha256=y5wTSMzF9hpOpIU_A7nIafL_LBU3QEie6LAYWoB-pBQ,3203
|
177
178
|
edu_rdm_integration/uploader_log/ui.py,sha256=YM9Buqp2wxE95Wf5gvAATBzuYzDOossK1sEmvFk07cI,2110
|
178
179
|
edu_rdm_integration/uploader_log/templates/ui-js/object-grid-buttons.js,sha256=2xyGe0wdVokM0RhpzRzcRvJPBkBmPe3SlZry4oP4Nzs,6201
|
179
|
-
edu_rdm_integration-3.4.
|
180
|
-
edu_rdm_integration-3.4.
|
181
|
-
edu_rdm_integration-3.4.
|
182
|
-
edu_rdm_integration-3.4.
|
183
|
-
edu_rdm_integration-3.4.
|
184
|
-
edu_rdm_integration-3.4.
|
180
|
+
edu_rdm_integration-3.4.4.dist-info/LICENSE,sha256=uw43Gjjj-1vXWCItfSrNDpbejnOwZMrNerUh8oWbq8Q,3458
|
181
|
+
edu_rdm_integration-3.4.4.dist-info/METADATA,sha256=bomV5xAghSNXhNvPbmxH7X_VQUt-DcaY_b23Ff6mp0g,75601
|
182
|
+
edu_rdm_integration-3.4.4.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
183
|
+
edu_rdm_integration-3.4.4.dist-info/namespace_packages.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
184
|
+
edu_rdm_integration-3.4.4.dist-info/top_level.txt,sha256=nRJV0O14UtNE-jGIYG03sohgFnZClvf57H5m6VBXe9Y,20
|
185
|
+
edu_rdm_integration-3.4.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{edu_rdm_integration-3.4.2.dist-info → edu_rdm_integration-3.4.4.dist-info}/namespace_packages.txt
RENAMED
File without changes
|
File without changes
|