mcm-cli 1.7.5__py3-none-any.whl → 1.8.0__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.
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mcm-cli
3
- Version: 1.7.5
3
+ Version: 1.8.0
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
7
7
  Author-email: mcm-help@moloco.com
8
8
  License: Apache-2.0 license
9
+ Platform: UNKNOWN
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: License :: OSI Approved :: Apache Software License
11
12
  Classifier: Operating System :: OS Independent
@@ -93,3 +94,5 @@ $
93
94
  ```
94
95
 
95
96
  © Moloco, Inc. 2023 All rights reserved. Released under Apache 2.0 License
97
+
98
+
@@ -1,14 +1,15 @@
1
1
  mcmcli/__init__.py,sha256=-U6lMZ9_99IXAKwnqnYXYr6NcO6TSmG-kxewgvJjU4k,575
2
- mcmcli/__main__.py,sha256=CmK7cg9IKPBqwf9I9XoufkwUslfZDbotL9ReHEiceaA,1844
3
- mcmcli/logging.py,sha256=xjRS5ey1ONx_d34qB1Fetb_SwPysoh2hzNDuNAaYYWQ,1739
2
+ mcmcli/__main__.py,sha256=xiXZDfSfen-divvv_HXqITkQ9oOboOs_vv0nomKddpQ,1844
3
+ mcmcli/logging.py,sha256=RmSM4xK7-e-me4Nl1rRXYysXwCVzNr5EIHxRlgCvwns,1749
4
4
  mcmcli/requests.py,sha256=IuySBQ8P_GoGF3f_TRysfgQNOhi2n9M84WK_eRXnoEU,2945
5
5
  mcmcli/command/account.py,sha256=FWXmzOLj4rVLVLEv-w0eDVlQVrkONvR1UewZbcTDgE4,24994
6
- mcmcli/command/admin.py,sha256=ynIWITIuLLUFDy2mqcKToLTsIwdNH4-k-6o-lWp1Rh0,19191
6
+ mcmcli/command/admin.py,sha256=VRph21dBWhVgaPoLnjAlBbWpIiFqcAmJ866K3fNBR6E,19663
7
7
  mcmcli/command/auth.py,sha256=Ak7ZNEskWPpMoeTJcbYlEpDBgzxn8N33Q2dNf67SsCs,2926
8
8
  mcmcli/command/campaign.py,sha256=eHE_i1lRlUuU1GeVMwXkJgBU_zOgU-b9Wo0SYC9TK7M,15444
9
9
  mcmcli/command/config.py,sha256=08C5ftAvdvpQ26Z329LqhP8AxTI629LS7Ou6glzrRgw,4396
10
- mcmcli/command/decision.py,sha256=L54-Uw-CyXELOIGMv464fxjaSFKCgqJGEKXFjqBDFIg,9285
10
+ mcmcli/command/decision.py,sha256=hBOISPFSSDRSAPcRAiLkjz-PMZHsDWgRLUjXZQInB14,9283
11
11
  mcmcli/command/report.py,sha256=N8IMyDZ5QpY11-KkZG-n5_ZzEeh-ME5s2s3wrAKEGjY,5387
12
+ mcmcli/command/userevent.py,sha256=bSXQUfcZlBdacrZat1W_hLF5nz9A2meUhZAuCnLDvhY,2906
12
13
  mcmcli/command/wallet.py,sha256=b7X4lLOoDMg2ItyyRPawtiA_p-r7dSsRcHNhCAmBjyc,11616
13
14
  mcmcli/data/account.py,sha256=pe7tPapP6vlUD5D5L5Nh5k2bkWdYOK01Mpt5fBYFnJs,1782
14
15
  mcmcli/data/account_user.py,sha256=27nQp52nMma5a3QszSJGqgq5Z0ivIb-nMZcZuhEgbEg,1328
@@ -23,10 +24,10 @@ mcmcli/data/seller.py,sha256=40SA7QekM3a3svDrDYLo_QYJ68VUxDO0KeGejJMp4k4,1004
23
24
  mcmcli/data/token.py,sha256=11wtyLHCAZHu0LVbNDPa-yipcL6lenxoYIKEI58VzFs,1744
24
25
  mcmcli/data/user_join_request.py,sha256=lXMO2hE_VpRg0JofVrYAVM82S-RLFkPrZk8-drvhoDI,1251
25
26
  mcmcli/data/wallet.py,sha256=eMUi8N0vJdg_E10TPhSPoZkZtmIG7gHyqgabQ8C5Lg8,3217
26
- mcm_cli-1.7.5.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
27
- mcm_cli-1.7.5.dist-info/METADATA,sha256=MWVvOXH-jPSep0wJluOvCXErAGS9ZrBE51aoES7dAik,3153
28
- mcm_cli-1.7.5.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
29
- mcm_cli-1.7.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
- mcm_cli-1.7.5.dist-info/entry_points.txt,sha256=qTHAWZ-ejSiU4t11RYwtAU8ScqhQPDeMVTG9y4wMVLg,60
31
- mcm_cli-1.7.5.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
32
- mcm_cli-1.7.5.dist-info/RECORD,,
27
+ mcm_cli-1.8.0.dist-info/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
28
+ mcm_cli-1.8.0.dist-info/METADATA,sha256=v3hvmmVyRvFunqyijHggZe2TW17eNnvGwhC7_wwv1dY,3173
29
+ mcm_cli-1.8.0.dist-info/NOTICE,sha256=Ldnl2MjRaXPxcldUdbI2NTybq60XAa2LowRhFrRTuiI,76
30
+ mcm_cli-1.8.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
31
+ mcm_cli-1.8.0.dist-info/entry_points.txt,sha256=AQPedI0vnP2FfE6pCSg7Zp8QKDLnkakl50bU0DNZu5w,61
32
+ mcm_cli-1.8.0.dist-info/top_level.txt,sha256=sh7oqIaqLQlMtKHlxHHgpV2xGMrBMPFWpSp0C6nvJ_Y,7
33
+ mcm_cli-1.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  mcm = mcmcli.__main__:console_entry_point
3
+
mcmcli/__main__.py CHANGED
@@ -37,7 +37,7 @@ def version():
37
37
  """
38
38
  Show the tool version
39
39
  """
40
- typer.echo(f"Version: mcm-cli v1.7.5")
40
+ typer.echo(f"Version: mcm-cli v1.8.0")
41
41
 
42
42
  app.add_typer(mcmcli.command.account.app, name="account", help="Ad account management")
43
43
  app.add_typer(mcmcli.command.admin.app, name="admin", help="Platform administration")
mcmcli/command/admin.py CHANGED
@@ -28,6 +28,7 @@ import mcmcli.command.auth
28
28
  import mcmcli.command.campaign
29
29
  import mcmcli.command.config
30
30
  import mcmcli.command.decision
31
+ import mcmcli.command.userevent
31
32
  import mcmcli.command.wallet
32
33
  import mcmcli.requests
33
34
  import random
@@ -83,8 +84,8 @@ def generate_sample_data(
83
84
  profile: str = typer.Option("default", help="Profile Name – The MCM CLI configuration profile to use."),
84
85
  ):
85
86
  """
86
- Generate sample impressions and clicks. This command invokes the Decision APIs, and posts the impression
87
- and click trackers to generate sample data in the platform.
87
+ Generate sample impressions, clicks, and purchase events. This command invokes the Decision APIs, and posts the impression
88
+ and click trackers to generate sample data in the platform. It also posts the purchase events to the User Event API.
88
89
  """
89
90
  if warn:
90
91
  typer.confirm("""⚠️ WARNING: This script is strictly for use on the TEST platform.⚠️
@@ -105,13 +106,15 @@ Please proceed only if you are certain.""", abort=True)
105
106
 
106
107
  # Initialize DecisionCommand
107
108
  d = mcmcli.command.decision.DecisionCommand(profile)
109
+ ue = mcmcli.command.userevent.UserEventCommand(profile)
108
110
 
109
111
  print(f"Invoking the Decision API {num_iterations} times to generate sample impressions and clicks...", end='', flush=True)
110
112
  thread, stopper = start_dot_printing()
111
113
 
112
114
  for i in range(num_iterations):
115
+ user_id = f"user-{random.randint(100_000, 999_999)}"
113
116
  # Call Decision API to get trackers
114
- _, error, decided_items = d.decide_items(ad_inventory_id, search_query)
117
+ _, error, decided_items = d.decide_items(ad_inventory_id, user_id, search_query)
115
118
  if error:
116
119
  print_error(f"Error calling Decision API: {error.message}")
117
120
  continue
@@ -136,6 +139,11 @@ Please proceed only if you are certain.""", abort=True)
136
139
  requests.post(click_url)
137
140
  except requests.RequestException as e:
138
141
  print(f"[{i}] Failed to post imp tracker: {click_url} - Error: {e}")
142
+
143
+ # Send Purchase user event with 10% probability
144
+ if random.random() >= 0.9:
145
+ continue
146
+ ue.insert_purchase_event(user_id, item.auction_result.ad_account_id, item.item_id, to_curl=False)
139
147
 
140
148
  print('.', end='', flush=True)
141
149
 
@@ -44,7 +44,9 @@ def decide_items(
44
44
  """
45
45
  d = DecisionCommand(profile)
46
46
 
47
- curl, error, ret = d.decide_items(inventory_id, search_query, num_items, items, location_filter, to_curl)
47
+ user_id = f"user-{random.randint(100_000, 999_999)}"
48
+
49
+ curl, error, ret = d.decide_items(inventory_id, user_id, search_query, num_items, items, location_filter, to_curl)
48
50
  if to_curl:
49
51
  print(curl)
50
52
  return
@@ -141,6 +143,7 @@ class DecisionCommand:
141
143
  def decide_items(
142
144
  self,
143
145
  inventory_id,
146
+ user_id,
144
147
  search_query = None,
145
148
  num_items = 5,
146
149
  items = False,
@@ -159,10 +162,10 @@ class DecisionCommand:
159
162
  "num_items": num_items
160
163
  },
161
164
  "user": {
162
- "user_id": f"mcmcli-user-{random.randint(100_000, 999_999)}"
165
+ "user_id": user_id
163
166
  },
164
167
  "device": {
165
- "persistent_id": f"mcmcli-device-{random.randint(100_000, 999_999)}"
168
+ "persistent_id": user_id
166
169
  },
167
170
  }
168
171
  if items:
@@ -0,0 +1,96 @@
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 datetime import datetime, timezone
15
+ from mcmcli.data.error import Error
16
+ from mcmcli.requests import CurlString, api_request
17
+ from typing import Optional
18
+
19
+ UTC = timezone.utc
20
+
21
+ import mcmcli.command.config
22
+ import sys
23
+ import typer
24
+
25
+ app = typer.Typer(add_completion=False)
26
+
27
+ class UserEventCommand:
28
+ def __init__(
29
+ self,
30
+ profile,
31
+ ):
32
+ self.config = mcmcli.command.config.get_config(profile)
33
+ if (self.config is None):
34
+ print(f"ERROR: Failed to load the CLI profile", file=sys.stderr, flush=True)
35
+ sys.exit()
36
+
37
+ self.profile = profile
38
+
39
+ self.api_base_url = f"{self.config['event_api_hostname']}/rmp/event/v1/platforms/{self.config['platform_id']}/userevents"
40
+ self.headers = {
41
+ "accept": "application/json",
42
+ "content-type": "application/json",
43
+ "x-api-key": self.config['event_api_key']
44
+ }
45
+
46
+ def insert_purchase_event(
47
+ self,
48
+ user_id: str,
49
+ account_id: str,
50
+ item_id: str,
51
+ to_curl: bool,
52
+ ) -> tuple[
53
+ Optional[CurlString],
54
+ Optional[Error],
55
+ ]:
56
+ # Get current timestamp in milliseconds
57
+ timestamp_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
58
+ _api_url = self.api_base_url
59
+ _payload = {
60
+ "id": f"event-{user_id}",
61
+ "timestamp":timestamp_ms,
62
+ "user_id": user_id,
63
+ "device": {
64
+ "persistent_id": user_id
65
+ },
66
+ "event_type": "PURCHASE",
67
+ "channel_type": "SITE",
68
+ "items": [
69
+ {
70
+ "id": item_id,
71
+ "quantity": 1,
72
+ "seller_id": account_id,
73
+ "price": {
74
+ "currency": "USD",
75
+ "amount": 100
76
+ }
77
+ }
78
+ ],
79
+ "revenue": {
80
+ "currency": "USD",
81
+ "amount": 100
82
+ },
83
+ "shipping_charge": {
84
+ "currency": "USD",
85
+ "amount": 0
86
+ },
87
+ }
88
+
89
+ curl, error, json_obj = api_request('POST', to_curl, _api_url, self.headers, _payload)
90
+ if curl:
91
+ return curl, None
92
+ if error:
93
+ return None, error
94
+
95
+ return None, None
96
+
mcmcli/logging.py CHANGED
@@ -17,7 +17,7 @@ import threading
17
17
  import time
18
18
  import typer
19
19
 
20
- def print_error(api_url, message):
20
+ def print_error(message, api_url="Unknown"):
21
21
  # Something's wrong. Print error and exit.
22
22
 
23
23
  _msg = typer.style(f"\nERROR: {message}", fg=typer.colors.RED, bold=True)