mcm-cli 0.45__tar.gz → 0.471__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. {mcm_cli-0.45/mcm_cli.egg-info → mcm_cli-0.471}/PKG-INFO +22 -8
  2. {mcm_cli-0.45 → mcm_cli-0.471}/README.md +21 -7
  3. {mcm_cli-0.45 → mcm_cli-0.471/mcm_cli.egg-info}/PKG-INFO +22 -8
  4. {mcm_cli-0.45 → mcm_cli-0.471}/mcm_cli.egg-info/SOURCES.txt +4 -0
  5. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/__main__.py +3 -3
  6. mcm_cli-0.471/mcmcli/command/account.py +461 -0
  7. mcm_cli-0.471/mcmcli/data/account.py +59 -0
  8. mcm_cli-0.471/mcmcli/data/account_user.py +50 -0
  9. mcm_cli-0.471/mcmcli/data/seller.py +38 -0
  10. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/data/wallet.py +1 -1
  11. {mcm_cli-0.45 → mcm_cli-0.471}/setup.py +1 -1
  12. {mcm_cli-0.45 → mcm_cli-0.471}/LICENSE +0 -0
  13. {mcm_cli-0.45 → mcm_cli-0.471}/NOTICE +0 -0
  14. {mcm_cli-0.45 → mcm_cli-0.471}/mcm_cli.egg-info/dependency_links.txt +0 -0
  15. {mcm_cli-0.45 → mcm_cli-0.471}/mcm_cli.egg-info/entry_points.txt +0 -0
  16. {mcm_cli-0.45 → mcm_cli-0.471}/mcm_cli.egg-info/requires.txt +0 -0
  17. {mcm_cli-0.45 → mcm_cli-0.471}/mcm_cli.egg-info/top_level.txt +0 -0
  18. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/__init__.py +0 -0
  19. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/command/auth.py +0 -0
  20. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/command/config.py +0 -0
  21. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/command/wallet.py +0 -0
  22. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/data/error.py +0 -0
  23. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/data/token.py +0 -0
  24. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/logging.py +0 -0
  25. {mcm_cli-0.45 → mcm_cli-0.471}/mcmcli/requests.py +0 -0
  26. {mcm_cli-0.45 → mcm_cli-0.471}/setup.cfg +0 -0
  27. {mcm_cli-0.45 → mcm_cli-0.471}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 0.45
3
+ Version: 0.471
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
@@ -32,30 +32,43 @@ Please contact the Moloco representative for more details.
32
32
 
33
33
  ## How to install
34
34
 
35
- Run the command from a terminal.
35
+ To install, run:
36
+ ```
37
+ pip install mcm-cli
38
+ ```
39
+
40
+ For installation from the source code, use:
36
41
  ```
37
42
  git clone https://github.com/moloco-mcm/mcm-cli.git && pip install mcm-cli
38
43
  ```
39
44
 
40
45
  ## How to upgrade
41
46
 
42
- Run the command from a terminal.
47
+ To upgrade, run:
48
+ ```
49
+ pip install --upgrade mcm-cli
50
+ ```
51
+
52
+ Or, if installed from the source code:
43
53
  ```
44
54
  git -C mcm-cli pull && pip install mcm-cli
45
55
  ```
46
56
 
47
57
  ## How to uninstall
48
58
 
49
- Run the command from a terminal.
59
+ To uninstall, run:
50
60
  ```
51
61
  pip uninstall mcm-cli
52
62
  ```
53
63
 
54
64
  ## How to use
55
- Run `$ mcm config init` to initialize the configuration. It saves the configuration to `~/.mcm/config.toml`.
56
-
57
- Use `--help` option to learn more of each command.
65
+ Initialize the configuration with:
66
+ ```
67
+ mcm config init
68
+ ```
69
+ This saves the configuration to `~/.mcm/config.toml`.
58
70
 
71
+ For more details on each command, use the `--help` option.
59
72
  ```
60
73
  $ mcm --help
61
74
 
@@ -65,9 +78,10 @@ $ mcm --help
65
78
  │ --help Show this message and exit. │
66
79
  ╰──────────────────────────────────────────────────────────────────╯
67
80
  ╭─ Commands ───────────────────────────────────────────────────────╮
81
+ │ account Ad account management │
68
82
  │ auth Authentication management │
69
83
  │ config Configurations │
70
- │ version
84
+ │ version Show the tool version
71
85
  │ wallet Wallet management │
72
86
  ╰──────────────────────────────────────────────────────────────────╯
73
87
 
@@ -6,30 +6,43 @@ Please contact the Moloco representative for more details.
6
6
 
7
7
  ## How to install
8
8
 
9
- Run the command from a terminal.
9
+ To install, run:
10
+ ```
11
+ pip install mcm-cli
12
+ ```
13
+
14
+ For installation from the source code, use:
10
15
  ```
11
16
  git clone https://github.com/moloco-mcm/mcm-cli.git && pip install mcm-cli
12
17
  ```
13
18
 
14
19
  ## How to upgrade
15
20
 
16
- Run the command from a terminal.
21
+ To upgrade, run:
22
+ ```
23
+ pip install --upgrade mcm-cli
24
+ ```
25
+
26
+ Or, if installed from the source code:
17
27
  ```
18
28
  git -C mcm-cli pull && pip install mcm-cli
19
29
  ```
20
30
 
21
31
  ## How to uninstall
22
32
 
23
- Run the command from a terminal.
33
+ To uninstall, run:
24
34
  ```
25
35
  pip uninstall mcm-cli
26
36
  ```
27
37
 
28
38
  ## How to use
29
- Run `$ mcm config init` to initialize the configuration. It saves the configuration to `~/.mcm/config.toml`.
30
-
31
- Use `--help` option to learn more of each command.
39
+ Initialize the configuration with:
40
+ ```
41
+ mcm config init
42
+ ```
43
+ This saves the configuration to `~/.mcm/config.toml`.
32
44
 
45
+ For more details on each command, use the `--help` option.
33
46
  ```
34
47
  $ mcm --help
35
48
 
@@ -39,9 +52,10 @@ $ mcm --help
39
52
  │ --help Show this message and exit. │
40
53
  ╰──────────────────────────────────────────────────────────────────╯
41
54
  ╭─ Commands ───────────────────────────────────────────────────────╮
55
+ │ account Ad account management │
42
56
  │ auth Authentication management │
43
57
  │ config Configurations │
44
- │ version
58
+ │ version Show the tool version
45
59
  │ wallet Wallet management │
46
60
  ╰──────────────────────────────────────────────────────────────────╯
47
61
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 0.45
3
+ Version: 0.471
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
@@ -32,30 +32,43 @@ Please contact the Moloco representative for more details.
32
32
 
33
33
  ## How to install
34
34
 
35
- Run the command from a terminal.
35
+ To install, run:
36
+ ```
37
+ pip install mcm-cli
38
+ ```
39
+
40
+ For installation from the source code, use:
36
41
  ```
37
42
  git clone https://github.com/moloco-mcm/mcm-cli.git && pip install mcm-cli
38
43
  ```
39
44
 
40
45
  ## How to upgrade
41
46
 
42
- Run the command from a terminal.
47
+ To upgrade, run:
48
+ ```
49
+ pip install --upgrade mcm-cli
50
+ ```
51
+
52
+ Or, if installed from the source code:
43
53
  ```
44
54
  git -C mcm-cli pull && pip install mcm-cli
45
55
  ```
46
56
 
47
57
  ## How to uninstall
48
58
 
49
- Run the command from a terminal.
59
+ To uninstall, run:
50
60
  ```
51
61
  pip uninstall mcm-cli
52
62
  ```
53
63
 
54
64
  ## How to use
55
- Run `$ mcm config init` to initialize the configuration. It saves the configuration to `~/.mcm/config.toml`.
56
-
57
- Use `--help` option to learn more of each command.
65
+ Initialize the configuration with:
66
+ ```
67
+ mcm config init
68
+ ```
69
+ This saves the configuration to `~/.mcm/config.toml`.
58
70
 
71
+ For more details on each command, use the `--help` option.
59
72
  ```
60
73
  $ mcm --help
61
74
 
@@ -65,9 +78,10 @@ $ mcm --help
65
78
  │ --help Show this message and exit. │
66
79
  ╰──────────────────────────────────────────────────────────────────╯
67
80
  ╭─ Commands ───────────────────────────────────────────────────────╮
81
+ │ account Ad account management │
68
82
  │ auth Authentication management │
69
83
  │ config Configurations │
70
- │ version
84
+ │ version Show the tool version
71
85
  │ wallet Wallet management │
72
86
  ╰──────────────────────────────────────────────────────────────────╯
73
87
 
@@ -12,10 +12,14 @@ mcmcli/__init__.py
12
12
  mcmcli/__main__.py
13
13
  mcmcli/logging.py
14
14
  mcmcli/requests.py
15
+ mcmcli/command/account.py
15
16
  mcmcli/command/auth.py
16
17
  mcmcli/command/config.py
17
18
  mcmcli/command/wallet.py
19
+ mcmcli/data/account.py
20
+ mcmcli/data/account_user.py
18
21
  mcmcli/data/error.py
22
+ mcmcli/data/seller.py
19
23
  mcmcli/data/token.py
20
24
  mcmcli/data/wallet.py
21
25
  tests/test_config.py
@@ -17,8 +17,7 @@
17
17
  """mcm cli entry point script."""
18
18
  # mcmcli/__main__.py
19
19
 
20
- from typing import Optional
21
-
20
+ import mcmcli.command.account
22
21
  import mcmcli.command.auth
23
22
  import mcmcli.command.config
24
23
  import mcmcli.command.wallet
@@ -32,8 +31,9 @@ def version():
32
31
  """
33
32
  Show the tool version
34
33
  """
35
- typer.echo(f"Version: mcm-cli v0.45")
34
+ typer.echo(f"Version: mcm-cli v0.471")
36
35
 
36
+ app.add_typer(mcmcli.command.account.app, name="account", help="Ad account management")
37
37
  app.add_typer(mcmcli.command.auth.app, name="auth", help="Authentication management")
38
38
  app.add_typer(mcmcli.command.config.app, name="config", help="Configurations")
39
39
  app.add_typer(mcmcli.command.wallet.app, name="wallet", help="Wallet management")
@@ -0,0 +1,461 @@
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 enum import Enum
16
+ from mcmcli.data.account import Account, AccountListWrapper
17
+ from mcmcli.data.account_user import User, UserWrapper, UserListWrapper
18
+ from mcmcli.data.error import Error
19
+ from mcmcli.data.seller import Seller, SellerListWrapper
20
+ from mcmcli.requests import CurlString, api_request
21
+ from typing import Any, Callable, Dict, Optional, Tuple, TypeVar
22
+
23
+ import csv
24
+ import mcmcli.command.auth
25
+ import mcmcli.command.config
26
+ import mcmcli.logging
27
+ import mcmcli.requests
28
+ import sys
29
+ import time
30
+ import typer
31
+
32
+ MAX_NUM_ITEMS_PER_PAGE = 5000
33
+ T = TypeVar('T')
34
+
35
+ class UserRole(Enum):
36
+ AD_ACCOUNT_OWNER = "AD_ACCOUNT_OWNER"
37
+
38
+ app = typer.Typer(add_completion=False)
39
+
40
+
41
+ @app.command()
42
+ def bulk_invite_ad_account_owners(
43
+ csv_file: str = typer.Option(help="CSV file path that contains the list of ad account id, user email address, and the user name."),
44
+ dry_run: bool = typer.Option(True, help="Perform a dry run without sending emails."),
45
+ skip_csv_header: bool = typer.Option(False, help="Skip the first line of the CSV file."),
46
+ create_account: bool = typer.Option(False, help="Create the ad account if it doesn't exist."),
47
+ create_user: bool = typer.Option(False, help="Create the ad account user if it doesn't exist."),
48
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
49
+ ):
50
+ """
51
+ Send campaign manager invitation emails to ad account owners. Ensure the CSV file has three columns:
52
+ ad account ID, user email address, and user name. Extra columns are ignored.
53
+ """
54
+ csv_data = _read_csv_file(csv_file, skip_csv_header)
55
+ if csv_data is None:
56
+ return
57
+
58
+ a = _create_account_command(profile)
59
+ if a is None:
60
+ return
61
+
62
+ if dry_run:
63
+ print(f"Dry run initiated.")
64
+
65
+ #
66
+ # get the list of sellers
67
+ #
68
+ _, error, seller_dictionary = a.list_sellers()
69
+ if error:
70
+ print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
71
+ return
72
+
73
+
74
+ print('Processing the command ', end='', file=sys.stderr, flush=True)
75
+ total_count = len(csv_data)
76
+ success_count = 0
77
+ account_creation_count = 0
78
+ for row in csv_data:
79
+ account_id = row[0].strip()
80
+ email_address = row[1].strip()
81
+ user_name = row[2].strip()
82
+ print('.', end='', file=sys.stderr, flush=True)
83
+
84
+ # Check if the ad account exists
85
+ if _lookup_dict(account_id, seller_dictionary) is None:
86
+ print(f"\nERROR: Could not find the ad account ID {account_id}", file=sys.stderr, flush=True)
87
+ continue
88
+
89
+ # Create the ad account if the seller doesn't have one yet and the CLI command tells to create the ad account.
90
+ seller_info = seller_dictionary[account_id]
91
+ if not seller_info.is_registered and create_account:
92
+ error, _ = a.create_account_with_retry(seller_info.id, seller_info.title, dry_run)
93
+ if error:
94
+ print(f"\nERROR: Failed to create the ad account {account_id}. {error.message}.", file=sys.stderr, flush=True)
95
+ continue
96
+ account_creation_count += 1
97
+
98
+ error = a.send_invitation_email_with_retry(account_id, email_address, user_name, create_user, dry_run)
99
+ if error:
100
+ print(f"\nERROR: Failed to send the mail for the ad account ID {account_id}. {error.message}.", file=sys.stderr, flush=True)
101
+ continue
102
+
103
+ success_count += 1
104
+
105
+ print(' Done', file=sys.stderr, flush=True)
106
+ if dry_run:
107
+ print(f"If this wasn't a dry-run, it would have sent {success_count} out of {total_count} emails and created {account_creation_count} new ad accounts.", flush=True)
108
+ else:
109
+ print(f'Sent {success_count} out of {total_count} emails, and created {account_creation_count} new ad accounts.', flush=True)
110
+ return
111
+
112
+
113
+ @app.command()
114
+ def bulk_check_user_registrations(
115
+ csv_file: str = typer.Option(help="CSV file path that contains the list of ad account id and user email address."),
116
+ skip_csv_header: bool = typer.Option(False, help="Skip the first line of the CSV file."),
117
+ profile: str = typer.Option("default", help="profile name of the MCM CLI."),
118
+ ):
119
+ """
120
+ Check user status. Ensure the CSV file has two columns: ad account ID and user email address. Extra columns are ignored.
121
+ """
122
+ csv_data = _read_csv_file(csv_file, skip_csv_header)
123
+ if csv_data is None:
124
+ return
125
+
126
+ a = _create_account_command(profile)
127
+ if a is None:
128
+ return
129
+
130
+ _, error, account_dictionary = a.list_accounts(to_curl=False)
131
+ if error:
132
+ print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
133
+ return
134
+
135
+ print('"Ad Account ID","Is Ad Account Exist","User Email","Is User Exist","User Role","User Status"')
136
+ for row in csv_data:
137
+ account_id = row[0].strip()
138
+ email_address = row[1].strip()
139
+ account = _lookup_dict(account_id, account_dictionary)
140
+ is_account_exist = "No Account Exists" if account is None else "Account Exists"
141
+
142
+ error, user_dictionary = a.list_account_users_with_retry(account_id)
143
+ if error:
144
+ print(f"\nERROR: {error.message}", file=sys.stderr, flush=True)
145
+ return
146
+
147
+ user = _lookup_dict(email_address, user_dictionary)
148
+ is_user_exist = "No User Exist" if user is None else "User Exists"
149
+ user_role = "" if user is None else user.role
150
+ user_status = "" if user is None else user.status
151
+ print(f'"{account_id}","{is_account_exist}","{email_address}","{is_user_exist}","{user_role}","{user_status}"')
152
+ return
153
+
154
+
155
+ class AccountCommand:
156
+ def __init__(
157
+ self,
158
+ profile,
159
+ auth_command: mcmcli.command.auth.AuthCommand,
160
+ token
161
+ ):
162
+ self.config = mcmcli.command.config.get_config(profile)
163
+ if (self.config is None):
164
+ print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
165
+ sys.exit()
166
+
167
+ self.profile = profile
168
+ self.auth_command = auth_command
169
+ self.api_base_url = f"{self.config['management_api_hostname']}/rmp/mgmt/v1/platforms/{self.config['platform_id']}"
170
+ self.headers = {
171
+ "accept": "application/json",
172
+ "content-type": "application/json",
173
+ "Authorization": f"Bearer {token}"
174
+ }
175
+
176
+
177
+ def refresh_token(
178
+ self,
179
+ ) -> None:
180
+ _, error, token = self.auth_command.get_token()
181
+ if error:
182
+ print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
183
+ return
184
+ self.headers["Authorization"] = f"Bearer {token.token}"
185
+
186
+
187
+ def retry_with_token_refresh(
188
+ self,
189
+ operation: Callable[[], Tuple[Optional[Error], Any]],
190
+ max_retries: int = 3,
191
+ delay: int = 1,
192
+ ) -> Tuple[
193
+ Optional[Error],
194
+ Any
195
+ ]:
196
+ retries = 0
197
+ while retries < max_retries:
198
+ error, result = operation()
199
+ if error and error.code == 16:
200
+ print(f"\nERROR: authentication token expired. Retrying...", file=sys.stderr, flush=True)
201
+ self.refresh_token()
202
+ retries += 1
203
+ time.sleep(delay)
204
+ continue
205
+ return error, result
206
+ return Error(code=16, message="Failed to regain an authentication token"), None
207
+
208
+
209
+ def send_invitation_email(
210
+ self,
211
+ account_id: str,
212
+ email_address: str,
213
+ user_name: str,
214
+ create_user: bool,
215
+ ) -> Optional[Error]:
216
+
217
+ error, user_dictionary = self.list_account_users(account_id)
218
+ if error:
219
+ return error
220
+
221
+ user = _lookup_dict(email_address, user_dictionary)
222
+ if user is None:
223
+ if not create_user:
224
+ _msg = f"\nERROR: The ad account {account_id} exists. But we could not find the user with email {email_address}"
225
+ print(_msg, file=sys.stderr, flush=True)
226
+ return Error(code=1, message = _msg)
227
+
228
+ _, error, _ = self.invite_user(account_id, email_address, user_name, to_curl=False)
229
+ return error
230
+
231
+ #
232
+ # The ad account and the user exists. We can just send the password reset email
233
+ #
234
+ _api_url = f"{self.api_base_url}/users/{user.id}/password-reset-tokens"
235
+ _, error, _ = api_request('POST', False, _api_url, self.headers)
236
+ return error
237
+
238
+ def send_invitation_email_with_retry(
239
+ self,
240
+ account_id: str,
241
+ email_address: str,
242
+ user_name: str,
243
+ create_user: bool,
244
+ dry_run: bool,
245
+ ) -> Optional[Error]:
246
+ if dry_run:
247
+ return None
248
+
249
+ error, _ = self.retry_with_token_refresh(
250
+ lambda: (
251
+ self.send_invitation_email(account_id, email_address, user_name, create_user),
252
+ None,
253
+ ),
254
+ )
255
+ return error
256
+
257
+
258
+ def invite_user(
259
+ self,
260
+ account_id,
261
+ user_email,
262
+ user_name,
263
+ role = UserRole.AD_ACCOUNT_OWNER,
264
+ to_curl = True,
265
+ ) -> tuple[
266
+ Optional[CurlString],
267
+ Optional[Error],
268
+ Optional[User]
269
+ ]:
270
+ _api_url = f"{self.api_base_url}/ad-accounts/{account_id}/users"
271
+ _payload = {
272
+ "user": {
273
+ "ad_account_id": account_id,
274
+ "email": user_email,
275
+ "name": user_name,
276
+ "role": role.value,
277
+ }
278
+ }
279
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
280
+ if curl:
281
+ return curl, None, None
282
+ if error:
283
+ return None, error, None
284
+
285
+ ret = UserWrapper(**json_obj).user
286
+ return None, None, ret
287
+
288
+ def create_account(
289
+ self,
290
+ account_id,
291
+ account_name,
292
+ to_curl
293
+ ) -> tuple[
294
+ Optional[CurlString],
295
+ Optional[Error],
296
+ Optional[Account]
297
+ ]:
298
+ _api_url = f"{self.api_base_url}/ad-accounts"
299
+ _payload = {
300
+ "ad_account": {
301
+ "id": account_id,
302
+ "title": account_name
303
+ }
304
+ }
305
+
306
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
307
+ if curl:
308
+ return curl, None, None
309
+ if error:
310
+ return None, error, None
311
+
312
+ account_info = Account(**json_obj['ad_account'])
313
+ return None, None, account_info
314
+
315
+
316
+ def create_account_with_retry(
317
+ self,
318
+ account_id: str,
319
+ account_name: str,
320
+ dry_run: bool,
321
+ ) -> tuple[
322
+ Optional[Error],
323
+ Optional[Account],
324
+ ]:
325
+ if dry_run:
326
+ #print(f"\nDRY RUN: create_account({account_id}, {account_name})")
327
+ return None, None
328
+
329
+ retries = 0
330
+ delay = 1
331
+ max_retries = 3
332
+ while retries < max_retries:
333
+ _, error, account = self.create_account(account_id, account_name, to_curl=False)
334
+ if error and error.code == 16:
335
+ print(f"\nERROR: authentication token expired. Retrying...", file=sys.stderr, flush=True)
336
+ self.refresh_token()
337
+ retries += 1
338
+ time.sleep(delay)
339
+ continue
340
+ return error, account
341
+ return Error(code=16, message="Failed to regain an authentication token"), None
342
+
343
+
344
+ def list_accounts(
345
+ self,
346
+ to_curl=False
347
+ ) -> tuple[
348
+ Optional[CurlString],
349
+ Optional[Error],
350
+ Dict[str, Account],
351
+ ]:
352
+ _api_url = f"{self.api_base_url}/ad-accounts"
353
+
354
+ curl, error, json_obj = api_request('GET', to_curl, _api_url, self.headers)
355
+ if curl:
356
+ return curl, None, {}
357
+ if error:
358
+ return None, error, {}
359
+
360
+ account_list = AccountListWrapper(**json_obj).ad_accounts
361
+
362
+ accounts = {}
363
+ for x in account_list:
364
+ accounts[x.id] = x
365
+ return None, None, accounts
366
+
367
+
368
+ def list_sellers(
369
+ self,
370
+ ) -> tuple[
371
+ Optional[CurlString],
372
+ Optional[Error],
373
+ Dict[str, Seller],
374
+ ]:
375
+ _api_url = f"{self.api_base_url}/sellers"
376
+
377
+ curl, error, json_obj = api_request('GET', False, _api_url, self.headers)
378
+ if curl:
379
+ return curl, None, {}
380
+ if error:
381
+ return None, error, {}
382
+
383
+ seller_list = SellerListWrapper(**json_obj).sellers
384
+
385
+ sellers = {}
386
+ for x in seller_list:
387
+ sellers[x.id] = x
388
+ return None, None, sellers
389
+
390
+
391
+ def list_account_users(
392
+ self,
393
+ account_id: str,
394
+ ) -> tuple [
395
+ Optional[Error],
396
+ Dict[str, User],
397
+ ]:
398
+ _api_url = f"{self.api_base_url}/ad-accounts/{account_id}/users"
399
+
400
+ _, error, json_obj = api_request('GET', False, _api_url, self.headers)
401
+ if error:
402
+ return error, {}
403
+ user_list = UserListWrapper(**json_obj).users
404
+
405
+ users = {}
406
+ for x in user_list:
407
+ users[x.email] = x
408
+
409
+ return None, users
410
+
411
+ def list_account_users_with_retry(
412
+ self,
413
+ account_id: str
414
+ ) -> tuple [
415
+ Optional[Error],
416
+ Dict[str, User],
417
+ ]:
418
+ return self.retry_with_token_refresh(
419
+ lambda: self.list_account_users(account_id),
420
+ )
421
+
422
+
423
+
424
+ #
425
+ # Helper functions
426
+ #
427
+ def _create_account_command(
428
+ profile: str
429
+ ) -> Optional[AccountCommand]:
430
+ auth = mcmcli.command.auth.AuthCommand(profile)
431
+ _, error, token = auth.get_token()
432
+ if error:
433
+ print(f"ERROR: {error.message}", file=sys.stderr, flush=True)
434
+ return None
435
+ return AccountCommand(profile, auth, token.token)
436
+
437
+
438
+ def _read_csv_file(
439
+ csv_file: str,
440
+ skip_csv_header: bool,
441
+ ) -> Optional[list[list]]:
442
+ try:
443
+ with open(csv_file, mode='r', newline='') as file:
444
+ csv_reader = csv.reader(file)
445
+ if skip_csv_header:
446
+ next(csv_reader)
447
+ return list(csv_reader)
448
+ except Exception as e:
449
+ print(f"ERROR: {e}", file=sys.stderr, flush=True)
450
+ return None
451
+
452
+
453
+ def _lookup_dict(
454
+ id: str,
455
+ dictionary: Dict[str, T],
456
+ ) -> Optional[T]:
457
+ if id in dictionary:
458
+ return dictionary[id]
459
+ return None
460
+
461
+
@@ -0,0 +1,59 @@
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
+
17
+ #
18
+ # API error response dataclasses
19
+ #
20
+ # list-accounts command's example response is as below:
21
+ #
22
+ # {
23
+ # "ad_accounts": [
24
+ # {
25
+ # "id": "0d139a1b-abcd-11ee-b5f8-12f12be0eb51",
26
+ # "title": "Smoke Distribution Inc.",
27
+ # "timezone": "America/New_York",
28
+ # "state_info": {
29
+ # "ad_account_id": "0d139a1b-abcd-11ee-b5f8-12f12be0eb51",
30
+ # "state": "ACTIVE",
31
+ # "state_case": "INIT_BY_PLATFORM",
32
+ # "state_updated_at": "2023-10-23T12:43:31.542660Z",
33
+ # "updated_at": "2023-10-23T12:43:31.542660Z"
34
+ # },
35
+ # "available_features": [],
36
+ # "created_at": "2023-10-23T12:43:31.542660Z",
37
+ # "updated_at": "2023-10-23T12:43:31.542660Z"
38
+ # }
39
+ # ]
40
+ # }
41
+
42
+ class State(BaseModel):
43
+ ad_account_id: str
44
+ state: str
45
+ state_case: str
46
+ state_updated_at: str
47
+ updated_at: str
48
+
49
+ class Account(BaseModel):
50
+ id: str
51
+ title: str
52
+ timezone: str
53
+ state_info: State
54
+ # available_features: []
55
+ created_at: str
56
+ updated_at: str
57
+
58
+ class AccountListWrapper(BaseModel):
59
+ ad_accounts: list[Account]
@@ -0,0 +1,50 @@
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
+
17
+ #
18
+ # Ad account users response example is as below:f
19
+ #
20
+ # {
21
+ # "users": [
22
+ # {
23
+ # "ad_account_id": "1234",
24
+ # "id": "CEdyiC4f1yyyySGEOGj7",
25
+ # "email": "user@example.com",
26
+ # "name": "John Doe",
27
+ # "role": "AD_ACCOUNT_OWNER",
28
+ # "status": "NOT_REGISTERED",
29
+ # "created_at": "2024-06-06T14:29:42Z",
30
+ # "updated_at": "2024-06-06T14:29:42Z"
31
+ # }
32
+ # ]
33
+ # }
34
+
35
+ class User(BaseModel):
36
+ ad_account_id: str
37
+ id: str
38
+ email: str
39
+ name: str
40
+ role: str
41
+ status: str
42
+ created_at: str
43
+ updated_at: str
44
+
45
+ class UserListWrapper(BaseModel):
46
+ users: list[User]
47
+
48
+ class UserWrapper(BaseModel):
49
+ user: User
50
+
@@ -0,0 +1,38 @@
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
+
17
+ #
18
+ # API error response dataclasses
19
+ #
20
+ # list-accounts command's example response is as below:
21
+ #
22
+ # {
23
+ # "sellers": [
24
+ # {
25
+ # "id": "4450",
26
+ # "title": "1 All Good",
27
+ # "is_registered": false
28
+ # }
29
+ # ]
30
+ # }
31
+
32
+ class Seller(BaseModel):
33
+ id: str
34
+ title: str
35
+ is_registered: bool
36
+
37
+ class SellerListWrapper(BaseModel):
38
+ sellers: list[Seller]
@@ -24,7 +24,7 @@ from pydantic import BaseModel
24
24
  # {
25
25
  # "id": "TjNwMMlONkaMqNyn",
26
26
  # "title": "",
27
- # "ad_account_id": "side_bar_1-ME",
27
+ # "ad_account_id": "1234",
28
28
  # "type": "PRE_PAYMENT",
29
29
  # "accounts": [
30
30
  # {
@@ -18,7 +18,7 @@ from setuptools import setup, find_packages
18
18
 
19
19
  setup(
20
20
  name='mcm-cli',
21
- version='0.45',
21
+ version='0.471',
22
22
  description='A command-line interface for Moloco Commerde Media',
23
23
  long_description=open('README.md').read(),
24
24
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes