mcm-cli 1.1.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/METADATA +1 -1
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/RECORD +13 -12
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/WHEEL +1 -1
- mcmcli/__main__.py +1 -1
- mcmcli/command/account.py +333 -0
- mcmcli/command/wallet.py +83 -19
- mcmcli/data/item.py +42 -0
- mcmcli/data/wallet.py +43 -0
- mcmcli/requests.py +11 -0
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/LICENSE +0 -0
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/NOTICE +0 -0
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/entry_points.txt +0 -0
- {mcm_cli-1.1.0.dist-info → mcm_cli-1.3.0.dist-info}/top_level.txt +0 -0
@@ -1,24 +1,25 @@
|
|
1
1
|
mcmcli/__init__.py,sha256=-U6lMZ9_99IXAKwnqnYXYr6NcO6TSmG-kxewgvJjU4k,575
|
2
|
-
mcmcli/__main__.py,sha256=
|
2
|
+
mcmcli/__main__.py,sha256=RgIMoKZR3IQ-lFOyXQqEEQYuJPgTxU0KpLdqIu0pUKA,1616
|
3
3
|
mcmcli/logging.py,sha256=xjRS5ey1ONx_d34qB1Fetb_SwPysoh2hzNDuNAaYYWQ,1739
|
4
|
-
mcmcli/requests.py,sha256=
|
5
|
-
mcmcli/command/account.py,sha256=
|
4
|
+
mcmcli/requests.py,sha256=ZoQULPpKdAvGtki25Jr6K2Xq2v213XLinzPHUeBi9wo,2601
|
5
|
+
mcmcli/command/account.py,sha256=kxJbBKYrw6OyCdaNZ0K0BEvKgZEAjj5azO-lNSeYLTM,25107
|
6
6
|
mcmcli/command/admin.py,sha256=nJ7rm0nm0jHPobg0PjNHIWaWURTQu6QEUEUKIo__GO0,3010
|
7
7
|
mcmcli/command/auth.py,sha256=QLdr_XFW5BVw9r4a7Kjj5BTBXpSux3AWI9eI03S8aiA,2480
|
8
8
|
mcmcli/command/config.py,sha256=sdzge-l_Yi2P_TlTgSLqShcGyPCzpW3QJzctpIvc-g4,4195
|
9
9
|
mcmcli/command/decision.py,sha256=Zjbmt71OVU-oL8Itt9O-SvwT9Lbxw-PAgRZaIgiXi-E,8411
|
10
|
-
mcmcli/command/wallet.py,sha256=
|
10
|
+
mcmcli/command/wallet.py,sha256=giPDktndwh-NSIZbCvBzCQ6_MttZsqOkCW1wJ-fFJ8Y,11708
|
11
11
|
mcmcli/data/account.py,sha256=pe7tPapP6vlUD5D5L5Nh5k2bkWdYOK01Mpt5fBYFnJs,1782
|
12
12
|
mcmcli/data/account_user.py,sha256=27nQp52nMma5a3QszSJGqgq5Z0ivIb-nMZcZuhEgbEg,1328
|
13
13
|
mcmcli/data/decision.py,sha256=bQ5j_PbPRSFa0sY5g9UVqdNzl_2epchcz1lHoDVuV90,2880
|
14
14
|
mcmcli/data/error.py,sha256=d6xa_jTXumlA0EzXy2PJQ86ajBb0Pm90fss9R3LuHUc,1094
|
15
|
+
mcmcli/data/item.py,sha256=Z2xTRhU8T4vyJADO0l6-XPyQXvb9DX_OAjExhSXpW2A,1091
|
15
16
|
mcmcli/data/seller.py,sha256=40SA7QekM3a3svDrDYLo_QYJ68VUxDO0KeGejJMp4k4,1004
|
16
17
|
mcmcli/data/token.py,sha256=11wtyLHCAZHu0LVbNDPa-yipcL6lenxoYIKEI58VzFs,1744
|
17
|
-
mcmcli/data/wallet.py,sha256=
|
18
|
-
mcm_cli-1.
|
19
|
-
mcm_cli-1.
|
20
|
-
mcm_cli-1.
|
21
|
-
mcm_cli-1.
|
22
|
-
mcm_cli-1.
|
23
|
-
mcm_cli-1.
|
24
|
-
mcm_cli-1.
|
18
|
+
mcmcli/data/wallet.py,sha256=eMUi8N0vJdg_E10TPhSPoZkZtmIG7gHyqgabQ8C5Lg8,3217
|
19
|
+
mcm_cli-1.3.0.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
|
20
|
+
mcm_cli-1.3.0.dist-info/METADATA,sha256=sTYKOH3XncIeSNsjAER9fFM2nKdrST922dpGg7khsGs,3018
|
21
|
+
mcm_cli-1.3.0.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
|
22
|
+
mcm_cli-1.3.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
23
|
+
mcm_cli-1.3.0.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
|
24
|
+
mcm_cli-1.3.0.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
|
25
|
+
mcm_cli-1.3.0.dist-info/RECORD,,
|
mcmcli/__main__.py
CHANGED
@@ -35,7 +35,7 @@ def version():
|
|
35
35
|
"""
|
36
36
|
Show the tool version
|
37
37
|
"""
|
38
|
-
typer.echo(f"Version: mcm-cli v1.
|
38
|
+
typer.echo(f"Version: mcm-cli v1.3.0")
|
39
39
|
|
40
40
|
app.add_typer(mcmcli.command.account.app, name="account", help="Ad account management")
|
41
41
|
app.add_typer(mcmcli.command.admin.app, name="admin", help="Platform administration")
|
mcmcli/command/account.py
CHANGED
@@ -16,6 +16,7 @@ from enum import Enum
|
|
16
16
|
from mcmcli.data.account import Account, AccountListWrapper
|
17
17
|
from mcmcli.data.account_user import User, UserWrapper, UserListWrapper
|
18
18
|
from mcmcli.data.error import Error
|
19
|
+
from mcmcli.data.item import ItemList, Item
|
19
20
|
from mcmcli.data.seller import Seller, SellerListWrapper
|
20
21
|
from mcmcli.requests import CurlString, api_request
|
21
22
|
from typing import Any, Callable, Dict, Optional, Tuple, TypeVar
|
@@ -95,6 +96,16 @@ def bulk_invite_ad_account_owners(
|
|
95
96
|
continue
|
96
97
|
account_creation_count += 1
|
97
98
|
|
99
|
+
#
|
100
|
+
# TODO: investigate if this step is necessary
|
101
|
+
# Activate the ad account
|
102
|
+
#
|
103
|
+
#if account:
|
104
|
+
# error = a.activate_account_with_retry(account)
|
105
|
+
# if error:
|
106
|
+
# print(f"\nERROR: Failed to activate the ad account {account_id}. {error.message}.", file=sys.stderr, flush=True)
|
107
|
+
# continue
|
108
|
+
|
98
109
|
error = a.send_invitation_email_with_retry(account_id, email_address, user_name, create_user, dry_run)
|
99
110
|
if error:
|
100
111
|
print(f"\nERROR: Failed to send the mail for the ad account ID {account_id}. {error.message}.", file=sys.stderr, flush=True)
|
@@ -151,6 +162,187 @@ def bulk_check_user_registrations(
|
|
151
162
|
print(f'"{account_id}","{is_account_exist}","{email_address}","{is_user_exist}","{user_role}","{user_status}"')
|
152
163
|
return
|
153
164
|
|
165
|
+
@app.command()
|
166
|
+
def delete_user(
|
167
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
168
|
+
user_email: str = typer.Option(help="User's email address"),
|
169
|
+
email_notification: bool = typer.Option(True, help="Send the email notification to the user"),
|
170
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
171
|
+
):
|
172
|
+
"""
|
173
|
+
Delete the email user. It will send the notification email to the user by default.
|
174
|
+
Please use the `--no-email-notification` option if you want to skip it.
|
175
|
+
"""
|
176
|
+
a = _create_account_command(profile)
|
177
|
+
if a is None:
|
178
|
+
return
|
179
|
+
|
180
|
+
err, users = a.list_account_users(account_id)
|
181
|
+
if err:
|
182
|
+
print(f"ERROR: {err}", file=sys.stderr, flush=True)
|
183
|
+
sys.exit()
|
184
|
+
if user_email not in users:
|
185
|
+
print(f"ERROR: The email user {user_email} was not found in the ad account {account_id}.", file=sys.stderr, flush=True)
|
186
|
+
sys.exit()
|
187
|
+
|
188
|
+
user_id = users[user_email].id
|
189
|
+
err = a.delete_user(account_id, user_id, email_notification)
|
190
|
+
if err:
|
191
|
+
print(f"ERROR: {err}", file=sys.stderr, flush=True)
|
192
|
+
sys.exit()
|
193
|
+
|
194
|
+
print(f'Successfully deleted the email user {user_email} from the ad account ID {account_id}.')
|
195
|
+
|
196
|
+
|
197
|
+
@app.command()
|
198
|
+
def invite_user(
|
199
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
200
|
+
user_email: str = typer.Option(help="User's email address"),
|
201
|
+
user_name: str = typer.Option(help="User's name"),
|
202
|
+
user_role: UserRole = typer.Option(UserRole.AD_ACCOUNT_OWNER.value, help="User Role"),
|
203
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
204
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
205
|
+
):
|
206
|
+
"""
|
207
|
+
Invite a user as an ad account owner. The process creates the ad account if it doesn’t exist
|
208
|
+
and sends an invitation email to the user.
|
209
|
+
"""
|
210
|
+
a = _create_account_command(profile)
|
211
|
+
if a is None:
|
212
|
+
return
|
213
|
+
|
214
|
+
#
|
215
|
+
# get the list of sellers
|
216
|
+
#
|
217
|
+
_, error, seller_dictionary = a.list_sellers()
|
218
|
+
if error:
|
219
|
+
print(f"ERROR: {error.message}")
|
220
|
+
sys.exit()
|
221
|
+
|
222
|
+
seller = _lookup_dict(account_id, seller_dictionary)
|
223
|
+
if seller is None:
|
224
|
+
print(f"ERROR: Could not find the ad account ID {account_id}")
|
225
|
+
sys.exit()
|
226
|
+
|
227
|
+
#
|
228
|
+
# create the ad account first
|
229
|
+
#
|
230
|
+
_, error, _ = a.create_account(seller.id, seller.title, to_curl=False)
|
231
|
+
if error and error.code != 6:
|
232
|
+
# error code 6 means the ad account already exists; we can ignore it.
|
233
|
+
print(f"ERROR: {error.message}")
|
234
|
+
sys.exit()
|
235
|
+
|
236
|
+
# We can assume that the ad account exists at this line, because we checked it above.
|
237
|
+
|
238
|
+
#
|
239
|
+
# invite the user
|
240
|
+
#
|
241
|
+
curl, error, user_join_request = a.invite_user(account_id, user_email, user_name, user_role, to_curl)
|
242
|
+
if to_curl:
|
243
|
+
print(curl)
|
244
|
+
elif error:
|
245
|
+
print(f"ERROR: {error.message}")
|
246
|
+
sys.exit()
|
247
|
+
else:
|
248
|
+
print(user_join_request)
|
249
|
+
|
250
|
+
|
251
|
+
@app.command()
|
252
|
+
def create_account(
|
253
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
254
|
+
account_name: str = typer.Option(help="Ad account name"),
|
255
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
256
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
257
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
258
|
+
):
|
259
|
+
"""
|
260
|
+
Create a new ad account.
|
261
|
+
"""
|
262
|
+
a = _create_account_command(profile)
|
263
|
+
if a is None:
|
264
|
+
return
|
265
|
+
|
266
|
+
curl, error, account_info = a.create_account(account_id, account_name, to_curl)
|
267
|
+
if to_curl:
|
268
|
+
print(curl)
|
269
|
+
sys.exit()
|
270
|
+
if error:
|
271
|
+
print(f"ERROR: {error.message}")
|
272
|
+
sys.exit()
|
273
|
+
if account_info is None:
|
274
|
+
print(f"ERROR: account information is None, which is not supposed to be.")
|
275
|
+
sys.exit()
|
276
|
+
|
277
|
+
if to_json:
|
278
|
+
print(account_info.model_dump_json())
|
279
|
+
sys.exit()
|
280
|
+
|
281
|
+
print(f"""Ad account ID {account_info.id} ("{account_info.title}") has been created.""")
|
282
|
+
|
283
|
+
|
284
|
+
@app.command()
|
285
|
+
def list_accounts(
|
286
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
287
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
288
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
289
|
+
):
|
290
|
+
"""
|
291
|
+
List all ad accounts on the platform.
|
292
|
+
"""
|
293
|
+
a = _create_account_command(profile)
|
294
|
+
if a is None:
|
295
|
+
return
|
296
|
+
|
297
|
+
curl, error, accounts = a.list_accounts(to_curl)
|
298
|
+
if to_curl:
|
299
|
+
print(curl)
|
300
|
+
return
|
301
|
+
if error:
|
302
|
+
print(f"ERROR: {error.message}")
|
303
|
+
return
|
304
|
+
if to_json:
|
305
|
+
json_dumps = [x.model_dump_json() for x in accounts.values()]
|
306
|
+
print(f"[{','.join(json_dumps)}]")
|
307
|
+
return
|
308
|
+
|
309
|
+
print("Ad Account ID, State, Ad Account Title")
|
310
|
+
for a in accounts.values():
|
311
|
+
print(f"{a.id}, {a.state_info.state}, {a.title}")
|
312
|
+
|
313
|
+
return
|
314
|
+
|
315
|
+
@app.command()
|
316
|
+
def list_account_items(
|
317
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
318
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
319
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
320
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
321
|
+
):
|
322
|
+
"""
|
323
|
+
List all items for a given ad account.
|
324
|
+
"""
|
325
|
+
a = _create_account_command(profile)
|
326
|
+
if a is None:
|
327
|
+
return
|
328
|
+
|
329
|
+
curl, error, items = a.list_account_items(account_id, to_curl)
|
330
|
+
|
331
|
+
if to_curl:
|
332
|
+
print(curl)
|
333
|
+
sys.exit()
|
334
|
+
if error:
|
335
|
+
print(f"ERROR: {error.message}")
|
336
|
+
sys.exit()
|
337
|
+
if to_json:
|
338
|
+
json_dumps = [x.model_dump_json() for x in items]
|
339
|
+
print(f"[{','.join(json_dumps)}]")
|
340
|
+
sys.exit()
|
341
|
+
|
342
|
+
print("Ad Account ID, Item ID, Is Active, Created At, Item Title")
|
343
|
+
for i in items:
|
344
|
+
print(f"{account_id}, {i.id}, {i.is_active}, {i.created_timestamp}, {i.title}")
|
345
|
+
|
154
346
|
|
155
347
|
class AccountCommand:
|
156
348
|
def __init__(
|
@@ -255,6 +447,21 @@ class AccountCommand:
|
|
255
447
|
return error
|
256
448
|
|
257
449
|
|
450
|
+
def delete_user(
|
451
|
+
self,
|
452
|
+
account_id,
|
453
|
+
user_id,
|
454
|
+
send_email_notification,
|
455
|
+
) -> Optional[Error]:
|
456
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/users/{user_id}?reason=UserDeleted"
|
457
|
+
if not send_email_notification:
|
458
|
+
_api_url += "&bypass_notification=true"
|
459
|
+
|
460
|
+
_, error, _ = api_request('DELETE', False, _api_url, self.headers)
|
461
|
+
|
462
|
+
return error
|
463
|
+
|
464
|
+
|
258
465
|
def invite_user(
|
259
466
|
self,
|
260
467
|
account_id,
|
@@ -285,6 +492,50 @@ class AccountCommand:
|
|
285
492
|
ret = UserWrapper(**json_obj).user
|
286
493
|
return None, None, ret
|
287
494
|
|
495
|
+
def activate_account(
|
496
|
+
self,
|
497
|
+
account: Account,
|
498
|
+
) -> Optional[Error]:
|
499
|
+
account.state_info.state = "ACTIVE"
|
500
|
+
account.state_info.state_case = "ACTIVATED"
|
501
|
+
|
502
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account.id}"
|
503
|
+
_payload = {
|
504
|
+
"ad_account": account.model_dump_json()
|
505
|
+
}
|
506
|
+
_, error, _ = api_request('POST', False, _api_url, self.headers, _payload)
|
507
|
+
if error:
|
508
|
+
return error
|
509
|
+
|
510
|
+
_api_url = f"{self.api_base_url}/ad_accounts/{account.id}/state-info"
|
511
|
+
_payload = account.state_info.model_dump_json()
|
512
|
+
_, error, _ = api_request('POST', False, _api_url, self.headers, _payload)
|
513
|
+
if error:
|
514
|
+
return error
|
515
|
+
|
516
|
+
return None
|
517
|
+
|
518
|
+
|
519
|
+
|
520
|
+
def activate_account_with_retry(
|
521
|
+
self,
|
522
|
+
account: Account,
|
523
|
+
) -> Optional[Error]:
|
524
|
+
retries = 0
|
525
|
+
delay = 1
|
526
|
+
max_retries = 3
|
527
|
+
while retries < max_retries:
|
528
|
+
error = self.activate_account(account)
|
529
|
+
if error and error.code == 16:
|
530
|
+
print(f"\nERROR: authentication token expired. Retrying...", file=sys.stderr, flush=True)
|
531
|
+
self.refresh_token()
|
532
|
+
retries += 1
|
533
|
+
time.sleep(delay)
|
534
|
+
continue
|
535
|
+
return error
|
536
|
+
return Error(code=16, message="Failed to regain an authentication token")
|
537
|
+
|
538
|
+
|
288
539
|
def create_account(
|
289
540
|
self,
|
290
541
|
account_id,
|
@@ -420,6 +671,88 @@ class AccountCommand:
|
|
420
671
|
)
|
421
672
|
|
422
673
|
|
674
|
+
def list_account_items(
|
675
|
+
self,
|
676
|
+
account_id,
|
677
|
+
to_curl=False
|
678
|
+
) -> tuple[
|
679
|
+
Optional[CurlString],
|
680
|
+
Optional[Error],
|
681
|
+
list[Item]
|
682
|
+
]:
|
683
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/items"
|
684
|
+
_payload = {
|
685
|
+
"ad_account_id": account_id,
|
686
|
+
"search_keyword":[],
|
687
|
+
"order_by": [{
|
688
|
+
"column": "ID",
|
689
|
+
"direction": "ASC"
|
690
|
+
}],
|
691
|
+
"filter": [
|
692
|
+
{
|
693
|
+
"column": "IS_ACTIVE",
|
694
|
+
"filter_operator": "EQ",
|
695
|
+
"value": "true or false",
|
696
|
+
}
|
697
|
+
],
|
698
|
+
"page_index": 1,
|
699
|
+
"page_size": MAX_NUM_ITEMS_PER_PAGE,
|
700
|
+
}
|
701
|
+
|
702
|
+
#
|
703
|
+
# Get active items
|
704
|
+
#
|
705
|
+
curl, error, active_items = self.list_account_items_(to_curl, _api_url, self.headers, _payload, True)
|
706
|
+
if curl:
|
707
|
+
return curl, None, []
|
708
|
+
if error:
|
709
|
+
return None, error, []
|
710
|
+
#
|
711
|
+
# Get inactive items
|
712
|
+
#
|
713
|
+
_, error, inactive_items = self.list_account_items_(to_curl, _api_url, self.headers, _payload, False)
|
714
|
+
if error:
|
715
|
+
return None, error, []
|
716
|
+
|
717
|
+
return None, None, active_items + inactive_items
|
718
|
+
|
719
|
+
|
720
|
+
def list_account_items_(
|
721
|
+
self,
|
722
|
+
to_curl,
|
723
|
+
_api_url,
|
724
|
+
_headers,
|
725
|
+
_payload,
|
726
|
+
_is_active
|
727
|
+
) -> tuple[
|
728
|
+
Optional[CurlString],
|
729
|
+
Optional[Error],
|
730
|
+
list[Item]
|
731
|
+
]:
|
732
|
+
items = []
|
733
|
+
num_items = 0
|
734
|
+
page_index = 1
|
735
|
+
while True:
|
736
|
+
_payload["page_index"] = page_index
|
737
|
+
_payload["filter"][0]["value"] = "true" if _is_active else "false"
|
738
|
+
|
739
|
+
curl, error, json_obj = api_request('POST', to_curl, _api_url, _headers, _payload)
|
740
|
+
if curl:
|
741
|
+
return curl, None, []
|
742
|
+
if error:
|
743
|
+
return None, error, []
|
744
|
+
|
745
|
+
item_group = ItemList(**json_obj)
|
746
|
+
items += item_group.rows
|
747
|
+
num_items += len(item_group.rows)
|
748
|
+
|
749
|
+
if num_items >= item_group.num_counts:
|
750
|
+
break
|
751
|
+
page_index += 1
|
752
|
+
|
753
|
+
return None, None, items
|
754
|
+
|
755
|
+
|
423
756
|
|
424
757
|
#
|
425
758
|
# Helper functions
|
mcmcli/command/wallet.py
CHANGED
@@ -12,9 +12,10 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from datetime import datetime
|
15
16
|
from enum import Enum
|
16
17
|
from mcmcli.data.error import Error
|
17
|
-
from mcmcli.data.wallet import Wallet, WalletsWrapper
|
18
|
+
from mcmcli.data.wallet import Wallet, WalletsWrapper, PlatformWalletsWrapper
|
18
19
|
from mcmcli.requests import CurlString, api_request
|
19
20
|
|
20
21
|
import json
|
@@ -36,20 +37,68 @@ class OperationType(Enum):
|
|
36
37
|
DEPOSIT = "deposit"
|
37
38
|
WITHDRAW = "withdraw"
|
38
39
|
|
40
|
+
def _get_wallet_balance(wallet: Wallet):
|
41
|
+
wa0 = wallet.accounts[0]
|
42
|
+
wa1 = wallet.accounts[1]
|
43
|
+
|
44
|
+
credits_amount_micro = wa0.balance.amount_micro if wa0.type == 'CREDITS' else wa1.balance.amount_micro
|
45
|
+
pre_paid_amount_micro = wa0.balance.amount_micro if wa0.type == 'PRE_PAID' else wa1.balance.amount_micro
|
46
|
+
credit_balance = float(credits_amount_micro) / float(1000000)
|
47
|
+
prepaid_balance = float(pre_paid_amount_micro) / float(1000000)
|
48
|
+
|
49
|
+
return (credit_balance, prepaid_balance)
|
50
|
+
|
51
|
+
|
52
|
+
@app.command()
|
53
|
+
def platform_balance(
|
54
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
55
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
56
|
+
):
|
57
|
+
"""
|
58
|
+
Get the current balances of all ad accounts in CSV format.
|
59
|
+
"""
|
60
|
+
auth = mcmcli.command.auth.AuthCommand(profile)
|
61
|
+
curl, error, token = auth.get_token()
|
62
|
+
if error:
|
63
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
64
|
+
return
|
65
|
+
|
66
|
+
# Get current UTC timestamp and format it as an ISO 8601 string with microseconds
|
67
|
+
current_timestamp = datetime.utcnow().isoformat(timespec='microseconds') + "Z"
|
68
|
+
|
69
|
+
wc = WalletCommand(profile, auth, token.token)
|
70
|
+
curl, error, platform_wallets = wc.get_platform_balance(to_curl)
|
71
|
+
if to_curl:
|
72
|
+
print(curl)
|
73
|
+
return
|
74
|
+
if error:
|
75
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
76
|
+
return
|
77
|
+
if platform_wallets is None:
|
78
|
+
print(f"ERROR: Cannot find the wallets", file=sys.stderr, flush=True)
|
79
|
+
return
|
80
|
+
|
81
|
+
print("ad_account_id,credit_balance,prepaid_balance,got_balance_info_at_utc")
|
82
|
+
for account_id, account_data in platform_wallets.items():
|
83
|
+
wallet = account_data.wallets[0]
|
84
|
+
credit_balance, prepaid_balance = _get_wallet_balance(wallet)
|
85
|
+
print(f'{account_id},{credit_balance},{prepaid_balance},{current_timestamp}')
|
86
|
+
|
87
|
+
|
39
88
|
@app.command()
|
40
89
|
def balance(
|
41
90
|
account_id: str = typer.Option(help="Ad account ID"),
|
42
91
|
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
43
92
|
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
44
|
-
profile: str = "default",
|
45
|
-
|
93
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
94
|
+
):
|
46
95
|
"""
|
47
96
|
Retrive the current balance of the given ad account's wallet.
|
48
97
|
"""
|
49
98
|
auth = mcmcli.command.auth.AuthCommand(profile)
|
50
99
|
curl, error, token = auth.get_token()
|
51
100
|
if error:
|
52
|
-
print(f"ERROR: {error.message}")
|
101
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
53
102
|
return
|
54
103
|
|
55
104
|
wc = WalletCommand(profile, auth, token.token)
|
@@ -58,24 +107,20 @@ def balance(
|
|
58
107
|
print(curl)
|
59
108
|
return
|
60
109
|
if error:
|
61
|
-
print(f"ERROR: {error.message}")
|
110
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
111
|
+
return
|
112
|
+
if wallet is None:
|
113
|
+
print(f"ERROR: Wallet does not exist.", file=sys.stderr, flush=True)
|
62
114
|
return
|
63
115
|
if to_json:
|
64
116
|
print(wallet.model_dump_json())
|
65
117
|
return
|
66
118
|
|
67
|
-
|
68
|
-
wa1 = wallet.accounts[1]
|
69
|
-
|
70
|
-
credits_amount_micro = wa0.balance.amount_micro if wa0.type == 'CREDITS' else wa1.balance.amount_micro
|
71
|
-
pre_paid_amount_micro = wa0.balance.amount_micro if wa0.type == 'PRE_PAID' else wa1.balance.amount_micro
|
72
|
-
credit_amount = float(credits_amount_micro) / float(1000000)
|
73
|
-
money_amount = float(pre_paid_amount_micro) / float(1000000)
|
119
|
+
credit_balance, prepaid_balance = _get_wallet_balance(wallet)
|
74
120
|
|
75
121
|
print(f"Ad account ID = {account_id}")
|
76
|
-
print(f"
|
77
|
-
print(f"PRE_PAID balance = {
|
78
|
-
print(f"CREDITS balance = {credit_amount}")
|
122
|
+
print(f"CREDITS balance = {credit_balance}")
|
123
|
+
print(f"PRE_PAID balance = {prepaid_balance}")
|
79
124
|
return
|
80
125
|
|
81
126
|
|
@@ -86,8 +131,8 @@ def deposit(
|
|
86
131
|
fund_amount: float = typer.Option(help="The fund amount to deposit"),
|
87
132
|
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
88
133
|
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
89
|
-
profile: str = "default",
|
90
|
-
|
134
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
135
|
+
):
|
91
136
|
"""
|
92
137
|
Add or top up the money amount to the current balance of the given ad account's wallet.
|
93
138
|
"""
|
@@ -137,8 +182,8 @@ def withdraw(
|
|
137
182
|
fund_amount: float = typer.Option(help="The amount of credit to add"),
|
138
183
|
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
139
184
|
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
140
|
-
profile: str = "default",
|
141
|
-
|
185
|
+
profile: str = typer.Option("default", help="profile name of the MCM CLI."),
|
186
|
+
):
|
142
187
|
"""
|
143
188
|
Withdraws the money amount from the current balance of the given ad account's wallet.
|
144
189
|
"""
|
@@ -202,6 +247,25 @@ class WalletCommand:
|
|
202
247
|
"Authorization": f"Bearer {token}"
|
203
248
|
}
|
204
249
|
|
250
|
+
def get_platform_balance(
|
251
|
+
self,
|
252
|
+
to_curl: bool,
|
253
|
+
) -> tuple[
|
254
|
+
None | CurlString,
|
255
|
+
None | Error,
|
256
|
+
None | dict[str, WalletsWrapper]
|
257
|
+
]:
|
258
|
+
_api_url = f"{self.api_base_url}/wallets-bulk/read"
|
259
|
+
|
260
|
+
curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers)
|
261
|
+
if curl:
|
262
|
+
return curl, None, None
|
263
|
+
if error:
|
264
|
+
return None, error, None
|
265
|
+
|
266
|
+
platform_wallets_wrapper = PlatformWalletsWrapper(**json_obj)
|
267
|
+
return None, None, platform_wallets_wrapper.result
|
268
|
+
|
205
269
|
|
206
270
|
def get_balance(
|
207
271
|
self,
|
mcmcli/data/item.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright 2023 Moloco, Inc
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from pydantic import BaseModel
|
16
|
+
from typing import Optional
|
17
|
+
|
18
|
+
#
|
19
|
+
# API response dataclasses
|
20
|
+
#
|
21
|
+
class Price(BaseModel):
|
22
|
+
currency: str
|
23
|
+
amount: float
|
24
|
+
|
25
|
+
class Item(BaseModel):
|
26
|
+
id: str
|
27
|
+
title: str
|
28
|
+
price: Price
|
29
|
+
sale_price: Price
|
30
|
+
link: str
|
31
|
+
image_link: str
|
32
|
+
category: str
|
33
|
+
review_count: str
|
34
|
+
rating_score: float
|
35
|
+
is_active: bool
|
36
|
+
is_new: Optional[bool]
|
37
|
+
created_timestamp: str
|
38
|
+
|
39
|
+
class ItemList(BaseModel):
|
40
|
+
rows: list[Item]
|
41
|
+
num_counts: int
|
42
|
+
created_timestamp_filter: str
|
mcmcli/data/wallet.py
CHANGED
@@ -67,3 +67,46 @@ class Wallet(BaseModel):
|
|
67
67
|
|
68
68
|
class WalletsWrapper(BaseModel):
|
69
69
|
wallets: list[Wallet]
|
70
|
+
|
71
|
+
|
72
|
+
#
|
73
|
+
# API response dataclasses for the platform balance API responses
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# {
|
77
|
+
# "result": {
|
78
|
+
# "10054": {
|
79
|
+
# "wallets": [
|
80
|
+
# {
|
81
|
+
# "id": "wRydQ9MhQx4ZyJED",
|
82
|
+
# "title": "",
|
83
|
+
# "ad_account_id": "10054",
|
84
|
+
# "type": "PRE_PAYMENT",
|
85
|
+
# "accounts": [
|
86
|
+
# {
|
87
|
+
# "type": "CREDITS",
|
88
|
+
# "balance": {
|
89
|
+
# "currency": "USD",
|
90
|
+
# "amount_micro": "1890020000"
|
91
|
+
# }
|
92
|
+
# },
|
93
|
+
# {
|
94
|
+
# "type": "PRE_PAID",
|
95
|
+
# "balance": {
|
96
|
+
# "currency": "USD",
|
97
|
+
# "amount_micro": "0"
|
98
|
+
# }
|
99
|
+
# }
|
100
|
+
# ],
|
101
|
+
# "created_at": "2024-09-24T17:26:09.437580Z",
|
102
|
+
# "updated_at": "2024-10-20T22:00:21.412482Z"
|
103
|
+
# }
|
104
|
+
# ]
|
105
|
+
# }
|
106
|
+
# }
|
107
|
+
# }
|
108
|
+
|
109
|
+
class PlatformWalletsWrapper(BaseModel):
|
110
|
+
result: dict[str, WalletsWrapper] # Dictionary with ad_account_id as keys
|
111
|
+
|
112
|
+
|
mcmcli/requests.py
CHANGED
@@ -41,6 +41,15 @@ def get(url, headers):
|
|
41
41
|
except Exception as e:
|
42
42
|
return e, None
|
43
43
|
|
44
|
+
|
45
|
+
def delete(url, headers):
|
46
|
+
try:
|
47
|
+
res = requests.delete(url, headers=headers)
|
48
|
+
return None, json.loads(res.text)
|
49
|
+
except Exception as e:
|
50
|
+
return e, None
|
51
|
+
|
52
|
+
|
44
53
|
def post(url, headers, payload):
|
45
54
|
try:
|
46
55
|
res = requests.post(url, headers=headers, json=payload)
|
@@ -62,6 +71,8 @@ def api_request(method, to_curl, url, headers, payload=None) -> tuple[CurlString
|
|
62
71
|
|
63
72
|
if method == 'GET':
|
64
73
|
error, json_obj = get(url, headers)
|
74
|
+
elif method == 'DELETE':
|
75
|
+
error, json_obj = delete(url, headers)
|
65
76
|
elif method == 'POST':
|
66
77
|
error, json_obj = post(url, headers, payload)
|
67
78
|
elif method == 'PUT':
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|