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.
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/METADATA +1 -1
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/RECORD +10 -10
- mcmcli/__main__.py +1 -1
- mcmcli/command/admin.py +143 -68
- mcmcli/command/decision.py +17 -16
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/LICENSE +0 -0
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/NOTICE +0 -0
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/WHEEL +0 -0
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/entry_points.txt +0 -0
- {mcm_cli-1.6.1.dist-info → mcm_cli-1.7.1.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
|
|
1
1
|
mcmcli/__init__.py,sha256=-U6lMZ9_99IXAKwnqnYXYr6NcO6TSmG-kxewgvJjU4k,575
|
2
|
-
mcmcli/__main__.py,sha256=
|
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=
|
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=
|
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.
|
27
|
-
mcm_cli-1.
|
28
|
-
mcm_cli-1.
|
29
|
-
mcm_cli-1.
|
30
|
-
mcm_cli-1.
|
31
|
-
mcm_cli-1.
|
32
|
-
mcm_cli-1.
|
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.
|
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
|
58
|
-
|
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
|
-
|
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
|
-
|
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
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
if
|
99
|
-
print(
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
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
|
-
|
297
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
298
|
+
profile: str = "default",
|
238
299
|
):
|
239
300
|
"""
|
240
|
-
|
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,
|
251
|
-
if
|
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(
|
312
|
+
print(error, file=sys.stderr, flush=True)
|
256
313
|
return
|
257
|
-
|
258
|
-
|
259
|
-
|
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:
|
mcmcli/command/decision.py
CHANGED
@@ -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-
|
160
|
+
"user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
|
160
161
|
},
|
161
162
|
"device": {
|
162
|
-
"persistent_id": "
|
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-
|
204
|
+
"user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
|
204
205
|
},
|
205
206
|
"device": {
|
206
|
-
"persistent_id": "
|
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-
|
246
|
+
"user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
|
246
247
|
},
|
247
248
|
"device": {
|
248
|
-
"persistent_id": "
|
249
|
+
"persistent_id": f"mcmcli-device-{random.randint(100_000, 999_999)}"
|
249
250
|
},
|
250
251
|
}
|
251
252
|
if items:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|