data-syncmaster 0.1.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.
Files changed (110) hide show
  1. data_syncmaster-0.1.1.dist-info/LICENSE.txt +203 -0
  2. data_syncmaster-0.1.1.dist-info/METADATA +115 -0
  3. data_syncmaster-0.1.1.dist-info/RECORD +110 -0
  4. data_syncmaster-0.1.1.dist-info/WHEEL +4 -0
  5. syncmaster/__init__.py +6 -0
  6. syncmaster/backend/__init__.py +2 -0
  7. syncmaster/backend/api/__init__.py +2 -0
  8. syncmaster/backend/api/deps.py +20 -0
  9. syncmaster/backend/api/monitoring.py +10 -0
  10. syncmaster/backend/api/router.py +10 -0
  11. syncmaster/backend/api/v1/__init__.py +2 -0
  12. syncmaster/backend/api/v1/auth/__init__.py +2 -0
  13. syncmaster/backend/api/v1/auth/router.py +32 -0
  14. syncmaster/backend/api/v1/auth/utils.py +26 -0
  15. syncmaster/backend/api/v1/connections.py +300 -0
  16. syncmaster/backend/api/v1/groups.py +225 -0
  17. syncmaster/backend/api/v1/queue.py +148 -0
  18. syncmaster/backend/api/v1/router.py +18 -0
  19. syncmaster/backend/api/v1/transfers/__init__.py +2 -0
  20. syncmaster/backend/api/v1/transfers/router.py +469 -0
  21. syncmaster/backend/api/v1/transfers/utils.py +17 -0
  22. syncmaster/backend/api/v1/users.py +75 -0
  23. syncmaster/backend/export_openapi_schema.py +26 -0
  24. syncmaster/backend/handler.py +203 -0
  25. syncmaster/backend/logger.py +2 -0
  26. syncmaster/backend/main.py +63 -0
  27. syncmaster/backend/pre_start.py +94 -0
  28. syncmaster/backend/services/__init__.py +4 -0
  29. syncmaster/backend/services/auth.py +58 -0
  30. syncmaster/backend/services/unit_of_work.py +44 -0
  31. syncmaster/config.py +110 -0
  32. syncmaster/db/__init__.py +2 -0
  33. syncmaster/db/alembic.ini +41 -0
  34. syncmaster/db/base.py +28 -0
  35. syncmaster/db/factory.py +37 -0
  36. syncmaster/db/migrations/README +1 -0
  37. syncmaster/db/migrations/__init__.py +2 -0
  38. syncmaster/db/migrations/env.py +87 -0
  39. syncmaster/db/migrations/script.py.mako +24 -0
  40. syncmaster/db/migrations/versions/2023-11-23_478240cdad4b_init.py +242 -0
  41. syncmaster/db/migrations/versions/__init__.py +2 -0
  42. syncmaster/db/mixins.py +33 -0
  43. syncmaster/db/models.py +194 -0
  44. syncmaster/db/repositories/__init__.py +22 -0
  45. syncmaster/db/repositories/base.py +109 -0
  46. syncmaster/db/repositories/connection.py +138 -0
  47. syncmaster/db/repositories/credentials_repository.py +87 -0
  48. syncmaster/db/repositories/group.py +264 -0
  49. syncmaster/db/repositories/queue.py +195 -0
  50. syncmaster/db/repositories/repository_with_owner.py +115 -0
  51. syncmaster/db/repositories/run.py +78 -0
  52. syncmaster/db/repositories/transfer.py +202 -0
  53. syncmaster/db/repositories/user.py +72 -0
  54. syncmaster/db/repositories/utils.py +25 -0
  55. syncmaster/db/utils.py +31 -0
  56. syncmaster/dto/__init__.py +2 -0
  57. syncmaster/dto/connections.py +60 -0
  58. syncmaster/dto/transfers.py +46 -0
  59. syncmaster/exceptions/__init__.py +13 -0
  60. syncmaster/exceptions/base.py +12 -0
  61. syncmaster/exceptions/connection.py +28 -0
  62. syncmaster/exceptions/credentials.py +8 -0
  63. syncmaster/exceptions/group.py +27 -0
  64. syncmaster/exceptions/queue.py +16 -0
  65. syncmaster/exceptions/run.py +19 -0
  66. syncmaster/exceptions/transfer.py +39 -0
  67. syncmaster/exceptions/user.py +11 -0
  68. syncmaster/schemas/__init__.py +2 -0
  69. syncmaster/schemas/v1/__init__.py +54 -0
  70. syncmaster/schemas/v1/auth.py +12 -0
  71. syncmaster/schemas/v1/connection_types.py +9 -0
  72. syncmaster/schemas/v1/connections/__init__.py +2 -0
  73. syncmaster/schemas/v1/connections/connection.py +146 -0
  74. syncmaster/schemas/v1/connections/hdfs.py +40 -0
  75. syncmaster/schemas/v1/connections/hive.py +40 -0
  76. syncmaster/schemas/v1/connections/oracle.py +58 -0
  77. syncmaster/schemas/v1/connections/postgres.py +48 -0
  78. syncmaster/schemas/v1/connections/s3.py +66 -0
  79. syncmaster/schemas/v1/file_formats.py +7 -0
  80. syncmaster/schemas/v1/groups.py +39 -0
  81. syncmaster/schemas/v1/page.py +40 -0
  82. syncmaster/schemas/v1/queue.py +32 -0
  83. syncmaster/schemas/v1/status.py +16 -0
  84. syncmaster/schemas/v1/transfer_types.py +6 -0
  85. syncmaster/schemas/v1/transfers/__init__.py +172 -0
  86. syncmaster/schemas/v1/transfers/db.py +23 -0
  87. syncmaster/schemas/v1/transfers/file/__init__.py +2 -0
  88. syncmaster/schemas/v1/transfers/file/base.py +47 -0
  89. syncmaster/schemas/v1/transfers/file/hdfs.py +27 -0
  90. syncmaster/schemas/v1/transfers/file/s3.py +27 -0
  91. syncmaster/schemas/v1/transfers/file_format.py +29 -0
  92. syncmaster/schemas/v1/transfers/run.py +37 -0
  93. syncmaster/schemas/v1/transfers/strategy.py +15 -0
  94. syncmaster/schemas/v1/types.py +5 -0
  95. syncmaster/schemas/v1/users.py +83 -0
  96. syncmaster/worker/__init__.py +2 -0
  97. syncmaster/worker/base.py +14 -0
  98. syncmaster/worker/config.py +18 -0
  99. syncmaster/worker/controller.py +127 -0
  100. syncmaster/worker/handlers/__init__.py +2 -0
  101. syncmaster/worker/handlers/base.py +49 -0
  102. syncmaster/worker/handlers/file/__init__.py +2 -0
  103. syncmaster/worker/handlers/file/base.py +56 -0
  104. syncmaster/worker/handlers/file/hdfs.py +14 -0
  105. syncmaster/worker/handlers/file/s3.py +20 -0
  106. syncmaster/worker/handlers/hive.py +41 -0
  107. syncmaster/worker/handlers/oracle.py +48 -0
  108. syncmaster/worker/handlers/postgres.py +47 -0
  109. syncmaster/worker/spark.py +93 -0
  110. syncmaster/worker/transfer.py +85 -0
@@ -0,0 +1,469 @@
1
+ # SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ import asyncio
4
+
5
+ from fastapi import APIRouter, Depends, Query, status
6
+ from kombu.exceptions import KombuError
7
+
8
+ from syncmaster.backend.api.deps import UnitOfWorkMarker
9
+ from syncmaster.backend.api.v1.transfers.utils import (
10
+ process_file_transfer_directory_path,
11
+ )
12
+ from syncmaster.backend.services import UnitOfWork, get_user
13
+ from syncmaster.db.models import Status, User
14
+ from syncmaster.db.utils import Permission
15
+ from syncmaster.exceptions.base import ActionNotAllowedError
16
+ from syncmaster.exceptions.connection import ConnectionNotFoundError
17
+ from syncmaster.exceptions.group import GroupNotFoundError
18
+ from syncmaster.exceptions.queue import DifferentTransferAndQueueGroupError
19
+ from syncmaster.exceptions.run import CannotConnectToTaskQueueError
20
+ from syncmaster.exceptions.transfer import (
21
+ DifferentTransferAndConnectionsGroupsError,
22
+ DifferentTypeConnectionsAndParamsError,
23
+ TransferNotFoundError,
24
+ )
25
+ from syncmaster.schemas.v1.status import (
26
+ StatusCopyTransferResponseSchema,
27
+ StatusResponseSchema,
28
+ )
29
+ from syncmaster.schemas.v1.transfers import (
30
+ CopyTransferSchema,
31
+ CreateTransferSchema,
32
+ ReadTransferSchema,
33
+ TransferPageSchema,
34
+ UpdateTransferSchema,
35
+ )
36
+ from syncmaster.schemas.v1.transfers.run import (
37
+ CreateRunSchema,
38
+ ReadRunSchema,
39
+ RunPageSchema,
40
+ )
41
+ from syncmaster.worker.config import celery
42
+
43
+ router = APIRouter(tags=["Transfers"])
44
+
45
+
46
+ @router.get("/transfers")
47
+ async def read_transfers(
48
+ group_id: int,
49
+ page: int = Query(gt=0, default=1),
50
+ page_size: int = Query(gt=0, le=200, default=20),
51
+ current_user: User = Depends(get_user(is_active=True)),
52
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
53
+ ) -> TransferPageSchema:
54
+ """Return transfers in page format"""
55
+ resource_role = await unit_of_work.transfer.get_group_permission(
56
+ user=current_user,
57
+ group_id=group_id,
58
+ )
59
+
60
+ if resource_role == Permission.NONE:
61
+ raise GroupNotFoundError
62
+
63
+ pagination = await unit_of_work.transfer.paginate(
64
+ page=page,
65
+ page_size=page_size,
66
+ group_id=group_id,
67
+ )
68
+
69
+ return TransferPageSchema.from_pagination(pagination=pagination)
70
+
71
+
72
+ @router.post("/transfers")
73
+ async def create_transfer(
74
+ transfer_data: CreateTransferSchema,
75
+ current_user: User = Depends(get_user(is_active=True)),
76
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
77
+ ) -> ReadTransferSchema:
78
+ group_permission = await unit_of_work.transfer.get_group_permission(
79
+ user=current_user,
80
+ group_id=transfer_data.group_id,
81
+ )
82
+
83
+ if group_permission < Permission.WRITE:
84
+ raise ActionNotAllowedError
85
+
86
+ target_connection = await unit_of_work.connection.read_by_id(
87
+ connection_id=transfer_data.target_connection_id,
88
+ )
89
+ source_connection = await unit_of_work.connection.read_by_id(
90
+ connection_id=transfer_data.source_connection_id,
91
+ )
92
+ queue = await unit_of_work.queue.read_by_id(transfer_data.queue_id)
93
+
94
+ if (
95
+ target_connection.group_id != source_connection.group_id
96
+ or target_connection.group_id != transfer_data.group_id
97
+ or source_connection.group_id != transfer_data.group_id
98
+ ):
99
+ raise DifferentTransferAndConnectionsGroupsError
100
+
101
+ if target_connection.data["type"] != transfer_data.target_params.type:
102
+ raise DifferentTypeConnectionsAndParamsError(
103
+ connection_type=target_connection.data["type"],
104
+ conn="target",
105
+ params_type=transfer_data.target_params.type,
106
+ )
107
+
108
+ if source_connection.data["type"] != transfer_data.source_params.type:
109
+ raise DifferentTypeConnectionsAndParamsError(
110
+ connection_type=source_connection.data["type"],
111
+ conn="source",
112
+ params_type=transfer_data.source_params.type,
113
+ )
114
+
115
+ if transfer_data.group_id != queue.group_id:
116
+ raise DifferentTransferAndQueueGroupError
117
+
118
+ transfer_data = process_file_transfer_directory_path(transfer_data) # type: ignore
119
+
120
+ async with unit_of_work:
121
+ transfer = await unit_of_work.transfer.create(
122
+ group_id=transfer_data.group_id,
123
+ name=transfer_data.name,
124
+ description=transfer_data.description,
125
+ target_connection_id=transfer_data.target_connection_id,
126
+ source_connection_id=transfer_data.source_connection_id,
127
+ source_params=transfer_data.source_params.dict(),
128
+ target_params=transfer_data.target_params.dict(),
129
+ strategy_params=transfer_data.strategy_params.dict(),
130
+ queue_id=transfer_data.queue_id,
131
+ )
132
+ return ReadTransferSchema.from_orm(transfer)
133
+
134
+
135
+ @router.get("/transfers/{transfer_id}")
136
+ async def read_transfer(
137
+ transfer_id: int,
138
+ current_user: User = Depends(get_user(is_active=True)),
139
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
140
+ ) -> ReadTransferSchema:
141
+ """Return transfer data by transfer ID"""
142
+ resource_role = await unit_of_work.transfer.get_resource_permission(
143
+ user=current_user,
144
+ resource_id=transfer_id,
145
+ )
146
+
147
+ if resource_role == Permission.NONE:
148
+ raise TransferNotFoundError
149
+
150
+ transfer = await unit_of_work.transfer.read_by_id(transfer_id=transfer_id)
151
+ return ReadTransferSchema.from_orm(transfer)
152
+
153
+
154
+ @router.post("/transfers/{transfer_id}/copy_transfer")
155
+ async def copy_transfer(
156
+ transfer_id: int,
157
+ transfer_data: CopyTransferSchema,
158
+ current_user: User = Depends(get_user(is_active=True)),
159
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
160
+ ) -> StatusCopyTransferResponseSchema:
161
+ # Check: user can copy transfer
162
+ target_source_transfer_rules = await asyncio.gather(
163
+ unit_of_work.transfer.get_resource_permission(
164
+ user=current_user,
165
+ resource_id=transfer_id,
166
+ ),
167
+ unit_of_work.transfer.get_group_permission(
168
+ user=current_user,
169
+ group_id=transfer_data.new_group_id,
170
+ ),
171
+ )
172
+ resource_role, target_group_role = target_source_transfer_rules
173
+
174
+ if resource_role == Permission.NONE:
175
+ raise TransferNotFoundError
176
+
177
+ if target_group_role < Permission.WRITE:
178
+ raise ActionNotAllowedError
179
+
180
+ # Check: user can delete transfer
181
+ if transfer_data.remove_source and resource_role < Permission.DELETE:
182
+ raise ActionNotAllowedError
183
+
184
+ transfer = await unit_of_work.transfer.read_by_id(transfer_id=transfer_id)
185
+ # Check: user can copy connection
186
+ target_source_connection_rules = await asyncio.gather(
187
+ unit_of_work.connection.get_resource_permission(
188
+ user=current_user,
189
+ resource_id=transfer.source_connection_id,
190
+ ),
191
+ unit_of_work.connection.get_resource_permission(
192
+ user=current_user,
193
+ resource_id=transfer.target_connection_id,
194
+ ),
195
+ )
196
+ source_connection_role, target_connection_role = target_source_connection_rules
197
+
198
+ if source_connection_role == Permission.NONE or target_connection_role == Permission.NONE:
199
+ raise ConnectionNotFoundError
200
+
201
+ # Check: new queue exists
202
+ new_queue = await unit_of_work.queue.read_by_id(queue_id=transfer_data.new_queue_id)
203
+
204
+ # Acheck: new_queue_id and new_group_id are similar
205
+ if new_queue.group_id != transfer_data.new_group_id:
206
+ raise DifferentTransferAndQueueGroupError
207
+
208
+ async with unit_of_work:
209
+ copied_source_connection = await unit_of_work.connection.copy(
210
+ connection_id=transfer.source_connection_id,
211
+ new_group_id=transfer_data.new_group_id,
212
+ new_name=transfer_data.new_source_connection_name,
213
+ )
214
+
215
+ copied_target_connection = copied_source_connection
216
+
217
+ if transfer.source_connection_id != transfer.target_connection_id: # Source and target are not the same
218
+ copied_target_connection = await unit_of_work.connection.copy(
219
+ connection_id=transfer.target_connection_id,
220
+ new_group_id=transfer_data.new_group_id,
221
+ new_name=transfer_data.new_target_connection_name,
222
+ )
223
+
224
+ copied_transfer = await unit_of_work.transfer.copy(
225
+ transfer_id=transfer_id,
226
+ new_group_id=transfer_data.new_group_id,
227
+ new_source_connection=copied_source_connection.id,
228
+ new_target_connection=copied_target_connection.id,
229
+ new_queue_id=transfer_data.new_queue_id,
230
+ new_name=transfer_data.new_name,
231
+ )
232
+
233
+ if transfer_data.remove_source:
234
+ await unit_of_work.transfer.delete(transfer_id=transfer_id)
235
+
236
+ return StatusCopyTransferResponseSchema(
237
+ ok=True,
238
+ status_code=status.HTTP_200_OK,
239
+ message="Transfer was copied.",
240
+ source_connection_id=copied_source_connection.id,
241
+ target_connection_id=copied_target_connection.id,
242
+ copied_transfer_id=copied_transfer.id,
243
+ )
244
+
245
+
246
+ @router.patch("/transfers/{transfer_id}")
247
+ async def update_transfer(
248
+ transfer_id: int,
249
+ transfer_data: UpdateTransferSchema,
250
+ current_user: User = Depends(get_user(is_active=True)),
251
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
252
+ ) -> ReadTransferSchema:
253
+ # Check: user can update transfer
254
+ resource_role = await unit_of_work.transfer.get_resource_permission(
255
+ user=current_user,
256
+ resource_id=transfer_id,
257
+ )
258
+
259
+ if resource_role == Permission.NONE:
260
+ raise TransferNotFoundError
261
+
262
+ if resource_role < Permission.WRITE:
263
+ raise ActionNotAllowedError
264
+
265
+ transfer = await unit_of_work.transfer.read_by_id(
266
+ transfer_id=transfer_id,
267
+ )
268
+
269
+ target_connection = await unit_of_work.connection.read_by_id(
270
+ connection_id=transfer_data.target_connection_id or transfer.target_connection_id,
271
+ )
272
+ source_connection = await unit_of_work.connection.read_by_id(
273
+ connection_id=transfer_data.source_connection_id or transfer.source_connection_id,
274
+ )
275
+
276
+ queue = await unit_of_work.queue.read_by_id(
277
+ transfer_data.new_queue_id or transfer.queue_id,
278
+ )
279
+
280
+ # Check: user can read new connections
281
+ target_connection_resource_role = await unit_of_work.connection.get_resource_permission(
282
+ user=current_user,
283
+ resource_id=target_connection.id,
284
+ )
285
+
286
+ source_connection_resource_role = await unit_of_work.connection.get_resource_permission(
287
+ user=current_user,
288
+ resource_id=source_connection.id,
289
+ )
290
+
291
+ if source_connection_resource_role == Permission.NONE or target_connection_resource_role == Permission.NONE:
292
+ raise ConnectionNotFoundError
293
+
294
+ # Check: connections and transfer group
295
+ if (
296
+ target_connection.group_id != source_connection.group_id
297
+ or target_connection.group_id != transfer.group_id
298
+ or source_connection.group_id != transfer.group_id
299
+ ):
300
+ raise DifferentTransferAndConnectionsGroupsError
301
+
302
+ if queue.group_id != transfer.group_id:
303
+ raise DifferentTransferAndQueueGroupError
304
+
305
+ if transfer_data.target_params and target_connection.data["type"] != transfer_data.target_params.type:
306
+ raise DifferentTypeConnectionsAndParamsError(
307
+ connection_type=target_connection.data["type"],
308
+ conn="target",
309
+ params_type=transfer_data.target_params.type,
310
+ )
311
+
312
+ if transfer_data.source_params and source_connection.data["type"] != transfer_data.source_params.type:
313
+ raise DifferentTypeConnectionsAndParamsError(
314
+ connection_type=source_connection.data["type"],
315
+ conn="source",
316
+ params_type=transfer_data.source_params.type,
317
+ )
318
+
319
+ transfer_data = process_file_transfer_directory_path(transfer_data) # type: ignore
320
+
321
+ async with unit_of_work:
322
+ transfer = await unit_of_work.transfer.update(
323
+ transfer=transfer,
324
+ name=transfer_data.name,
325
+ description=transfer_data.description,
326
+ target_connection_id=transfer_data.target_connection_id,
327
+ source_connection_id=transfer_data.source_connection_id,
328
+ source_params=transfer_data.source_params.dict() if transfer_data.source_params else {},
329
+ target_params=transfer_data.target_params.dict() if transfer_data.target_params else {},
330
+ strategy_params=transfer_data.strategy_params.dict() if transfer_data.strategy_params else {},
331
+ is_scheduled=transfer_data.is_scheduled,
332
+ schedule=transfer_data.schedule,
333
+ new_queue_id=transfer_data.new_queue_id,
334
+ )
335
+ return ReadTransferSchema.from_orm(transfer)
336
+
337
+
338
+ @router.delete("/transfers/{transfer_id}")
339
+ async def delete_transfer(
340
+ transfer_id: int,
341
+ current_user: User = Depends(get_user(is_active=True)),
342
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
343
+ ) -> StatusResponseSchema:
344
+ resource_role = await unit_of_work.transfer.get_resource_permission(
345
+ user=current_user,
346
+ resource_id=transfer_id,
347
+ )
348
+
349
+ if resource_role == Permission.NONE:
350
+ raise TransferNotFoundError
351
+
352
+ if resource_role < Permission.DELETE:
353
+ raise ActionNotAllowedError
354
+
355
+ async with unit_of_work:
356
+ await unit_of_work.transfer.delete(
357
+ transfer_id=transfer_id,
358
+ )
359
+
360
+ return StatusResponseSchema(
361
+ ok=True,
362
+ status_code=status.HTTP_200_OK,
363
+ message="Transfer was deleted",
364
+ )
365
+
366
+
367
+ @router.get("/runs")
368
+ async def read_runs(
369
+ transfer_id: int,
370
+ page: int = Query(gt=0, default=1),
371
+ page_size: int = Query(gt=0, le=200, default=20),
372
+ current_user: User = Depends(get_user(is_active=True)),
373
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
374
+ ) -> RunPageSchema:
375
+ """Return runs of transfer with pagination"""
376
+ resource_rule = await unit_of_work.transfer.get_resource_permission(
377
+ user=current_user,
378
+ resource_id=transfer_id,
379
+ )
380
+
381
+ if resource_rule == Permission.NONE:
382
+ raise TransferNotFoundError
383
+
384
+ pagination = await unit_of_work.run.paginate(
385
+ transfer_id=transfer_id,
386
+ page=page,
387
+ page_size=page_size,
388
+ )
389
+
390
+ return RunPageSchema.from_pagination(pagination=pagination)
391
+
392
+
393
+ @router.get("/runs/{run_id}")
394
+ async def read_run(
395
+ run_id: int,
396
+ current_user: User = Depends(get_user(is_active=True)),
397
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
398
+ ) -> ReadRunSchema:
399
+ run = await unit_of_work.run.read_by_id(run_id=run_id)
400
+
401
+ resource_role = await unit_of_work.transfer.get_resource_permission(
402
+ user=current_user,
403
+ resource_id=run.transfer_id,
404
+ )
405
+
406
+ if resource_role == Permission.NONE:
407
+ raise TransferNotFoundError
408
+
409
+ return ReadRunSchema.from_orm(run)
410
+
411
+
412
+ @router.post("/runs")
413
+ async def start_run(
414
+ create_run_data: CreateRunSchema,
415
+ current_user: User = Depends(get_user(is_active=True)),
416
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
417
+ ) -> ReadRunSchema:
418
+ # Check: user can start transfer
419
+ resource_rule = await unit_of_work.transfer.get_resource_permission(
420
+ user=current_user,
421
+ resource_id=create_run_data.transfer_id,
422
+ )
423
+
424
+ if resource_rule == Permission.NONE:
425
+ raise TransferNotFoundError
426
+
427
+ if resource_rule < Permission.WRITE:
428
+ raise ActionNotAllowedError
429
+
430
+ transfer = await unit_of_work.transfer.read_by_id(transfer_id=create_run_data.transfer_id)
431
+
432
+ async with unit_of_work:
433
+ run = await unit_of_work.run.create(transfer_id=create_run_data.transfer_id)
434
+ try:
435
+ celery.send_task("run_transfer_task", kwargs={"run_id": run.id}, queue=transfer.queue.name)
436
+ except KombuError as e:
437
+ async with unit_of_work:
438
+ run = await unit_of_work.run.update(
439
+ run_id=run.id,
440
+ status=Status.FAILED,
441
+ )
442
+ raise CannotConnectToTaskQueueError(run_id=run.id) from e
443
+ return ReadRunSchema.from_orm(run)
444
+
445
+
446
+ @router.post("/runs/{run_id}/stop")
447
+ async def stop_run(
448
+ run_id: int,
449
+ current_user: User = Depends(get_user(is_active=True)),
450
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
451
+ ) -> ReadRunSchema:
452
+ run = await unit_of_work.run.read_by_id(run_id=run_id)
453
+
454
+ # Check: user can stop transfer
455
+ resource_rule = await unit_of_work.transfer.get_resource_permission(
456
+ user=current_user,
457
+ resource_id=run.transfer_id,
458
+ )
459
+
460
+ if resource_rule == Permission.NONE:
461
+ raise TransferNotFoundError
462
+
463
+ if resource_rule < Permission.WRITE:
464
+ raise ActionNotAllowedError
465
+
466
+ async with unit_of_work:
467
+ run = await unit_of_work.run.stop(run_id=run_id)
468
+ # TODO: add immdiate stop transfer after stop Run
469
+ return ReadRunSchema.from_orm(run)
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from syncmaster.schemas.v1.transfers import CreateTransferSchema, UpdateTransferSchema
4
+
5
+
6
+ def process_file_transfer_directory_path(
7
+ transfer_data: UpdateTransferSchema | CreateTransferSchema,
8
+ ) -> UpdateTransferSchema | CreateTransferSchema:
9
+ if transfer_data.source_params is not None:
10
+ if hasattr(transfer_data.source_params, "directory_path"): # s3 or hdfs connection
11
+ transfer_data.source_params.directory_path = str(transfer_data.source_params.directory_path)
12
+
13
+ if transfer_data.target_params is not None:
14
+ if hasattr(transfer_data.source_params, "directory_path"): # s3 or hdfs connection
15
+ transfer_data.target_params.directory_path = str(transfer_data.target_params.directory_path) # type: ignore
16
+
17
+ return transfer_data
@@ -0,0 +1,75 @@
1
+ # SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ import logging
4
+
5
+ from fastapi import APIRouter, Depends, Query
6
+
7
+ from syncmaster.backend.api.deps import UnitOfWorkMarker
8
+ from syncmaster.backend.services import UnitOfWork, get_user
9
+ from syncmaster.db.models import User
10
+ from syncmaster.exceptions import ActionNotAllowedError
11
+ from syncmaster.schemas.v1.status import StatusResponseSchema
12
+ from syncmaster.schemas.v1.users import ReadUserSchema, UpdateUserSchema, UserPageSchema
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ router = APIRouter(tags=["Users"])
18
+
19
+
20
+ @router.get("/users")
21
+ async def get_users(
22
+ page: int = Query(gt=0, default=1),
23
+ page_size: int = Query(gt=0, le=200, default=20),
24
+ current_user: User = Depends(get_user(is_active=True)),
25
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
26
+ ) -> UserPageSchema:
27
+ pagination = await unit_of_work.user.paginate(
28
+ page=page, page_size=page_size, is_superuser=current_user.is_superuser
29
+ )
30
+ return UserPageSchema.from_pagination(pagination)
31
+
32
+
33
+ @router.get("/users/{user_id}", dependencies=[Depends(get_user(is_active=True))])
34
+ async def read_user(
35
+ user_id: int,
36
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
37
+ ) -> ReadUserSchema:
38
+ user = await unit_of_work.user.read_by_id(user_id=user_id, is_active=True)
39
+ return ReadUserSchema.from_orm(user)
40
+
41
+
42
+ @router.patch("/users/{user_id}")
43
+ async def update_user(
44
+ user_id: int,
45
+ user_data: UpdateUserSchema,
46
+ current_user: User = Depends(get_user(is_active=True)),
47
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
48
+ ) -> ReadUserSchema:
49
+ if user_id != current_user.id and not current_user.is_superuser:
50
+ raise ActionNotAllowedError
51
+ async with unit_of_work:
52
+ change_user = await unit_of_work.user.update(user_id=user_id, data=user_data.dict())
53
+ return ReadUserSchema.from_orm(change_user)
54
+
55
+
56
+ @router.post("/users/{user_id}/activate", dependencies=[Depends(get_user(is_superuser=True))])
57
+ async def activate_user(
58
+ user_id: int,
59
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
60
+ ) -> StatusResponseSchema:
61
+ async with unit_of_work:
62
+ user = await unit_of_work.user.update(user_id=user_id, data={"is_active": True})
63
+ logger.info("User %s active=%s id=%d", user, user.is_active, user.id)
64
+ return StatusResponseSchema(ok=True, status_code=200, message="User was activated")
65
+
66
+
67
+ @router.post("/users/{user_id}/deactivate", dependencies=[Depends(get_user(is_superuser=True))])
68
+ async def deactivate_user(
69
+ user_id: int,
70
+ unit_of_work: UnitOfWork = Depends(UnitOfWorkMarker),
71
+ ):
72
+ async with unit_of_work:
73
+ user = await unit_of_work.user.update(user_id=user_id, data={"is_active": False})
74
+ logger.info("User %s active=%s id=%d", user, user.is_active, user.id)
75
+ return StatusResponseSchema(ok=True, status_code=200, message="User was deactivated")
@@ -0,0 +1,26 @@
1
+ #!/bin/env python3
2
+ # SPDX-FileCopyrightText: 2023-2024 MTS (Mobile Telesystems)
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import json
6
+ import sys
7
+
8
+ from fastapi import FastAPI
9
+
10
+ from syncmaster.backend.main import get_application
11
+ from syncmaster.config import Settings
12
+
13
+
14
+ def get_openapi_schema(app: FastAPI) -> dict:
15
+ return app.openapi()
16
+
17
+
18
+ if __name__ == "__main__":
19
+ settings = Settings()
20
+ app = get_application(settings)
21
+ schema = get_openapi_schema(app)
22
+ file_path = sys.argv[1]
23
+ if not file_path:
24
+ raise ValueError("File path not sent")
25
+ with open(file_path, "w") as file:
26
+ json.dump(schema, file)