vinted-api-kit 1.0.1__tar.gz → 1.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.
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/PKG-INFO +3 -20
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/README.md +2 -19
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/pyproject.toml +4 -1
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/__init__.py +3 -1
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/api/catalog.py +20 -16
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/api/items.py +8 -23
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/client.py +3 -4
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/exceptions.py +4 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/models/item.py +29 -7
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/session.py +5 -2
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/utils.py +6 -1
- vinted_api_kit-1.0.1/.editorconfig +0 -22
- vinted_api_kit-1.0.1/.github/workflows/ci.yaml +0 -35
- vinted_api_kit-1.0.1/.github/workflows/coverage.yaml +0 -31
- vinted_api_kit-1.0.1/.pre-commit-config.yaml +0 -27
- vinted_api_kit-1.0.1/CHANGELOG.md +0 -78
- vinted_api_kit-1.0.1/Makefile +0 -91
- vinted_api_kit-1.0.1/TECHNICAL_BACKLOG.md +0 -242
- vinted_api_kit-1.0.1/assets/logo.png +0 -0
- vinted_api_kit-1.0.1/examples/base_usage.py +0 -32
- vinted_api_kit-1.0.1/examples/raw_data.py +0 -23
- vinted_api_kit-1.0.1/examples/with_proxy.py +0 -29
- vinted_api_kit-1.0.1/test_library_request.py +0 -31
- vinted_api_kit-1.0.1/tests/__init__.py +0 -0
- vinted_api_kit-1.0.1/tests/api/__init__.py +0 -0
- vinted_api_kit-1.0.1/tests/api/test_catalog.py +0 -131
- vinted_api_kit-1.0.1/tests/api/test_items.py +0 -82
- vinted_api_kit-1.0.1/tests/conftest.py +0 -74
- vinted_api_kit-1.0.1/tests/models/__init__.py +0 -0
- vinted_api_kit-1.0.1/tests/models/test_catalog_item.py +0 -54
- vinted_api_kit-1.0.1/tests/models/test_config.py +0 -56
- vinted_api_kit-1.0.1/tests/models/test_detailed_item.py +0 -41
- vinted_api_kit-1.0.1/tests/storage/__init__.py +0 -0
- vinted_api_kit-1.0.1/tests/storage/test_json.py +0 -95
- vinted_api_kit-1.0.1/tests/storage/test_mozilla.py +0 -49
- vinted_api_kit-1.0.1/tests/storage/test_pickle.py +0 -59
- vinted_api_kit-1.0.1/tests/test_auth.py +0 -62
- vinted_api_kit-1.0.1/tests/test_client.py +0 -53
- vinted_api_kit-1.0.1/tests/test_session.py +0 -272
- vinted_api_kit-1.0.1/tests/test_utils.py +0 -33
- vinted_api_kit-1.0.1/tox.ini +0 -11
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/.gitignore +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/LICENSE +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/api/__init__.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/api/base.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/auth.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/constants.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/models/__init__.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/models/config.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/storage/__init__.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/storage/base.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/storage/json.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/storage/mozilla.py +0 -0
- {vinted_api_kit-1.0.1 → vinted_api_kit-1.1.0}/vinted/storage/pickle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vinted-api-kit
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Lightweight asynchronous Python client library for accessing Vinted API and scraping item data.
|
|
5
5
|
Project-URL: Homepage, https://github.com/vlymar1/vinted-api-kit
|
|
6
6
|
Project-URL: Documentation, https://github.com/vlymar1/vinted-api-kit
|
|
@@ -50,7 +50,6 @@ Description-Content-Type: text/markdown
|
|
|
50
50
|
- 🚀 **Asynchronous** - Built with asyncio for high performance
|
|
51
51
|
- 🌍 **Auto-locale Detection** - Automatically detects locale from URL
|
|
52
52
|
- 🔍 **Item Search** - Search catalog with filters, sorting and pagination
|
|
53
|
-
- 📦 **Item Details** - Get complete item information with rich metadata
|
|
54
53
|
- 🍪 **Cookie Persistence** - Automatic session management with multiple storage formats
|
|
55
54
|
- 🔐 **Proxy Support** - Simple string-based proxy configuration
|
|
56
55
|
- 📊 **Type Hints** - Full typing support with Literal types for better IDE experience
|
|
@@ -165,22 +164,7 @@ raw_items = await client.search_items(
|
|
|
165
164
|
|
|
166
165
|
### Item Details
|
|
167
166
|
|
|
168
|
-
>
|
|
169
|
-
```python
|
|
170
|
-
item = await client.item_details(
|
|
171
|
-
url="https://www.vinted.com/items/1234567890"
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
print(f"Title: {item.title}")
|
|
175
|
-
print(f"Brand: {item.brand_title}")
|
|
176
|
-
print(f"Size: {item.size_title}")
|
|
177
|
-
print(f"Price: {item.price} {item.currency}")
|
|
178
|
-
print(f"Total: {item.total_item_price}")
|
|
179
|
-
print(f"Description: {item.description}")
|
|
180
|
-
|
|
181
|
-
# Or get raw JSON
|
|
182
|
-
raw_item = await client.item_details(url, raw_data=True)
|
|
183
|
-
```
|
|
167
|
+
> ⚠️ **Deprecated since 1.1.0.** Vinted removed the item details API, so `item_details()` raises `VintedDeprecatedError`.
|
|
184
168
|
|
|
185
169
|
### Parameters
|
|
186
170
|
|
|
@@ -352,8 +336,7 @@ vinted-api-kit/
|
|
|
352
336
|
│ ├── constants.py # Constants and type definitions
|
|
353
337
|
│ ├── exceptions.py # Custom exceptions
|
|
354
338
|
│ └── utils.py # Utility functions
|
|
355
|
-
|
|
356
|
-
└── examples/ # Usage examples
|
|
339
|
+
└── tests/ # Test suite
|
|
357
340
|
```
|
|
358
341
|
|
|
359
342
|
### Testing
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
- 🚀 **Asynchronous** - Built with asyncio for high performance
|
|
19
19
|
- 🌍 **Auto-locale Detection** - Automatically detects locale from URL
|
|
20
20
|
- 🔍 **Item Search** - Search catalog with filters, sorting and pagination
|
|
21
|
-
- 📦 **Item Details** - Get complete item information with rich metadata
|
|
22
21
|
- 🍪 **Cookie Persistence** - Automatic session management with multiple storage formats
|
|
23
22
|
- 🔐 **Proxy Support** - Simple string-based proxy configuration
|
|
24
23
|
- 📊 **Type Hints** - Full typing support with Literal types for better IDE experience
|
|
@@ -133,22 +132,7 @@ raw_items = await client.search_items(
|
|
|
133
132
|
|
|
134
133
|
### Item Details
|
|
135
134
|
|
|
136
|
-
>
|
|
137
|
-
```python
|
|
138
|
-
item = await client.item_details(
|
|
139
|
-
url="https://www.vinted.com/items/1234567890"
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
print(f"Title: {item.title}")
|
|
143
|
-
print(f"Brand: {item.brand_title}")
|
|
144
|
-
print(f"Size: {item.size_title}")
|
|
145
|
-
print(f"Price: {item.price} {item.currency}")
|
|
146
|
-
print(f"Total: {item.total_item_price}")
|
|
147
|
-
print(f"Description: {item.description}")
|
|
148
|
-
|
|
149
|
-
# Or get raw JSON
|
|
150
|
-
raw_item = await client.item_details(url, raw_data=True)
|
|
151
|
-
```
|
|
135
|
+
> ⚠️ **Deprecated since 1.1.0.** Vinted removed the item details API, so `item_details()` raises `VintedDeprecatedError`.
|
|
152
136
|
|
|
153
137
|
### Parameters
|
|
154
138
|
|
|
@@ -320,8 +304,7 @@ vinted-api-kit/
|
|
|
320
304
|
│ ├── constants.py # Constants and type definitions
|
|
321
305
|
│ ├── exceptions.py # Custom exceptions
|
|
322
306
|
│ └── utils.py # Utility functions
|
|
323
|
-
|
|
324
|
-
└── examples/ # Usage examples
|
|
307
|
+
└── tests/ # Test suite
|
|
325
308
|
```
|
|
326
309
|
|
|
327
310
|
### Testing
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vinted-api-kit"
|
|
7
|
-
version = "1.0
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "Lightweight asynchronous Python client library for accessing Vinted API and scraping item data."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Lymar Volodymyr",email = "volodymyr.lymar1@gmail.com"},
|
|
@@ -71,6 +71,9 @@ all = ["format", "lint", "type-check", "test-cov"]
|
|
|
71
71
|
[tool.hatch.build.targets.wheel]
|
|
72
72
|
packages = ["vinted"]
|
|
73
73
|
|
|
74
|
+
[tool.hatch.build.targets.sdist]
|
|
75
|
+
include = ["vinted", "README.md", "LICENSE"]
|
|
76
|
+
|
|
74
77
|
[tool.ruff]
|
|
75
78
|
exclude = [
|
|
76
79
|
".git",
|
|
@@ -4,6 +4,7 @@ from vinted.exceptions import (
|
|
|
4
4
|
VintedAPIError,
|
|
5
5
|
VintedAuthError,
|
|
6
6
|
VintedConfigError,
|
|
7
|
+
VintedDeprecatedError,
|
|
7
8
|
VintedError,
|
|
8
9
|
VintedNetworkError,
|
|
9
10
|
VintedRateLimitError,
|
|
@@ -11,7 +12,7 @@ from vinted.exceptions import (
|
|
|
11
12
|
)
|
|
12
13
|
from vinted.models.item import CatalogItem, DetailedItem
|
|
13
14
|
|
|
14
|
-
__version__ = "1.0
|
|
15
|
+
__version__ = "1.1.0"
|
|
15
16
|
|
|
16
17
|
__all__ = [
|
|
17
18
|
"VintedClient",
|
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
|
24
25
|
"VintedNetworkError",
|
|
25
26
|
"VintedConfigError",
|
|
26
27
|
"VintedValidationError",
|
|
28
|
+
"VintedDeprecatedError",
|
|
27
29
|
"SortOrder",
|
|
28
30
|
"StorageFormat",
|
|
29
31
|
]
|
|
@@ -8,7 +8,7 @@ and returns either raw JSON items or parsed `CatalogItem` instances.
|
|
|
8
8
|
import logging
|
|
9
9
|
import time
|
|
10
10
|
from typing import Any, Union
|
|
11
|
-
from urllib.parse import parse_qsl, urlparse
|
|
11
|
+
from urllib.parse import parse_qsl, urljoin, urlparse
|
|
12
12
|
|
|
13
13
|
from vinted.exceptions import VintedValidationError
|
|
14
14
|
|
|
@@ -50,7 +50,7 @@ class CatalogAPI(BaseAPI):
|
|
|
50
50
|
"""
|
|
51
51
|
self._validate_catalog_url(url)
|
|
52
52
|
self.session.configure_from_url(url)
|
|
53
|
-
api_url = f"{self.base_url}/
|
|
53
|
+
api_url = f"{self.base_url}/web/gateway/svc-catalogue/items"
|
|
54
54
|
|
|
55
55
|
params = self._build_params(url, per_page, page)
|
|
56
56
|
params["time"] = timestamp or int(time.time())
|
|
@@ -69,7 +69,11 @@ class CatalogAPI(BaseAPI):
|
|
|
69
69
|
if raw_data:
|
|
70
70
|
return items
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
catalog_items = [CatalogItem(raw_data=item) for item in items]
|
|
73
|
+
for catalog_item in catalog_items:
|
|
74
|
+
catalog_item.url = urljoin(f"{self.base_url}/", catalog_item.url)
|
|
75
|
+
|
|
76
|
+
return catalog_items
|
|
73
77
|
|
|
74
78
|
def _build_params(self, url: str, per_page: int, page: int) -> dict:
|
|
75
79
|
"""Build API query params from a public catalog URL.
|
|
@@ -83,17 +87,19 @@ class CatalogAPI(BaseAPI):
|
|
|
83
87
|
catalog_id = self._extract_catalog_id(parsed.path)
|
|
84
88
|
catalog_ids_query = self._join_values(query_params, "catalog[]")
|
|
85
89
|
|
|
90
|
+
status_ids = self._extract_values(query_params, "status_ids[]") + self._extract_values(
|
|
91
|
+
query_params, "status[]"
|
|
92
|
+
)
|
|
93
|
+
|
|
86
94
|
params = {
|
|
87
95
|
"search_text": "+".join(self._extract_values(query_params, "search_text")),
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"city_ids": self._join_values(query_params, "city_ids[]"),
|
|
96
|
-
"is_for_swap": ",".join("1" for _ in self._extract_values(query_params, "disposal[]")),
|
|
96
|
+
"attribute_ids[catalog]": str(catalog_id) if catalog_id else catalog_ids_query,
|
|
97
|
+
"attribute_ids[color]": self._join_values(query_params, "color_ids[]"),
|
|
98
|
+
"attribute_ids[brand]": self._join_values(query_params, "brand_ids[]"),
|
|
99
|
+
"attribute_ids[size]": self._join_values(query_params, "size_ids[]"),
|
|
100
|
+
"attribute_ids[material]": self._join_values(query_params, "material_ids[]"),
|
|
101
|
+
"attribute_ids[status]": ",".join(status_ids),
|
|
102
|
+
"attribute_ids[patterns]": self._join_values(query_params, "patterns_ids[]"),
|
|
97
103
|
"currency": self._join_values(query_params, "currency"),
|
|
98
104
|
"price_to": self._join_values(query_params, "price_to"),
|
|
99
105
|
"price_from": self._join_values(query_params, "price_from"),
|
|
@@ -113,10 +119,8 @@ class CatalogAPI(BaseAPI):
|
|
|
113
119
|
raise VintedValidationError("Invalid catalog URL: expected an absolute HTTP(S) URL")
|
|
114
120
|
|
|
115
121
|
path = parsed.path.rstrip("/")
|
|
116
|
-
if path and not
|
|
117
|
-
raise VintedValidationError(
|
|
118
|
-
"Invalid catalog URL: expected path '/catalog' or '/api/v2/catalog/items'"
|
|
119
|
-
)
|
|
122
|
+
if path and not path.startswith("/catalog"):
|
|
123
|
+
raise VintedValidationError("Invalid catalog URL: expected path '/catalog'")
|
|
120
124
|
|
|
121
125
|
@staticmethod
|
|
122
126
|
def _extract_catalog_id(path: str) -> int | None:
|
|
@@ -6,10 +6,10 @@ URL and returns either a parsed `DetailedItem` or raw JSON.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Union
|
|
10
10
|
from urllib.parse import urlparse
|
|
11
11
|
|
|
12
|
-
from vinted.exceptions import VintedValidationError
|
|
12
|
+
from vinted.exceptions import VintedDeprecatedError, VintedValidationError
|
|
13
13
|
|
|
14
14
|
from ..models import DetailedItem
|
|
15
15
|
from .base import BaseAPI
|
|
@@ -29,33 +29,18 @@ class ItemsAPI(BaseAPI):
|
|
|
29
29
|
url: str,
|
|
30
30
|
raw_data: bool = False,
|
|
31
31
|
) -> Union[DetailedItem, dict]:
|
|
32
|
-
"""
|
|
32
|
+
"""Deprecated: always raises `VintedDeprecatedError`.
|
|
33
|
+
|
|
34
|
+
Vinted removed the item details API endpoint.
|
|
33
35
|
|
|
34
36
|
Args:
|
|
35
37
|
url: Public Vinted item URL.
|
|
36
38
|
raw_data: If True, return raw JSON dictionary instead of
|
|
37
39
|
a `DetailedItem` instance.
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
`DetailedItem` or raw dict depending on `raw_data`.
|
|
41
40
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
api_url = f"{self.base_url}/api/v2/items/{product_id}/details"
|
|
46
|
-
|
|
47
|
-
logger.debug("Fetching item details: %s", api_url)
|
|
48
|
-
|
|
49
|
-
response = await self.session.request(api_url)
|
|
50
|
-
data = response.json()
|
|
51
|
-
item_data: dict[Any, Any] = data.get("item", {})
|
|
52
|
-
|
|
53
|
-
logger.debug("Item details fetched successfully")
|
|
54
|
-
|
|
55
|
-
if raw_data:
|
|
56
|
-
return item_data
|
|
57
|
-
|
|
58
|
-
return DetailedItem(raw_data=item_data)
|
|
41
|
+
raise VintedDeprecatedError(
|
|
42
|
+
"item_details() no longer works: Vinted removed the item details API"
|
|
43
|
+
)
|
|
59
44
|
|
|
60
45
|
@staticmethod
|
|
61
46
|
def _extract_product_id(url: str) -> str:
|
|
@@ -163,14 +163,13 @@ class VintedClient:
|
|
|
163
163
|
url: str,
|
|
164
164
|
raw_data: bool = False,
|
|
165
165
|
) -> Union[DetailedItem, dict]:
|
|
166
|
-
"""
|
|
166
|
+
"""Deprecated: always raises `VintedDeprecatedError`.
|
|
167
|
+
|
|
168
|
+
Vinted removed the item details API endpoint.
|
|
167
169
|
|
|
168
170
|
Args:
|
|
169
171
|
url: Item page URL or API endpoint.
|
|
170
172
|
raw_data: When True returns raw dict instead of `DetailedItem`.
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
`DetailedItem` or raw dict when `raw_data` is True.
|
|
174
173
|
"""
|
|
175
174
|
return await self._items.get_details(url=url, raw_data=raw_data)
|
|
176
175
|
|
|
@@ -8,6 +8,8 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from ..exceptions import VintedDeprecatedError
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
13
15
|
class CatalogItem:
|
|
@@ -15,7 +17,12 @@ class CatalogItem:
|
|
|
15
17
|
|
|
16
18
|
The class stores the original `raw_data` (hidden from the repr)
|
|
17
19
|
and extracts commonly-used fields like `id`, `title`, `price` and
|
|
18
|
-
`photo` for convenience.
|
|
20
|
+
`photo` for convenience. Brand, size and condition come from
|
|
21
|
+
`item_box` in the language of the Vinted domain.
|
|
22
|
+
|
|
23
|
+
`created_at_ts` and `raw_timestamp` are deprecated: the catalog API
|
|
24
|
+
no longer returns the photo timestamp, so they always hold epoch
|
|
25
|
+
and 0. They will be removed in the next major release.
|
|
19
26
|
"""
|
|
20
27
|
|
|
21
28
|
raw_data: dict[str, Any] = field(repr=False)
|
|
@@ -23,8 +30,10 @@ class CatalogItem:
|
|
|
23
30
|
title: str = field(init=False)
|
|
24
31
|
brand_title: str = field(init=False)
|
|
25
32
|
size_title: str = field(init=False)
|
|
33
|
+
condition: str = field(init=False)
|
|
26
34
|
currency: str = field(init=False)
|
|
27
35
|
price: float = field(init=False)
|
|
36
|
+
total_item_price: float = field(init=False)
|
|
28
37
|
photo: str = field(init=False)
|
|
29
38
|
url: str = field(init=False)
|
|
30
39
|
created_at_ts: datetime = field(init=False)
|
|
@@ -33,12 +42,19 @@ class CatalogItem:
|
|
|
33
42
|
def __post_init__(self):
|
|
34
43
|
self.id = self.raw_data.get("id", 0)
|
|
35
44
|
self.title = self.raw_data.get("title", "")
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
item_box = self.raw_data.get("item_box") or {}
|
|
47
|
+
self.brand_title = item_box.get("first_line", "")
|
|
48
|
+
# second_line is "<size> · <condition>", the size part is optional
|
|
49
|
+
*sizes, self.condition = item_box.get("second_line", "").split(" · ")
|
|
50
|
+
self.size_title = sizes[0] if sizes else ""
|
|
38
51
|
|
|
39
52
|
price = self.raw_data.get("price") or {}
|
|
40
53
|
self.currency = price.get("currency_code", "")
|
|
41
|
-
self.price = price.get("amount"
|
|
54
|
+
self.price = float(price.get("amount") or 0.0)
|
|
55
|
+
|
|
56
|
+
total_item_price = self.raw_data.get("total_item_price") or {}
|
|
57
|
+
self.total_item_price = float(total_item_price.get("amount") or 0.0)
|
|
42
58
|
|
|
43
59
|
photo = self.raw_data.get("photo") or {}
|
|
44
60
|
self.photo = photo.get("url", "")
|
|
@@ -69,13 +85,19 @@ class CatalogItem:
|
|
|
69
85
|
return hash(self.id)
|
|
70
86
|
|
|
71
87
|
def is_new_item(self, minutes: int = 1) -> bool:
|
|
72
|
-
"""
|
|
88
|
+
"""Deprecated: always raises `VintedDeprecatedError`.
|
|
89
|
+
|
|
90
|
+
The check compared `created_at_ts` with the current time, but the
|
|
91
|
+
catalog API no longer returns the photo timestamp. The method will
|
|
92
|
+
be removed in the next major release.
|
|
73
93
|
|
|
74
94
|
Args:
|
|
75
95
|
minutes: Age threshold in minutes (default: 1).
|
|
76
96
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
raise VintedDeprecatedError(
|
|
98
|
+
"CatalogItem.is_new_item() no longer works: the Vinted catalog API "
|
|
99
|
+
"does not return item timestamps anymore"
|
|
100
|
+
)
|
|
79
101
|
|
|
80
102
|
|
|
81
103
|
@dataclass
|
|
@@ -28,7 +28,7 @@ from .constants import (
|
|
|
28
28
|
HTTP_STATUS_UNAUTHORIZED,
|
|
29
29
|
)
|
|
30
30
|
from .storage import CookieStorage
|
|
31
|
-
from .utils import format_proxy_for_log, get_accept_language
|
|
31
|
+
from .utils import format_proxy_for_log, get_accept_language, get_locale_code
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
|
|
@@ -93,7 +93,10 @@ class HttpSession:
|
|
|
93
93
|
if host.startswith("vinted."):
|
|
94
94
|
self.locale = host[len("vinted.") :]
|
|
95
95
|
accept_language = get_accept_language(self.locale)
|
|
96
|
-
|
|
96
|
+
# `locale` selects the marketplace whose items the catalog returns
|
|
97
|
+
self.session.headers.update(
|
|
98
|
+
{"Accept-Language": accept_language, "locale": get_locale_code(self.locale)}
|
|
99
|
+
)
|
|
97
100
|
|
|
98
101
|
self.session.headers.update({"Referer": self.base_url})
|
|
99
102
|
logger.debug("Configured: base_url=%s, locale=%s", self.base_url, self.locale)
|
|
@@ -28,12 +28,17 @@ def format_proxy_for_log(proxy: str | None) -> str:
|
|
|
28
28
|
return proxy
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def get_locale_code(locale: str) -> str:
|
|
32
|
+
"""Return the full locale code for `locale` (e.g. "sk" -> "sk-SK")."""
|
|
33
|
+
return LOCALE_TO_ACCEPT_LANGUAGE.get(locale, "en-US")
|
|
34
|
+
|
|
35
|
+
|
|
31
36
|
def get_accept_language(locale: str) -> str:
|
|
32
37
|
"""Return an Accept-Language header value for `locale`.
|
|
33
38
|
|
|
34
39
|
The returned value contains the full locale code and a fallback
|
|
35
40
|
language prefix with a lower q-value.
|
|
36
41
|
"""
|
|
37
|
-
locale_code =
|
|
42
|
+
locale_code = get_locale_code(locale)
|
|
38
43
|
lang_prefix = locale_code.split("-")[0]
|
|
39
44
|
return "%s,%s;q=0.9" % (locale_code, lang_prefix)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
root = true
|
|
2
|
-
|
|
3
|
-
[**]
|
|
4
|
-
charset = utf-8
|
|
5
|
-
end_of_line = lf
|
|
6
|
-
indent_size = 4
|
|
7
|
-
indent_style = space
|
|
8
|
-
insert_final_newline = true
|
|
9
|
-
trim_trailing_whitespace = true
|
|
10
|
-
max_line_length = 99
|
|
11
|
-
|
|
12
|
-
[**.{yml,yaml,json,conf}]
|
|
13
|
-
indent_size = 2
|
|
14
|
-
|
|
15
|
-
[**.{md,txt,rst}]
|
|
16
|
-
trim_trailing_whitespace = false
|
|
17
|
-
|
|
18
|
-
[**.rst]
|
|
19
|
-
indent_size = 2
|
|
20
|
-
|
|
21
|
-
[Makefile]
|
|
22
|
-
indent_style = tab
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
lint-and-test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
strategy:
|
|
11
|
-
matrix:
|
|
12
|
-
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
13
|
-
name: Lint, Type Check & Test (Python ${{ matrix.python-version }})
|
|
14
|
-
steps:
|
|
15
|
-
- name: Checkout code
|
|
16
|
-
uses: actions/checkout@v4
|
|
17
|
-
|
|
18
|
-
- name: Set up Python
|
|
19
|
-
uses: actions/setup-python@v5
|
|
20
|
-
with:
|
|
21
|
-
python-version: ${{ matrix.python-version }}
|
|
22
|
-
|
|
23
|
-
- name: Install hatch and create env
|
|
24
|
-
run: |
|
|
25
|
-
python -m pip install --upgrade pip
|
|
26
|
-
pip install hatch
|
|
27
|
-
|
|
28
|
-
- name: Run linting
|
|
29
|
-
run: hatch run lint
|
|
30
|
-
|
|
31
|
-
- name: Run type check
|
|
32
|
-
run: hatch run type-check
|
|
33
|
-
|
|
34
|
-
- name: Run tests (no coverage)
|
|
35
|
-
run: hatch run test
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Coverage
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
coverage:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- name: Checkout code
|
|
13
|
-
uses: actions/checkout@v4
|
|
14
|
-
|
|
15
|
-
- name: Set up Python
|
|
16
|
-
uses: actions/setup-python@v5
|
|
17
|
-
with:
|
|
18
|
-
python-version: "3.12"
|
|
19
|
-
|
|
20
|
-
- name: Install dependencies
|
|
21
|
-
run: |
|
|
22
|
-
python -m pip install --upgrade pip
|
|
23
|
-
pip install hatch
|
|
24
|
-
|
|
25
|
-
- name: Run tests with coverage
|
|
26
|
-
run: hatch run pytest --cov=vinted --cov-branch --cov-report=xml
|
|
27
|
-
|
|
28
|
-
- name: Upload results to Codecov
|
|
29
|
-
uses: codecov/codecov-action@v5
|
|
30
|
-
with:
|
|
31
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
repos:
|
|
2
|
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev: v5.0.0
|
|
4
|
-
hooks:
|
|
5
|
-
- id: trailing-whitespace
|
|
6
|
-
- id: end-of-file-fixer
|
|
7
|
-
- id: check-yaml
|
|
8
|
-
- id: check-toml
|
|
9
|
-
- id: check-case-conflict
|
|
10
|
-
- id: detect-private-key
|
|
11
|
-
- id: check-added-large-files
|
|
12
|
-
args: [--maxkb=500]
|
|
13
|
-
|
|
14
|
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
15
|
-
rev: v0.12.5
|
|
16
|
-
hooks:
|
|
17
|
-
- id: ruff
|
|
18
|
-
args:
|
|
19
|
-
- --fix
|
|
20
|
-
- id: ruff-format
|
|
21
|
-
types: [python]
|
|
22
|
-
|
|
23
|
-
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
24
|
-
rev: v1.13.0
|
|
25
|
-
hooks:
|
|
26
|
-
- id: mypy
|
|
27
|
-
additional_dependencies: [curl-cffi]
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
### Changed
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Fixed
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## [1.0.1] - 2026-06-06
|
|
20
|
-
|
|
21
|
-
### Fixed
|
|
22
|
-
|
|
23
|
-
- Locale detection now correctly handles multi-part Vinted domains such as `vinted.co.uk` and sets the expected `Accept-Language` header
|
|
24
|
-
- Cookie refresh no longer deletes persisted session cookies before a successful refresh and save completes
|
|
25
|
-
- Runtime exceptions now match the documented public contract for configuration, validation, rate-limit, authentication, and cookie save failures
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## [1.0.0] - 2026-01-05
|
|
29
|
-
|
|
30
|
-
### Added
|
|
31
|
-
- Multiple cookie storage backends: `json`, `mozilla`, and `pickle`
|
|
32
|
-
- Dataclass-based models (`CatalogItem`, `DetailedItem`) for performance and clarity
|
|
33
|
-
- Custom exception hierarchy to surface network, auth, and validation errors distinctly
|
|
34
|
-
- Automatic locale detection from target Vinted URLs
|
|
35
|
-
- `SortOrder` and `StorageFormat` type literals for improved typing
|
|
36
|
-
|
|
37
|
-
### Changed
|
|
38
|
-
- Refactored architecture: separated `api/`, `storage/`, and `models/` layers for better maintainability
|
|
39
|
-
- Default cookie storage format changed to `json` for security hardening
|
|
40
|
-
- SSL verification enabled by default for all HTTP requests
|
|
41
|
-
- Proxy configuration simplified to a single string parameter (`proxy="user:pass@host:port"`)
|
|
42
|
-
- Cookie persistence now uses strategy pattern
|
|
43
|
-
|
|
44
|
-
### Fixed
|
|
45
|
-
- JWT token expiration parsing (base64url padding handling)
|
|
46
|
-
- Retry/logging behavior for authentication failures
|
|
47
|
-
- Cookie persistence reliability
|
|
48
|
-
- Proxy handling edge cases
|
|
49
|
-
- Token expiration detection accuracy
|
|
50
|
-
|
|
51
|
-
## [0.1.0.post1] - 2025-08-07
|
|
52
|
-
|
|
53
|
-
### Fixed
|
|
54
|
-
- Logo display compatibility for PyPI
|
|
55
|
-
- Added project badges to README.md for better presentation
|
|
56
|
-
- Corrected CHANGELOG formatting and metadata
|
|
57
|
-
|
|
58
|
-
## [0.1.0] - 2025-08-07
|
|
59
|
-
|
|
60
|
-
### Added
|
|
61
|
-
- Asynchronous HTTP client for Vinted API with cookie management
|
|
62
|
-
- `VintedApi.search_items()` method for item searching with filters
|
|
63
|
-
- `VintedApi.item_details()` method for detailed item information
|
|
64
|
-
- Support for multiple Vinted domains (fr, de, sk, pl, it, etc.)
|
|
65
|
-
- Proxy support for web scraping
|
|
66
|
-
- Automatic authentication and session handling
|
|
67
|
-
- Cookie persistence between requests
|
|
68
|
-
- JWT token expiration detection and refresh
|
|
69
|
-
- Comprehensive error handling with retry logic
|
|
70
|
-
- Full typing support and async/await patterns
|
|
71
|
-
- CI/CD pipeline with GitHub Actions
|
|
72
|
-
- 80%+ test coverage
|
|
73
|
-
|
|
74
|
-
[Unreleased]: https://github.com/vlymar1/vinted-api-kit/compare/v1.0.1...HEAD
|
|
75
|
-
[1.0.1]: https://github.com/vlymar1/vinted-api-kit/compare/v1.0.0...v1.0.1
|
|
76
|
-
[1.0.0]: https://github.com/vlymar1/vinted-api-kit/compare/v0.1.0...v1.0.0
|
|
77
|
-
[0.1.0.post1]: https://github.com/vlymar1/vinted-api-kit/compare/v0.1.0...v0.1.0.post1
|
|
78
|
-
[0.1.0]: https://github.com/vlymar1/vinted-api-kit/releases/tag/v0.1.0
|