ozonapi-async 0.7.2__tar.gz → 0.7.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.
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/PKG-INFO +2 -1
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/pyproject.toml +1 -1
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/readme.md +1 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/__init__.py +1 -1
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/config.py +1 -1
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/core.py +65 -27
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/exceptions.py +4 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/method_rate_limiter.py +17 -10
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/rate_limiter.py +15 -9
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/sessions.py +11 -5
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/fbs.py +2 -2
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/PKG-INFO +2 -1
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/LICENSE +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/setup.cfg +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/delivery.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/localization.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/postings.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/prices.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/products.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/requests.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/warehouses.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/attributes_and_characteristics.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/barcodes.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/beta.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/prices_and_stocks.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/products.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/warehouses.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/base.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_attribute.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_attribute_values.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_attribute_values_search.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_tree.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/v1__barcode_add.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/v1__barcode_generate.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/base.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/beta/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/beta/v1__analytics_stocks.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__addressee.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__analytics_data.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__barcodes.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__cancellation.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__customer.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__customer_address.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__delivery_method.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__filter_with.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__financial_data.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__financial_data_products.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__legal_info.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__optional.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__posting.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__product.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__requirements.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__tariffication.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__fbs_posting_delivering.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__posting_fbs_get_by_barcode.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__posting_fbs_product_change.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_get.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_unfulfilled_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_multiboxqty_set.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/mixins.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/base.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v1__product_import_prices.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v1__product_info_stocks_by_warehouse_fbs.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v2__products_stocks.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v4__product_info_stocks.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v5__product_info_prices.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/base.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_archive.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_attributes_update.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_import_by_sku.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_import_info.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_info_subscription.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_rating_by_sku.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_related_sku_get.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_unarchive.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_update_offer_id.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v2__product_pictures_info.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v2__products_delete.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_import.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_info_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v4__product_info_attributes.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/__init__.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/v1__delivery_method_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/v1__warehouse_list.py +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/SOURCES.txt +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/dependency_links.txt +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/requires.txt +0 -0
- {ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ozonapi-async
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Асинхронный клиент для работы с API маркетплейса Ozon
|
|
5
5
|
Author: Alexander Ulianov
|
|
6
6
|
License: MIT
|
|
@@ -113,6 +113,7 @@ async def main():
|
|
|
113
113
|
config = SellerAPIConfig(
|
|
114
114
|
client_id="your_client_id",
|
|
115
115
|
api_key="your_api_key",
|
|
116
|
+
log_level="DEBUG"
|
|
116
117
|
)
|
|
117
118
|
|
|
118
119
|
async with SellerAPI(config=config) as api:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ozonapi-async"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.4"
|
|
8
8
|
description = "Асинхронный клиент для работы с API маркетплейса Ozon"
|
|
9
9
|
readme = "readme.md"
|
|
10
10
|
keywords = ["ozon", "api", "async", "ecommerce", "seller", "client"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Асинхронный интерфейс для взаимодействия с API маркетплейса Ozon.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.7.
|
|
5
|
+
__version__ = "0.7.4"
|
|
6
6
|
__author__ = "Alexander Ulianov"
|
|
7
7
|
__repository__ = "https://github.com/a-ulianov/OzonAPI"
|
|
8
8
|
__docs__ = "https://github.com/a-ulianov/OzonAPI#readme"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import sys
|
|
3
4
|
from types import TracebackType
|
|
4
5
|
from typing import Any, Literal, Optional, ClassVar
|
|
@@ -24,14 +25,7 @@ from .exceptions import (
|
|
|
24
25
|
APIError,
|
|
25
26
|
APIForbiddenError,
|
|
26
27
|
APINotFoundError,
|
|
27
|
-
APIServerError,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
logger.remove()
|
|
31
|
-
logger.add(
|
|
32
|
-
sys.stderr,
|
|
33
|
-
level=APIConfig().log_level,
|
|
34
|
-
enqueue=True,
|
|
28
|
+
APIServerError, APITooManyRequestsError,
|
|
35
29
|
)
|
|
36
30
|
|
|
37
31
|
|
|
@@ -50,6 +44,15 @@ class APIManager:
|
|
|
50
44
|
_initialized: ClassVar[bool] = False
|
|
51
45
|
_instance_count: ClassVar[int] = 0
|
|
52
46
|
|
|
47
|
+
_class_logger: ClassVar = logger
|
|
48
|
+
_class_logger.remove()
|
|
49
|
+
_class_logger.add(
|
|
50
|
+
sys.stderr,
|
|
51
|
+
level=APIConfig().log_level,
|
|
52
|
+
enqueue=True,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
53
56
|
def __init__(
|
|
54
57
|
self,
|
|
55
58
|
client_id: Optional[str] = None,
|
|
@@ -64,12 +67,13 @@ class APIManager:
|
|
|
64
67
|
api_key: Ключ API для аутентификации
|
|
65
68
|
config: Конфигурация клиента
|
|
66
69
|
"""
|
|
67
|
-
self._config =
|
|
70
|
+
self._config = self.load_config(config)
|
|
68
71
|
self._client_id = client_id or self._config.client_id
|
|
69
72
|
self._api_key = api_key or self._config.api_key
|
|
70
73
|
self._instance_id = id(self)
|
|
71
74
|
self._registered = False
|
|
72
75
|
self._closed = False
|
|
76
|
+
self._instance_logger = self._get_instance_logger()
|
|
73
77
|
|
|
74
78
|
if self._client_id is None or self._api_key is None:
|
|
75
79
|
raise ValueError(
|
|
@@ -78,27 +82,53 @@ class APIManager:
|
|
|
78
82
|
|
|
79
83
|
if APIManager._rate_limiter_manager is None:
|
|
80
84
|
APIManager._rate_limiter_manager = RateLimiterManager(
|
|
81
|
-
cleanup_interval=self._config.cleanup_interval
|
|
85
|
+
cleanup_interval=self._config.cleanup_interval,
|
|
86
|
+
instance_logger=self.logger
|
|
82
87
|
)
|
|
83
88
|
if APIManager._session_manager is None:
|
|
84
89
|
APIManager._session_manager = SessionManager(
|
|
85
90
|
timeout=self._config.request_timeout,
|
|
86
|
-
connector_limit=self._config.connector_limit
|
|
91
|
+
connector_limit=self._config.connector_limit,
|
|
92
|
+
instance_logger=self.logger
|
|
87
93
|
)
|
|
88
94
|
if APIManager._method_rate_limiter_manager is None:
|
|
89
95
|
APIManager._method_rate_limiter_manager = MethodRateLimiterManager(
|
|
90
|
-
cleanup_interval=self._config.cleanup_interval
|
|
96
|
+
cleanup_interval=self._config.cleanup_interval,
|
|
97
|
+
instance_logger=self.logger
|
|
91
98
|
)
|
|
92
99
|
|
|
93
100
|
APIManager._instance_count += 1
|
|
94
101
|
self._validate_credentials()
|
|
95
|
-
logger.debug(f"API-клиент инициализирован для ClientID {self._client_id}")
|
|
102
|
+
self.logger.debug(f"API-клиент инициализирован для ClientID {self._client_id}")
|
|
96
103
|
|
|
97
104
|
@classmethod
|
|
98
|
-
def load_config(cls) -> APIConfig:
|
|
105
|
+
def load_config(cls, user_config: APIConfig | None = None) -> APIConfig:
|
|
99
106
|
"""Создает конфигурацию с загрузкой из .env файла."""
|
|
100
107
|
load_dotenv()
|
|
101
|
-
|
|
108
|
+
base_config = APIConfig()
|
|
109
|
+
|
|
110
|
+
if user_config is None:
|
|
111
|
+
return base_config
|
|
112
|
+
else:
|
|
113
|
+
return base_config.model_copy(
|
|
114
|
+
update=user_config.model_dump(
|
|
115
|
+
exclude_unset=True,
|
|
116
|
+
exclude_defaults=True
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _get_instance_logger(self) -> logging.Logger:
|
|
121
|
+
"""Инициализирует и возвращает настроенный логер для экземпляра."""
|
|
122
|
+
instance_logger = logger.bind(client_id=self._client_id)
|
|
123
|
+
|
|
124
|
+
instance_logger.remove()
|
|
125
|
+
instance_logger.add(
|
|
126
|
+
sys.stderr,
|
|
127
|
+
level=self._config.log_level,
|
|
128
|
+
enqueue=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return instance_logger
|
|
102
132
|
|
|
103
133
|
@classmethod
|
|
104
134
|
async def initialize(cls) -> None:
|
|
@@ -109,7 +139,7 @@ class APIManager:
|
|
|
109
139
|
if cls._method_rate_limiter_manager:
|
|
110
140
|
await cls._method_rate_limiter_manager.start()
|
|
111
141
|
cls._initialized = True
|
|
112
|
-
|
|
142
|
+
cls._class_logger.debug("Выполнена инициализация ресурсов API-менеджера")
|
|
113
143
|
|
|
114
144
|
@classmethod
|
|
115
145
|
async def shutdown(cls) -> None:
|
|
@@ -122,7 +152,7 @@ class APIManager:
|
|
|
122
152
|
if cls._session_manager:
|
|
123
153
|
await cls._session_manager.close_all()
|
|
124
154
|
cls._initialized = False
|
|
125
|
-
|
|
155
|
+
cls._class_logger.debug("Выполнена деинициализация ресурсов API-менеджера")
|
|
126
156
|
|
|
127
157
|
def _validate_credentials(self) -> None:
|
|
128
158
|
"""Валидация учетных данных."""
|
|
@@ -132,7 +162,7 @@ class APIManager:
|
|
|
132
162
|
raise ValueError("api_key не должен быть пустой строкой")
|
|
133
163
|
|
|
134
164
|
if self._config.max_requests_per_second > 50:
|
|
135
|
-
logger.warning(
|
|
165
|
+
self.logger.warning(
|
|
136
166
|
f"Максимальное кол-во запросов в секунду согласно документации Ozon - 50. "
|
|
137
167
|
f"Установлено: {self._config.max_requests_per_second}"
|
|
138
168
|
)
|
|
@@ -184,7 +214,7 @@ class APIManager:
|
|
|
184
214
|
if APIManager._session_manager:
|
|
185
215
|
await APIManager._session_manager.close_all()
|
|
186
216
|
|
|
187
|
-
logger.debug(f"Работа API-клиента для ClientID {self._client_id} завершена")
|
|
217
|
+
self.logger.debug(f"Работа API-клиента для ClientID {self._client_id} завершена")
|
|
188
218
|
|
|
189
219
|
@property
|
|
190
220
|
def client_id(self) -> str:
|
|
@@ -201,6 +231,11 @@ class APIManager:
|
|
|
201
231
|
"""Проверяет закрыт ли клиент."""
|
|
202
232
|
return self._closed
|
|
203
233
|
|
|
234
|
+
@property
|
|
235
|
+
def logger(self):
|
|
236
|
+
"""Возвращает логер экземпляра."""
|
|
237
|
+
return self._instance_logger
|
|
238
|
+
|
|
204
239
|
@classmethod
|
|
205
240
|
def get_instance_count(cls) -> int:
|
|
206
241
|
"""Получает количество активных экземпляров."""
|
|
@@ -210,7 +245,7 @@ class APIManager:
|
|
|
210
245
|
"""Создает декоратор повторов на основе конфигурации."""
|
|
211
246
|
|
|
212
247
|
def log_retry(retry_state):
|
|
213
|
-
logger.debug(
|
|
248
|
+
self.logger.debug(
|
|
214
249
|
f"Попытка {retry_state.attempt_number} совершения запроса для ClientID {self._client_id}"
|
|
215
250
|
f" завершилась исключением: {retry_state.outcome.exception()}"
|
|
216
251
|
)
|
|
@@ -220,6 +255,7 @@ class APIManager:
|
|
|
220
255
|
(
|
|
221
256
|
# Обрабатываемые механизмом retry ошибки
|
|
222
257
|
APIServerError,
|
|
258
|
+
APITooManyRequestsError,
|
|
223
259
|
asyncio.TimeoutError
|
|
224
260
|
)
|
|
225
261
|
),
|
|
@@ -229,7 +265,7 @@ class APIManager:
|
|
|
229
265
|
min=self._config.retry_min_wait,
|
|
230
266
|
max=self._config.retry_max_wait
|
|
231
267
|
),
|
|
232
|
-
before_sleep=before_sleep_log(logger, 30),
|
|
268
|
+
before_sleep=before_sleep_log(self.logger, 30),
|
|
233
269
|
after=log_retry,
|
|
234
270
|
reraise=True,
|
|
235
271
|
)
|
|
@@ -257,13 +293,14 @@ class APIManager:
|
|
|
257
293
|
"error_details": details,
|
|
258
294
|
})
|
|
259
295
|
|
|
260
|
-
|
|
296
|
+
APIManager._class_logger.error(f"Ошибка API: {message}", extra=log_context)
|
|
261
297
|
|
|
262
298
|
error_map = {
|
|
263
299
|
400: APIClientError,
|
|
264
300
|
403: APIForbiddenError,
|
|
265
301
|
404: APINotFoundError,
|
|
266
302
|
409: APIConflictError,
|
|
303
|
+
429: APITooManyRequestsError,
|
|
267
304
|
500: APIServerError,
|
|
268
305
|
}
|
|
269
306
|
|
|
@@ -298,6 +335,7 @@ class APIManager:
|
|
|
298
335
|
APIForbiddenError: При ошибках доступа (403)
|
|
299
336
|
APINotFoundError: При отсутствии ресурса (404)
|
|
300
337
|
APIConflictError: При конфликте данных (409)
|
|
338
|
+
APITooManyRequestsError: При превышении кол-ва запросов (429)
|
|
301
339
|
APIServerError: При ошибках сервера (500)
|
|
302
340
|
APIError: При прочих ошибках
|
|
303
341
|
"""
|
|
@@ -319,7 +357,7 @@ class APIManager:
|
|
|
319
357
|
"has_payload": json is not None,
|
|
320
358
|
}
|
|
321
359
|
|
|
322
|
-
logger.debug("Отправка запроса к API", extra=log_context)
|
|
360
|
+
self.logger.debug("Отправка запроса к API", extra=log_context)
|
|
323
361
|
|
|
324
362
|
await self._ensure_registered()
|
|
325
363
|
|
|
@@ -354,21 +392,21 @@ class APIManager:
|
|
|
354
392
|
if error:
|
|
355
393
|
raise error
|
|
356
394
|
|
|
357
|
-
logger.debug("Успешный ответ от API", extra=log_context)
|
|
395
|
+
self.logger.debug("Успешный ответ от API", extra=log_context)
|
|
358
396
|
return data
|
|
359
397
|
|
|
360
398
|
except asyncio.TimeoutError:
|
|
361
|
-
logger.error("Таймаут запроса к API", extra=log_context)
|
|
399
|
+
self.logger.error("Таймаут запроса к API", extra=log_context)
|
|
362
400
|
raise APIError(408, "Request timeout")
|
|
363
401
|
except asyncio.CancelledError:
|
|
364
|
-
logger.warning("Запрос к API отменен", extra=log_context)
|
|
402
|
+
self.logger.warning("Запрос к API отменен", extra=log_context)
|
|
365
403
|
raise
|
|
366
404
|
except (aiohttp.ClientError, ConnectionError, OSError) as e:
|
|
367
405
|
log_context.update({
|
|
368
406
|
"error_type": type(e).__name__,
|
|
369
407
|
"error_message": str(e)
|
|
370
408
|
})
|
|
371
|
-
logger.error(
|
|
409
|
+
self.logger.error(
|
|
372
410
|
f"Сетевая ошибка при выполнении запроса к API: {str(e)}",
|
|
373
411
|
extra=log_context
|
|
374
412
|
)
|
|
@@ -26,6 +26,10 @@ class APIConflictError(APIError):
|
|
|
26
26
|
"""Ошибка 409: Конфликт запроса."""
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
|
+
class APITooManyRequestsError(APIError):
|
|
30
|
+
"""Ошибка 429: Слишком много запросов."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
29
33
|
|
|
30
34
|
class APIServerError(APIError):
|
|
31
35
|
"""Ошибка 500: Внутренняя ошибка сервера."""
|
|
@@ -22,7 +22,12 @@ class MethodRateLimiterManager:
|
|
|
22
22
|
Обеспечивает раздельные лимиты для каждого метода и client_id.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
cleanup_interval: float = 300.0,
|
|
28
|
+
min_instance_ttl: float = 300.0,
|
|
29
|
+
instance_logger = logger
|
|
30
|
+
) -> None:
|
|
26
31
|
self._rate_limiters: dict[str, AsyncLimiter] = {}
|
|
27
32
|
self._limiter_configs: dict[str, MethodRateLimitConfig] = {}
|
|
28
33
|
self._last_used: dict[str, float] = {}
|
|
@@ -32,12 +37,13 @@ class MethodRateLimiterManager:
|
|
|
32
37
|
self._shutdown = False
|
|
33
38
|
self._cleanup_interval = cleanup_interval
|
|
34
39
|
self._min_instance_ttl = min_instance_ttl
|
|
40
|
+
self._logger = instance_logger
|
|
35
41
|
|
|
36
42
|
async def start(self) -> None:
|
|
37
43
|
"""Запуск фоновых задач менеджера."""
|
|
38
44
|
if self._cleanup_task is None:
|
|
39
45
|
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
40
|
-
|
|
46
|
+
self._logger.debug("Менеджер ограничителей методов запущен")
|
|
41
47
|
|
|
42
48
|
async def shutdown(self) -> None:
|
|
43
49
|
"""Корректное завершение работы менеджера."""
|
|
@@ -47,9 +53,9 @@ class MethodRateLimiterManager:
|
|
|
47
53
|
try:
|
|
48
54
|
await self._cleanup_task
|
|
49
55
|
except asyncio.CancelledError:
|
|
50
|
-
|
|
56
|
+
self._logger.debug("Задача очистки ограничителей методов отменена")
|
|
51
57
|
self._cleanup_task = None
|
|
52
|
-
|
|
58
|
+
self._logger.debug("Менеджер ограничителей методов остановлен")
|
|
53
59
|
|
|
54
60
|
@staticmethod
|
|
55
61
|
def _generate_limiter_key(client_id: str, method_identifier: str) -> str:
|
|
@@ -77,7 +83,7 @@ class MethodRateLimiterManager:
|
|
|
77
83
|
self._rate_limiters[limiter_key] = limiter
|
|
78
84
|
self._limiter_configs[limiter_key] = config
|
|
79
85
|
self._last_instance_creation[limiter_key] = current_time
|
|
80
|
-
|
|
86
|
+
self._logger.debug(
|
|
81
87
|
f"Инициализирован ограничитель запросов для метода {config.method_identifier} "
|
|
82
88
|
f"ClientID {client_id}: {config.limit_requests} запросов в {config.interval_seconds} сек"
|
|
83
89
|
)
|
|
@@ -107,7 +113,7 @@ class MethodRateLimiterManager:
|
|
|
107
113
|
self._last_used.pop(limiter_key, None)
|
|
108
114
|
self._last_instance_creation.pop(limiter_key, None)
|
|
109
115
|
if config:
|
|
110
|
-
|
|
116
|
+
self._logger.debug(f"Очищен ограничитель для метода {config.method_identifier}")
|
|
111
117
|
|
|
112
118
|
async def _cleanup_loop(self) -> None:
|
|
113
119
|
"""Фоновая задача для очистки неиспользуемых ограничителей."""
|
|
@@ -118,7 +124,7 @@ class MethodRateLimiterManager:
|
|
|
118
124
|
except asyncio.CancelledError:
|
|
119
125
|
break
|
|
120
126
|
except Exception as e:
|
|
121
|
-
|
|
127
|
+
self._logger.error(f"Ошибка в cleanup loop методов: {e}")
|
|
122
128
|
await asyncio.sleep(60)
|
|
123
129
|
|
|
124
130
|
async def get_limiter_stats(self) -> dict[str, dict[str, Any]]:
|
|
@@ -164,9 +170,10 @@ def method_rate_limit(limit_requests: int, interval_seconds: float):
|
|
|
164
170
|
|
|
165
171
|
@wraps(method)
|
|
166
172
|
async def wrapper(self, *args, **kwargs):
|
|
173
|
+
_logger = self._logger if hasattr(self, '_logger') else logger
|
|
167
174
|
# Проверяем, что экземпляр имеет необходимые атрибуты
|
|
168
175
|
if not hasattr(self, '_client_id') or not hasattr(self, '_method_rate_limiter_manager'):
|
|
169
|
-
|
|
176
|
+
_logger.warning(
|
|
170
177
|
f"Метод {method_identifier} вызван без инициализации ограничителей. "
|
|
171
178
|
"Ограничения не применяются."
|
|
172
179
|
)
|
|
@@ -174,7 +181,7 @@ def method_rate_limit(limit_requests: int, interval_seconds: float):
|
|
|
174
181
|
|
|
175
182
|
# Дополнительная проверка, что менеджер не None
|
|
176
183
|
if self._method_rate_limiter_manager is None:
|
|
177
|
-
|
|
184
|
+
_logger.warning(
|
|
178
185
|
f"Менеджер ограничителей методов не инициализирован для {method_identifier}. "
|
|
179
186
|
"Ограничения не применяются."
|
|
180
187
|
)
|
|
@@ -187,7 +194,7 @@ def method_rate_limit(limit_requests: int, interval_seconds: float):
|
|
|
187
194
|
|
|
188
195
|
# Применяем ограничитель запросов
|
|
189
196
|
async with method_limiter:
|
|
190
|
-
|
|
197
|
+
_logger.debug(
|
|
191
198
|
f"Применен ограничитель метода {method_identifier} для ClientID {self._client_id}: "
|
|
192
199
|
f"{limit_requests} запросов в {interval_seconds} сек"
|
|
193
200
|
)
|
|
@@ -27,7 +27,12 @@ class RateLimiterManager:
|
|
|
27
27
|
Обеспечивает общий лимит запросов для всех экземпляров с одинаковым client_id.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
cleanup_interval: float = 300.0,
|
|
33
|
+
min_instance_ttl: float = 300.0,
|
|
34
|
+
instance_logger = logger,
|
|
35
|
+
) -> None:
|
|
31
36
|
self._rate_limiters: dict[str, AsyncLimiter] = {}
|
|
32
37
|
self._instance_refs: dict[str, set[int]] = {}
|
|
33
38
|
self._configs: dict[str, RateLimiterConfig] = {}
|
|
@@ -37,12 +42,13 @@ class RateLimiterManager:
|
|
|
37
42
|
self._shutdown = False
|
|
38
43
|
self._cleanup_interval = cleanup_interval
|
|
39
44
|
self._min_instance_ttl = min_instance_ttl
|
|
45
|
+
self._logger = instance_logger
|
|
40
46
|
|
|
41
47
|
async def start(self) -> None:
|
|
42
48
|
"""Запуск фоновых задач менеджера."""
|
|
43
49
|
if self._cleanup_task is None:
|
|
44
50
|
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
45
|
-
|
|
51
|
+
self._logger.debug(f"Менеджер ограничителей общих клиентских запросов запущен")
|
|
46
52
|
|
|
47
53
|
async def shutdown(self) -> None:
|
|
48
54
|
"""Корректное завершение работы менеджера."""
|
|
@@ -52,10 +58,10 @@ class RateLimiterManager:
|
|
|
52
58
|
try:
|
|
53
59
|
await self._cleanup_task
|
|
54
60
|
except asyncio.CancelledError:
|
|
55
|
-
|
|
61
|
+
self._logger.debug("Задача очистки общих ограничителей клиентских запросов отменена")
|
|
56
62
|
self._cleanup_task = None
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
self._logger.debug("Менеджер общих ограничителей клиентских запросов остановлен")
|
|
59
65
|
|
|
60
66
|
async def get_limiter(self, client_id: str, config: RateLimiterConfig) -> AsyncLimiter:
|
|
61
67
|
"""
|
|
@@ -77,7 +83,7 @@ class RateLimiterManager:
|
|
|
77
83
|
self._instance_refs[client_id] = set()
|
|
78
84
|
if client_id not in self._last_instance_creation:
|
|
79
85
|
self._last_instance_creation[client_id] = time.monotonic()
|
|
80
|
-
|
|
86
|
+
self._logger.debug(f"Инициализирован новый общий ограничитель запросов для ClientID {client_id}: {config}")
|
|
81
87
|
|
|
82
88
|
return self._rate_limiters[client_id]
|
|
83
89
|
|
|
@@ -95,7 +101,7 @@ class RateLimiterManager:
|
|
|
95
101
|
self._instance_refs[client_id] = set()
|
|
96
102
|
self._last_instance_creation[client_id] = current_time
|
|
97
103
|
self._instance_refs[client_id].add(instance_id)
|
|
98
|
-
|
|
104
|
+
self._logger.debug(f"Зарегистрировано подключение к API {instance_id} для ClientID {client_id}")
|
|
99
105
|
|
|
100
106
|
async def unregister_instance(self, client_id: str, instance_id: int) -> None:
|
|
101
107
|
"""
|
|
@@ -108,7 +114,7 @@ class RateLimiterManager:
|
|
|
108
114
|
async with self._lock:
|
|
109
115
|
if client_id in self._instance_refs:
|
|
110
116
|
self._instance_refs[client_id].discard(instance_id)
|
|
111
|
-
|
|
117
|
+
self._logger.debug(f"Отменена регистрация подключения к API {instance_id} для ClientID {client_id}")
|
|
112
118
|
|
|
113
119
|
async def _cleanup_unused_limiters(self) -> None:
|
|
114
120
|
"""Очистка неиспользуемых ограничителей кол-ва запросов с учетом минимального времени жизни."""
|
|
@@ -130,7 +136,7 @@ class RateLimiterManager:
|
|
|
130
136
|
self._configs.pop(client_id, None)
|
|
131
137
|
self._instance_refs.pop(client_id, None)
|
|
132
138
|
self._last_instance_creation.pop(client_id, None)
|
|
133
|
-
|
|
139
|
+
self._logger.debug(f"Очищены ресурсы общего ограничителя запросов для ClientID {client_id}")
|
|
134
140
|
|
|
135
141
|
async def _cleanup_loop(self) -> None:
|
|
136
142
|
"""Фоновая задача для очистки неиспользуемых ограничителей кол-ва запросов."""
|
|
@@ -141,7 +147,7 @@ class RateLimiterManager:
|
|
|
141
147
|
except asyncio.CancelledError:
|
|
142
148
|
break
|
|
143
149
|
except Exception as e:
|
|
144
|
-
|
|
150
|
+
self._logger.error(f"Ошибка в cleanup loop: {e}")
|
|
145
151
|
await asyncio.sleep(60) # Пауза при ошибках
|
|
146
152
|
|
|
147
153
|
async def get_active_client_ids(self) -> list[str]:
|
|
@@ -9,12 +9,18 @@ from loguru import logger
|
|
|
9
9
|
class SessionManager:
|
|
10
10
|
"""Менеджер для управления HTTP-сессиями."""
|
|
11
11
|
|
|
12
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
timeout: float = 30.0,
|
|
15
|
+
connector_limit: int = 100,
|
|
16
|
+
instance_logger = logger
|
|
17
|
+
) -> None:
|
|
13
18
|
self._sessions: dict[str, ClientSession] = {}
|
|
14
19
|
self._session_refs: dict[str, set[int]] = {}
|
|
15
20
|
self._lock = asyncio.Lock()
|
|
16
21
|
self._timeout = ClientTimeout(total=timeout)
|
|
17
22
|
self._connector_limit = connector_limit
|
|
23
|
+
self._logger = instance_logger
|
|
18
24
|
|
|
19
25
|
@asynccontextmanager
|
|
20
26
|
async def get_session(self, client_id: str, api_key: str, instance_id: int) -> AsyncIterator[ClientSession]:
|
|
@@ -40,7 +46,7 @@ class SessionManager:
|
|
|
40
46
|
connector=TCPConnector(limit=self._connector_limit)
|
|
41
47
|
)
|
|
42
48
|
self._session_refs[client_id] = set()
|
|
43
|
-
|
|
49
|
+
self._logger.debug(f"Создана новая сессия для ClientID {client_id}")
|
|
44
50
|
|
|
45
51
|
self._session_refs[client_id].add(instance_id)
|
|
46
52
|
|
|
@@ -61,7 +67,7 @@ class SessionManager:
|
|
|
61
67
|
self._session_refs.pop(client_id, None)
|
|
62
68
|
if not session.closed:
|
|
63
69
|
await session.close()
|
|
64
|
-
|
|
70
|
+
self._logger.debug(f"Сессия для ClientID {client_id} закрыта")
|
|
65
71
|
|
|
66
72
|
async def close_all(self) -> None:
|
|
67
73
|
"""Закрывает все сессии."""
|
|
@@ -69,7 +75,7 @@ class SessionManager:
|
|
|
69
75
|
for client_id, session in list(self._sessions.items()):
|
|
70
76
|
if not session.closed:
|
|
71
77
|
await session.close()
|
|
72
|
-
|
|
78
|
+
self._logger.debug(f"Сессия для ClientID {client_id} закрыта")
|
|
73
79
|
self._sessions.clear()
|
|
74
80
|
self._session_refs.clear()
|
|
75
|
-
|
|
81
|
+
self._logger.debug("Все сессии закрыты")
|
|
@@ -164,7 +164,7 @@ class SellerFBSAPI(APIManager):
|
|
|
164
164
|
)
|
|
165
165
|
return PostingFBSListResponse(**response)
|
|
166
166
|
|
|
167
|
-
@method_rate_limit(limit_requests=
|
|
167
|
+
@method_rate_limit(limit_requests=2, interval_seconds=1)
|
|
168
168
|
async def posting_fbs_get(
|
|
169
169
|
self: "SellerFBSAPI",
|
|
170
170
|
request: PostingFBSGetRequest
|
|
@@ -172,7 +172,7 @@ class SellerFBSAPI(APIManager):
|
|
|
172
172
|
"""Метод для получения информации об отправлении FBS по его номеру.
|
|
173
173
|
|
|
174
174
|
Notes:
|
|
175
|
-
•
|
|
175
|
+
• Метод часто возвращает 429 (TooManyRequestsError), поэтому установлено ограничение 2 запроса в секунду (экспериментальное значение).
|
|
176
176
|
• Чтобы получать актуальную дату отгрузки, регулярно обновляйте информацию об отправлениях или подключите пуш-уведомления.
|
|
177
177
|
• Для получения дополнительных данных используйте параметр `with_` в запросе.
|
|
178
178
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ozonapi-async
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Асинхронный клиент для работы с API маркетплейса Ozon
|
|
5
5
|
Author: Alexander Ulianov
|
|
6
6
|
License: MIT
|
|
@@ -113,6 +113,7 @@ async def main():
|
|
|
113
113
|
config = SellerAPIConfig(
|
|
114
114
|
client_id="your_client_id",
|
|
115
115
|
api_key="your_api_key",
|
|
116
|
+
log_level="DEBUG"
|
|
116
117
|
)
|
|
117
118
|
|
|
118
119
|
async with SellerAPI(config=config) as api:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/__init__.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/delivery.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/localization.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/postings.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/prices.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/products.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/requests.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/warehouses.py
RENAMED
|
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
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/v1__barcode_add.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/beta/v1__analytics_stocks.py
RENAMED
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/__init__.py
RENAMED
|
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
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_get.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_list.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/__init__.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/base.py
RENAMED
|
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
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_list.py
RENAMED
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.2 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|