amazon-sp-cli 0.1.2__tar.gz → 0.1.3__tar.gz

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.
Files changed (22) hide show
  1. {amazon_sp_cli-0.1.2/amazon_sp_cli.egg-info → amazon_sp_cli-0.1.3}/PKG-INFO +1 -1
  2. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli/main.py +49 -84
  3. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3/amazon_sp_cli.egg-info}/PKG-INFO +1 -1
  4. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli.egg-info/SOURCES.txt +1 -1
  5. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/setup.py +1 -1
  6. amazon_sp_cli-0.1.2/tests/test_coupon.py → amazon_sp_cli-0.1.3/tests/test_sale_price.py +23 -28
  7. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/LICENSE +0 -0
  8. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/MANIFEST.in +0 -0
  9. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/README.md +0 -0
  10. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli/__init__.py +0 -0
  11. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli/__main__.py +0 -0
  12. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli/auth.py +0 -0
  13. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli/client.py +0 -0
  14. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli.egg-info/dependency_links.txt +0 -0
  15. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli.egg-info/entry_points.txt +0 -0
  16. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli.egg-info/requires.txt +0 -0
  17. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/amazon_sp_cli.egg-info/top_level.txt +0 -0
  18. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/pyproject.toml +0 -0
  19. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/setup.cfg +0 -0
  20. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/tests/__init__.py +0 -0
  21. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/tests/test_auth.py +0 -0
  22. {amazon_sp_cli-0.1.2 → amazon_sp_cli-0.1.3}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-sp-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: CLI tool for Amazon Selling Partner API (SP-API) operations
5
5
  Home-page: https://github.com/stellaraether/amazon-sp-cli
6
6
  Author: Lunan Li
@@ -189,43 +189,33 @@ def create_discount(ctx, sku, percent, all_variations):
189
189
  @click.argument("discount", type=float)
190
190
  @click.option(
191
191
  "--type",
192
- "coupon_type",
192
+ "discount_type",
193
193
  type=click.Choice(["percentage", "fixed"]),
194
194
  default="percentage",
195
- help="Coupon type: percentage or fixed amount off",
195
+ help="Discount type: percentage or fixed amount off",
196
196
  )
197
- @click.option("--min-purchase", type=float, default=0, help="Minimum purchase amount required")
198
197
  @click.option("--start-date", help="Start date (YYYY-MM-DD). Defaults to today")
199
198
  @click.option("--end-date", help="End date (YYYY-MM-DD). Defaults to 30 days from start")
200
- @click.option("--budget", type=float, default=500, help="Total coupon budget in USD (default: 500)")
201
- @click.option("--customer-budget", type=float, help="Maximum discount per customer (defaults to full discount)")
202
- @click.option("--prime-only", is_flag=True, help="Prime members only")
203
- @click.option("--clip-coupon", is_flag=True, default=True, help="Require customers to clip coupon (default: True)")
204
- @click.option("--output", "-o", type=click.File("w"), help="Save coupon data to file")
199
+ @click.option("--output", "-o", type=click.File("w"), help="Save price adjustment data to file")
205
200
  @click.pass_context
206
- def create_coupon(
201
+ def sale_price(
207
202
  ctx,
208
203
  sku,
209
204
  discount,
210
- coupon_type,
211
- min_purchase,
205
+ discount_type,
212
206
  start_date,
213
207
  end_date,
214
- budget,
215
- customer_budget,
216
- prime_only,
217
- clip_coupon,
218
208
  output,
219
209
  ):
220
- """Create a coupon for a SKU.
210
+ """Generate sale price data for a SKU.
221
211
 
222
- Since SP-API doesn't support direct coupon creation, this generates
223
- the coupon specification for use in Seller Central.
212
+ SP-API does not support direct sale price creation. This generates
213
+ the feed data you can submit via the Feeds API or use in Seller Central.
224
214
 
225
215
  Examples:
226
- amz-sp create-coupon PAW2603190101 20
227
- amz-sp create-coupon PAW2603190101 5 --type fixed --budget 1000
228
- amz-sp create-coupon PAW2603190101 15 --prime-only --start-date 2026-05-01
216
+ amz-sp sale-price PAW2603190101 20
217
+ amz-sp sale-price PAW2603190101 5 --type fixed
218
+ amz-sp sale-price PAW2603190101 15 --start-date 2026-05-01 --end-date 2026-05-31
229
219
  """
230
220
  client = ctx.obj["client"]
231
221
 
@@ -251,94 +241,69 @@ def create_coupon(
251
241
  end_str = end.strftime("%Y-%m-%dT%H:%M:%SZ")
252
242
 
253
243
  # Calculate discount values
254
- if coupon_type == "percentage":
244
+ if discount_type == "percentage":
255
245
  discount_amount = round(current_price * discount / 100, 2)
256
246
  discount_display = f"{discount}%"
257
247
  else:
258
248
  discount_amount = discount
259
249
  discount_display = f"${discount}"
260
250
 
261
- sale_price = max(0, current_price - discount_amount)
251
+ new_sale_price = max(0, current_price - discount_amount)
262
252
 
263
- # Customer budget defaults to discount amount if not specified
264
- per_customer = customer_budget or discount_amount
265
-
266
- # Build coupon specification
267
- coupon = {
268
- "coupon_specification": {
253
+ # Build sale price feed data
254
+ feed = {
255
+ "sku": sku,
256
+ "asin": response.get("summaries", [{}])[0].get("asin"),
257
+ "pricing": {
258
+ "original_price": current_price,
259
+ "sale_price": new_sale_price,
260
+ "discount_amount": discount_amount,
261
+ "discount_display": discount_display,
262
+ },
263
+ "schedule": {
264
+ "start_date": start_str,
265
+ "end_date": end_str,
266
+ "duration_days": (end - start).days,
267
+ },
268
+ "feed_data": {
269
+ "messageId": 1,
269
270
  "sku": sku,
270
- "asin": response.get("summaries", [{}])[0].get("asin"),
271
- "coupon_type": coupon_type,
272
- "discount": {
273
- "percentage" if coupon_type == "percentage" else "fixed_amount": discount,
274
- "display": discount_display,
275
- },
276
- "pricing": {
277
- "original_price": current_price,
278
- "discounted_price": sale_price,
279
- "savings": discount_amount,
271
+ "operationType": "PARTIAL_UPDATE",
272
+ "productType": "PET_TOY",
273
+ "attributes": {
274
+ "list_price": [{"currency": "USD", "value": current_price}],
275
+ "sale_price": [
276
+ {
277
+ "currency": "USD",
278
+ "value": new_sale_price,
279
+ "effective_date": start_str,
280
+ "end_date": end_str,
281
+ }
282
+ ],
280
283
  },
281
- "requirements": {
282
- "minimum_purchase": min_purchase if min_purchase > 0 else None,
283
- "prime_only": prime_only,
284
- "clip_required": clip_coupon,
285
- },
286
- "schedule": {
287
- "start_date": start_str,
288
- "end_date": end_str,
289
- "duration_days": (end - start).days,
290
- },
291
- "budget": {
292
- "total_budget": budget,
293
- "per_customer_max": per_customer,
294
- "estimated_redemptions": int(budget / discount_amount) if discount_amount > 0 else 0,
295
- },
296
- "status": "DRAFT",
297
284
  },
298
- "seller_central_steps": [
299
- "1. Go to Seller Central → Advertising → Coupons",
300
- "2. Click 'Create a new coupon'",
301
- "3. Search for the ASIN or SKU above",
302
- "4. Select discount type: " + ("Percentage Off" if coupon_type == "percentage" else "Money Off"),
303
- "5. Enter discount value: " + str(discount),
304
- "6. Set budget: $" + str(budget),
305
- "7. Set schedule: " + start_str + " to " + end_str,
306
- f"8. {'Enable Prime-only targeting' if prime_only else 'Target all customers'}",
307
- "9. Review and submit",
308
- ],
309
- "notes": [
310
- "Coupons typically take 4-8 hours to activate after submission",
311
- "Amazon charges $0.60 per redemption (US marketplace)",
312
- "Coupon will display on product detail page and search results",
313
- f"Estimated cost per redemption: ${discount_amount + 0.60:.2f} (discount + Amazon fee)",
314
- ],
315
285
  }
316
286
 
317
- # Remove None values
318
- if coupon["coupon_specification"]["requirements"]["minimum_purchase"] is None:
319
- del coupon["coupon_specification"]["requirements"]["minimum_purchase"]
320
-
321
287
  # Output
322
- output_json = json.dumps(coupon, indent=2)
288
+ output_json = json.dumps(feed, indent=2)
323
289
 
324
290
  if output:
325
291
  output.write(output_json)
326
- click.echo(f"✓ Coupon specification saved to {output.name}")
292
+ click.echo(f"✓ Sale price data saved to {output.name}")
327
293
 
328
294
  click.echo(output_json)
329
295
 
330
296
  # Summary
331
297
  click.echo("\n" + "=" * 50)
332
- click.echo("SUMMARY")
298
+ click.echo("SALE PRICE SUMMARY")
333
299
  click.echo("=" * 50)
334
300
  click.echo(f"SKU: {sku}")
335
301
  click.echo(f"Discount: {discount_display}")
336
- click.echo(f"Price: ${current_price} → ${sale_price}")
337
- click.echo(f"Budget: ${budget}")
302
+ click.echo(f"Price: ${current_price} → ${new_sale_price}")
338
303
  click.echo(f"Duration: {start_str} to {end_str}")
339
- click.echo(f"Prime Only: {'Yes' if prime_only else 'No'}")
340
- click.echo("\n⚠️ SP-API does not support direct coupon creation.")
341
- click.echo(" Use the Seller Central steps above to create the coupon.")
304
+ click.echo("\n⚠️ SP-API does not support direct sale price creation.")
305
+ click.echo(" Submit the feed_data above via the Feeds API")
306
+ click.echo(" or update manually in Seller Central.")
342
307
 
343
308
  except Exception as e:
344
309
  click.echo(f"Error: {e}", err=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-sp-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: CLI tool for Amazon Selling Partner API (SP-API) operations
5
5
  Home-page: https://github.com/stellaraether/amazon-sp-cli
6
6
  Author: Lunan Li
@@ -17,4 +17,4 @@ amazon_sp_cli.egg-info/top_level.txt
17
17
  tests/__init__.py
18
18
  tests/test_auth.py
19
19
  tests/test_client.py
20
- tests/test_coupon.py
20
+ tests/test_sale_price.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
2
2
 
3
3
  setup(
4
4
  name="amazon-sp-cli",
5
- version="0.1.2",
5
+ version="0.1.3",
6
6
  description="CLI tool for Amazon Selling Partner API (SP-API) operations",
7
7
  author="Lunan Li",
8
8
  author_email="lunan@stellaraether.com",
@@ -1,4 +1,4 @@
1
- """Tests for coupon command."""
1
+ """Tests for sale-price command."""
2
2
 
3
3
  import json
4
4
  from unittest.mock import Mock, patch
@@ -9,8 +9,8 @@ from click.testing import CliRunner
9
9
  from amazon_sp_cli.main import cli
10
10
 
11
11
 
12
- class TestCreateCoupon:
13
- """Test create-coupon command."""
12
+ class TestSalePrice:
13
+ """Test sale-price command."""
14
14
 
15
15
  @pytest.fixture
16
16
  def mock_listing_response(self):
@@ -27,8 +27,8 @@ class TestCreateCoupon:
27
27
 
28
28
  @patch("amazon_sp_cli.main.SPAPIAuth")
29
29
  @patch("amazon_sp_cli.main.SPAPIClient")
30
- def test_create_coupon_percentage(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
31
- """Test creating a percentage coupon."""
30
+ def test_sale_price_percentage(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
31
+ """Test generating percentage sale price."""
32
32
  mock_client = Mock()
33
33
  mock_client.get_listing.return_value = mock_listing_response
34
34
  mock_client_class.return_value = mock_client
@@ -36,18 +36,18 @@ class TestCreateCoupon:
36
36
  mock_auth = Mock()
37
37
  mock_auth_class.return_value = mock_auth
38
38
 
39
- result = runner.invoke(cli, ["create-coupon", "TEST-SKU", "20"])
39
+ result = runner.invoke(cli, ["sale-price", "TEST-SKU", "20"])
40
40
 
41
41
  assert result.exit_code == 0
42
42
  assert "20.0%" in result.output
43
43
  assert "$29.99" in result.output
44
- assert "$23.99" in result.output # 20% off
45
- assert "Seller Central" in result.output
44
+ assert "$23.99" in result.output
45
+ assert "feed_data" in result.output
46
46
 
47
47
  @patch("amazon_sp_cli.main.SPAPIAuth")
48
48
  @patch("amazon_sp_cli.main.SPAPIClient")
49
- def test_create_coupon_fixed(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
50
- """Test creating a fixed amount coupon."""
49
+ def test_sale_price_fixed(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
50
+ """Test generating fixed amount sale price."""
51
51
  mock_client = Mock()
52
52
  mock_client.get_listing.return_value = mock_listing_response
53
53
  mock_client_class.return_value = mock_client
@@ -55,16 +55,16 @@ class TestCreateCoupon:
55
55
  mock_auth = Mock()
56
56
  mock_auth_class.return_value = mock_auth
57
57
 
58
- result = runner.invoke(cli, ["create-coupon", "TEST-SKU", "5", "--type", "fixed"])
58
+ result = runner.invoke(cli, ["sale-price", "TEST-SKU", "5", "--type", "fixed"])
59
59
 
60
60
  assert result.exit_code == 0
61
61
  assert "$5" in result.output
62
- assert "$24.99" in result.output # $29.99 - $5
62
+ assert "$24.99" in result.output
63
63
 
64
64
  @patch("amazon_sp_cli.main.SPAPIAuth")
65
65
  @patch("amazon_sp_cli.main.SPAPIClient")
66
- def test_create_coupon_with_options(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
67
- """Test creating coupon with all options."""
66
+ def test_sale_price_with_dates(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
67
+ """Test generating sale price with custom dates."""
68
68
  mock_client = Mock()
69
69
  mock_client.get_listing.return_value = mock_listing_response
70
70
  mock_client_class.return_value = mock_client
@@ -75,12 +75,9 @@ class TestCreateCoupon:
75
75
  result = runner.invoke(
76
76
  cli,
77
77
  [
78
- "create-coupon",
78
+ "sale-price",
79
79
  "TEST-SKU",
80
80
  "15",
81
- "--prime-only",
82
- "--budget",
83
- "1000",
84
81
  "--start-date",
85
82
  "2026-05-01",
86
83
  "--end-date",
@@ -89,15 +86,13 @@ class TestCreateCoupon:
89
86
  )
90
87
 
91
88
  assert result.exit_code == 0
92
- assert "Prime Only: Yes" in result.output
93
- assert "$1000" in result.output
94
89
  assert "2026-05-01" in result.output
95
90
  assert "2026-06-01" in result.output
96
91
 
97
92
  @patch("amazon_sp_cli.main.SPAPIAuth")
98
93
  @patch("amazon_sp_cli.main.SPAPIClient")
99
- def test_create_coupon_output_file(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
100
- """Test saving coupon to file."""
94
+ def test_sale_price_output_file(self, mock_client_class, mock_auth_class, runner, mock_listing_response):
95
+ """Test saving sale price data to file."""
101
96
  mock_client = Mock()
102
97
  mock_client.get_listing.return_value = mock_listing_response
103
98
  mock_client_class.return_value = mock_client
@@ -106,20 +101,20 @@ class TestCreateCoupon:
106
101
  mock_auth_class.return_value = mock_auth
107
102
 
108
103
  with runner.isolated_filesystem():
109
- result = runner.invoke(cli, ["create-coupon", "TEST-SKU", "10", "-o", "coupon.json"])
104
+ result = runner.invoke(cli, ["sale-price", "TEST-SKU", "10", "-o", "sale-price.json"])
110
105
 
111
106
  assert result.exit_code == 0
112
107
  assert "saved to" in result.output
113
108
 
114
109
  # Verify file was created
115
- with open("coupon.json") as f:
110
+ with open("sale-price.json") as f:
116
111
  data = json.load(f)
117
- assert data["coupon_specification"]["sku"] == "TEST-SKU"
118
- assert data["coupon_specification"]["coupon_type"] == "percentage"
112
+ assert data["sku"] == "TEST-SKU"
113
+ assert data["pricing"]["discount_display"] == "10.0%"
119
114
 
120
115
  @patch("amazon_sp_cli.main.SPAPIAuth")
121
116
  @patch("amazon_sp_cli.main.SPAPIClient")
122
- def test_create_coupon_no_price(self, mock_client_class, mock_auth_class, runner):
117
+ def test_sale_price_no_price(self, mock_client_class, mock_auth_class, runner):
123
118
  """Test error when listing has no price."""
124
119
  mock_client = Mock()
125
120
  mock_client.get_listing.return_value = {
@@ -131,7 +126,7 @@ class TestCreateCoupon:
131
126
  mock_auth = Mock()
132
127
  mock_auth_class.return_value = mock_auth
133
128
 
134
- result = runner.invoke(cli, ["create-coupon", "TEST-SKU", "20"])
129
+ result = runner.invoke(cli, ["sale-price", "TEST-SKU", "20"])
135
130
 
136
131
  assert result.exit_code != 0
137
132
  assert "Error" in result.output
File without changes
File without changes
File without changes
File without changes