wildberries-cli 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.3
2
+ Name: wildberries-cli
3
+ Version: 0.1.0
4
+ Summary: Wildberries seller platform CLI powered by wildberries-sdk
5
+ Keywords: wildberries,cli,marketplace
6
+ Author: OpenAI Codex
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ Classifier: Programming Language :: Python :: 3
29
+ Classifier: Programming Language :: Python :: 3.11
30
+ Classifier: Programming Language :: Python :: 3.12
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Environment :: Console
33
+ Classifier: Topic :: Utilities
34
+ Requires-Dist: typer>=0.12.0
35
+ Requires-Dist: rich>=13.7.0
36
+ Requires-Dist: tomlkit>=0.13.0
37
+ Requires-Dist: wildberries-sdk>=0.1.47
38
+ Requires-Python: >=3.11
39
+ Project-URL: Homepage, https://github.com/jzakirov/wildberries-cli
40
+ Project-URL: Source, https://github.com/jzakirov/wildberries-cli
41
+ Project-URL: Issues, https://github.com/jzakirov/wildberries-cli/issues
42
+ Description-Content-Type: text/markdown
43
+
44
+ # wildberries-cli
45
+
46
+ Wildberries seller platform CLI built on top of [`wildberries-sdk`](https://github.com/eslazarev/wildberries-sdk).
47
+
48
+ The executable name is `wb`.
49
+
50
+ ```bash
51
+ wb general seller-info
52
+ wb tariffs commission --locale en
53
+ wb reports sales --date-from 2026-01-01T00:00:00+03:00
54
+ wb communications feedbacks list --unanswered --take 100 --skip 0
55
+ wb orders-fbs orders new --pretty
56
+ wb raw methods reports
57
+ ```
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install wildberries-cli
63
+ ```
64
+
65
+ Or with uv:
66
+
67
+ ```bash
68
+ uv tool install wildberries-cli
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ Run the interactive setup wizard:
74
+
75
+ ```bash
76
+ wb config init
77
+ ```
78
+
79
+ Config is stored in `~/.config/wildberries-cli/config.toml`.
80
+
81
+ Example:
82
+
83
+ ```toml
84
+ [core]
85
+ api_token = "your_wb_api_token"
86
+ timeout_seconds = 30.0
87
+ retries = 3
88
+
89
+ [defaults]
90
+ locale = "ru" # optional (ru|en|zh)
91
+ ```
92
+
93
+ ### Environment Variables
94
+
95
+ | Environment variable | Config key |
96
+ |---|---|
97
+ | `WB_API_TOKEN` | `core.api_token` |
98
+ | `WB_TIMEOUT` | `core.timeout_seconds` |
99
+ | `WB_RETRIES` | `core.retries` |
100
+ | `WB_LOCALE` | `defaults.locale` |
101
+
102
+ CLI flags override both config and env values.
103
+
104
+ ## Command Overview
105
+
106
+ ### `config`
107
+
108
+ ```bash
109
+ wb config init
110
+ wb config show
111
+ wb config show --reveal
112
+ wb config set core.retries 5
113
+ ```
114
+
115
+ ### `general`
116
+
117
+ ```bash
118
+ wb general ping
119
+ wb general seller-info
120
+ wb general users --limit 50 --offset 0
121
+ wb general users --invited-only
122
+ ```
123
+
124
+ ### `tariffs`
125
+
126
+ ```bash
127
+ wb tariffs commission [--locale ru|en|zh]
128
+ wb tariffs box --date YYYY-MM-DD
129
+ wb tariffs pallet --date YYYY-MM-DD
130
+ wb tariffs return --date YYYY-MM-DD
131
+ wb tariffs acceptance-coefficients [--warehouse-ids "1,2,3"]
132
+ ```
133
+
134
+ ### `reports`
135
+
136
+ ```bash
137
+ wb reports orders --date-from 2026-01-01T00:00:00+03:00 [--flag 0|1]
138
+ wb reports sales --date-from 2026-01-01T00:00:00+03:00 [--flag 0|1]
139
+ wb reports stocks --date-from 2026-01-01T00:00:00+03:00
140
+ wb reports incomes --date-from 2026-01-01T00:00:00+03:00
141
+ ```
142
+
143
+ ### `communications`
144
+
145
+ ```bash
146
+ wb communications feedbacks list --unanswered --take 100 --skip 0
147
+ wb communications feedbacks get <feedback-id>
148
+ wb communications feedbacks answer <feedback-id> --text "Thanks for your feedback"
149
+ wb communications feedbacks answer <feedback-id> --text -
150
+
151
+ wb communications questions list --unanswered --take 100 --skip 0
152
+ wb communications questions get <question-id>
153
+ wb communications questions answer <question-id> --text "Yes, this fits..." [--state wbRu]
154
+ ```
155
+
156
+ ### `products`
157
+
158
+ ```bash
159
+ wb products cards limits
160
+ wb products cards list --body-file cards-query.json [--locale ru|en|zh]
161
+ wb products objects list [--name socks] [--parent-id 123]
162
+ wb products directories colors [--locale en]
163
+ wb products tags list
164
+ ```
165
+
166
+ `wb products cards list` expects the WB SDK request JSON for `content_v2_get_cards_list_post`.
167
+
168
+ ### `orders-fbs`
169
+
170
+ ```bash
171
+ wb orders-fbs orders new
172
+ wb orders-fbs orders list --limit 100 --next 0
173
+ wb orders-fbs orders status --order 123 --order 456
174
+ wb orders-fbs orders stickers --order 123 --type zplv --width 58 --height 40
175
+
176
+ wb orders-fbs supplies list --limit 100 --next 0
177
+ wb orders-fbs supplies create --name "Batch 2026-02-23"
178
+ ```
179
+
180
+ ### `raw` (direct SDK fallback)
181
+
182
+ Use `raw` for any `wildberries-sdk` `DefaultApi` method that does not yet have a curated command.
183
+
184
+ ```bash
185
+ wb raw modules
186
+ wb raw methods reports
187
+ wb raw signature reports api_v1_supplier_sales_get
188
+ wb raw call general api_v1_seller_info_get
189
+ wb raw call tariffs api_v1_tariffs_commission_get --arg locale=ru
190
+ wb raw call reports api_v1_supplier_sales_get --arg-json date_from='"2026-01-01T00:00:00+03:00"'
191
+ ```
192
+
193
+ Notes:
194
+ - `--arg` passes values as strings.
195
+ - `--arg-json` passes parsed JSON values (numbers, booleans, arrays, objects, quoted strings).
196
+ - `--kwargs-json` accepts a full JSON object for method kwargs.
197
+
198
+ ## Output
199
+
200
+ - Default output is JSON to stdout.
201
+ - Errors are structured JSON to stderr.
202
+ - `--pretty` renders Rich tables for selected list endpoints (and pretty JSON otherwise).
203
+
204
+ Example error shape:
205
+
206
+ ```json
207
+ {"error":{"type":"auth_error","message":"Authentication failed. Check WB_API_TOKEN.","status_code":401}}
208
+ ```
209
+
210
+ ## Publishing to PyPI
211
+
212
+ This repository includes GitHub Actions trusted publishing (`.github/workflows/publish.yml`):
213
+
214
+ - Push a tag like `v0.1.0`
215
+ - Workflow runs `uv build`
216
+ - Workflow runs `uv publish` with OIDC (PyPI trusted publisher)
217
+
218
+ ## Scope (v1)
219
+
220
+ The curated command surface is intentionally narrow. Wildberries SDK exposes many modules and hundreds of methods, so `wb raw` is included to provide immediate access to the full SDK while curated commands are expanded over time.
221
+
222
+ ## License
223
+
224
+ MIT (see `LICENSE`).
@@ -0,0 +1,181 @@
1
+ # wildberries-cli
2
+
3
+ Wildberries seller platform CLI built on top of [`wildberries-sdk`](https://github.com/eslazarev/wildberries-sdk).
4
+
5
+ The executable name is `wb`.
6
+
7
+ ```bash
8
+ wb general seller-info
9
+ wb tariffs commission --locale en
10
+ wb reports sales --date-from 2026-01-01T00:00:00+03:00
11
+ wb communications feedbacks list --unanswered --take 100 --skip 0
12
+ wb orders-fbs orders new --pretty
13
+ wb raw methods reports
14
+ ```
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install wildberries-cli
20
+ ```
21
+
22
+ Or with uv:
23
+
24
+ ```bash
25
+ uv tool install wildberries-cli
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ Run the interactive setup wizard:
31
+
32
+ ```bash
33
+ wb config init
34
+ ```
35
+
36
+ Config is stored in `~/.config/wildberries-cli/config.toml`.
37
+
38
+ Example:
39
+
40
+ ```toml
41
+ [core]
42
+ api_token = "your_wb_api_token"
43
+ timeout_seconds = 30.0
44
+ retries = 3
45
+
46
+ [defaults]
47
+ locale = "ru" # optional (ru|en|zh)
48
+ ```
49
+
50
+ ### Environment Variables
51
+
52
+ | Environment variable | Config key |
53
+ |---|---|
54
+ | `WB_API_TOKEN` | `core.api_token` |
55
+ | `WB_TIMEOUT` | `core.timeout_seconds` |
56
+ | `WB_RETRIES` | `core.retries` |
57
+ | `WB_LOCALE` | `defaults.locale` |
58
+
59
+ CLI flags override both config and env values.
60
+
61
+ ## Command Overview
62
+
63
+ ### `config`
64
+
65
+ ```bash
66
+ wb config init
67
+ wb config show
68
+ wb config show --reveal
69
+ wb config set core.retries 5
70
+ ```
71
+
72
+ ### `general`
73
+
74
+ ```bash
75
+ wb general ping
76
+ wb general seller-info
77
+ wb general users --limit 50 --offset 0
78
+ wb general users --invited-only
79
+ ```
80
+
81
+ ### `tariffs`
82
+
83
+ ```bash
84
+ wb tariffs commission [--locale ru|en|zh]
85
+ wb tariffs box --date YYYY-MM-DD
86
+ wb tariffs pallet --date YYYY-MM-DD
87
+ wb tariffs return --date YYYY-MM-DD
88
+ wb tariffs acceptance-coefficients [--warehouse-ids "1,2,3"]
89
+ ```
90
+
91
+ ### `reports`
92
+
93
+ ```bash
94
+ wb reports orders --date-from 2026-01-01T00:00:00+03:00 [--flag 0|1]
95
+ wb reports sales --date-from 2026-01-01T00:00:00+03:00 [--flag 0|1]
96
+ wb reports stocks --date-from 2026-01-01T00:00:00+03:00
97
+ wb reports incomes --date-from 2026-01-01T00:00:00+03:00
98
+ ```
99
+
100
+ ### `communications`
101
+
102
+ ```bash
103
+ wb communications feedbacks list --unanswered --take 100 --skip 0
104
+ wb communications feedbacks get <feedback-id>
105
+ wb communications feedbacks answer <feedback-id> --text "Thanks for your feedback"
106
+ wb communications feedbacks answer <feedback-id> --text -
107
+
108
+ wb communications questions list --unanswered --take 100 --skip 0
109
+ wb communications questions get <question-id>
110
+ wb communications questions answer <question-id> --text "Yes, this fits..." [--state wbRu]
111
+ ```
112
+
113
+ ### `products`
114
+
115
+ ```bash
116
+ wb products cards limits
117
+ wb products cards list --body-file cards-query.json [--locale ru|en|zh]
118
+ wb products objects list [--name socks] [--parent-id 123]
119
+ wb products directories colors [--locale en]
120
+ wb products tags list
121
+ ```
122
+
123
+ `wb products cards list` expects the WB SDK request JSON for `content_v2_get_cards_list_post`.
124
+
125
+ ### `orders-fbs`
126
+
127
+ ```bash
128
+ wb orders-fbs orders new
129
+ wb orders-fbs orders list --limit 100 --next 0
130
+ wb orders-fbs orders status --order 123 --order 456
131
+ wb orders-fbs orders stickers --order 123 --type zplv --width 58 --height 40
132
+
133
+ wb orders-fbs supplies list --limit 100 --next 0
134
+ wb orders-fbs supplies create --name "Batch 2026-02-23"
135
+ ```
136
+
137
+ ### `raw` (direct SDK fallback)
138
+
139
+ Use `raw` for any `wildberries-sdk` `DefaultApi` method that does not yet have a curated command.
140
+
141
+ ```bash
142
+ wb raw modules
143
+ wb raw methods reports
144
+ wb raw signature reports api_v1_supplier_sales_get
145
+ wb raw call general api_v1_seller_info_get
146
+ wb raw call tariffs api_v1_tariffs_commission_get --arg locale=ru
147
+ wb raw call reports api_v1_supplier_sales_get --arg-json date_from='"2026-01-01T00:00:00+03:00"'
148
+ ```
149
+
150
+ Notes:
151
+ - `--arg` passes values as strings.
152
+ - `--arg-json` passes parsed JSON values (numbers, booleans, arrays, objects, quoted strings).
153
+ - `--kwargs-json` accepts a full JSON object for method kwargs.
154
+
155
+ ## Output
156
+
157
+ - Default output is JSON to stdout.
158
+ - Errors are structured JSON to stderr.
159
+ - `--pretty` renders Rich tables for selected list endpoints (and pretty JSON otherwise).
160
+
161
+ Example error shape:
162
+
163
+ ```json
164
+ {"error":{"type":"auth_error","message":"Authentication failed. Check WB_API_TOKEN.","status_code":401}}
165
+ ```
166
+
167
+ ## Publishing to PyPI
168
+
169
+ This repository includes GitHub Actions trusted publishing (`.github/workflows/publish.yml`):
170
+
171
+ - Push a tag like `v0.1.0`
172
+ - Workflow runs `uv build`
173
+ - Workflow runs `uv publish` with OIDC (PyPI trusted publisher)
174
+
175
+ ## Scope (v1)
176
+
177
+ The curated command surface is intentionally narrow. Wildberries SDK exposes many modules and hundreds of methods, so `wb raw` is included to provide immediate access to the full SDK while curated commands are expanded over time.
178
+
179
+ ## License
180
+
181
+ MIT (see `LICENSE`).
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.10.4,<0.11.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "wildberries-cli"
7
+ version = "0.1.0"
8
+ description = "Wildberries seller platform CLI powered by wildberries-sdk"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "OpenAI Codex" }]
13
+ dependencies = [
14
+ "typer>=0.12.0",
15
+ "rich>=13.7.0",
16
+ "tomlkit>=0.13.0",
17
+ "wildberries-sdk>=0.1.47",
18
+ ]
19
+ keywords = ["wildberries", "cli", "marketplace"]
20
+ classifiers = [
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Environment :: Console",
26
+ "Topic :: Utilities",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/jzakirov/wildberries-cli"
31
+ Source = "https://github.com/jzakirov/wildberries-cli"
32
+ Issues = "https://github.com/jzakirov/wildberries-cli/issues"
33
+
34
+ [project.scripts]
35
+ wb = "wildberries_cli.main:app"
36
+
37
+ [tool.uv.build-backend]
38
+ module-name = "wildberries_cli"
39
+ module-root = ""
40
+
41
+ [dependency-groups]
42
+ dev = ["ruff>=0.9.0"]
43
+
44
+ [tool.ruff]
45
+ target-version = "py311"
46
+ line-length = 100
47
+
48
+ [tool.ruff.format]
49
+ quote-style = "double"
50
+ indent-style = "space"
@@ -0,0 +1 @@
1
+ """wildberries-cli package."""
@@ -0,0 +1,65 @@
1
+ """CLI argument parsing helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any, Iterable
9
+
10
+ from wildberries_cli.output import read_text_arg
11
+
12
+
13
+ def load_json_input(body_json: str | None = None, body_file: str | None = None) -> Any:
14
+ """Load JSON from one source (`--body-json` or `--body-file`)."""
15
+ if body_json and body_file:
16
+ raise ValueError("Use only one of --body-json or --body-file")
17
+ if body_json is None and body_file is None:
18
+ raise ValueError("Body JSON is required")
19
+
20
+ if body_json is not None:
21
+ text = read_text_arg(body_json)
22
+ else:
23
+ if body_file == "-":
24
+ text = read_text_arg("-")
25
+ else:
26
+ text = Path(str(body_file)).read_text()
27
+
28
+ return json.loads(text)
29
+
30
+
31
+ def parse_rfc3339ish(value: str) -> datetime:
32
+ """Parse an RFC3339-ish datetime/date string accepted by the SDK."""
33
+ text = value.strip()
34
+ if text.endswith("Z"):
35
+ text = text[:-1] + "+00:00"
36
+ try:
37
+ return datetime.fromisoformat(text)
38
+ except ValueError as exc:
39
+ raise ValueError(f"Invalid datetime '{value}'. Use ISO/RFC3339 (e.g. 2024-01-01 or 2024-01-01T12:00:00+03:00).") from exc
40
+
41
+
42
+ def parse_kv_pairs(items: Iterable[str]) -> dict[str, str]:
43
+ out: dict[str, str] = {}
44
+ for item in items:
45
+ key, value = _split_kv(item)
46
+ out[key] = value
47
+ return out
48
+
49
+
50
+ def parse_json_kv_pairs(items: Iterable[str]) -> dict[str, Any]:
51
+ out: dict[str, Any] = {}
52
+ for item in items:
53
+ key, value = _split_kv(item)
54
+ out[key] = json.loads(value)
55
+ return out
56
+
57
+
58
+ def _split_kv(item: str) -> tuple[str, str]:
59
+ if "=" not in item:
60
+ raise ValueError(f"Expected KEY=VALUE, got '{item}'")
61
+ key, value = item.split("=", 1)
62
+ key = key.strip()
63
+ if not key:
64
+ raise ValueError(f"Expected KEY=VALUE, got '{item}'")
65
+ return key, value
@@ -0,0 +1,159 @@
1
+ """Dynamic Wildberries SDK client factory and API invocation helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import inspect
7
+ import pkgutil
8
+ import time
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ import typer
13
+ import wildberries_sdk
14
+
15
+ from wildberries_cli.output import print_error
16
+
17
+ if TYPE_CHECKING:
18
+ from wildberries_cli.config import Config
19
+
20
+
21
+ @dataclass
22
+ class ModuleClient:
23
+ module_name: str
24
+ sdk_module: Any
25
+ api: Any
26
+
27
+
28
+ def list_sdk_modules() -> list[str]:
29
+ return sorted(m.name for m in pkgutil.iter_modules(wildberries_sdk.__path__))
30
+
31
+
32
+ def list_methods(module_name: str) -> list[str]:
33
+ cls = _get_default_api_class(normalize_module_name(module_name))
34
+ methods = []
35
+ for name, fn in inspect.getmembers(cls, inspect.isfunction):
36
+ if name.startswith("_"):
37
+ continue
38
+ methods.append(name)
39
+ return sorted(methods)
40
+
41
+
42
+ def method_signature(module_name: str, method_name: str) -> str:
43
+ cls = _get_default_api_class(normalize_module_name(module_name))
44
+ fn = getattr(cls, method_name, None)
45
+ if fn is None or not callable(fn):
46
+ raise AttributeError(f"Method '{method_name}' not found in module '{module_name}'")
47
+ return f"{method_name}{inspect.signature(fn)}"
48
+
49
+
50
+ def get_module_client(module_name: str, cfg: "Config", *, require_token: bool = True) -> ModuleClient:
51
+ module_name = normalize_module_name(module_name)
52
+ if require_token:
53
+ _require_token(cfg)
54
+
55
+ sdk_module = importlib.import_module(f"wildberries_sdk.{module_name}")
56
+ Configuration = getattr(sdk_module, "Configuration")
57
+ ApiClient = getattr(sdk_module, "ApiClient")
58
+ DefaultApi = getattr(sdk_module, "DefaultApi")
59
+
60
+ conf_kwargs: dict[str, Any] = {}
61
+ if cfg.api_token:
62
+ conf_kwargs["api_key"] = {"HeaderApiKey": cfg.api_token}
63
+ if cfg.retries:
64
+ conf_kwargs["retries"] = cfg.retries
65
+ conf = Configuration(**conf_kwargs)
66
+ api_client = ApiClient(conf)
67
+ api = DefaultApi(api_client)
68
+ return ModuleClient(module_name=module_name, sdk_module=sdk_module, api=api)
69
+
70
+
71
+ def call_api(
72
+ module_name: str,
73
+ method_name: str,
74
+ cfg: "Config",
75
+ *,
76
+ require_token: bool = True,
77
+ **kwargs: Any,
78
+ ) -> Any:
79
+ client = get_module_client(module_name, cfg, require_token=require_token)
80
+ fn = getattr(client.api, method_name, None)
81
+ if fn is None or not callable(fn):
82
+ print_error("validation_error", f"Unknown method '{method_name}' for module '{client.module_name}'")
83
+ raise typer.Exit(1)
84
+ return call_with_retry(fn, cfg, **kwargs)
85
+
86
+
87
+ def call_with_retry(fn: Any, cfg: "Config", **kwargs: Any) -> Any:
88
+ sig = inspect.signature(fn)
89
+ if "_request_timeout" in sig.parameters and "_request_timeout" not in kwargs:
90
+ kwargs["_request_timeout"] = cfg.timeout_seconds
91
+
92
+ max_attempts = max(1, int(cfg.retries))
93
+ for attempt in range(max_attempts):
94
+ try:
95
+ return fn(**kwargs)
96
+ except Exception as exc:
97
+ status = getattr(exc, "status", None)
98
+ if status in {429, 500, 502, 503, 504} and attempt < max_attempts - 1:
99
+ delay = _retry_after_seconds(exc) or min(2 ** attempt, 10)
100
+ time.sleep(delay)
101
+ continue
102
+ _handle_exception(exc)
103
+ raise typer.Exit(1)
104
+
105
+ raise typer.Exit(1)
106
+
107
+
108
+ def normalize_module_name(value: str) -> str:
109
+ return value.replace("-", "_").strip()
110
+
111
+
112
+ def _require_token(cfg: "Config") -> None:
113
+ if not cfg.api_token:
114
+ print_error(
115
+ "auth_error",
116
+ "No WB API token configured. Set WB_API_TOKEN, use --api-token, or run `wb config init`.",
117
+ )
118
+ raise typer.Exit(1)
119
+
120
+
121
+ def _get_default_api_class(module_name: str) -> Any:
122
+ sdk_module = importlib.import_module(f"wildberries_sdk.{module_name}")
123
+ return getattr(sdk_module, "DefaultApi")
124
+
125
+
126
+ def _retry_after_seconds(exc: Exception) -> int | None:
127
+ headers = getattr(exc, "headers", None)
128
+ if not headers:
129
+ return None
130
+ try:
131
+ value = headers.get("Retry-After") # type: ignore[union-attr]
132
+ if value is None:
133
+ return None
134
+ return max(1, int(value))
135
+ except Exception:
136
+ return None
137
+
138
+
139
+ def _handle_exception(exc: Exception) -> None:
140
+ status = getattr(exc, "status", None)
141
+ message = str(exc)
142
+ detail = getattr(exc, "body", None)
143
+
144
+ if status == 401:
145
+ print_error("auth_error", "Authentication failed. Check WB_API_TOKEN.", status_code=status, detail=detail)
146
+ return
147
+ if status == 403:
148
+ print_error("forbidden", "Permission denied or token lacks required scope.", status_code=status, detail=detail)
149
+ return
150
+ if status == 404:
151
+ print_error("not_found", "Resource not found.", status_code=status, detail=detail)
152
+ return
153
+ if status == 429:
154
+ print_error("rate_limit", "Rate limit exceeded.", status_code=status, detail=detail)
155
+ return
156
+ if status is not None:
157
+ print_error("api_error", message, status_code=status, detail=detail)
158
+ return
159
+ print_error("cli_error", message)
@@ -0,0 +1 @@
1
+ """wb command modules."""