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.
@@ -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('edu_rdm_integration', 'ExportingDataSubStageAttachment') # noqa: N806
19
- ExportingDataSubStageUploaderClientLog = apps.get_model('edu_rdm_integration', 'ExportingDataSubStageUploaderClientLog') # noqa: N806
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 sub_stage_attachment in ExportingDataSubStageAttachment.objects.filter(
27
- attachment_size__isnull=True,
28
- id__in=attachment_ids
29
- ):
30
- if sub_stage_attachment.attachment.field.storage.exists(sub_stage_attachment.attachment.name):
31
- sub_stage_attachment.attachment_size = sub_stage_attachment.attachment.size
32
- else:
33
- sub_stage_attachment.attachment_size = ATTACHMENT_SIZES
34
- sub_stage_with_deleted_attachments.append(sub_stage_attachment)
35
-
36
- ExportingDataSubStageAttachment.objects.bulk_update(sub_stage_with_deleted_attachments, ['attachment_size'])
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.2
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=Pj_n-ytsC0lhyU67qvH8UHHQ-c-TH5MZFfy-UF6y6M4,1809
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.2.dist-info/LICENSE,sha256=uw43Gjjj-1vXWCItfSrNDpbejnOwZMrNerUh8oWbq8Q,3458
180
- edu_rdm_integration-3.4.2.dist-info/METADATA,sha256=MyujvyOvHWMyJpk9lZTwTeWORxzV_fMisjEO_DEDvIA,74646
181
- edu_rdm_integration-3.4.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
182
- edu_rdm_integration-3.4.2.dist-info/namespace_packages.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
183
- edu_rdm_integration-3.4.2.dist-info/top_level.txt,sha256=nRJV0O14UtNE-jGIYG03sohgFnZClvf57H5m6VBXe9Y,20
184
- edu_rdm_integration-3.4.2.dist-info/RECORD,,
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,,