mcm-cli 1.6.1__py3-none-any.whl → 1.7.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 1.6.1
3
+ Version: 1.7.1
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,13 +1,13 @@
1
1
  mcmcli/__init__.py,sha256=-U6lMZ9_99IXAKwnqnYXYr6NcO6TSmG-kxewgvJjU4k,575
2
- mcmcli/__main__.py,sha256=8Bwhb-VptKSRYlH1LwgGgKZ8pzdc51zSunv9d2cPI0k,1844
2
+ mcmcli/__main__.py,sha256=J1wszWrZ7eii0SNqtJg9S75ElgQPp8ivpswiJBYjE_s,1844
3
3
  mcmcli/logging.py,sha256=xjRS5ey1ONx_d34qB1Fetb_SwPysoh2hzNDuNAaYYWQ,1739
4
4
  mcmcli/requests.py,sha256=IuySBQ8P_GoGF3f_TRysfgQNOhi2n9M84WK_eRXnoEU,2945
5
5
  mcmcli/command/account.py,sha256=FWXmzOLj4rVLVLEv-w0eDVlQVrkONvR1UewZbcTDgE4,24994
6
- mcmcli/command/admin.py,sha256=2GC0B3oqQsT5N5mHUyezgZvzPkB4-oGtddguOWfdLJs,15980
6
+ mcmcli/command/admin.py,sha256=oMIGRPB2GspKLi27dVmL5U4gjLLD3ZvGQ5JHP-ZLv4E,19068
7
7
  mcmcli/command/auth.py,sha256=Ak7ZNEskWPpMoeTJcbYlEpDBgzxn8N33Q2dNf67SsCs,2926
8
8
  mcmcli/command/campaign.py,sha256=eHE_i1lRlUuU1GeVMwXkJgBU_zOgU-b9Wo0SYC9TK7M,15444
9
9
  mcmcli/command/config.py,sha256=08C5ftAvdvpQ26Z329LqhP8AxTI629LS7Ou6glzrRgw,4396
10
- mcmcli/command/decision.py,sha256=iLvEDEa2k0LAgoXrOvcdn-HcFRo90E8Dxht_Ls9EGCY,8690
10
+ mcmcli/command/decision.py,sha256=3eCz4360q37vxNZVBUEDEVKGEqatjMA1nKswQh8GXsA,8993
11
11
  mcmcli/command/report.py,sha256=N8IMyDZ5QpY11-KkZG-n5_ZzEeh-ME5s2s3wrAKEGjY,5387
12
12
  mcmcli/command/wallet.py,sha256=vG2rg7tPwGsV9YB7cpvkiocbqtB1reqXn7dmolFmzD4,11536
13
13
  mcmcli/data/account.py,sha256=pe7tPapP6vlUD5D5L5Nh5k2bkWdYOK01Mpt5fBYFnJs,1782
@@ -23,10 +23,10 @@ mcmcli/data/seller.py,sha256=40SA7QekM3a3svDrDYLo_QYJ68VUxDO0KeGejJMp4k4,1004
23
23
  mcmcli/data/token.py,sha256=11wtyLHCAZHu0LVbNDPa-yipcL6lenxoYIKEI58VzFs,1744
24
24
  mcmcli/data/user_join_request.py,sha256=lXMO2hE_VpRg0JofVrYAVM82S-RLFkPrZk8-drvhoDI,1251
25
25
  mcmcli/data/wallet.py,sha256=eMUi8N0vJdg_E10TPhSPoZkZtmIG7gHyqgabQ8C5Lg8,3217
26
- mcm_cli-1.6.1.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
27
- mcm_cli-1.6.1.dist-info/METADATA,sha256=dgSb4q2UwtTILG4P54BcMsiglP27lmXi_kHUwkNcc2E,3056
28
- mcm_cli-1.6.1.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
29
- mcm_cli-1.6.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
- mcm_cli-1.6.1.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
31
- mcm_cli-1.6.1.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
32
- mcm_cli-1.6.1.dist-info/RECORD,,
26
+ mcm_cli-1.7.1.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
27
+ mcm_cli-1.7.1.dist-info/METADATA,sha256=xcrBBZZZ2ftAJUglEnEyx4G0bA8KV-8HTABosPHb9OI,3056
28
+ mcm_cli-1.7.1.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
29
+ mcm_cli-1.7.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
+ mcm_cli-1.7.1.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
31
+ mcm_cli-1.7.1.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
32
+ mcm_cli-1.7.1.dist-info/RECORD,,
mcmcli/__main__.py CHANGED
@@ -37,7 +37,7 @@ def version():
37
37
  """
38
38
  Show the tool version
39
39
  """
40
- typer.echo(f"Version: mcm-cli v1.6.1")
40
+ typer.echo(f"Version: mcm-cli v1.7.1")
41
41
 
42
42
  app.add_typer(mcmcli.command.account.app, name="account", help="Ad account management")
43
43
  app.add_typer(mcmcli.command.admin.app, name="admin", help="Platform administration")
mcmcli/command/admin.py CHANGED
@@ -27,8 +27,11 @@ import mcmcli.command.account
27
27
  import mcmcli.command.auth
28
28
  import mcmcli.command.campaign
29
29
  import mcmcli.command.config
30
+ import mcmcli.command.decision
30
31
  import mcmcli.command.wallet
31
32
  import mcmcli.requests
33
+ import random
34
+ import requests
32
35
  import sys
33
36
  import typer
34
37
 
@@ -40,76 +43,104 @@ def _create_admin_command(profile):
40
43
  auth = AuthCommand(profile)
41
44
  return AdminCommand(profile, auth)
42
45
 
43
- @app.command()
44
- def list_wallet_balances(
45
- profile: str = typer.Option("default", help="profile name of the MCM CLI."),
46
- ):
47
- """
48
- List the wallet balances of all of the ad accounts
49
- """
50
- admin = _create_admin_command(profile)
51
- if admin is None:
52
- return
53
- admin.list_wallet_balances()
54
-
55
46
 
56
47
  @app.command()
57
- def list_all_campaigns(
58
- profile: str = "default",
48
+ def block_item(
49
+ item_id: str = typer.Option(help="Item ID"),
50
+ account_id: str = typer.Option(None, help="The Ad Account ID is applicable only for MSPI catalogs. If this value is provided, only the item associated with the specified seller ID will be removed from ad serving. If it is not provided, the specified item will be removed for all sellers in the MSPI catalog."),
51
+ to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
52
+ profile: str = typer.Option("default", help="Profile name of the MCM CLI."),
59
53
  ):
60
54
  """
61
- List the campaigigns of all of the active ad accounts
55
+ Item Kill Switch Command.
56
+ This API immediately blocks an item or an ad account item from appearing in ads by marking it as `blocked`.
62
57
  """
58
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
59
+
60
+ # print(f"invoked block_item(item_id={item_id}, account_id={account_id}, blocked='Requested at {timestamp}')");
63
61
  admin = _create_admin_command(profile)
64
62
  if admin is None:
65
63
  return
66
- error, account_campaigns = admin.list_all_campaigns()
64
+
65
+ curl, error, result = admin.block_item(item_id=item_id, account_id=account_id, to_curl=to_curl)
66
+ if curl:
67
+ print(curl)
68
+ return
67
69
  if error:
68
- print(error, file=sys.stderr, flush=True)
70
+ print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
69
71
  return
72
+
73
+ print(result.model_dump_json())
74
+ return
70
75
 
71
- print("Account ID,Account Title,Campaign ID,Campaign Title,Ad Type,Start,End,Budget Period,Budget Amount,Enabling State,State,Created At,Updated At")
72
- for account_campaign in account_campaigns:
73
- a, c = account_campaign
74
-
75
- print(f'"{a.id}","{a.title}",', end="", flush=True)
76
- print(f'"{c.id}","{c.title}","{c.ad_type}",', end="", flush=True)
77
- print(f'"{c.schedule.start}","{c.schedule.end}",', end="", flush=True)
78
- print(f'"{c.budget.period}","{int(c.budget.amount.amount_micro) / 1000000}",', end="", flush=True)
79
- print(f'"{c.enabling_state}","{c.state}",', end="", flush=True)
80
- print(f'"{c.created_at}","{c.updated_at}",', end="", flush=True)
81
- # print(f'"{";".join(c.catalog_item_ids)}"')
82
- print("", flush=True)
83
76
 
84
77
  @app.command()
85
- def list_platform_users(
86
- to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
87
- to_json: bool = typer.Option(False, help="Print raw output in json"),
88
- profile: str = "default",
78
+ def generate_sample_data(
79
+ ad_inventory_id: str = typer.Option(help="The ad inventory ID to use when calling the Decision API."),
80
+ num_iterations: int = typer.Option(100, help="How many times to call the Decision API."),
81
+ warn: bool = typer.Option(True, help="Shows a warning message before running. Use `--no-warn` if you want to skip the warning."),
82
+ profile: str = typer.Option("default", help="Profile Name – The MCM CLI configuration profile to use."),
89
83
  ):
90
84
  """
91
- List the users of the platform
85
+ Generate sample impressions and clicks. This command invokes the Decision APIs, and posts the impression
86
+ and click trackers to generate sample data in the platform.
92
87
  """
93
- admin = _create_admin_command(profile)
94
- if admin is None:
95
- return
88
+ if warn:
89
+ typer.confirm("""⚠️ WARNING: This script is strictly for use on the TEST platform.⚠️
90
+ Running it on any other platform may corrupt data or confuse the ML system.
91
+ It will generate sample impressions and clicks.
92
+ Please proceed only if you are certain.""", abort=True)
96
93
 
97
- curl, error, users = admin.list_platform_users(to_curl)
98
- if to_curl:
99
- print(curl)
100
- return
101
- if error:
102
- print(error, file=sys.stderr, flush=True)
103
- return
104
- if to_json:
105
- json_dumps = [x.model_dump_json() for x in users]
106
- print(f"[{','.join(json_dumps)}]")
107
- return
94
+ config = mcmcli.command.config.get_config(profile)
95
+ if (config is None):
96
+ print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
97
+ sys.exit()
98
+
99
+ platform_id = config['platform_id']
100
+ if (not platform_id.endswith("_TEST")):
101
+ print(f"ERROR: The platform {platform_id} is not a TEST platform.", file=sys.stderr, flush=True)
102
+ sys.exit()
108
103
 
109
- print('User ID,Created At,Updated At,Status,Email,Name,Roles')
110
- for u in users:
111
- roles = [f'{x.name} of {x.resource_type} {x.resource_id}' for x in u.roles]
112
- print(f'{u.id},{u.created_at},{u.updated_at},{u.status},{u.email},{u.name},{';'.join(roles)}')
104
+
105
+ # Initialize DecisionCommand
106
+ d = mcmcli.command.decision.DecisionCommand(profile)
107
+
108
+ print(f"Invoking the Decision API {num_iterations} times to generate sample impressions and clicks...", end='', flush=True)
109
+ thread, stopper = start_dot_printing()
110
+
111
+ for i in range(num_iterations):
112
+ # Call Decision API to get trackers
113
+ _, error, decided_items = d.decide_items(ad_inventory_id)
114
+ if error:
115
+ print_error(f"Error calling Decision API: {error.message}")
116
+ continue
117
+
118
+ if not decided_items or not decided_items.decided_items or len(decided_items.decided_items) == 0:
119
+ print_error("The Decision API returned an empty response.")
120
+ continue
121
+
122
+ for item in decided_items.decided_items:
123
+ # Post impression tracker
124
+ for imp_url in item.imp_trackers:
125
+ try:
126
+ requests.post(imp_url)
127
+ except requests.RequestException as e:
128
+ print(f"[{i}] Failed to post imp tracker: {imp_url} - Error: {e}")
129
+
130
+ # Post to click trackers with 20% probability
131
+ if random.random() >= 0.8:
132
+ continue
133
+ for click_url in item.click_trackers:
134
+ try:
135
+ requests.post(click_url)
136
+ except requests.RequestException as e:
137
+ print(f"[{i}] Failed to post imp tracker: {click_url} - Error: {e}")
138
+
139
+ print('.', end='', flush=True)
140
+
141
+ stop_dot_printing(thread, stopper)
142
+ print("\nDone generating sample data!")
143
+ return
113
144
 
114
145
 
115
146
  @app.command()
@@ -139,6 +170,35 @@ def get_platform_user(
139
170
  return
140
171
 
141
172
 
173
+ @app.command()
174
+ def list_all_campaigns(
175
+ profile: str = "default",
176
+ ):
177
+ """
178
+ List the campaigigns of all of the active ad accounts
179
+ """
180
+ admin = _create_admin_command(profile)
181
+ if admin is None:
182
+ return
183
+ error, account_campaigns = admin.list_all_campaigns()
184
+ if error:
185
+ print(error, file=sys.stderr, flush=True)
186
+ return
187
+
188
+ print("Account ID,Account Title,Campaign ID,Campaign Title,Ad Type,Start,End,Budget Period,Budget Amount,Enabling State,State,Created At,Updated At")
189
+ for account_campaign in account_campaigns:
190
+ a, c = account_campaign
191
+
192
+ print(f'"{a.id}","{a.title}",', end="", flush=True)
193
+ print(f'"{c.id}","{c.title}","{c.ad_type}",', end="", flush=True)
194
+ print(f'"{c.schedule.start}","{c.schedule.end}",', end="", flush=True)
195
+ print(f'"{c.budget.period}","{int(c.budget.amount.amount_micro) / 1000000}",', end="", flush=True)
196
+ print(f'"{c.enabling_state}","{c.state}",', end="", flush=True)
197
+ print(f'"{c.created_at}","{c.updated_at}",', end="", flush=True)
198
+ # print(f'"{";".join(c.catalog_item_ids)}"')
199
+ print("", flush=True)
200
+
201
+
142
202
  @app.command()
143
203
  def list_items(
144
204
  account_id: str = typer.Option(help="Ad account ID"),
@@ -197,6 +257,7 @@ def list_items(
197
257
  else:
198
258
  print(f"{account_id},{i.id},{i.is_active},\"{i.title}\",,")
199
259
 
260
+
200
261
  @app.command()
201
262
  def list_off_campaign_items(
202
263
  account_id: str = typer.Option(help="Ad account ID"),
@@ -229,34 +290,48 @@ def list_off_campaign_items(
229
290
  if i.id not in campaign_item_obj and i.is_active:
230
291
  print(f"{i.id}, {i.title}")
231
292
 
293
+
232
294
  @app.command()
233
- def block_item(
234
- item_id: str = typer.Option(help="Item ID"),
235
- account_id: str = typer.Option(None, help="The Ad Account ID is applicable only for MSPI catalogs. If this value is provided, only the item associated with the specified seller ID will be removed from ad serving. If it is not provided, the specified item will be removed for all sellers in the MSPI catalog."),
295
+ def list_platform_users(
236
296
  to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
237
- profile: str = typer.Option("default", help="Profile name of the MCM CLI."),
297
+ to_json: bool = typer.Option(False, help="Print raw output in json"),
298
+ profile: str = "default",
238
299
  ):
239
300
  """
240
- Item Kill Switch Command.
241
- This API immediately blocks an item or an ad account item from appearing in ads by marking it as “blocked.”
301
+ List the users of the platform
242
302
  """
243
- timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
244
-
245
- # print(f"invoked block_item(item_id={item_id}, account_id={account_id}, blocked='Requested at {timestamp}')");
246
303
  admin = _create_admin_command(profile)
247
304
  if admin is None:
248
305
  return
249
-
250
- curl, error, result = admin.block_item(item_id=item_id, account_id=account_id, to_curl=to_curl)
251
- if curl:
306
+
307
+ curl, error, users = admin.list_platform_users(to_curl)
308
+ if to_curl:
252
309
  print(curl)
253
310
  return
254
311
  if error:
255
- print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
312
+ print(error, file=sys.stderr, flush=True)
256
313
  return
257
-
258
- print(result.model_dump_json())
259
- return
314
+ if to_json:
315
+ json_dumps = [x.model_dump_json() for x in users]
316
+ print(f"[{','.join(json_dumps)}]")
317
+ return
318
+
319
+ print('User ID,Created At,Updated At,Status,Email,Name,Roles')
320
+ for u in users:
321
+ roles = [f'{x.name} of {x.resource_type} {x.resource_id}' for x in u.roles]
322
+ print(f'{u.id},{u.created_at},{u.updated_at},{u.status},{u.email},{u.name},{';'.join(roles)}')
323
+
324
+ @app.command()
325
+ def list_wallet_balances(
326
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
327
+ ):
328
+ """
329
+ List the wallet balances of all of the ad accounts
330
+ """
331
+ admin = _create_admin_command(profile)
332
+ if admin is None:
333
+ return
334
+ admin.list_wallet_balances()
260
335
 
261
336
 
262
337
  class AdminCommand:
@@ -21,6 +21,7 @@ import json
21
21
  import mcmcli.command.auth
22
22
  import mcmcli.command.config
23
23
  import mcmcli.logging
24
+ import random
24
25
  import sys
25
26
  import typer
26
27
 
@@ -139,10 +140,10 @@ class DecisionCommand:
139
140
  def decide_items(
140
141
  self,
141
142
  inventory_id,
142
- num_items,
143
- items,
144
- location_filter,
145
- to_curl,
143
+ num_items = 5,
144
+ items = False,
145
+ location_filter = None,
146
+ to_curl = False,
146
147
  ) -> tuple[
147
148
  Optional[CurlString],
148
149
  Optional[Error],
@@ -156,10 +157,10 @@ class DecisionCommand:
156
157
  "num_items": num_items
157
158
  },
158
159
  "user": {
159
- "user_id": "user-1"
160
+ "user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
160
161
  },
161
162
  "device": {
162
- "persistent_id": "persistent-device-1"
163
+ "persistent_id": f"mcmcli-device-{random.randint(100_000, 999_999)}"
163
164
  },
164
165
  }
165
166
  if items:
@@ -185,9 +186,9 @@ class DecisionCommand:
185
186
  def decide_creative(
186
187
  self,
187
188
  inventory_id,
188
- items,
189
- location_filter,
190
- to_curl
189
+ items = False,
190
+ location_filter = None,
191
+ to_curl = False,
191
192
  ) -> tuple[
192
193
  Optional[CurlString],
193
194
  Optional[Error],
@@ -200,10 +201,10 @@ class DecisionCommand:
200
201
  "inventory_id": inventory_id
201
202
  },
202
203
  "user": {
203
- "user_id": "user-1"
204
+ "user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
204
205
  },
205
206
  "device": {
206
- "persistent_id": "persistent-device-1"
207
+ "persistent_id": f"mcmcli-device-{random.randint(100_000, 999_999)}"
207
208
  },
208
209
  }
209
210
  if items:
@@ -229,9 +230,9 @@ class DecisionCommand:
229
230
  def decide_creative_bulk(
230
231
  self,
231
232
  inventory_id_list,
232
- items,
233
- location_filter,
234
- to_curl
233
+ items = False,
234
+ location_filter = None,
235
+ to_curl = False,
235
236
  ) -> tuple[
236
237
  Optional[CurlString],
237
238
  Optional[Error],
@@ -242,10 +243,10 @@ class DecisionCommand:
242
243
  "request_id": "request-1",
243
244
  "inventories": list(map(lambda x: { "inventory_id": x }, inventory_id_list.split(','))),
244
245
  "user": {
245
- "user_id": "user-1"
246
+ "user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
246
247
  },
247
248
  "device": {
248
- "persistent_id": "persistent-device-1"
249
+ "persistent_id": f"mcmcli-device-{random.randint(100_000, 999_999)}"
249
250
  },
250
251
  }
251
252
  if items: