mcm-cli 1.4.0__py3-none-any.whl → 1.4.2__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.4.0.dist-info → mcm_cli-1.4.2.dist-info}/METADATA +3 -2
- mcm_cli-1.4.2.dist-info/RECORD +32 -0
- mcmcli/__main__.py +5 -1
- mcmcli/command/account.py +10 -12
- mcmcli/command/admin.py +352 -19
- mcmcli/command/auth.py +14 -0
- mcmcli/command/campaign.py +317 -0
- mcmcli/command/config.py +2 -0
- mcmcli/command/decision.py +4 -0
- mcmcli/command/report.py +150 -0
- mcmcli/command/wallet.py +37 -36
- mcmcli/data/campaign.py +86 -0
- mcmcli/data/platform_user.py +64 -0
- mcmcli/data/report.py +110 -0
- mcmcli/data/user_join_request.py +44 -0
- mcm_cli-1.4.0.dist-info/RECORD +0 -26
- {mcm_cli-1.4.0.dist-info → mcm_cli-1.4.2.dist-info}/LICENSE +0 -0
- {mcm_cli-1.4.0.dist-info → mcm_cli-1.4.2.dist-info}/NOTICE +0 -0
- {mcm_cli-1.4.0.dist-info → mcm_cli-1.4.2.dist-info}/WHEEL +0 -0
- {mcm_cli-1.4.0.dist-info → mcm_cli-1.4.2.dist-info}/entry_points.txt +0 -0
- {mcm_cli-1.4.0.dist-info → mcm_cli-1.4.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,317 @@
|
|
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
|
+
from mcmcli.command.auth import AuthCommand, AuthHeaderName, AuthHeaderValue
|
15
|
+
from mcmcli.data.campaign import Campaign, CampaignList
|
16
|
+
from mcmcli.data.error import Error
|
17
|
+
from mcmcli.data.item import Item, ItemList
|
18
|
+
from mcmcli.requests import CurlString, api_request
|
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
|
+
def _create_campaign_command(profile):
|
32
|
+
auth = AuthCommand(profile)
|
33
|
+
return CampaignCommand(profile, auth)
|
34
|
+
|
35
|
+
@app.command()
|
36
|
+
def list_campaigns(
|
37
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
38
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
39
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
40
|
+
profile: str = "default",
|
41
|
+
):
|
42
|
+
"""
|
43
|
+
List all the campaigns of an ad account.
|
44
|
+
"""
|
45
|
+
c = _create_campaign_command(profile)
|
46
|
+
if c is None:
|
47
|
+
return
|
48
|
+
curl, error, campaigns = c.list_campaigns(account_id, to_curl)
|
49
|
+
|
50
|
+
if to_curl:
|
51
|
+
print(curl)
|
52
|
+
return
|
53
|
+
if error:
|
54
|
+
print(f"ERROR: {error.message}")
|
55
|
+
return
|
56
|
+
if to_json:
|
57
|
+
json_dumps = [x.model_dump_json() for x in campaigns]
|
58
|
+
print(f"[{','.join(json_dumps)}]")
|
59
|
+
return
|
60
|
+
|
61
|
+
print("Ad Account ID, Campaign ID, Ad Type, Starts At, Ends At, Is Enabled, Is Active, Campaign Title")
|
62
|
+
for c in campaigns:
|
63
|
+
print(f"{c.ad_account_id}, {c.id}, {c.ad_type}, {c.schedule.start}, {c.schedule.end}, {c.enabling_state}, {c.state}, {c.title}")
|
64
|
+
|
65
|
+
return
|
66
|
+
|
67
|
+
@app.command()
|
68
|
+
def read_campaign(
|
69
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
70
|
+
campaign_id: str = typer.Option(help="Campaign ID"),
|
71
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
72
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
73
|
+
profile: str = "default",
|
74
|
+
):
|
75
|
+
"""
|
76
|
+
Read the campaign information
|
77
|
+
"""
|
78
|
+
command = _create_campaign_command(profile)
|
79
|
+
if command is None:
|
80
|
+
return
|
81
|
+
curl, error, c = command.read_campaign(account_id, campaign_id, to_curl)
|
82
|
+
|
83
|
+
if to_curl:
|
84
|
+
print(curl)
|
85
|
+
return
|
86
|
+
if error:
|
87
|
+
print(f"ERROR: {error.message}")
|
88
|
+
return
|
89
|
+
if to_json:
|
90
|
+
json_dumps = c.model_dump_json()
|
91
|
+
print(json_dumps)
|
92
|
+
return
|
93
|
+
|
94
|
+
print(f"Ad Account ID = {c.ad_account_id}")
|
95
|
+
print(f"Campaign ID = {c.id}")
|
96
|
+
print(f"Campaign title = {c.title}")
|
97
|
+
print(f"Ad Type = {c.ad_type}")
|
98
|
+
print(f"Campaign begins at {c.schedule.start}")
|
99
|
+
print(f"Campaign ends at {c.schedule.end}")
|
100
|
+
print(f"Budget = {int(c.budget.amount.amount_micro) / 1000000} {c.budget.amount.currency} {c.budget.period}")
|
101
|
+
print(f"Goal = {c.goal.model_dump()}")
|
102
|
+
print(f"Registered items = {c.catalog_item_ids}")
|
103
|
+
|
104
|
+
return
|
105
|
+
|
106
|
+
@app.command()
|
107
|
+
def list_campaign_items(
|
108
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
109
|
+
campaign_id: str = typer.Option(help="Campaign ID"),
|
110
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
111
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
112
|
+
profile: str = "default",
|
113
|
+
):
|
114
|
+
"""
|
115
|
+
List all the items of a given campaign.
|
116
|
+
"""
|
117
|
+
c = _create_campaign_command(profile)
|
118
|
+
if c is None:
|
119
|
+
return
|
120
|
+
|
121
|
+
|
122
|
+
curl, error, items = c.list_campaign_items(account_id, campaign_id, to_curl)
|
123
|
+
if to_curl:
|
124
|
+
print(curl)
|
125
|
+
return
|
126
|
+
if error:
|
127
|
+
print(f"ERROR: {error.message}")
|
128
|
+
return
|
129
|
+
if to_json:
|
130
|
+
json_dumps = [x.model_dump_json() for x in items]
|
131
|
+
print(f"[{','.join(json_dumps)}]")
|
132
|
+
return
|
133
|
+
|
134
|
+
print("Ad Account Id, Campaign ID, Item ID, Is Listed In Campaign, Created At, Item Title")
|
135
|
+
for i in items:
|
136
|
+
listing_status = "Listed" if i.is_active else "Not Listed"
|
137
|
+
print(f'{account_id}, {campaign_id}, {i.id}, {listing_status}, {i.created_timestamp}, "{i.title}"')
|
138
|
+
|
139
|
+
return
|
140
|
+
|
141
|
+
@app.command()
|
142
|
+
def add_items_to_campaign(
|
143
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
144
|
+
campaign_id: str = typer.Option(help="Campaign ID"),
|
145
|
+
item_ids: str = typer.Option(help="Item IDs to add separated by comma(,) like 'p123,p456"),
|
146
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
147
|
+
to_json: bool = typer.Option(False, help="Print raw output in json"),
|
148
|
+
profile: str = "default",
|
149
|
+
):
|
150
|
+
"""
|
151
|
+
Add the item IDs to the given campaign of the account
|
152
|
+
"""
|
153
|
+
c = _create_campaign_command(profile)
|
154
|
+
if c is None:
|
155
|
+
return
|
156
|
+
|
157
|
+
_, error, campaign = c.read_campaign(account_id, campaign_id)
|
158
|
+
if error:
|
159
|
+
print(f"ERROR: {error.message}")
|
160
|
+
return
|
161
|
+
|
162
|
+
#
|
163
|
+
# Add the item ID to the campaign
|
164
|
+
#
|
165
|
+
x = set(campaign.catalog_item_ids) | set(item_ids.split(','))
|
166
|
+
campaign.catalog_item_ids = list(x)
|
167
|
+
|
168
|
+
#
|
169
|
+
# Update the campaign
|
170
|
+
#
|
171
|
+
curl, error, campaign = c.update_campaign(campaign, to_curl)
|
172
|
+
if to_curl:
|
173
|
+
print(curl)
|
174
|
+
return
|
175
|
+
if error:
|
176
|
+
print(f"ERROR: {error.message}")
|
177
|
+
return
|
178
|
+
if to_json:
|
179
|
+
print(campaign.model_dump_json())
|
180
|
+
return
|
181
|
+
|
182
|
+
print(f"Added the item IDs ({item_ids}) to the campaign.")
|
183
|
+
return
|
184
|
+
|
185
|
+
class CampaignCommand:
|
186
|
+
def __init__(self, profile, auth_command):
|
187
|
+
self.config = mcmcli.command.config.get_config(profile)
|
188
|
+
if (self.config is None):
|
189
|
+
print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
|
190
|
+
sys.exit()
|
191
|
+
|
192
|
+
self.profile = profile
|
193
|
+
self.auth_command = auth_command
|
194
|
+
self.api_base_url = f"{self.config['management_api_hostname']}/rmp/mgmt/v1/platforms/{self.config['platform_id']}"
|
195
|
+
self.headers = {
|
196
|
+
"accept": "application/json",
|
197
|
+
"content-type": "application/json"
|
198
|
+
}
|
199
|
+
|
200
|
+
self.refresh_token()
|
201
|
+
|
202
|
+
|
203
|
+
def refresh_token(
|
204
|
+
self,
|
205
|
+
) -> None:
|
206
|
+
error, auth_header_name, auth_header_value = self.auth_command.get_auth_credential()
|
207
|
+
if error:
|
208
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
209
|
+
sys.exit()
|
210
|
+
|
211
|
+
self.headers[auth_header_name] = auth_header_value
|
212
|
+
|
213
|
+
|
214
|
+
def read_campaign(self, account_id, campaign_id, to_curl=False) -> tuple[CurlString, Error, Campaign]:
|
215
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/campaigns/{campaign_id}"
|
216
|
+
curl, error, json_obj = api_request('GET', to_curl, _api_url, self.headers)
|
217
|
+
if curl:
|
218
|
+
return curl, None, None
|
219
|
+
if error:
|
220
|
+
return None, error, None
|
221
|
+
|
222
|
+
campaign = Campaign(**json_obj['campaign'])
|
223
|
+
return None, None, campaign
|
224
|
+
|
225
|
+
def update_campaign(self, campaign: Campaign, to_curl=False) -> tuple[CurlString, Error, Campaign]:
|
226
|
+
if campaign is None:
|
227
|
+
return Error(code=0, message="invalid campaign info"), None
|
228
|
+
|
229
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{campaign.ad_account_id}/campaigns/{campaign.id}"
|
230
|
+
_payload = {
|
231
|
+
"campaign": campaign.model_dump()
|
232
|
+
}
|
233
|
+
curl, error, json_obj = api_request('PUT', to_curl, _api_url, self.headers, _payload)
|
234
|
+
if curl:
|
235
|
+
return curl, None, None
|
236
|
+
if error:
|
237
|
+
return None, error, None
|
238
|
+
|
239
|
+
c = Campaign(**json_obj['campaign'])
|
240
|
+
return None, None, c
|
241
|
+
|
242
|
+
def list_campaigns(self, account_id, to_curl=False) -> tuple[CurlString, Error, list[Campaign]]:
|
243
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/campaigns"
|
244
|
+
|
245
|
+
curl, error, json_obj = api_request('GET', to_curl, _api_url, self.headers)
|
246
|
+
if curl:
|
247
|
+
return curl, None, None
|
248
|
+
if error:
|
249
|
+
return None, error, None
|
250
|
+
|
251
|
+
campaigns = CampaignList(**json_obj)
|
252
|
+
return None, None, campaigns.campaigns
|
253
|
+
|
254
|
+
def list_campaign_items(self, account_id, campaign_id, to_curl=False) -> tuple[CurlString, Error, list[Item]]:
|
255
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/campaigns/{campaign_id}/items"
|
256
|
+
_payload = {
|
257
|
+
"ad_account_id": account_id,
|
258
|
+
"campaign_id": campaign_id,
|
259
|
+
"search_keyword":[],
|
260
|
+
"order_by": [{
|
261
|
+
"column": "ID",
|
262
|
+
"direction": "ASC"
|
263
|
+
}],
|
264
|
+
"filter": [
|
265
|
+
{
|
266
|
+
"column": "IS_ACTIVE",
|
267
|
+
"filter_operator": "EQ",
|
268
|
+
"value": "true",
|
269
|
+
}
|
270
|
+
],
|
271
|
+
"page_index": 1,
|
272
|
+
"page_size": MAX_NUM_ITEMS_PER_PAGE,
|
273
|
+
}
|
274
|
+
|
275
|
+
#
|
276
|
+
# Get active items
|
277
|
+
#
|
278
|
+
curl, error, active_items = self.list_campaign_items_(to_curl, _api_url, self.headers, _payload, _is_active=True)
|
279
|
+
if curl:
|
280
|
+
return curl, None, None
|
281
|
+
if error:
|
282
|
+
return None, error, None
|
283
|
+
|
284
|
+
#
|
285
|
+
# Get inactive items
|
286
|
+
#
|
287
|
+
_, error, inactive_items = self.list_campaign_items_(to_curl, _api_url, self.headers, _payload, _is_active=False)
|
288
|
+
if error:
|
289
|
+
return None, error, None
|
290
|
+
|
291
|
+
return None, None, active_items + inactive_items
|
292
|
+
|
293
|
+
|
294
|
+
def list_campaign_items_(self, to_curl, _api_url, _headers, _payload, _is_active) -> tuple[CurlString, Error, list[Item]]:
|
295
|
+
items = []
|
296
|
+
num_items = 0
|
297
|
+
page_index = 1
|
298
|
+
while True:
|
299
|
+
_payload["page_index"] = page_index
|
300
|
+
_payload["filter"][0]["value"] = "true" if _is_active else "false"
|
301
|
+
|
302
|
+
curl, error, json_obj = api_request('POST', to_curl, _api_url, _headers, _payload)
|
303
|
+
if curl:
|
304
|
+
return curl, None, None
|
305
|
+
if error:
|
306
|
+
return None, error, None
|
307
|
+
|
308
|
+
item_group = ItemList(**json_obj)
|
309
|
+
items += item_group.rows
|
310
|
+
num_items += len(item_group.rows)
|
311
|
+
|
312
|
+
if num_items >= item_group.num_counts:
|
313
|
+
break
|
314
|
+
page_index += 1
|
315
|
+
|
316
|
+
return None, None, items
|
317
|
+
|
mcmcli/command/config.py
CHANGED
@@ -88,6 +88,8 @@ def init(profile: str = "default"):
|
|
88
88
|
"decision_api_key_name": typer.prompt("Friendly name of the decision API key", default="unknown"),
|
89
89
|
"event_api_key": typer.prompt("Event API key", default="unknown"),
|
90
90
|
"event_api_key_name": typer.prompt("Friendly name of the event API key", default="unknown"),
|
91
|
+
"management_api_key": typer.prompt("Management API key", default="unknown"),
|
92
|
+
"management_api_key_name": typer.prompt("Friendly name of the management API key", default="unknown"),
|
91
93
|
}
|
92
94
|
print(f"The profile [{profile}] has been created.")
|
93
95
|
|
mcmcli/command/decision.py
CHANGED
@@ -123,6 +123,10 @@ class DecisionCommand:
|
|
123
123
|
if (self.config is None):
|
124
124
|
print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
|
125
125
|
sys.exit()
|
126
|
+
|
127
|
+
if 'decision_api_key' not in self.config or self.config['decision_api_key'] is None or self.config['decision_api_key'] == "":
|
128
|
+
print(f"ERROR: Decision API key is not set in the profile [{profile}]", file=sys.stderr, flush=True)
|
129
|
+
sys.exit()
|
126
130
|
|
127
131
|
self.profile = profile
|
128
132
|
self.api_base_url = f"{self.config['decision_api_hostname']}/rmp/decision/v1/platforms/{self.config['platform_id']}"
|
mcmcli/command/report.py
ADDED
@@ -0,0 +1,150 @@
|
|
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
|
+
from mcmcli.command.auth import AuthCommand, AuthHeaderName, AuthHeaderValue
|
15
|
+
from mcmcli.data.error import Error
|
16
|
+
from mcmcli.data.report import Report, ReportList
|
17
|
+
from mcmcli.requests import CurlString, api_request
|
18
|
+
|
19
|
+
import json
|
20
|
+
import mcmcli.command.auth
|
21
|
+
import mcmcli.command.config
|
22
|
+
import mcmcli.logging
|
23
|
+
import mcmcli.requests
|
24
|
+
import shortuuid
|
25
|
+
import sys
|
26
|
+
import typer
|
27
|
+
|
28
|
+
app = typer.Typer(add_completion=False)
|
29
|
+
|
30
|
+
def _create_report_command(profile):
|
31
|
+
auth = AuthCommand(profile)
|
32
|
+
return ReportCommand(profile, auth)
|
33
|
+
|
34
|
+
@app.command()
|
35
|
+
def platform_summary(
|
36
|
+
start_date: str = typer.Option(help="Start date of the report window (YYYY-MM-DD)."),
|
37
|
+
end_date: str = typer.Option(help="End date of the report window (YYYY-MM-DD)."),
|
38
|
+
group_by: str = typer.Option(False, help="Group it by DATE, AD_ACCOUNT, or CAMPAIGN"),
|
39
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
40
|
+
profile: str = "default",
|
41
|
+
):
|
42
|
+
"""
|
43
|
+
Retrive the Platform-wide summary report.
|
44
|
+
"""
|
45
|
+
c = _create_report_command(profile)
|
46
|
+
if c is None:
|
47
|
+
return
|
48
|
+
|
49
|
+
curl, error, ReportList = c.report_platform_summary(start_date, end_date, group_by, to_curl)
|
50
|
+
if error:
|
51
|
+
print(f"ERROR: {error.message}")
|
52
|
+
return
|
53
|
+
if to_curl:
|
54
|
+
print(curl)
|
55
|
+
return
|
56
|
+
|
57
|
+
print(ReportList.model_dump_json(indent=4))
|
58
|
+
return
|
59
|
+
|
60
|
+
@app.command()
|
61
|
+
def account_summary(
|
62
|
+
account_id: str = typer.Option(help="Ad account ID"),
|
63
|
+
start_date: str = typer.Option(help="Start date of the report window."),
|
64
|
+
end_date: str = typer.Option(help="End date of the report window."),
|
65
|
+
group_by: str = typer.Option(False, help="Group it by DATE, AD_ACCOUNT, or CAMPAIGN"),
|
66
|
+
to_curl: bool = typer.Option(False, help="Generate the curl command instead of executing it."),
|
67
|
+
profile: str = "default",
|
68
|
+
):
|
69
|
+
"""
|
70
|
+
Retrive the ad account summary report.
|
71
|
+
"""
|
72
|
+
c = _create_report_command(profile)
|
73
|
+
if c is None:
|
74
|
+
return
|
75
|
+
|
76
|
+
curl, error, ReportList = c.report_account_summary(account_id, start_date, end_date, group_by, to_curl)
|
77
|
+
if to_curl:
|
78
|
+
print(curl)
|
79
|
+
return
|
80
|
+
if error:
|
81
|
+
print(f"ERROR: {error.message}")
|
82
|
+
return
|
83
|
+
|
84
|
+
print(ReportList.model_dump_json(indent=4))
|
85
|
+
return
|
86
|
+
|
87
|
+
class ReportCommand:
|
88
|
+
def __init__(self, profile, auth_command):
|
89
|
+
self.config = mcmcli.command.config.get_config(profile)
|
90
|
+
if (self.config is None):
|
91
|
+
print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
|
92
|
+
sys.exit()
|
93
|
+
|
94
|
+
self.profile = profile
|
95
|
+
self.auth_command = auth_command
|
96
|
+
self.api_base_url = f"{self.config['management_api_hostname']}/rmp/mgmt/v1/platforms/{self.config['platform_id']}"
|
97
|
+
self.headers = {
|
98
|
+
"accept": "application/json",
|
99
|
+
"content-type": "application/json",
|
100
|
+
}
|
101
|
+
|
102
|
+
self.refresh_token()
|
103
|
+
|
104
|
+
def refresh_token(
|
105
|
+
self,
|
106
|
+
) -> None:
|
107
|
+
error, auth_header_name, auth_header_value = self.auth_command.get_auth_credential()
|
108
|
+
if error:
|
109
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
110
|
+
sys.exit()
|
111
|
+
|
112
|
+
self.headers[auth_header_name] = auth_header_value
|
113
|
+
|
114
|
+
|
115
|
+
def report_platform_summary(self, start_date, end_date, group_by, to_curl) -> tuple[CurlString, Error, ReportList]:
|
116
|
+
_api_url = f"{self.api_base_url}/report"
|
117
|
+
return self._report_summary(_api_url, start_date, end_date, group_by, to_curl)
|
118
|
+
|
119
|
+
def report_account_summary(self, account_id, start_date, end_date, group_by, to_curl) -> tuple[CurlString, Error, ReportList]:
|
120
|
+
_api_url = f"{self.api_base_url}/ad-accounts/{account_id}/report"
|
121
|
+
return self._report_summary(_api_url, start_date, end_date, group_by, to_curl)
|
122
|
+
|
123
|
+
def _report_summary(self, url, start_date, end_date, group_by, to_curl) -> tuple[CurlString, Error, ReportList]:
|
124
|
+
if group_by and group_by != 'DATE' and group_by != 'AD_ACCOUNT' and group_by != 'CAMPAIGN':
|
125
|
+
error = Error(code=0, message="Invalid --group-by value. It should be 'DATE', 'AD_ACCOUNT', or 'CAMPAIGN'")
|
126
|
+
return None, error, None
|
127
|
+
|
128
|
+
_payload = {
|
129
|
+
"timezone": self.config['timezone'],
|
130
|
+
"date_start": start_date,
|
131
|
+
"date_end": end_date
|
132
|
+
}
|
133
|
+
if group_by:
|
134
|
+
_payload['group_by'] = [
|
135
|
+
group_by
|
136
|
+
]
|
137
|
+
|
138
|
+
curl, error, json_obj = api_request('POST', to_curl, url, self.headers, _payload)
|
139
|
+
if curl:
|
140
|
+
return curl, None, None
|
141
|
+
if error:
|
142
|
+
return None, error, None
|
143
|
+
|
144
|
+
report_list = ReportList(**json_obj)
|
145
|
+
if not report_list.rows:
|
146
|
+
return None, Error(code=0, message="No reports generated"), None
|
147
|
+
|
148
|
+
return None, None, report_list
|
149
|
+
|
150
|
+
|
mcmcli/command/wallet.py
CHANGED
@@ -11,8 +11,8 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
|
15
|
-
from datetime import datetime
|
14
|
+
from mcmcli.command.auth import AuthCommand, AuthHeaderName, AuthHeaderValue
|
15
|
+
from datetime import datetime, UTC
|
16
16
|
from enum import Enum
|
17
17
|
from mcmcli.data.error import Error
|
18
18
|
from mcmcli.data.wallet import Wallet, WalletsWrapper, PlatformWalletsWrapper
|
@@ -48,6 +48,9 @@ def _get_wallet_balance(wallet: Wallet):
|
|
48
48
|
|
49
49
|
return (credit_balance, prepaid_balance)
|
50
50
|
|
51
|
+
def _create_wallet_command(profile):
|
52
|
+
auth = AuthCommand(profile)
|
53
|
+
return WalletCommand(profile, auth)
|
51
54
|
|
52
55
|
@app.command()
|
53
56
|
def platform_balance(
|
@@ -57,17 +60,11 @@ def platform_balance(
|
|
57
60
|
"""
|
58
61
|
Get the current balances of all ad accounts in CSV format.
|
59
62
|
"""
|
60
|
-
|
61
|
-
|
62
|
-
if error:
|
63
|
-
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
63
|
+
c = _create_wallet_command(profile)
|
64
|
+
if c is None:
|
64
65
|
return
|
65
66
|
|
66
|
-
|
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)
|
67
|
+
curl, error, platform_wallets = c.get_platform_balance(to_curl)
|
71
68
|
if to_curl:
|
72
69
|
print(curl)
|
73
70
|
return
|
@@ -78,6 +75,9 @@ def platform_balance(
|
|
78
75
|
print(f"ERROR: Cannot find the wallets", file=sys.stderr, flush=True)
|
79
76
|
return
|
80
77
|
|
78
|
+
# Get current UTC timestamp and format it as an ISO 8601 string with microseconds
|
79
|
+
current_timestamp = datetime.now(UTC).isoformat(timespec='microseconds') + "Z"
|
80
|
+
|
81
81
|
print("ad_account_id,credit_balance,prepaid_balance,got_balance_info_at_utc")
|
82
82
|
for account_id, account_data in platform_wallets.items():
|
83
83
|
wallet = account_data.wallets[0]
|
@@ -95,14 +95,11 @@ def balance(
|
|
95
95
|
"""
|
96
96
|
Retrive the current balance of the given ad account's wallet.
|
97
97
|
"""
|
98
|
-
|
99
|
-
|
100
|
-
if error:
|
101
|
-
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
98
|
+
c = _create_wallet_command(profile)
|
99
|
+
if c is None:
|
102
100
|
return
|
103
101
|
|
104
|
-
|
105
|
-
curl, error, wallet = wc.get_balance(account_id, to_curl)
|
102
|
+
curl, error, wallet = c.get_balance(account_id, to_curl)
|
106
103
|
if to_curl:
|
107
104
|
print(curl)
|
108
105
|
return
|
@@ -136,16 +133,12 @@ def deposit(
|
|
136
133
|
"""
|
137
134
|
Add or top up the money amount to the current balance of the given ad account's wallet.
|
138
135
|
"""
|
139
|
-
|
140
|
-
|
141
|
-
if error:
|
142
|
-
print(f"ERROR: {error.message}")
|
136
|
+
c = _create_wallet_command(profile)
|
137
|
+
if c is None:
|
143
138
|
return
|
144
139
|
|
145
|
-
wc = WalletCommand(profile, auth, token.token)
|
146
|
-
|
147
140
|
# Check the wallet first
|
148
|
-
curl, error, wallet =
|
141
|
+
curl, error, wallet = c.get_balance(account_id, to_curl=False)
|
149
142
|
if curl:
|
150
143
|
print(curl)
|
151
144
|
return
|
@@ -154,7 +147,7 @@ def deposit(
|
|
154
147
|
return
|
155
148
|
|
156
149
|
# Deposit funds
|
157
|
-
curl, error, wallet =
|
150
|
+
curl, error, wallet = c.update_balance(OperationType.DEPOSIT, account_id, wallet.id, fund_type, fund_amount, to_curl)
|
158
151
|
if curl:
|
159
152
|
print(curl)
|
160
153
|
return
|
@@ -187,16 +180,12 @@ def withdraw(
|
|
187
180
|
"""
|
188
181
|
Withdraws the money amount from the current balance of the given ad account's wallet.
|
189
182
|
"""
|
190
|
-
|
191
|
-
|
192
|
-
if error:
|
193
|
-
print(f"ERROR: {error.message}")
|
183
|
+
c = _create_wallet_command(profile)
|
184
|
+
if c is None:
|
194
185
|
return
|
195
186
|
|
196
|
-
wc = WalletCommand(profile, auth, token.token)
|
197
|
-
|
198
187
|
# Check the wallet first
|
199
|
-
curl, error, wallet =
|
188
|
+
curl, error, wallet = c.get_balance(account_id, to_curl=False)
|
200
189
|
if to_curl:
|
201
190
|
print(curl)
|
202
191
|
return
|
@@ -205,7 +194,7 @@ def withdraw(
|
|
205
194
|
return
|
206
195
|
|
207
196
|
# Withdraw funds
|
208
|
-
curl, error, wallet =
|
197
|
+
curl, error, wallet = c.update_balance(OperationType.WITHDRAW, account_id, wallet.id, fund_type, fund_amount, to_curl)
|
209
198
|
if to_curl:
|
210
199
|
print(curl)
|
211
200
|
return
|
@@ -230,8 +219,7 @@ class WalletCommand:
|
|
230
219
|
def __init__(
|
231
220
|
self,
|
232
221
|
profile,
|
233
|
-
auth_command:
|
234
|
-
token
|
222
|
+
auth_command: AuthCommand,
|
235
223
|
):
|
236
224
|
self.config = mcmcli.command.config.get_config(profile)
|
237
225
|
if (self.config is None):
|
@@ -244,9 +232,22 @@ class WalletCommand:
|
|
244
232
|
self.headers = {
|
245
233
|
"accept": "application/json",
|
246
234
|
"content-type": "application/json",
|
247
|
-
"Authorization": f"Bearer {token}"
|
248
235
|
}
|
249
236
|
|
237
|
+
self.refresh_token()
|
238
|
+
|
239
|
+
|
240
|
+
def refresh_token(
|
241
|
+
self,
|
242
|
+
) -> None:
|
243
|
+
error, auth_header_name, auth_header_value = self.auth_command.get_auth_credential()
|
244
|
+
if error:
|
245
|
+
print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
|
246
|
+
sys.exit()
|
247
|
+
|
248
|
+
self.headers[auth_header_name] = auth_header_value
|
249
|
+
|
250
|
+
|
250
251
|
def get_platform_balance(
|
251
252
|
self,
|
252
253
|
to_curl: bool,
|