amazon-sp-cli 0.2.2__tar.gz → 0.2.4__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.
- {amazon_sp_cli-0.2.2/amazon_sp_cli.egg-info → amazon_sp_cli-0.2.4}/PKG-INFO +1 -1
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/README.md +2 -3
- amazon_sp_cli-0.2.4/amazon_sp_cli/__init__.py +2 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/models/a_plus.py +47 -2
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4/amazon_sp_cli.egg-info}/PKG-INFO +1 -1
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/setup.py +8 -1
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/tests/test_a_plus.py +103 -0
- amazon_sp_cli-0.2.2/amazon_sp_cli/__init__.py +0 -2
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/LICENSE +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/MANIFEST.in +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/__main__.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/auth.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/cli.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/client.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/commands/__init__.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/commands/a_plus.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/commands/auth.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/commands/pricing.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/main.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli/models/__init__.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli.egg-info/SOURCES.txt +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli.egg-info/dependency_links.txt +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli.egg-info/entry_points.txt +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli.egg-info/requires.txt +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/amazon_sp_cli.egg-info/top_level.txt +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/pyproject.toml +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/setup.cfg +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/tests/__init__.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/tests/test_auth.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/tests/test_client.py +0 -0
- {amazon_sp_cli-0.2.2 → amazon_sp_cli-0.2.4}/tests/test_pricing.py +0 -0
|
@@ -120,10 +120,9 @@ python3 -m amazon_sp_cli get-price PAW2603190101-BLU
|
|
|
120
120
|
|
|
121
121
|
This repository uses automated releases. To publish a new version:
|
|
122
122
|
|
|
123
|
-
1. Open a **standalone PR** that only bumps `
|
|
123
|
+
1. Open a **standalone PR** that only bumps `__version__` in `amazon_sp_cli/__init__.py`.
|
|
124
124
|
2. The PR must not include code, test, or documentation changes — a CI check enforces this.
|
|
125
|
-
3. Once the PR is merged to `main`, the `Auto Release` workflow
|
|
126
|
-
4. The tag push triggers the `Publish to PyPI` workflow, which runs the full test suite and publishes the package.
|
|
125
|
+
3. Once the PR is merged to `main`, the `Auto Release` workflow runs the full test suite, creates a tag (e.g. `v0.2.1`), creates a GitHub release, and publishes to PyPI.
|
|
127
126
|
|
|
128
127
|
Do not create tags manually.
|
|
129
128
|
|
|
@@ -18,14 +18,26 @@ class TextComponent:
|
|
|
18
18
|
class ImageComponent:
|
|
19
19
|
"""Image component for A+ Content modules."""
|
|
20
20
|
|
|
21
|
-
def __init__(
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
upload_destination_id: str,
|
|
24
|
+
image_crop: dict = None,
|
|
25
|
+
alt_text: str = None,
|
|
26
|
+
image_crop_specification: dict = None,
|
|
27
|
+
):
|
|
22
28
|
self.upload_destination_id = upload_destination_id
|
|
23
29
|
self.image_crop = image_crop
|
|
30
|
+
self.alt_text = alt_text
|
|
31
|
+
self.image_crop_specification = image_crop_specification
|
|
24
32
|
|
|
25
33
|
def to_dict(self) -> dict:
|
|
26
34
|
result = {"uploadDestinationId": self.upload_destination_id}
|
|
27
35
|
if self.image_crop:
|
|
28
36
|
result["imageCrop"] = self.image_crop
|
|
37
|
+
if self.alt_text:
|
|
38
|
+
result["altText"] = self.alt_text
|
|
39
|
+
if self.image_crop_specification:
|
|
40
|
+
result["imageCropSpecification"] = self.image_crop_specification
|
|
29
41
|
return result
|
|
30
42
|
|
|
31
43
|
|
|
@@ -157,6 +169,19 @@ class StandardImageTextOverlayModule:
|
|
|
157
169
|
return result
|
|
158
170
|
|
|
159
171
|
|
|
172
|
+
class StandardCompanyLogoModule:
|
|
173
|
+
"""Standard Company Logo module."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, company_logo=None):
|
|
176
|
+
self.company_logo = company_logo
|
|
177
|
+
|
|
178
|
+
def to_dict(self) -> dict:
|
|
179
|
+
result = {}
|
|
180
|
+
if self.company_logo:
|
|
181
|
+
result["companyLogo"] = self.company_logo.to_dict()
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
160
185
|
class ContentModule:
|
|
161
186
|
"""A+ Content module wrapper."""
|
|
162
187
|
|
|
@@ -168,6 +193,7 @@ class ContentModule:
|
|
|
168
193
|
"STANDARD_COMPARISON_TABLE": "standardComparisonTable",
|
|
169
194
|
"STANDARD_TEXT": "standardText",
|
|
170
195
|
"STANDARD_IMAGE_TEXT_OVERLAY": "standardImageTextOverlay",
|
|
196
|
+
"STANDARD_COMPANY_LOGO": "standardCompanyLogo",
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
def __init__(
|
|
@@ -180,6 +206,7 @@ class ContentModule:
|
|
|
180
206
|
standard_comparison_table: StandardComparisonTableModule = None,
|
|
181
207
|
standard_text: StandardTextModule = None,
|
|
182
208
|
standard_image_text_overlay: StandardImageTextOverlayModule = None,
|
|
209
|
+
standard_company_logo: StandardCompanyLogoModule = None,
|
|
183
210
|
):
|
|
184
211
|
self.module_type = module_type
|
|
185
212
|
self.standard_image_text = standard_image_text
|
|
@@ -189,6 +216,7 @@ class ContentModule:
|
|
|
189
216
|
self.standard_comparison_table = standard_comparison_table
|
|
190
217
|
self.standard_text = standard_text
|
|
191
218
|
self.standard_image_text_overlay = standard_image_text_overlay
|
|
219
|
+
self.standard_company_logo = standard_company_logo
|
|
192
220
|
|
|
193
221
|
def to_dict(self) -> dict:
|
|
194
222
|
result = {"contentModuleType": self.module_type}
|
|
@@ -208,6 +236,8 @@ class ContentModule:
|
|
|
208
236
|
result["standardText"] = self.standard_text.to_dict()
|
|
209
237
|
elif field_name == "standardImageTextOverlay" and self.standard_image_text_overlay:
|
|
210
238
|
result["standardImageTextOverlay"] = self.standard_image_text_overlay.to_dict()
|
|
239
|
+
elif field_name == "standardCompanyLogo" and self.standard_company_logo:
|
|
240
|
+
result["standardCompanyLogo"] = self.standard_company_logo.to_dict()
|
|
211
241
|
|
|
212
242
|
return result
|
|
213
243
|
|
|
@@ -287,7 +317,7 @@ def build_content_from_json(name: str, data: dict) -> APlusContentDocument:
|
|
|
287
317
|
|
|
288
318
|
def build_module_from_json(data: dict) -> ContentModule:
|
|
289
319
|
"""Build ContentModule from JSON dict."""
|
|
290
|
-
module_type = data.get("moduleType", "STANDARD_TEXT")
|
|
320
|
+
module_type = data.get("contentModuleType") or data.get("moduleType", "STANDARD_TEXT")
|
|
291
321
|
|
|
292
322
|
if module_type == "STANDARD_IMAGE_TEXT":
|
|
293
323
|
return ContentModule(
|
|
@@ -350,5 +380,20 @@ def build_module_from_json(data: dict) -> ContentModule:
|
|
|
350
380
|
image=ImageComponent(data["imageId"]) if data.get("imageId") else None,
|
|
351
381
|
),
|
|
352
382
|
)
|
|
383
|
+
elif module_type == "STANDARD_COMPANY_LOGO":
|
|
384
|
+
return ContentModule(
|
|
385
|
+
module_type=module_type,
|
|
386
|
+
standard_company_logo=StandardCompanyLogoModule(
|
|
387
|
+
company_logo=(
|
|
388
|
+
ImageComponent(
|
|
389
|
+
data["imageId"],
|
|
390
|
+
alt_text=data.get("altText"),
|
|
391
|
+
image_crop_specification=data.get("imageCropSpecification"),
|
|
392
|
+
)
|
|
393
|
+
if data.get("imageId")
|
|
394
|
+
else None
|
|
395
|
+
),
|
|
396
|
+
),
|
|
397
|
+
)
|
|
353
398
|
else:
|
|
354
399
|
raise ValueError(f"Unsupported moduleType: {module_type}")
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
1
4
|
from setuptools import find_packages, setup
|
|
2
5
|
|
|
6
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
7
|
+
with open(os.path.join(here, "amazon_sp_cli", "__init__.py")) as f:
|
|
8
|
+
version = re.search(r'__version__ = "([^"]+)"', f.read()).group(1)
|
|
9
|
+
|
|
3
10
|
setup(
|
|
4
11
|
name="amazon-sp-cli",
|
|
5
|
-
version=
|
|
12
|
+
version=version,
|
|
6
13
|
description="CLI tool for Amazon Selling Partner API (SP-API) operations",
|
|
7
14
|
author="Lunan Li",
|
|
8
15
|
author_email="lunan@stellaraether.com",
|
|
@@ -10,6 +10,7 @@ from amazon_sp_cli.main import cli
|
|
|
10
10
|
from amazon_sp_cli.models.a_plus import (
|
|
11
11
|
APlusContentDocument,
|
|
12
12
|
ContentModule,
|
|
13
|
+
StandardCompanyLogoModule,
|
|
13
14
|
StandardTextModule,
|
|
14
15
|
TextComponent,
|
|
15
16
|
build_content_from_json,
|
|
@@ -103,6 +104,38 @@ class TestBuildFromJson:
|
|
|
103
104
|
with pytest.raises(ValueError, match="Unsupported moduleType"):
|
|
104
105
|
build_module_from_json({"moduleType": "UNKNOWN"})
|
|
105
106
|
|
|
107
|
+
def test_build_module_from_json_uses_content_module_type(self):
|
|
108
|
+
data = {"contentModuleType": "STANDARD_TEXT", "headline": "Hello"}
|
|
109
|
+
mod = build_module_from_json(data)
|
|
110
|
+
assert mod.module_type == "STANDARD_TEXT"
|
|
111
|
+
|
|
112
|
+
def test_build_module_company_logo(self):
|
|
113
|
+
data = {
|
|
114
|
+
"contentModuleType": "STANDARD_COMPANY_LOGO",
|
|
115
|
+
"imageId": "logo-123",
|
|
116
|
+
"altText": "Company Logo",
|
|
117
|
+
"imageCropSpecification": {
|
|
118
|
+
"size": {
|
|
119
|
+
"width": {"value": 600, "units": "pixels"},
|
|
120
|
+
"height": {"value": 180, "units": "pixels"},
|
|
121
|
+
},
|
|
122
|
+
"offset": {
|
|
123
|
+
"x": {"value": 0, "units": "pixels"},
|
|
124
|
+
"y": {"value": 0, "units": "pixels"},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
mod = build_module_from_json(data)
|
|
129
|
+
assert mod.module_type == "STANDARD_COMPANY_LOGO"
|
|
130
|
+
result = mod.to_dict()
|
|
131
|
+
assert result["standardCompanyLogo"]["companyLogo"]["uploadDestinationId"] == "logo-123"
|
|
132
|
+
assert result["standardCompanyLogo"]["companyLogo"]["altText"] == "Company Logo"
|
|
133
|
+
assert result["standardCompanyLogo"]["companyLogo"]["imageCropSpecification"]["size"]["width"]["value"] == 600
|
|
134
|
+
|
|
135
|
+
def test_standard_company_logo_module_to_dict_empty(self):
|
|
136
|
+
mod = StandardCompanyLogoModule()
|
|
137
|
+
assert mod.to_dict() == {}
|
|
138
|
+
|
|
106
139
|
def test_build_module_image_text(self):
|
|
107
140
|
data = {
|
|
108
141
|
"moduleType": "STANDARD_IMAGE_TEXT",
|
|
@@ -128,6 +161,76 @@ class TestBuildFromJson:
|
|
|
128
161
|
assert result["standardComparisonTable"]["headline"]["value"] == "Compare"
|
|
129
162
|
assert result["standardComparisonTable"]["comparisonTableRows"][0]["name"] == "Feature"
|
|
130
163
|
|
|
164
|
+
def test_integration_full_document_from_json(self):
|
|
165
|
+
data = {
|
|
166
|
+
"modules": [
|
|
167
|
+
{
|
|
168
|
+
"moduleType": "STANDARD_COMPANY_LOGO",
|
|
169
|
+
"imageId": "aplus-media/sc/76a7fb65-607b-4e96-a3fa-a145d1397725.jpg",
|
|
170
|
+
"altText": "Pawified Logo",
|
|
171
|
+
"imageCropSpecification": {
|
|
172
|
+
"size": {
|
|
173
|
+
"width": {"value": 600, "units": "pixels"},
|
|
174
|
+
"height": {"value": 180, "units": "pixels"},
|
|
175
|
+
},
|
|
176
|
+
"offset": {
|
|
177
|
+
"x": {"value": 0, "units": "pixels"},
|
|
178
|
+
"y": {"value": 0, "units": "pixels"},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"moduleType": "STANDARD_IMAGE_TEXT",
|
|
184
|
+
"headline": "Pure Joy in Every Bite",
|
|
185
|
+
"body": "Treat your feline friend to the finest premium catnip...",
|
|
186
|
+
"imageId": "aplus-media/sc/77967e76-a03c-4514-85e1-371e7f2395d1.jpg",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"moduleType": "STANDARD_IMAGE_TEXT",
|
|
190
|
+
"headline": "100% Natural & Safe",
|
|
191
|
+
"body": "Made in the USA with all-natural...",
|
|
192
|
+
"imageId": "aplus-media/sc/215948ec-b306-4a0b-8491-f1342dc83f91.jpg",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"moduleType": "STANDARD_IMAGE_TEXT",
|
|
196
|
+
"headline": "Perfect for Playtime",
|
|
197
|
+
"body": "Each 2-pack includes convenient 5g sachets...",
|
|
198
|
+
"imageId": "aplus-media/sc/2e6493d5-e7a4-4c3a-8e47-a78e5f5b4802.jpg",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"moduleType": "STANDARD_IMAGE_TEXT",
|
|
202
|
+
"headline": "Premium Quality You Can Trust",
|
|
203
|
+
"body": "Pawified is committed to delivering premium pet products...",
|
|
204
|
+
"imageId": "aplus-media/sc/ef864286-0fec-4472-ad20-8b7dfee4922e.jpg",
|
|
205
|
+
},
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
doc = build_content_from_json("pawified-catnip", data)
|
|
209
|
+
assert doc.name == "pawified-catnip"
|
|
210
|
+
assert len(doc.content_module_list) == 5
|
|
211
|
+
assert doc.content_module_list[0].module_type == "STANDARD_COMPANY_LOGO"
|
|
212
|
+
assert doc.content_module_list[1].module_type == "STANDARD_IMAGE_TEXT"
|
|
213
|
+
|
|
214
|
+
result = doc.to_dict()
|
|
215
|
+
modules = result["contentModuleList"]
|
|
216
|
+
assert len(modules) == 5
|
|
217
|
+
|
|
218
|
+
logo_module = modules[0]["standardCompanyLogo"]
|
|
219
|
+
assert "image" not in logo_module["companyLogo"]
|
|
220
|
+
assert (
|
|
221
|
+
logo_module["companyLogo"]["uploadDestinationId"]
|
|
222
|
+
== "aplus-media/sc/76a7fb65-607b-4e96-a3fa-a145d1397725.jpg"
|
|
223
|
+
)
|
|
224
|
+
assert logo_module["companyLogo"]["altText"] == "Pawified Logo"
|
|
225
|
+
assert logo_module["companyLogo"]["imageCropSpecification"]["size"]["width"]["value"] == 600
|
|
226
|
+
|
|
227
|
+
for i in range(1, 5):
|
|
228
|
+
mod = modules[i]["standardImageText"]
|
|
229
|
+
assert "headline" in mod
|
|
230
|
+
assert "body" in mod
|
|
231
|
+
assert "image" in mod
|
|
232
|
+
assert mod["image"]["uploadDestinationId"].startswith("aplus-media/sc/")
|
|
233
|
+
|
|
131
234
|
|
|
132
235
|
class TestAPlusCLI:
|
|
133
236
|
@pytest.fixture
|
|
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
|
|
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
|