amazon-sp-cli 0.2.7__tar.gz → 0.2.9__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.7/amazon_sp_cli.egg-info → amazon_sp_cli-0.2.9}/PKG-INFO +1 -1
- amazon_sp_cli-0.2.9/amazon_sp_cli/__init__.py +3 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/client.py +16 -0
- amazon_sp_cli-0.2.9/amazon_sp_cli/commands/inventory.py +31 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/main.py +2 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9/amazon_sp_cli.egg-info}/PKG-INFO +1 -1
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli.egg-info/SOURCES.txt +2 -0
- amazon_sp_cli-0.2.9/tests/test_inventory.py +118 -0
- amazon_sp_cli-0.2.7/amazon_sp_cli/__init__.py +0 -2
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/LICENSE +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/MANIFEST.in +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/README.md +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/__main__.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/auth.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/cli.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/commands/__init__.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/commands/a_plus.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/commands/auth.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/commands/listings.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/commands/pricing.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/models/__init__.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli/models/a_plus.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli.egg-info/dependency_links.txt +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli.egg-info/entry_points.txt +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli.egg-info/requires.txt +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/amazon_sp_cli.egg-info/top_level.txt +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/pyproject.toml +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/setup.cfg +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/setup.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/__init__.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/test_a_plus.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/test_auth.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/test_client.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/test_listings.py +0 -0
- {amazon_sp_cli-0.2.7 → amazon_sp_cli-0.2.9}/tests/test_pricing.py +0 -0
|
@@ -140,6 +140,22 @@ class SPAPIClient:
|
|
|
140
140
|
|
|
141
141
|
return self.request("PUT", path, data)
|
|
142
142
|
|
|
143
|
+
def get_fba_inventory(self, seller_skus: str = None, next_token: str = None, details: bool = True) -> dict:
|
|
144
|
+
"""Get FBA inventory summaries."""
|
|
145
|
+
path = "/fba/inventory/v1/getInventorySummaries"
|
|
146
|
+
params = {
|
|
147
|
+
"details": "true" if details else "false",
|
|
148
|
+
"granularityType": "Marketplace",
|
|
149
|
+
"granularityId": self.marketplace_id,
|
|
150
|
+
"marketplaceIds": self.marketplace_id,
|
|
151
|
+
}
|
|
152
|
+
if seller_skus:
|
|
153
|
+
params["sellerSkus"] = seller_skus
|
|
154
|
+
if next_token:
|
|
155
|
+
params["nextToken"] = next_token
|
|
156
|
+
path += "?" + urlencode(params)
|
|
157
|
+
return self.request("GET", path)
|
|
158
|
+
|
|
143
159
|
def get_catalog_item(self, asin: str) -> dict:
|
|
144
160
|
"""Get catalog item details."""
|
|
145
161
|
path = f"/catalog/2022-04-01/items/{asin}"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Inventory commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..cli import handle_errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register_inventory_commands(cli_group, ensure_auth_client):
|
|
11
|
+
"""Register inventory CLI commands."""
|
|
12
|
+
|
|
13
|
+
@cli_group.command("list-fba-inventory")
|
|
14
|
+
@click.option("--sku", help="Filter by specific SKU (comma-separated for multiple)")
|
|
15
|
+
@click.option("--next-token", help="Pagination token from a previous response")
|
|
16
|
+
@click.pass_context
|
|
17
|
+
@handle_errors
|
|
18
|
+
def list_fba_inventory(ctx, sku, next_token):
|
|
19
|
+
"""List FBA inventory summaries."""
|
|
20
|
+
_, client = ensure_auth_client(ctx)
|
|
21
|
+
response = client.get_fba_inventory(seller_skus=sku, next_token=next_token)
|
|
22
|
+
|
|
23
|
+
payload = response.get("payload", {})
|
|
24
|
+
summaries = payload.get("inventorySummaries", [])
|
|
25
|
+
result = {"inventory": summaries}
|
|
26
|
+
|
|
27
|
+
pagination = payload.get("pagination", {})
|
|
28
|
+
if pagination.get("nextToken"):
|
|
29
|
+
result["nextToken"] = pagination["nextToken"]
|
|
30
|
+
|
|
31
|
+
click.echo(json.dumps(result, indent=2))
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from .cli import _ensure_auth_client, cli
|
|
4
4
|
from .commands.a_plus import register_a_plus_commands
|
|
5
5
|
from .commands.auth import register_auth_commands
|
|
6
|
+
from .commands.inventory import register_inventory_commands
|
|
6
7
|
from .commands.listings import register_listings_commands
|
|
7
8
|
from .commands.pricing import register_pricing_commands
|
|
8
9
|
|
|
@@ -10,3 +11,4 @@ register_auth_commands(cli)
|
|
|
10
11
|
register_listings_commands(cli, _ensure_auth_client)
|
|
11
12
|
register_pricing_commands(cli, _ensure_auth_client)
|
|
12
13
|
register_a_plus_commands(cli, _ensure_auth_client)
|
|
14
|
+
register_inventory_commands(cli, _ensure_auth_client)
|
|
@@ -18,6 +18,7 @@ amazon_sp_cli.egg-info/top_level.txt
|
|
|
18
18
|
amazon_sp_cli/commands/__init__.py
|
|
19
19
|
amazon_sp_cli/commands/a_plus.py
|
|
20
20
|
amazon_sp_cli/commands/auth.py
|
|
21
|
+
amazon_sp_cli/commands/inventory.py
|
|
21
22
|
amazon_sp_cli/commands/listings.py
|
|
22
23
|
amazon_sp_cli/commands/pricing.py
|
|
23
24
|
amazon_sp_cli/models/__init__.py
|
|
@@ -26,5 +27,6 @@ tests/__init__.py
|
|
|
26
27
|
tests/test_a_plus.py
|
|
27
28
|
tests/test_auth.py
|
|
28
29
|
tests/test_client.py
|
|
30
|
+
tests/test_inventory.py
|
|
29
31
|
tests/test_listings.py
|
|
30
32
|
tests/test_pricing.py
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Tests for inventory commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import Mock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from amazon_sp_cli.main import cli
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestListFbaInventory:
|
|
13
|
+
"""Test list-fba-inventory command."""
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def runner(self):
|
|
17
|
+
"""Create Click test runner."""
|
|
18
|
+
return CliRunner()
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_inventory_response(self):
|
|
22
|
+
"""Mock FBA inventory response."""
|
|
23
|
+
return {
|
|
24
|
+
"payload": {
|
|
25
|
+
"inventorySummaries": [
|
|
26
|
+
{
|
|
27
|
+
"sellerSku": "SKU-123",
|
|
28
|
+
"asin": "B09BBL8T4Z",
|
|
29
|
+
"fnSku": "X123456789",
|
|
30
|
+
"productName": "Test Product",
|
|
31
|
+
"condition": "NewItem",
|
|
32
|
+
"inventoryDetails": {
|
|
33
|
+
"fulfillableQuantity": 10,
|
|
34
|
+
"inboundWorkingQuantity": 5,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"pagination": {"nextToken": "token123"},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@patch("amazon_sp_cli.cli.SPAPIAuth")
|
|
43
|
+
@patch("amazon_sp_cli.cli.SPAPIClient")
|
|
44
|
+
def test_list_fba_inventory(self, mock_client_class, mock_auth_class, runner, mock_inventory_response):
|
|
45
|
+
"""Test listing FBA inventory."""
|
|
46
|
+
mock_client = Mock()
|
|
47
|
+
mock_client.get_fba_inventory.return_value = mock_inventory_response
|
|
48
|
+
mock_client_class.return_value = mock_client
|
|
49
|
+
|
|
50
|
+
mock_auth = Mock()
|
|
51
|
+
mock_auth_class.return_value = mock_auth
|
|
52
|
+
|
|
53
|
+
result = runner.invoke(cli, ["list-fba-inventory"])
|
|
54
|
+
|
|
55
|
+
assert result.exit_code == 0
|
|
56
|
+
# Strip PATH warning if present
|
|
57
|
+
output_text = result.output
|
|
58
|
+
if "{" in output_text:
|
|
59
|
+
brace_idx = output_text.index("{")
|
|
60
|
+
output_text = output_text[brace_idx:]
|
|
61
|
+
output = json.loads(output_text)
|
|
62
|
+
assert len(output["inventory"]) == 1
|
|
63
|
+
assert output["inventory"][0]["sellerSku"] == "SKU-123"
|
|
64
|
+
assert output["nextToken"] == "token123"
|
|
65
|
+
|
|
66
|
+
@patch("amazon_sp_cli.cli.SPAPIAuth")
|
|
67
|
+
@patch("amazon_sp_cli.cli.SPAPIClient")
|
|
68
|
+
def test_list_fba_inventory_with_sku_filter(
|
|
69
|
+
self, mock_client_class, mock_auth_class, runner, mock_inventory_response
|
|
70
|
+
):
|
|
71
|
+
"""Test listing FBA inventory filtered by SKU."""
|
|
72
|
+
mock_client = Mock()
|
|
73
|
+
mock_client.get_fba_inventory.return_value = mock_inventory_response
|
|
74
|
+
mock_client_class.return_value = mock_client
|
|
75
|
+
|
|
76
|
+
mock_auth = Mock()
|
|
77
|
+
mock_auth_class.return_value = mock_auth
|
|
78
|
+
|
|
79
|
+
result = runner.invoke(cli, ["list-fba-inventory", "--sku", "SKU-123,SKU-456"])
|
|
80
|
+
|
|
81
|
+
assert result.exit_code == 0
|
|
82
|
+
mock_client.get_fba_inventory.assert_called_once_with(seller_skus="SKU-123,SKU-456", next_token=None)
|
|
83
|
+
|
|
84
|
+
@patch("amazon_sp_cli.cli.SPAPIAuth")
|
|
85
|
+
@patch("amazon_sp_cli.cli.SPAPIClient")
|
|
86
|
+
def test_list_fba_inventory_with_next_token(
|
|
87
|
+
self, mock_client_class, mock_auth_class, runner, mock_inventory_response
|
|
88
|
+
):
|
|
89
|
+
"""Test listing FBA inventory with pagination token."""
|
|
90
|
+
mock_client = Mock()
|
|
91
|
+
mock_client.get_fba_inventory.return_value = mock_inventory_response
|
|
92
|
+
mock_client_class.return_value = mock_client
|
|
93
|
+
|
|
94
|
+
mock_auth = Mock()
|
|
95
|
+
mock_auth_class.return_value = mock_auth
|
|
96
|
+
|
|
97
|
+
result = runner.invoke(cli, ["list-fba-inventory", "--next-token", "token456"])
|
|
98
|
+
|
|
99
|
+
assert result.exit_code == 0
|
|
100
|
+
mock_client.get_fba_inventory.assert_called_once_with(seller_skus=None, next_token="token456")
|
|
101
|
+
|
|
102
|
+
@patch("amazon_sp_cli.cli.SPAPIAuth")
|
|
103
|
+
@patch("amazon_sp_cli.cli.SPAPIClient")
|
|
104
|
+
def test_list_fba_inventory_empty(self, mock_client_class, mock_auth_class, runner):
|
|
105
|
+
"""Test listing FBA inventory with no results."""
|
|
106
|
+
mock_client = Mock()
|
|
107
|
+
mock_client.get_fba_inventory.return_value = {"payload": {"inventorySummaries": []}}
|
|
108
|
+
mock_client_class.return_value = mock_client
|
|
109
|
+
|
|
110
|
+
mock_auth = Mock()
|
|
111
|
+
mock_auth_class.return_value = mock_auth
|
|
112
|
+
|
|
113
|
+
result = runner.invoke(cli, ["list-fba-inventory"])
|
|
114
|
+
|
|
115
|
+
assert result.exit_code == 0
|
|
116
|
+
output = json.loads(result.output)
|
|
117
|
+
assert output["inventory"] == []
|
|
118
|
+
assert "nextToken" not in output
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|