mcm-cli 1.1.0__tar.gz → 1.3.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. {mcm_cli-1.1.0/mcm_cli.egg-info → mcm_cli-1.3.0}/PKG-INFO +1 -1
  2. {mcm_cli-1.1.0 → mcm_cli-1.3.0/mcm_cli.egg-info}/PKG-INFO +1 -1
  3. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcm_cli.egg-info/SOURCES.txt +1 -0
  4. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/__main__.py +1 -1
  5. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/account.py +333 -0
  6. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/wallet.py +83 -19
  7. mcm_cli-1.3.0/mcmcli/data/item.py +42 -0
  8. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/wallet.py +43 -0
  9. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/requests.py +11 -0
  10. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/setup.py +1 -1
  11. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/LICENSE +0 -0
  12. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/NOTICE +0 -0
  13. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/README.md +0 -0
  14. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcm_cli.egg-info/dependency_links.txt +0 -0
  15. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcm_cli.egg-info/entry_points.txt +0 -0
  16. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcm_cli.egg-info/requires.txt +0 -0
  17. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcm_cli.egg-info/top_level.txt +0 -0
  18. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/__init__.py +0 -0
  19. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/admin.py +0 -0
  20. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/auth.py +0 -0
  21. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/config.py +0 -0
  22. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/command/decision.py +0 -0
  23. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/account.py +0 -0
  24. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/account_user.py +0 -0
  25. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/decision.py +0 -0
  26. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/error.py +0 -0
  27. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/seller.py +0 -0
  28. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/data/token.py +0 -0
  29. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/mcmcli/logging.py +0 -0
  30. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/setup.cfg +0 -0
  31. {mcm_cli-1.1.0 → mcm_cli-1.3.0}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: A command-line interface for Moloco Commerde Media
5
5
  Home-page: https://github.com/moloco-mcm/mcm-cli
6
6
  Author: Moloco MCM Team
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: A command-line interface for Moloco Commerde Media
5
5
  Home-page: https://github.com/moloco-mcm/mcm-cli
6
6
  Author: Moloco MCM Team
@@ -22,6 +22,7 @@ mcmcli/data/account.py
22
22
  mcmcli/data/account_user.py
23
23
  mcmcli/data/decision.py
24
24
  mcmcli/data/error.py
25
+ mcmcli/data/item.py
25
26
  mcmcli/data/seller.py
26
27
  mcmcli/data/token.py
27
28
  mcmcli/data/wallet.py
@@ -35,7 +35,7 @@ def version():
35
35
  """
36
36
  Show the tool version
37
37
  """
38
- typer.echo(f"Version: mcm-cli v1.1.0")
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")
@@ -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
@@ -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
- wa0 = wallet.accounts[0]
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"Wallet ID = {wallet.id}")
77
- print(f"PRE_PAID balance = {money_amount}")
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,
@@ -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
@@ -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
+
@@ -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':
@@ -18,7 +18,7 @@ from setuptools import setup, find_packages
18
18
 
19
19
  setup(
20
20
  name='mcm-cli',
21
- version='1.1.0',
21
+ version='1.3.0',
22
22
  description='A command-line interface for Moloco Commerde Media',
23
23
  long_description=open('README.md').read(),
24
24
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes