amazon-ads-cli 0.1.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.
- amazon_ads_cli/__init__.py +0 -0
- amazon_ads_cli/main.py +179 -0
- amazon_ads_cli-0.1.0.dist-info/METADATA +26 -0
- amazon_ads_cli-0.1.0.dist-info/RECORD +7 -0
- amazon_ads_cli-0.1.0.dist-info/WHEEL +5 -0
- amazon_ads_cli-0.1.0.dist-info/entry_points.txt +2 -0
- amazon_ads_cli-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
amazon_ads_cli/main.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Amazon Ads CLI - Command line interface for Amazon Advertising API v3."""
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from ad_api.api import sponsored_products, reports
|
|
8
|
+
from ad_api.base import Marketplaces
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
@click.option('--profile', '-p', default='default', help='Credential profile')
|
|
13
|
+
@click.pass_context
|
|
14
|
+
def cli(ctx, profile):
|
|
15
|
+
"""Amazon Ads CLI - Manage campaigns, keywords, and reports."""
|
|
16
|
+
ctx.ensure_object(dict)
|
|
17
|
+
ctx.obj['profile'] = profile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cli.group()
|
|
21
|
+
def campaigns():
|
|
22
|
+
"""Campaign management commands."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@campaigns.command('list')
|
|
27
|
+
@click.pass_context
|
|
28
|
+
def list_campaigns(ctx):
|
|
29
|
+
"""List all campaigns."""
|
|
30
|
+
result = sponsored_products.CampaignsV3(marketplace=Marketplaces.NA).list_campaigns(body={})
|
|
31
|
+
campaigns = result.payload.get('campaigns', [])
|
|
32
|
+
|
|
33
|
+
click.echo(f"\n{'Campaign':<30} {'State':<10} {'Budget':<10} {'Type'}")
|
|
34
|
+
click.echo("-" * 65)
|
|
35
|
+
for camp in campaigns:
|
|
36
|
+
name = camp['name'][:28]
|
|
37
|
+
state = camp['state']
|
|
38
|
+
budget = f"${camp['budget']['budget']}"
|
|
39
|
+
ctype = camp.get('targetingType', 'N/A')
|
|
40
|
+
click.echo(f"{name:<30} {state:<10} {budget:<10} {ctype}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@campaigns.command('pause')
|
|
44
|
+
@click.argument('campaign-id')
|
|
45
|
+
@click.pass_context
|
|
46
|
+
def pause_campaign(ctx, campaign_id):
|
|
47
|
+
"""Pause a campaign."""
|
|
48
|
+
try:
|
|
49
|
+
result = sponsored_products.CampaignsV3(marketplace=Marketplaces.NA).edit_campaigns(
|
|
50
|
+
body={"campaigns": [{"campaignId": campaign_id, "state": "PAUSED"}]}
|
|
51
|
+
)
|
|
52
|
+
click.echo(f"✅ Campaign {campaign_id} paused")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
click.echo(f"❌ Error: {e}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@campaigns.command('enable')
|
|
58
|
+
@click.argument('campaign-id')
|
|
59
|
+
@click.pass_context
|
|
60
|
+
def enable_campaign(ctx, campaign_id):
|
|
61
|
+
"""Enable a campaign."""
|
|
62
|
+
try:
|
|
63
|
+
result = sponsored_products.CampaignsV3(marketplace=Marketplaces.NA).edit_campaigns(
|
|
64
|
+
body={"campaigns": [{"campaignId": campaign_id, "state": "ENABLED"}]}
|
|
65
|
+
)
|
|
66
|
+
click.echo(f"✅ Campaign {campaign_id} enabled")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
click.echo(f"❌ Error: {e}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@cli.group()
|
|
72
|
+
def keywords():
|
|
73
|
+
"""Keyword management commands."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@keywords.command('list')
|
|
78
|
+
@click.argument('campaign-id')
|
|
79
|
+
@click.pass_context
|
|
80
|
+
def list_keywords(ctx, campaign_id):
|
|
81
|
+
"""List keywords for a campaign."""
|
|
82
|
+
result = sponsored_products.KeywordsV3(marketplace=Marketplaces.NA).list_keywords(body={})
|
|
83
|
+
keywords = [k for k in result.payload.get('keywords', []) if k.get('campaignId') == campaign_id]
|
|
84
|
+
|
|
85
|
+
click.echo(f"\n{'Keyword':<35} {'Match':<10} {'Bid':<8} {'State'}")
|
|
86
|
+
click.echo("-" * 70)
|
|
87
|
+
for kw in keywords:
|
|
88
|
+
text = kw['keywordText'][:33]
|
|
89
|
+
match = kw['matchType']
|
|
90
|
+
bid = f"${kw['bid']}"
|
|
91
|
+
state = kw['state']
|
|
92
|
+
click.echo(f"{text:<35} {match:<10} {bid:<8} {state}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@cli.group()
|
|
96
|
+
def report():
|
|
97
|
+
"""Report commands."""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@report.command('today')
|
|
102
|
+
@click.pass_context
|
|
103
|
+
def report_today(ctx):
|
|
104
|
+
"""Get today's performance report."""
|
|
105
|
+
today = datetime.now().strftime('%Y-%m-%d')
|
|
106
|
+
|
|
107
|
+
click.echo(f"Requesting report for {today}...")
|
|
108
|
+
|
|
109
|
+
report_body = {
|
|
110
|
+
"name": f"SP_Today_{today}",
|
|
111
|
+
"startDate": today,
|
|
112
|
+
"endDate": today,
|
|
113
|
+
"configuration": {
|
|
114
|
+
"adProduct": "SPONSORED_PRODUCTS",
|
|
115
|
+
"columns": [
|
|
116
|
+
"impressions", "clicks", "cost",
|
|
117
|
+
"purchases14d", "sales14d",
|
|
118
|
+
"campaignName", "campaignId"
|
|
119
|
+
],
|
|
120
|
+
"reportTypeId": "spCampaigns",
|
|
121
|
+
"format": "GZIP_JSON",
|
|
122
|
+
"groupBy": ["campaign"],
|
|
123
|
+
"timeUnit": "SUMMARY"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# Submit report
|
|
129
|
+
result = reports.Reports(marketplace=Marketplaces.NA).post_report(body=report_body)
|
|
130
|
+
report_id = result.payload['reportId']
|
|
131
|
+
|
|
132
|
+
click.echo(f"Report submitted: {report_id}")
|
|
133
|
+
click.echo("Polling for completion...")
|
|
134
|
+
|
|
135
|
+
# Poll
|
|
136
|
+
import time
|
|
137
|
+
for i in range(20):
|
|
138
|
+
result = reports.Reports(marketplace=Marketplaces.NA).get_report(reportId=report_id)
|
|
139
|
+
status = result.payload.get('status')
|
|
140
|
+
|
|
141
|
+
if status == 'COMPLETED':
|
|
142
|
+
# Download
|
|
143
|
+
import requests
|
|
144
|
+
import gzip
|
|
145
|
+
|
|
146
|
+
url = result.payload.get('url')
|
|
147
|
+
response = requests.get(url)
|
|
148
|
+
data = gzip.decompress(response.content)
|
|
149
|
+
report_data = json.loads(data)
|
|
150
|
+
|
|
151
|
+
click.echo(f"\n{'Campaign':<30} {'Impr':>8} {'Clicks':>7} {'Spend':>8} {'Sales':>8} {'ACOS'}")
|
|
152
|
+
click.echo("-" * 75)
|
|
153
|
+
|
|
154
|
+
for row in report_data:
|
|
155
|
+
camp_name = row.get('campaignName', 'N/A')[:28]
|
|
156
|
+
impr = int(row.get('impressions', 0) or 0)
|
|
157
|
+
clicks = int(row.get('clicks', 0) or 0)
|
|
158
|
+
cost = float(row.get('cost', 0) or 0)
|
|
159
|
+
sales = float(row.get('sales14d', 0) or 0)
|
|
160
|
+
acos = (cost / sales * 100) if sales > 0 else 0
|
|
161
|
+
|
|
162
|
+
click.echo(f"{camp_name:<30} {impr:>8} {clicks:>7} ${cost:>7.2f} ${sales:>7.2f} {acos:>5.1f}%")
|
|
163
|
+
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
elif status == 'FAILED':
|
|
167
|
+
click.echo(f"❌ Report failed: {result.payload.get('failureReason')}")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
time.sleep(3)
|
|
171
|
+
|
|
172
|
+
click.echo("⏳ Report still processing...")
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
click.echo(f"❌ Error: {e}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == '__main__':
|
|
179
|
+
cli()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: amazon-ads-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for Amazon Advertising API v3
|
|
5
|
+
Home-page: https://github.com/stellaraether/amazon-ads-cli
|
|
6
|
+
Author: Lunan Li
|
|
7
|
+
Author-email: lunan@stellaraether.com
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Requires-Dist: click>=8.0
|
|
18
|
+
Requires-Dist: python-amazon-ad-api>=0.8.0
|
|
19
|
+
Requires-Dist: requests>=2.27.0
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
amazon_ads_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
amazon_ads_cli/main.py,sha256=GdFvKFk-YED1aEpR62CaxAPE23_TFdQFOvAdXp8HO2c,5721
|
|
3
|
+
amazon_ads_cli-0.1.0.dist-info/METADATA,sha256=jhg00whQ_oDToRRPsBQGkKho1CtCu8ornaUQP0azHM8,875
|
|
4
|
+
amazon_ads_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
amazon_ads_cli-0.1.0.dist-info/entry_points.txt,sha256=P38Vgnekn6df-4yzQA8Orq2lh-PI3vsrzjlgmUOfijE,52
|
|
6
|
+
amazon_ads_cli-0.1.0.dist-info/top_level.txt,sha256=GRjjCDi-AmBdH1rQI6sZocWzPi9pMEZx8V05VXiBNcw,15
|
|
7
|
+
amazon_ads_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
amazon_ads_cli
|