mcm-cli 1.4.0__py3-none-any.whl → 1.4.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {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,
|