mcm-cli 0.471__py3-none-any.whl → 1.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 0.471
3
+ Version: 1.0.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
@@ -81,6 +81,7 @@ $ mcm --help
81
81
  │ account Ad account management │
82
82
  │ auth Authentication management │
83
83
  │ config Configurations │
84
+ │ decision Decision command │
84
85
  │ version Show the tool version │
85
86
  │ wallet Wallet management │
86
87
  ╰──────────────────────────────────────────────────────────────────╯
@@ -1,21 +1,23 @@
1
1
  mcmcli/__init__.py,sha256=-U6lMZ9_99IXAKwnqnYXYr6NcO6TSmG-kxewgvJjU4k,575
2
- mcmcli/__main__.py,sha256=XCMJhGalbQkBsQrBBEcI_CN-3uPuMLdWYT6I8ROyITM,1357
2
+ mcmcli/__main__.py,sha256=Ky33jkUC1pB1BELZU1BQY2_7G_MfAkXaewQb1Z1Zaw0,1473
3
3
  mcmcli/logging.py,sha256=xjRS5ey1ONx_d34qB1Fetb_SwPysoh2hzNDuNAaYYWQ,1739
4
4
  mcmcli/requests.py,sha256=50N_LiWIWr8-3EPs_yR9f4uEQf8rQ68s_FoMYRhgjzI,2343
5
5
  mcmcli/command/account.py,sha256=EtcYZsC5LL5ue-hKsZZl3_0oZ09nJdVi774yqiTykl8,14712
6
6
  mcmcli/command/auth.py,sha256=QLdr_XFW5BVw9r4a7Kjj5BTBXpSux3AWI9eI03S8aiA,2480
7
7
  mcmcli/command/config.py,sha256=sdzge-l_Yi2P_TlTgSLqShcGyPCzpW3QJzctpIvc-g4,4195
8
- mcmcli/command/wallet.py,sha256=TB3WrmniREsDk0ui20p3ha6OZMwA2wAJaxQ9QDGtKnA,8968
8
+ mcmcli/command/decision.py,sha256=Zjbmt71OVU-oL8Itt9O-SvwT9Lbxw-PAgRZaIgiXi-E,8411
9
+ mcmcli/command/wallet.py,sha256=0h09ccx0lgmQUdAdaAdvjMvXCJ4qyeJXjovZMGZWcbg,8984
9
10
  mcmcli/data/account.py,sha256=pe7tPapP6vlUD5D5L5Nh5k2bkWdYOK01Mpt5fBYFnJs,1782
10
11
  mcmcli/data/account_user.py,sha256=27nQp52nMma5a3QszSJGqgq5Z0ivIb-nMZcZuhEgbEg,1328
12
+ mcmcli/data/decision.py,sha256=bQ5j_PbPRSFa0sY5g9UVqdNzl_2epchcz1lHoDVuV90,2880
11
13
  mcmcli/data/error.py,sha256=d6xa_jTXumlA0EzXy2PJQ86ajBb0Pm90fss9R3LuHUc,1094
12
14
  mcmcli/data/seller.py,sha256=40SA7QekM3a3svDrDYLo_QYJ68VUxDO0KeGejJMp4k4,1004
13
15
  mcmcli/data/token.py,sha256=11wtyLHCAZHu0LVbNDPa-yipcL6lenxoYIKEI58VzFs,1744
14
16
  mcmcli/data/wallet.py,sha256=W-CksF9SPqiv3jZg07Wy8ehVUP5Ot1Gbq2LEGNQCOC8,1906
15
- mcm_cli-0.471.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
16
- mcm_cli-0.471.dist-info/METADATA,sha256=vWG49igEdd1cL-zRp4QbUOVb02qpsEQUwQfhFdpTWfI,2945
17
- mcm_cli-0.471.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
18
- mcm_cli-0.471.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
19
- mcm_cli-0.471.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
20
- mcm_cli-0.471.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
21
- mcm_cli-0.471.dist-info/RECORD,,
17
+ mcm_cli-1.0.1.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
18
+ mcm_cli-1.0.1.dist-info/METADATA,sha256=YK3oZIzaU9UjXZ-bq1IWqEpd2-oc3p9MHNkv5Le6cTc,3018
19
+ mcm_cli-1.0.1.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
20
+ mcm_cli-1.0.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
21
+ mcm_cli-1.0.1.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
22
+ mcm_cli-1.0.1.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
23
+ mcm_cli-1.0.1.dist-info/RECORD,,
mcmcli/__main__.py CHANGED
@@ -20,6 +20,7 @@
20
20
  import mcmcli.command.account
21
21
  import mcmcli.command.auth
22
22
  import mcmcli.command.config
23
+ import mcmcli.command.decision
23
24
  import mcmcli.command.wallet
24
25
  import mcmcli.logging
25
26
  import typer
@@ -31,11 +32,12 @@ def version():
31
32
  """
32
33
  Show the tool version
33
34
  """
34
- typer.echo(f"Version: mcm-cli v0.471")
35
+ typer.echo(f"Version: mcm-cli v1.0.1")
35
36
 
36
37
  app.add_typer(mcmcli.command.account.app, name="account", help="Ad account management")
37
38
  app.add_typer(mcmcli.command.auth.app, name="auth", help="Authentication management")
38
39
  app.add_typer(mcmcli.command.config.app, name="config", help="Configurations")
40
+ app.add_typer(mcmcli.command.decision.app, name="decision", help="Decision command")
39
41
  app.add_typer(mcmcli.command.wallet.app, name="wallet", help="Wallet management")
40
42
 
41
43
  if __name__ == "__main__":
@@ -0,0 +1,265 @@
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 mcmcli.data.error import Error
16
+ from mcmcli.data.decision import DecidedCreative, DecidedCreativeBulkList, DecidedItemList
17
+ from mcmcli.requests import CurlString, api_request
18
+ from typing import Any, Callable, Dict, Optional, Tuple, TypeVar
19
+
20
+ import json
21
+ import mcmcli.command.auth
22
+ import mcmcli.command.config
23
+ import mcmcli.logging
24
+ import sys
25
+ import typer
26
+
27
+ MAX_NUM_ITEMS_PER_PAGE = 5000
28
+
29
+ app = typer.Typer(add_completion=False)
30
+
31
+ @app.command()
32
+ def decide_items(
33
+ inventory_id: str = typer.Option(help="Ad inventory ID"),
34
+ num_items: int = typer.Option(help="Number of items requested for the inventory."),
35
+ items: str = typer.Option(None, help="The main item ids of the page. For example, homepage inventories don't have any main items, and product-detail-page inventories have one main item."),
36
+ location_filter: str = typer.Option(None, help="Location filter value"),
37
+ to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
38
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
39
+ ):
40
+ """
41
+ Request item decision by auction.
42
+ """
43
+ d = DecisionCommand(profile)
44
+
45
+ curl, error, ret = d.decide_items(inventory_id, num_items, items, location_filter, to_curl)
46
+ if to_curl:
47
+ print(curl)
48
+ return
49
+ if error:
50
+ print(f"ERROR: {error.message}")
51
+ return
52
+ if ret is None:
53
+ print(f"ERROR: Unknown error")
54
+ return
55
+
56
+ print(ret.model_dump_json())
57
+ return
58
+
59
+
60
+ @app.command()
61
+ def decide_creative(
62
+ inventory_id: str = typer.Option(help="Ad inventory ID"),
63
+ items: str = typer.Option(None, help="The main item ids of the page. For example, homepage inventories don't have any main items, and product-detail-page inventories have one main item."),
64
+ location_filter: str = typer.Option(None, help="Location filter value"),
65
+ to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
66
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
67
+ ):
68
+ """
69
+ Request item decision by creative auction.
70
+ """
71
+ d = DecisionCommand(profile)
72
+
73
+ curl, error, ret = d.decide_creative(inventory_id, items, location_filter, to_curl)
74
+ if to_curl:
75
+ print(curl)
76
+ return
77
+ if error:
78
+ print(f"ERROR: {error.message}")
79
+ return
80
+ if ret is None:
81
+ print(f"ERROR: Unknown error")
82
+ return
83
+
84
+ print(ret.model_dump_json())
85
+ return
86
+
87
+
88
+ @app.command()
89
+ def decide_creative_bulk(
90
+ inventory_id_list: str = typer.Option(help="Ad inventory IDs separated by comma(,)"),
91
+ items: str = typer.Option(None, help="The main item ids of the page. For example, homepage inventories don't have any main items, and product-detail-page inventories have one main item."),
92
+ location_filter: str = typer.Option(None, help="Location filter value"),
93
+ to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
94
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
95
+ ):
96
+ """
97
+ Request item decision by creative auction for multiple inventories.
98
+ """
99
+ d = DecisionCommand(profile)
100
+
101
+ curl, error, ret = d.decide_creative_bulk(inventory_id_list, items, location_filter, to_curl)
102
+ if to_curl:
103
+ print(curl)
104
+ return
105
+ if error:
106
+ print(f"ERROR: {error.message}")
107
+ return
108
+ if ret is None:
109
+ print(f"ERROR: Unknown error")
110
+ return
111
+
112
+ print(ret.model_dump_json())
113
+ return
114
+
115
+
116
+
117
+ class DecisionCommand:
118
+ def __init__(
119
+ self,
120
+ profile,
121
+ ):
122
+ self.config = mcmcli.command.config.get_config(profile)
123
+ if (self.config is None):
124
+ print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
125
+ sys.exit()
126
+
127
+ self.profile = profile
128
+ self.api_base_url = f"{self.config['decision_api_hostname']}/rmp/decision/v1/platforms/{self.config['platform_id']}"
129
+ self.headers = {
130
+ "accept": "application/json",
131
+ "content-type": "application/json",
132
+ "x-api-key": self.config['decision_api_key']
133
+ }
134
+
135
+ def decide_items(
136
+ self,
137
+ inventory_id,
138
+ num_items,
139
+ items,
140
+ location_filter,
141
+ to_curl,
142
+ ) -> tuple[
143
+ Optional[CurlString],
144
+ Optional[Error],
145
+ Optional[DecidedItemList],
146
+ ]:
147
+ _api_url = f"{self.api_base_url}/auction"
148
+ _payload = {
149
+ "request_id": "request-1",
150
+ "inventory": {
151
+ "inventory_id": inventory_id,
152
+ "num_items": num_items
153
+ },
154
+ "user": {
155
+ "user_id": "user-1"
156
+ },
157
+ "device": {
158
+ "persistent_id": "persistent-device-1"
159
+ },
160
+ }
161
+ if items:
162
+ _payload["inventory"]["items"] = items.split(',')
163
+
164
+ if location_filter:
165
+ _payload["filtering"] = {
166
+ "location": {
167
+ "locations": location_filter.split(',')
168
+ }
169
+ }
170
+
171
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
172
+ if curl:
173
+ return curl, None, None
174
+ if error:
175
+ return None, error, None
176
+
177
+ decided_items = DecidedItemList(**json_obj)
178
+ return None, None, decided_items
179
+
180
+
181
+ def decide_creative(
182
+ self,
183
+ inventory_id,
184
+ items,
185
+ location_filter,
186
+ to_curl
187
+ ) -> tuple[
188
+ Optional[CurlString],
189
+ Optional[Error],
190
+ Optional[DecidedCreative],
191
+ ]:
192
+ _api_url = f"{self.api_base_url}/creative-auction"
193
+ _payload = {
194
+ "request_id": "request-1",
195
+ "inventory": {
196
+ "inventory_id": inventory_id
197
+ },
198
+ "user": {
199
+ "user_id": "user-1"
200
+ },
201
+ "device": {
202
+ "persistent_id": "persistent-device-1"
203
+ },
204
+ }
205
+ if items:
206
+ _payload["inventory"]["items"] = items.split(',')
207
+
208
+ if location_filter:
209
+ _payload["filtering"] = {
210
+ "location": {
211
+ "locations": location_filter.split(',')
212
+ }
213
+ }
214
+
215
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
216
+ if curl:
217
+ return curl, None, None
218
+ if error:
219
+ return None, error, None
220
+
221
+ decided_creative = DecidedCreative(**json_obj)
222
+ return None, None, decided_creative
223
+
224
+
225
+ def decide_creative_bulk(
226
+ self,
227
+ inventory_id_list,
228
+ items,
229
+ location_filter,
230
+ to_curl
231
+ ) -> tuple[
232
+ Optional[CurlString],
233
+ Optional[Error],
234
+ Optional[DecidedCreativeBulkList],
235
+ ]:
236
+ _api_url = f"{self.api_base_url}/creative-auction-bulk"
237
+ _payload = {
238
+ "request_id": "request-1",
239
+ "inventories": list(map(lambda x: { "inventory_id": x }, inventory_id_list.split(','))),
240
+ "user": {
241
+ "user_id": "user-1"
242
+ },
243
+ "device": {
244
+ "persistent_id": "persistent-device-1"
245
+ },
246
+ }
247
+ if items:
248
+ for inventory in _payload["inventories"]:
249
+ inventory["items"] = items.split(',')
250
+
251
+ if location_filter:
252
+ _payload["filtering"] = {
253
+ "location": {
254
+ "locations": location_filter.split(',')
255
+ }
256
+ }
257
+
258
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
259
+ if curl:
260
+ return curl, None, None
261
+ if error:
262
+ return None, error, None
263
+
264
+ return None, None, DecidedCreativeBulkList(**json_obj)
265
+
mcmcli/command/wallet.py CHANGED
@@ -127,7 +127,7 @@ def deposit(
127
127
  credits_amount = float(credits_amount_micro) / float(1000000)
128
128
  pre_paid_amount = float(pre_paid_amount_micro) / float(1000000)
129
129
 
130
- print(f"Deposited to the wallet. Currence balance of the ad account id {account_id} is {pre_paid_amount} for PRE_PAID and {credits_amount} for CREDITS.")
130
+ print(f"Funds have been deposited into the wallet. The current balance for ad account ID {account_id} is {pre_paid_amount} in PRE_PAID and {credits_amount} in CREDITS.")
131
131
  return
132
132
 
133
133
  @app.command()
@@ -178,7 +178,7 @@ def withdraw(
178
178
  credits_amount = float(credits_amount_micro) / float(1000000)
179
179
  pre_paid_amount = float(pre_paid_amount_micro) / float(1000000)
180
180
 
181
- print(f"Withdrew the funds from to the wallet. Currence balance of the ad account id {account_id} is {pre_paid_amount} for PRE_PAID and {credits_amount} for CREDITS.")
181
+ print(f"Funds were withdrawn out of the wallet. The current balance of ad account ID {account_id} is {pre_paid_amount} for PRE_PAID and {credits_amount} for CREDITS.")
182
182
  return
183
183
 
184
184
  class WalletCommand:
@@ -0,0 +1,104 @@
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
+ # Item decision API's response example:
22
+ #
23
+ # {
24
+ # "request_id": "request-1",
25
+ # "decided_items": [
26
+ # {
27
+ # "item_id": "1111833",
28
+ # "auction_result": {
29
+ # "ad_account_id": "2444",
30
+ # "campaign_id": "zWHhpyNbzYcy5FAy",
31
+ # "win_price": {
32
+ # "currency": "USD",
33
+ # "amount_micro": "500000000"
34
+ # },
35
+ # "campaign_text_entry": ""
36
+ # },
37
+ # "imp_trackers": [
38
+ # "https://myplatform-evt.rmp-api.moloco.com/t/i/MYPLATFORM_TEST?source=2X0op"
39
+ # ],
40
+ # "click_trackers": [
41
+ # "https://myplatform-evt.rmp-api.moloco.com/t/c/MYPLATFORM_TEST?source=2X0opp"
42
+ # ],
43
+ # "track_id": "2X0op"
44
+ # }
45
+ # ]
46
+ # }
47
+
48
+ class MicroPrice(BaseModel):
49
+ currency: str
50
+ amount_micro: str
51
+
52
+ class AuctionResult(BaseModel):
53
+ ad_account_id: str
54
+ campaign_id: str
55
+ win_price: Optional[MicroPrice]
56
+ campaign_text_entry: Optional[str]
57
+
58
+ class DecidedItem(BaseModel):
59
+ item_id: str
60
+ auction_result: Optional[AuctionResult]
61
+ imp_trackers: list[str]
62
+ click_trackers: list[str]
63
+ track_id: Optional[str]
64
+
65
+ class DecidedItemList(BaseModel):
66
+ request_id: str
67
+ decided_items: list[DecidedItem]
68
+
69
+ class CreativeBanner(BaseModel):
70
+ creative_id: str
71
+ image_url: str
72
+ imp_trackers: list[str]
73
+ click_trackers: list[str]
74
+
75
+ class CreativeItem(BaseModel):
76
+ item_id: str
77
+ imp_trackers: list[str]
78
+ click_trackers: list[str]
79
+
80
+ class LandingUrl(BaseModel):
81
+ id: str
82
+ url: str
83
+
84
+ class DecidedCreative(BaseModel):
85
+ request_id: str
86
+ auction_result: Optional[AuctionResult]
87
+ banner: Optional[CreativeBanner]
88
+ items: list[CreativeItem]
89
+ landing_url:Optional[LandingUrl]
90
+
91
+ class CreativeBannerWrapper(BaseModel):
92
+ banner: CreativeBanner
93
+
94
+ class DecidedCreativeBulk(BaseModel):
95
+ inventory_id: str
96
+ auction_result: Optional[AuctionResult]
97
+ creatives: list[CreativeBannerWrapper]
98
+ items: list[DecidedItem]
99
+ landing_url: Optional[LandingUrl]
100
+
101
+ class DecidedCreativeBulkList(BaseModel):
102
+ request_id: str
103
+ results: list[DecidedCreativeBulk]
104
+