ozonapi-async 0.7.3__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.3 → ozonapi_async-0.7.4}/PKG-INFO +2 -1
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/pyproject.toml +1 -1
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/readme.md +1 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/__init__.py +1 -1
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/config.py +1 -1
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/core.py +48 -23
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/method_rate_limiter.py +17 -10
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/rate_limiter.py +15 -9
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/sessions.py +11 -5
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/PKG-INFO +2 -1
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/LICENSE +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/setup.cfg +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/delivery.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/localization.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/postings.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/prices.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/products.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/requests.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/warehouses.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/core/exceptions.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/attributes_and_characteristics.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/barcodes.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/beta.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/fbs.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/prices_and_stocks.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/products.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/methods/warehouses.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/base.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_attribute.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_attribute_values.py +0 -0
- {ozonapi_async-0.7.3 → 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.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/attributes_and_characteristics/v1__description_category_tree.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/v1__barcode_add.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/barcodes/v1__barcode_generate.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/base.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/beta/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/beta/v1__analytics_stocks.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__addressee.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__analytics_data.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__barcodes.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__cancellation.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__customer.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__customer_address.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__delivery_method.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__filter_with.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__financial_data.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__financial_data_products.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__legal_info.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__optional.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__posting.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__product.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__requirements.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/entities/posting__tariffication.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__fbs_posting_delivering.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__posting_fbs_get_by_barcode.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v2__posting_fbs_product_change.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_get.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_unfulfilled_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_multiboxqty_set.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/mixins.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/base.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v1__product_import_prices.py +0 -0
- {ozonapi_async-0.7.3 → 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.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v2__products_stocks.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v4__product_info_stocks.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/v5__product_info_prices.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/base.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_archive.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_attributes_update.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_import_by_sku.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_import_info.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_info_subscription.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_rating_by_sku.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_related_sku_get.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_unarchive.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v1__product_update_offer_id.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v2__product_pictures_info.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v2__products_delete.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_import.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_info_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v3__product_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/products/v4__product_info_attributes.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/__init__.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/v1__delivery_method_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/warehouses/v1__warehouse_list.py +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/SOURCES.txt +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/dependency_links.txt +0 -0
- {ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi_async.egg-info/requires.txt +0 -0
- {ozonapi_async-0.7.3 → 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
|
|
@@ -27,13 +28,6 @@ from .exceptions import (
|
|
|
27
28
|
APIServerError, APITooManyRequestsError,
|
|
28
29
|
)
|
|
29
30
|
|
|
30
|
-
logger.remove()
|
|
31
|
-
logger.add(
|
|
32
|
-
sys.stderr,
|
|
33
|
-
level=APIConfig().log_level,
|
|
34
|
-
enqueue=True,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
31
|
|
|
38
32
|
class APIManager:
|
|
39
33
|
"""
|
|
@@ -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,
|
|
@@ -70,6 +73,7 @@ class APIManager:
|
|
|
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,21 +82,24 @@ 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
105
|
def load_config(cls, user_config: APIConfig | None = None) -> APIConfig:
|
|
@@ -110,6 +117,19 @@ class APIManager:
|
|
|
110
117
|
)
|
|
111
118
|
)
|
|
112
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
|
|
132
|
+
|
|
113
133
|
@classmethod
|
|
114
134
|
async def initialize(cls) -> None:
|
|
115
135
|
"""Инициализация ресурсов."""
|
|
@@ -119,7 +139,7 @@ class APIManager:
|
|
|
119
139
|
if cls._method_rate_limiter_manager:
|
|
120
140
|
await cls._method_rate_limiter_manager.start()
|
|
121
141
|
cls._initialized = True
|
|
122
|
-
|
|
142
|
+
cls._class_logger.debug("Выполнена инициализация ресурсов API-менеджера")
|
|
123
143
|
|
|
124
144
|
@classmethod
|
|
125
145
|
async def shutdown(cls) -> None:
|
|
@@ -132,7 +152,7 @@ class APIManager:
|
|
|
132
152
|
if cls._session_manager:
|
|
133
153
|
await cls._session_manager.close_all()
|
|
134
154
|
cls._initialized = False
|
|
135
|
-
|
|
155
|
+
cls._class_logger.debug("Выполнена деинициализация ресурсов API-менеджера")
|
|
136
156
|
|
|
137
157
|
def _validate_credentials(self) -> None:
|
|
138
158
|
"""Валидация учетных данных."""
|
|
@@ -142,7 +162,7 @@ class APIManager:
|
|
|
142
162
|
raise ValueError("api_key не должен быть пустой строкой")
|
|
143
163
|
|
|
144
164
|
if self._config.max_requests_per_second > 50:
|
|
145
|
-
logger.warning(
|
|
165
|
+
self.logger.warning(
|
|
146
166
|
f"Максимальное кол-во запросов в секунду согласно документации Ozon - 50. "
|
|
147
167
|
f"Установлено: {self._config.max_requests_per_second}"
|
|
148
168
|
)
|
|
@@ -194,7 +214,7 @@ class APIManager:
|
|
|
194
214
|
if APIManager._session_manager:
|
|
195
215
|
await APIManager._session_manager.close_all()
|
|
196
216
|
|
|
197
|
-
logger.debug(f"Работа API-клиента для ClientID {self._client_id} завершена")
|
|
217
|
+
self.logger.debug(f"Работа API-клиента для ClientID {self._client_id} завершена")
|
|
198
218
|
|
|
199
219
|
@property
|
|
200
220
|
def client_id(self) -> str:
|
|
@@ -211,6 +231,11 @@ class APIManager:
|
|
|
211
231
|
"""Проверяет закрыт ли клиент."""
|
|
212
232
|
return self._closed
|
|
213
233
|
|
|
234
|
+
@property
|
|
235
|
+
def logger(self):
|
|
236
|
+
"""Возвращает логер экземпляра."""
|
|
237
|
+
return self._instance_logger
|
|
238
|
+
|
|
214
239
|
@classmethod
|
|
215
240
|
def get_instance_count(cls) -> int:
|
|
216
241
|
"""Получает количество активных экземпляров."""
|
|
@@ -220,7 +245,7 @@ class APIManager:
|
|
|
220
245
|
"""Создает декоратор повторов на основе конфигурации."""
|
|
221
246
|
|
|
222
247
|
def log_retry(retry_state):
|
|
223
|
-
logger.debug(
|
|
248
|
+
self.logger.debug(
|
|
224
249
|
f"Попытка {retry_state.attempt_number} совершения запроса для ClientID {self._client_id}"
|
|
225
250
|
f" завершилась исключением: {retry_state.outcome.exception()}"
|
|
226
251
|
)
|
|
@@ -240,7 +265,7 @@ class APIManager:
|
|
|
240
265
|
min=self._config.retry_min_wait,
|
|
241
266
|
max=self._config.retry_max_wait
|
|
242
267
|
),
|
|
243
|
-
before_sleep=before_sleep_log(logger, 30),
|
|
268
|
+
before_sleep=before_sleep_log(self.logger, 30),
|
|
244
269
|
after=log_retry,
|
|
245
270
|
reraise=True,
|
|
246
271
|
)
|
|
@@ -268,7 +293,7 @@ class APIManager:
|
|
|
268
293
|
"error_details": details,
|
|
269
294
|
})
|
|
270
295
|
|
|
271
|
-
|
|
296
|
+
APIManager._class_logger.error(f"Ошибка API: {message}", extra=log_context)
|
|
272
297
|
|
|
273
298
|
error_map = {
|
|
274
299
|
400: APIClientError,
|
|
@@ -332,7 +357,7 @@ class APIManager:
|
|
|
332
357
|
"has_payload": json is not None,
|
|
333
358
|
}
|
|
334
359
|
|
|
335
|
-
logger.debug("Отправка запроса к API", extra=log_context)
|
|
360
|
+
self.logger.debug("Отправка запроса к API", extra=log_context)
|
|
336
361
|
|
|
337
362
|
await self._ensure_registered()
|
|
338
363
|
|
|
@@ -367,21 +392,21 @@ class APIManager:
|
|
|
367
392
|
if error:
|
|
368
393
|
raise error
|
|
369
394
|
|
|
370
|
-
logger.debug("Успешный ответ от API", extra=log_context)
|
|
395
|
+
self.logger.debug("Успешный ответ от API", extra=log_context)
|
|
371
396
|
return data
|
|
372
397
|
|
|
373
398
|
except asyncio.TimeoutError:
|
|
374
|
-
logger.error("Таймаут запроса к API", extra=log_context)
|
|
399
|
+
self.logger.error("Таймаут запроса к API", extra=log_context)
|
|
375
400
|
raise APIError(408, "Request timeout")
|
|
376
401
|
except asyncio.CancelledError:
|
|
377
|
-
logger.warning("Запрос к API отменен", extra=log_context)
|
|
402
|
+
self.logger.warning("Запрос к API отменен", extra=log_context)
|
|
378
403
|
raise
|
|
379
404
|
except (aiohttp.ClientError, ConnectionError, OSError) as e:
|
|
380
405
|
log_context.update({
|
|
381
406
|
"error_type": type(e).__name__,
|
|
382
407
|
"error_message": str(e)
|
|
383
408
|
})
|
|
384
|
-
logger.error(
|
|
409
|
+
self.logger.error(
|
|
385
410
|
f"Сетевая ошибка при выполнении запроса к API: {str(e)}",
|
|
386
411
|
extra=log_context
|
|
387
412
|
)
|
|
@@ -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("Все сессии закрыты")
|
|
@@ -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.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/__init__.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/delivery.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/localization.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/postings.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/prices.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/products.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/common/enumerations/requests.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → 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
|
|
File without changes
|
|
File without changes
|
{ozonapi_async-0.7.3 → 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.3 → 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.3 → 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.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/fbs/v3__posting_fbs_get.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → 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.3 → ozonapi_async-0.7.4}/src/ozonapi/seller/schemas/prices_and_stocks/__init__.py
RENAMED
|
File without changes
|
{ozonapi_async-0.7.3 → 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.3 → 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.3 → 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
|