Skip to content

Commit 729c5f1

Browse files
authored
Merge pull request #1170 from ChildMindInstitute/hotfix/M2-5876-ema-export-test
Hotfix/M2-5876 ema export arguments
2 parents b500a96 + d351fb0 commit 729c5f1

13 files changed

Lines changed: 94 additions & 102 deletions

File tree

Pipfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ ruff = "~=0.1.14"
8989

9090
[requires]
9191
python_version = "3.10"
92+
93+
[scripts]
94+
cli = "python src/cli.py"

src/apps/activities/commands/reindex_items.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import asyncio
21
import uuid
3-
from functools import wraps
42

53
import typer
64
from rich import print
@@ -16,19 +14,12 @@
1614
from apps.applets.domain.applet_full import AppletFull
1715
from apps.applets.service.applet import AppletService
1816
from apps.workspaces.crud.user_applet_access import UserAppletAccessCRUD
17+
from infrastructure.commands.utils import coro
1918
from infrastructure.database import atomic, session_manager
2019

2120
app = typer.Typer()
2221

2322

24-
def coro(f):
25-
@wraps(f)
26-
def wrapper(*args, **kwargs):
27-
return asyncio.run(f(*args, **kwargs))
28-
29-
return wrapper
30-
31-
3223
def print_results(applets: list[tuple[uuid.UUID, str]]):
3324
table = Table(
3425
show_header=True,

src/apps/answers/commands/convert_assessments.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
1-
import asyncio
2-
from functools import wraps
31
from typing import Optional
42

53
import typer
64
from rich import print
75

86
from apps.answers.crud.assessment_crud import AssessmentCRUD
7+
from infrastructure.commands.utils import coro
98
from infrastructure.database import atomic, session_manager
109

1110
app = typer.Typer()
1211

1312

14-
def coro(f):
15-
@wraps(f)
16-
def wrapper(*args, **kwargs):
17-
return asyncio.run(f(*args, **kwargs))
18-
19-
return wrapper
20-
21-
2213
@app.command(short_help="Convert current assessments to version agnostic")
2314
@coro
2415
async def convert(

src/apps/applets/commands/applet_ema.py

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import calendar
32
import codecs
43
import csv
@@ -7,8 +6,7 @@
76
import os
87
import tracemalloc
98
import uuid
10-
from functools import wraps
11-
from typing import BinaryIO, TypeVar, cast
9+
from typing import BinaryIO, Optional, TypeVar, cast
1210

1311
import typer
1412
from pydantic import parse_obj_as
@@ -39,6 +37,7 @@
3937
from apps.workspaces.db.schemas.user_applet_access import UserAppletAccessSchema
4038
from apps.workspaces.domain.constants import Role
4139
from config import settings
40+
from infrastructure.commands.utils import coro
4241
from infrastructure.database import atomic, session_manager
4342
from infrastructure.dependency.cdn import get_operations_bucket
4443
from infrastructure.utility import CDNClient, ObjectNotFoundError
@@ -132,18 +131,12 @@ def is_last_day_of_month(date: datetime.date):
132131
return date.day == mdays[date.month]
133132

134133

135-
def coro(f):
136-
@wraps(f)
137-
def wrapper(*args, **kwargs):
138-
return asyncio.run(f(*args, **kwargs))
139-
140-
return wrapper
141-
142-
143-
def ensure_configured():
144-
if not APPLET_ID:
134+
def get_applet_id(applet_id: uuid.UUID | None = None) -> uuid.UUID:
135+
_applet_id = str(applet_id) if applet_id else APPLET_ID
136+
if not _applet_id:
145137
print("[bold red]Error: applet export not configured[/bold red]")
146138
exit(1)
139+
return uuid.UUID(_applet_id)
147140

148141

149142
def create_csv(data: list[dict], columns: list | None = None, append_to: BinaryIO | None = None) -> BinaryIO | None:
@@ -170,7 +163,7 @@ async def save_csv(path: str, data: list[dict], cdn_client: CDNClient, columns:
170163
await cdn_client.upload(path, f)
171164

172165

173-
async def _export_flows():
166+
async def _export_flows(applet_id: uuid.UUID, path_prefix: str):
174167
"""
175168
select
176169
split_part(fh.applet_id , '_', 1) applet_id,
@@ -200,7 +193,7 @@ async def _export_flows():
200193
)
201194
.join(FlowItemHistory, FlowItemHistory.activity_flow_id == FlowHistory.id_version)
202195
.join(ActivityHistory, ActivityHistory.id_version == FlowItemHistory.activity_id)
203-
.where(FlowHistory.applet_id.like(f"{APPLET_ID}_%"))
196+
.where(FlowHistory.applet_id.like(f"{applet_id}_%"))
204197
.order_by(text("applet_version"), FlowHistory.order, FlowItemHistory.order)
205198
)
206199
res = await session.execute(
@@ -210,7 +203,7 @@ async def _export_flows():
210203
data = res.all()
211204

212205
cdn_client = await get_operations_bucket()
213-
key = cdn_client.generate_key(PATH_PREFIX, str(APPLET_ID), PATH_FLOW_FILE_NAME)
206+
key = cdn_client.generate_key(path_prefix, str(applet_id), PATH_FLOW_FILE_NAME)
214207
await save_csv(key, parse_obj_as(list[dict], data), cdn_client)
215208

216209

@@ -219,22 +212,28 @@ async def _export_flows():
219212
f' flow data as csv file'
220213
)
221214
@coro
222-
async def export_flows():
215+
async def export_flows(
216+
applet_id: Optional[uuid.UUID] = typer.Option(None, "--applet_id", "-a"),
217+
path_prefix: Optional[str] = typer.Option(PATH_PREFIX, "--path-prefix", "-p"),
218+
):
223219
"""
224220
Create and upload to s3 csv file with flow data
225221
"""
226-
ensure_configured()
227-
print("Flow export start")
222+
assert path_prefix
223+
applet_id = get_applet_id(applet_id)
224+
print(f"Flow export start {applet_id}")
228225
tracemalloc.start()
229-
await _export_flows()
226+
await _export_flows(applet_id, path_prefix)
230227
_, peak = tracemalloc.get_traced_memory()
231228
tracemalloc.stop()
232229
print("Flow export finished")
233230
print("Peak memory usage:", peak)
234231

235232

236233
##### Daily user flow schedule stuff
237-
async def get_user_flow_events(session: AsyncSession, scheduled_date: datetime.date) -> list[FlowEventRawRow]:
234+
async def get_user_flow_events(
235+
session: AsyncSession, scheduled_date: datetime.date, applet_id: uuid.UUID
236+
) -> list[FlowEventRawRow]:
238237
cte = (
239238
select(
240239
EventSchema.applet_id,
@@ -297,7 +296,7 @@ async def get_user_flow_events(session: AsyncSession, scheduled_date: datetime.d
297296
),
298297
)
299298
.where(
300-
AppletSchema.id == uuid.UUID(APPLET_ID),
299+
AppletSchema.id == applet_id,
301300
cte.c.event_id != null(),
302301
ActivityFlowSchema.is_hidden == false(),
303302
)
@@ -391,21 +390,24 @@ def filter_events(raw_events_rows: list[TRawRow], schedule_date: datetime.date)
391390
@coro
392391
async def export_flow_schedule(
393392
run_date: datetime.datetime = typer.Argument(None, help="run date"),
393+
applet_id: Optional[uuid.UUID] = typer.Option(None, "--applet_id", "-a"),
394+
path_prefix: Optional[str] = typer.Option(PATH_PREFIX, "--path-prefix", "-p"),
394395
force: bool = typer.Option(
395396
False,
396397
"--force",
397398
"-f",
398399
help="Force run even if job executed before",
399400
),
400401
):
401-
ensure_configured()
402+
assert path_prefix
403+
applet_id = get_applet_id(applet_id)
402404
scheduled_date = run_date.date() if run_date else datetime.date.today()
403405

404-
job_name = f"export_flow_schedule_{scheduled_date}"
406+
job_name = f"export_flow_schedule_{applet_id}_{scheduled_date}"
405407

406408
session_maker = session_manager.get_session()
407409
async with session_maker() as session:
408-
owner_role = await UserAppletAccessCRUD(session).get_applet_owner(uuid.UUID(APPLET_ID))
410+
owner_role = await UserAppletAccessCRUD(session).get_applet_owner(applet_id)
409411
owner_id = owner_role.user_id
410412

411413
job_service = JobService(session, owner_id)
@@ -422,12 +424,12 @@ async def export_flow_schedule(
422424
if job.status != JobStatus.in_progress:
423425
await job_service.change_status(job.id, JobStatus.in_progress)
424426

425-
print(f"Flow schedule export start ({scheduled_date})")
427+
print(f"Flow schedule export start {applet_id} ({scheduled_date})")
426428
tracemalloc.start()
427429

428430
try:
429431
async with session_maker() as session:
430-
raw_data = await get_user_flow_events(session, scheduled_date)
432+
raw_data = await get_user_flow_events(session, scheduled_date, applet_id)
431433
print(f"Num raw rows is {len(raw_data)}")
432434
filtered = filter_events(raw_data, scheduled_date)
433435
print(f"Num filtered rows is {len(filtered)}")
@@ -449,13 +451,13 @@ async def export_flow_schedule(
449451
result.append(outrow)
450452

451453
cdn_client = await get_operations_bucket()
452-
unique_prefix = f"{APPLET_ID}/flow-schedule"
454+
unique_prefix = f"{applet_id}/flow-schedule"
453455

454456
prev_filename = PATH_USER_FLOW_SCHEDULE_FILE_NAME.format(date=scheduled_date - datetime.timedelta(days=1))
455-
prev_key = cdn_client.generate_key(PATH_PREFIX, unique_prefix, prev_filename)
457+
prev_key = cdn_client.generate_key(path_prefix, unique_prefix, prev_filename)
456458

457459
filename = PATH_USER_FLOW_SCHEDULE_FILE_NAME.format(date=scheduled_date)
458-
key = cdn_client.generate_key(PATH_PREFIX, unique_prefix, filename)
460+
key = cdn_client.generate_key(path_prefix, unique_prefix, filename)
459461

460462
path = settings.uploads_dir / filename
461463

@@ -488,7 +490,9 @@ async def export_flow_schedule(
488490

489491

490492
##### Daily user activity schedule stuff
491-
async def get_user_activity_events(session: AsyncSession, scheduled_date: datetime.date) -> list[ActivityEventRawRow]:
493+
async def get_user_activity_events(
494+
session: AsyncSession, scheduled_date: datetime.date, applet_id: uuid.UUID
495+
) -> list[ActivityEventRawRow]:
492496
cte = (
493497
select(
494498
EventSchema.applet_id,
@@ -551,7 +555,7 @@ async def get_user_activity_events(session: AsyncSession, scheduled_date: dateti
551555
),
552556
)
553557
.where(
554-
AppletSchema.id == uuid.UUID(APPLET_ID),
558+
AppletSchema.id == applet_id,
555559
cte.c.event_id != null(),
556560
ActivitySchema.is_hidden == false(),
557561
)
@@ -566,21 +570,24 @@ async def get_user_activity_events(session: AsyncSession, scheduled_date: dateti
566570
@coro
567571
async def export_activity_schedule(
568572
run_date: datetime.datetime = typer.Argument(None, help="run date"),
573+
applet_id: Optional[uuid.UUID] = typer.Option(None, "--applet_id", "-a"),
574+
path_prefix: Optional[str] = typer.Option(PATH_PREFIX, "--path-prefix", "-p"),
569575
force: bool = typer.Option(
570576
False,
571577
"--force",
572578
"-f",
573579
help="Force run even if job executed before",
574580
),
575581
):
576-
ensure_configured()
582+
assert path_prefix
583+
applet_id = get_applet_id(applet_id)
577584
scheduled_date = run_date.date() if run_date else datetime.date.today()
578585

579-
job_name = f"export_activity_schedule_{scheduled_date}"
586+
job_name = f"export_activity_schedule_{applet_id}_{scheduled_date}"
580587

581588
session_maker = session_manager.get_session()
582589
async with session_maker() as session:
583-
owner_role = await UserAppletAccessCRUD(session).get_applet_owner(uuid.UUID(APPLET_ID))
590+
owner_role = await UserAppletAccessCRUD(session).get_applet_owner(applet_id)
584591
owner_id = owner_role.user_id
585592

586593
job_service = JobService(session, owner_id)
@@ -596,13 +603,13 @@ async def export_activity_schedule(
596603
raise
597604
if job.status != JobStatus.in_progress:
598605
await job_service.change_status(job.id, JobStatus.in_progress)
599-
print("Activity schedule export start")
606+
print(f"Activity schedule export start {applet_id} ({scheduled_date})")
600607
tracemalloc.start()
601608

602609
try:
603610
session_maker = session_manager.get_session()
604611
async with session_maker() as session:
605-
raw_data = await get_user_activity_events(session, scheduled_date)
612+
raw_data = await get_user_activity_events(session, scheduled_date, applet_id)
606613
print(f"Num raw rows is {len(raw_data)}")
607614
filtered = filter_events(raw_data, scheduled_date)
608615
print(f"Num filtered rows is {len(filtered)}")
@@ -624,13 +631,13 @@ async def export_activity_schedule(
624631
result.append(outrow)
625632

626633
cdn_client = await get_operations_bucket()
627-
unique_prefix = f"{APPLET_ID}/activity-schedule"
634+
unique_prefix = f"{applet_id}/activity-schedule"
628635

629636
prev_filename = PATH_USER_ACTIVITY_SCHEDULE_FILE_NAME.format(date=scheduled_date - datetime.timedelta(days=1))
630-
prev_key = cdn_client.generate_key(PATH_PREFIX, unique_prefix, prev_filename)
637+
prev_key = cdn_client.generate_key(path_prefix, unique_prefix, prev_filename)
631638

632639
filename = PATH_USER_ACTIVITY_SCHEDULE_FILE_NAME.format(date=scheduled_date)
633-
key = cdn_client.generate_key(PATH_PREFIX, unique_prefix, filename)
640+
key = cdn_client.generate_key(path_prefix, unique_prefix, filename)
634641

635642
path = settings.uploads_dir / filename
636643

src/apps/schedule/commands/remove_events.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
1-
import asyncio
2-
from functools import wraps
3-
41
import typer
52
from sqlalchemy import select
63
from sqlalchemy.ext.asyncio import AsyncSession
74
from sqlalchemy.orm import Query
85

96
from apps.activities.db.schemas.activity import ActivitySchema
107
from apps.schedule.service import ScheduleService
8+
from infrastructure.commands.utils import coro
119
from infrastructure.database import atomic, session_manager
1210

1311
app = typer.Typer()
1412

1513

16-
def coro(f):
17-
@wraps(f)
18-
def wrapper(*args, **kwargs):
19-
return asyncio.run(f(*args, **kwargs))
20-
21-
return wrapper
22-
23-
2414
async def get_assessments(session: AsyncSession) -> list[ActivitySchema]:
2515
query: Query = select(ActivitySchema)
2616
query = query.where(ActivitySchema.is_reviewable.is_(True))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from apps.shared.commands.encryption import app as encryption_cli # noqa: F401
12
from apps.shared.commands.patch_commands import app as patch # noqa: F401
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import typer
2+
from sqlalchemy import Unicode
3+
from sqlalchemy.dialects.postgresql import dialect
4+
from sqlalchemy_utils import StringEncryptedType
5+
6+
from apps.shared.encryption import get_key
7+
from infrastructure.commands.utils import coro
8+
9+
app = typer.Typer()
10+
11+
12+
@app.command(short_help="Encrypt data (internal encryption in DB)")
13+
@coro
14+
async def encrypt(data: str):
15+
encrypted_val = StringEncryptedType(Unicode, get_key).process_bind_param(data, dialect=dialect.name)
16+
17+
print(encrypted_val)
18+
19+
20+
@app.command(short_help="Decrypt data (internal encryption in DB)")
21+
@coro
22+
async def decrypt(encrypted_data: str):
23+
decrypted = StringEncryptedType(Unicode, get_key).process_result_value(encrypted_data, dialect=dialect.name)
24+
25+
print(decrypted)

0 commit comments

Comments
 (0)