mcm-cli 1.6.1__py3-none-any.whl → 1.7.0__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.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,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=r1C_-MzJ3UISn0P0SKNlEDQCECf4VSvioELtlePMvRA,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=0uB5djhbdjy3HMIQ0r3bfcmYyBfwZo_F9mgWavPnr3E,18514
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.0.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
27
+ mcm_cli-1.7.0.dist-info/METADATA,sha256=_9mCAcpzeBGhL5404uuUTscJqqAiwFfb5pmnkzjam9U,3056
28
+ mcm_cli-1.7.0.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
29
+ mcm_cli-1.7.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
+ mcm_cli-1.7.0.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
31
+ mcm_cli-1.7.0.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
32
+ mcm_cli-1.7.0.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.0")
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,90 @@ 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
96
-
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
88
+ if warn:
89
+ typer.confirm("This will generate sample impressions and clicks. Are you sure you want to continue?", abort=True)
108
90
 
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)}')
91
+ # Initialize DecisionCommand
92
+ d = mcmcli.command.decision.DecisionCommand(profile)
93
+
94
+ print(f"Invoking the Decision API {num_iterations} times to generate sample impressions and clicks...", end='', flush=True)
95
+ thread, stopper = start_dot_printing()
96
+
97
+ for i in range(num_iterations):
98
+ # Call Decision API to get trackers
99
+ _, error, decided_items = d.decide_items(ad_inventory_id)
100
+ if error:
101
+ print_error(f"Error calling Decision API: {error.message}")
102
+ continue
103
+
104
+ if not decided_items or not decided_items.decided_items or len(decided_items.decided_items) == 0:
105
+ print_error("The Decision API returned an empty response.")
106
+ continue
107
+
108
+ for item in decided_items.decided_items:
109
+ # Post impression tracker
110
+ for imp_url in item.imp_trackers:
111
+ try:
112
+ requests.post(imp_url)
113
+ except requests.RequestException as e:
114
+ print(f"[{i}] Failed to post imp tracker: {imp_url} - Error: {e}")
115
+
116
+ # Post to click trackers with 20% probability
117
+ if random.random() >= 0.8:
118
+ continue
119
+ for click_url in item.click_trackers:
120
+ try:
121
+ requests.post(click_url)
122
+ except requests.RequestException as e:
123
+ print(f"[{i}] Failed to post imp tracker: {click_url} - Error: {e}")
124
+
125
+ print('.', end='', flush=True)
126
+
127
+ stop_dot_printing(thread, stopper)
128
+ print("\nDone generating sample data!")
129
+ return
113
130
 
114
131
 
115
132
  @app.command()
@@ -139,6 +156,35 @@ def get_platform_user(
139
156
  return
140
157
 
141
158
 
159
+ @app.command()
160
+ def list_all_campaigns(
161
+ profile: str = "default",
162
+ ):
163
+ """
164
+ List the campaigigns of all of the active ad accounts
165
+ """
166
+ admin = _create_admin_command(profile)
167
+ if admin is None:
168
+ return
169
+ error, account_campaigns = admin.list_all_campaigns()
170
+ if error:
171
+ print(error, file=sys.stderr, flush=True)
172
+ return
173
+
174
+ print("Account ID,Account Title,Campaign ID,Campaign Title,Ad Type,Start,End,Budget Period,Budget Amount,Enabling State,State,Created At,Updated At")
175
+ for account_campaign in account_campaigns:
176
+ a, c = account_campaign
177
+
178
+ print(f'"{a.id}","{a.title}",', end="", flush=True)
179
+ print(f'"{c.id}","{c.title}","{c.ad_type}",', end="", flush=True)
180
+ print(f'"{c.schedule.start}","{c.schedule.end}",', end="", flush=True)
181
+ print(f'"{c.budget.period}","{int(c.budget.amount.amount_micro) / 1000000}",', end="", flush=True)
182
+ print(f'"{c.enabling_state}","{c.state}",', end="", flush=True)
183
+ print(f'"{c.created_at}","{c.updated_at}",', end="", flush=True)
184
+ # print(f'"{";".join(c.catalog_item_ids)}"')
185
+ print("", flush=True)
186
+
187
+
142
188
  @app.command()
143
189
  def list_items(
144
190
  account_id: str = typer.Option(help="Ad account ID"),
@@ -197,6 +243,7 @@ def list_items(
197
243
  else:
198
244
  print(f"{account_id},{i.id},{i.is_active},\"{i.title}\",,")
199
245
 
246
+
200
247
  @app.command()
201
248
  def list_off_campaign_items(
202
249
  account_id: str = typer.Option(help="Ad account ID"),
@@ -229,34 +276,48 @@ def list_off_campaign_items(
229
276
  if i.id not in campaign_item_obj and i.is_active:
230
277
  print(f"{i.id}, {i.title}")
231
278
 
279
+
232
280
  @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."),
281
+ def list_platform_users(
236
282
  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."),
283
+ to_json: bool = typer.Option(False, help="Print raw output in json"),
284
+ profile: str = "default",
238
285
  ):
239
286
  """
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.”
287
+ List the users of the platform
242
288
  """
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
289
  admin = _create_admin_command(profile)
247
290
  if admin is None:
248
291
  return
249
-
250
- curl, error, result = admin.block_item(item_id=item_id, account_id=account_id, to_curl=to_curl)
251
- if curl:
292
+
293
+ curl, error, users = admin.list_platform_users(to_curl)
294
+ if to_curl:
252
295
  print(curl)
253
296
  return
254
297
  if error:
255
- print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
298
+ print(error, file=sys.stderr, flush=True)
256
299
  return
257
-
258
- print(result.model_dump_json())
259
- return
300
+ if to_json:
301
+ json_dumps = [x.model_dump_json() for x in users]
302
+ print(f"[{','.join(json_dumps)}]")
303
+ return
304
+
305
+ print('User ID,Created At,Updated At,Status,Email,Name,Roles')
306
+ for u in users:
307
+ roles = [f'{x.name} of {x.resource_type} {x.resource_id}' for x in u.roles]
308
+ print(f'{u.id},{u.created_at},{u.updated_at},{u.status},{u.email},{u.name},{';'.join(roles)}')
309
+
310
+ @app.command()
311
+ def list_wallet_balances(
312
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
313
+ ):
314
+ """
315
+ List the wallet balances of all of the ad accounts
316
+ """
317
+ admin = _create_admin_command(profile)
318
+ if admin is None:
319
+ return
320
+ admin.list_wallet_balances()
260
321
 
261
322
 
262
323
  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: