blocket-api 0.5.1__tar.gz → 0.5.2__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.
- {blocket_api-0.5.1 → blocket_api-0.5.2}/PKG-INFO +30 -16
- {blocket_api-0.5.1 → blocket_api-0.5.2}/README.md +19 -7
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api/__init__.py +2 -0
- blocket_api-0.5.2/blocket_api/_params.py +166 -0
- blocket_api-0.5.2/blocket_api/async_blocket.py +187 -0
- blocket_api-0.5.2/blocket_api/blocket.py +168 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api/constants.py +5 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api.egg-info/PKG-INFO +30 -16
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api.egg-info/SOURCES.txt +2 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api.egg-info/requires.txt +8 -5
- {blocket_api-0.5.1 → blocket_api-0.5.2}/pyproject.toml +12 -6
- blocket_api-0.5.1/blocket_api/blocket.py +0 -196
- {blocket_api-0.5.1 → blocket_api-0.5.2}/LICENSE +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api/ad_parser.py +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api.egg-info/dependency_links.txt +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/blocket_api.egg-info/top_level.txt +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.2}/setup.cfg +0 -0
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A python API wrapper for blocket.se
|
|
5
5
|
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: httpx>=0.28.1
|
|
10
|
-
Requires-Dist: pre-commit>=4.5.1
|
|
11
|
-
Requires-Dist: pytest>=9.0.2
|
|
12
|
-
Requires-Dist: respx>=0.22.0
|
|
13
|
-
Requires-Dist: mypy>=1.19.1
|
|
14
|
-
Requires-Dist: mkdocs>=1.6.1
|
|
15
|
-
Requires-Dist: mkdocs-material>=9.7.1
|
|
16
|
-
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
17
10
|
Requires-Dist: beautifulsoup4>=4.14.3
|
|
18
|
-
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
15
|
+
Requires-Dist: respx>=0.22.0; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=1.19.1; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
18
|
+
Requires-Dist: flask>=3.1.3; extra == "dev"
|
|
19
|
+
Requires-Dist: htpy>=26.5.1; extra == "dev"
|
|
20
|
+
Requires-Dist: gunicorn>=26.0.0; extra == "dev"
|
|
19
21
|
Dynamic: license-file
|
|
20
22
|
|
|
21
|
-
<p align="center">
|
|
22
|
-
<img src="https://blocket-api.se/blocket-api.png" />
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
23
|
# BlocketAPI
|
|
26
24
|
|
|
27
|
-
[](https://pypi.org/project/blocket_api/) [](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE)  
|
|
28
26
|
|
|
29
27
|
BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
30
28
|
|
|
@@ -35,10 +33,13 @@ BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
|
35
33
|
Install BlocketAPI via PyPI...
|
|
36
34
|
|
|
37
35
|
```sh
|
|
38
|
-
pip install blocket-api
|
|
36
|
+
pip install blocket-api # using pip
|
|
37
|
+
uv add blocket-api # using uv
|
|
39
38
|
```
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
## 📜 Docs
|
|
41
|
+
|
|
42
|
+
The full documentation can be found at [blocket-api.se](https://blocket-api.se/).
|
|
42
43
|
|
|
43
44
|
## 💁♀️ Usage
|
|
44
45
|
|
|
@@ -119,6 +120,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
119
120
|
api.get_ad(McAd(12345678))
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
### Async support
|
|
124
|
+
|
|
125
|
+
```py
|
|
126
|
+
import asyncio
|
|
127
|
+
from blocket_api import AsyncBlocketAPI
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
async with AsyncBlocketAPI() as api:
|
|
131
|
+
results = await api.search("iPhone 15")
|
|
132
|
+
|
|
133
|
+
asyncio.run(main())
|
|
134
|
+
```
|
|
135
|
+
|
|
122
136
|
## 📝 Notes
|
|
123
137
|
|
|
124
138
|
- REST API: https://blocket-api.se
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://blocket-api.se/blocket-api.png" />
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
# BlocketAPI
|
|
6
2
|
|
|
7
|
-
[](https://pypi.org/project/blocket_api/) [](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE)  
|
|
8
4
|
|
|
9
5
|
BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
10
6
|
|
|
@@ -15,10 +11,13 @@ BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
|
15
11
|
Install BlocketAPI via PyPI...
|
|
16
12
|
|
|
17
13
|
```sh
|
|
18
|
-
pip install blocket-api
|
|
14
|
+
pip install blocket-api # using pip
|
|
15
|
+
uv add blocket-api # using uv
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
## 📜 Docs
|
|
19
|
+
|
|
20
|
+
The full documentation can be found at [blocket-api.se](https://blocket-api.se/).
|
|
22
21
|
|
|
23
22
|
## 💁♀️ Usage
|
|
24
23
|
|
|
@@ -99,6 +98,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
99
98
|
api.get_ad(McAd(12345678))
|
|
100
99
|
```
|
|
101
100
|
|
|
101
|
+
### Async support
|
|
102
|
+
|
|
103
|
+
```py
|
|
104
|
+
import asyncio
|
|
105
|
+
from blocket_api import AsyncBlocketAPI
|
|
106
|
+
|
|
107
|
+
async def main():
|
|
108
|
+
async with AsyncBlocketAPI() as api:
|
|
109
|
+
results = await api.search("iPhone 15")
|
|
110
|
+
|
|
111
|
+
asyncio.run(main())
|
|
112
|
+
```
|
|
113
|
+
|
|
102
114
|
## 📝 Notes
|
|
103
115
|
|
|
104
116
|
- REST API: https://blocket-api.se
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
|
|
2
|
+
from .async_blocket import AsyncBlocketAPI
|
|
2
3
|
from .blocket import BlocketAPI, Location
|
|
3
4
|
from .constants import (
|
|
4
5
|
BoatSortOrder,
|
|
@@ -17,6 +18,7 @@ from .constants import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
21
|
+
"AsyncBlocketAPI",
|
|
20
22
|
"BlocketAPI",
|
|
21
23
|
"Location",
|
|
22
24
|
"BoatAd",
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
from .constants import (
|
|
6
|
+
BoatType,
|
|
7
|
+
CarColor,
|
|
8
|
+
CarModel,
|
|
9
|
+
CarSortOrder,
|
|
10
|
+
CarTransmission,
|
|
11
|
+
CarWheelDrive,
|
|
12
|
+
Category,
|
|
13
|
+
Location,
|
|
14
|
+
McModel,
|
|
15
|
+
McSortOrder,
|
|
16
|
+
McType,
|
|
17
|
+
SortOrder,
|
|
18
|
+
SubCategory,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_ParamValue = str | int | float | bool | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _build_params(
|
|
25
|
+
defaults: dict[str, _ParamValue],
|
|
26
|
+
extras: Sequence[tuple[str, _ParamValue]],
|
|
27
|
+
) -> list[tuple[str, _ParamValue]]:
|
|
28
|
+
params: list[tuple[str, _ParamValue]] = [
|
|
29
|
+
(k, v) for k, v in defaults.items() if v is not None
|
|
30
|
+
]
|
|
31
|
+
params.extend(extras)
|
|
32
|
+
return params
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_search_params(
|
|
36
|
+
query: str,
|
|
37
|
+
*,
|
|
38
|
+
page: int = 1,
|
|
39
|
+
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
40
|
+
locations: list[Location] = [],
|
|
41
|
+
category: Category | None = None,
|
|
42
|
+
sub_category: SubCategory | None = None,
|
|
43
|
+
) -> list[tuple[str, _ParamValue]]:
|
|
44
|
+
if category and sub_category:
|
|
45
|
+
raise AssertionError("Cannot specify both category and sub_categories")
|
|
46
|
+
|
|
47
|
+
defaults: dict[str, _ParamValue] = {
|
|
48
|
+
"q": query,
|
|
49
|
+
"page": page,
|
|
50
|
+
"sort": sort_order.value,
|
|
51
|
+
"category": category.value if category else None,
|
|
52
|
+
"sub_category": sub_category.value if sub_category else None,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
extras = [("location", loc.value) for loc in locations]
|
|
56
|
+
|
|
57
|
+
return _build_params(defaults, extras)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_car_params(
|
|
61
|
+
query: str | None = None,
|
|
62
|
+
*,
|
|
63
|
+
page: int = 1,
|
|
64
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
65
|
+
locations: list[Location] = [],
|
|
66
|
+
models: list[CarModel] = [],
|
|
67
|
+
price_from: int | None = None,
|
|
68
|
+
price_to: int | None = None,
|
|
69
|
+
year_from: int | None = None,
|
|
70
|
+
year_to: int | None = None,
|
|
71
|
+
milage_from: int | None = None,
|
|
72
|
+
milage_to: int | None = None,
|
|
73
|
+
colors: list[CarColor] = [],
|
|
74
|
+
transmissions: list[CarTransmission] = [],
|
|
75
|
+
horsepower_from: int | None = None,
|
|
76
|
+
horsepower_to: int | None = None,
|
|
77
|
+
wheel_drive: list[CarWheelDrive] = [],
|
|
78
|
+
org_id: int | None = None,
|
|
79
|
+
) -> list[tuple[str, _ParamValue]]:
|
|
80
|
+
defaults: dict[str, _ParamValue] = {
|
|
81
|
+
"q": query,
|
|
82
|
+
"page": page,
|
|
83
|
+
"sort": sort_order.value,
|
|
84
|
+
"price_from": price_from,
|
|
85
|
+
"price_to": price_to,
|
|
86
|
+
"year_from": year_from,
|
|
87
|
+
"year_to": year_to,
|
|
88
|
+
"milage_from": milage_from,
|
|
89
|
+
"milage_to": milage_to,
|
|
90
|
+
"engine_effect_from": horsepower_from,
|
|
91
|
+
"engine_effect_to": horsepower_to,
|
|
92
|
+
"orgId": org_id,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
extras: list[tuple[str, _ParamValue]] = []
|
|
96
|
+
extras.extend(("location", loc.value) for loc in locations)
|
|
97
|
+
extras.extend(("make", model.value) for model in models)
|
|
98
|
+
extras.extend(("exterior_colour", color.value) for color in colors)
|
|
99
|
+
extras.extend(("transmission", t.value) for t in transmissions)
|
|
100
|
+
extras.extend(("wheel_drive", w.value) for w in wheel_drive)
|
|
101
|
+
|
|
102
|
+
return _build_params(defaults, extras)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_boat_params(
|
|
106
|
+
query: str | None = None,
|
|
107
|
+
*,
|
|
108
|
+
page: int = 1,
|
|
109
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
110
|
+
types: list[BoatType] = [],
|
|
111
|
+
locations: list[Location] = [],
|
|
112
|
+
price_from: int | None = None,
|
|
113
|
+
price_to: int | None = None,
|
|
114
|
+
length_from: int | None = None,
|
|
115
|
+
length_to: int | None = None,
|
|
116
|
+
org_id: int | None = None,
|
|
117
|
+
) -> list[tuple[str, _ParamValue]]:
|
|
118
|
+
defaults: dict[str, _ParamValue] = {
|
|
119
|
+
"q": query,
|
|
120
|
+
"page": page,
|
|
121
|
+
"sort": sort_order.value,
|
|
122
|
+
"price_from": price_from,
|
|
123
|
+
"price_to": price_to,
|
|
124
|
+
"length_feet_from": length_from,
|
|
125
|
+
"length_feet_to": length_to,
|
|
126
|
+
"orgId": org_id,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
extras: list[tuple[str, _ParamValue]] = []
|
|
130
|
+
extras.extend(("class", t.value) for t in types)
|
|
131
|
+
extras.extend(("location", loc.value) for loc in locations)
|
|
132
|
+
|
|
133
|
+
return _build_params(defaults, extras)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_mc_params(
|
|
137
|
+
query: str | None = None,
|
|
138
|
+
*,
|
|
139
|
+
page: int = 1,
|
|
140
|
+
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
141
|
+
models: list[McModel] = [],
|
|
142
|
+
types: list[McType] = [],
|
|
143
|
+
locations: list[Location] = [],
|
|
144
|
+
price_from: int | None = None,
|
|
145
|
+
price_to: int | None = None,
|
|
146
|
+
engine_volume_from: int | None = None,
|
|
147
|
+
engine_volume_to: int | None = None,
|
|
148
|
+
org_id: int | None = None,
|
|
149
|
+
) -> list[tuple[str, _ParamValue]]:
|
|
150
|
+
defaults: dict[str, _ParamValue] = {
|
|
151
|
+
"q": query,
|
|
152
|
+
"page": page,
|
|
153
|
+
"sort": sort_order.value,
|
|
154
|
+
"price_from": price_from,
|
|
155
|
+
"price_to": price_to,
|
|
156
|
+
"engine_volume_from": engine_volume_from,
|
|
157
|
+
"engine_volume_to": engine_volume_to,
|
|
158
|
+
"orgId": org_id,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
extras: list[tuple[str, _ParamValue]] = []
|
|
162
|
+
extras.extend(("make", m.value) for m in models)
|
|
163
|
+
extras.extend(("location", loc.value) for loc in locations)
|
|
164
|
+
extras.extend(("type", t.value) for t in types)
|
|
165
|
+
|
|
166
|
+
return _build_params(defaults, extras)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ._params import (
|
|
8
|
+
build_boat_params,
|
|
9
|
+
build_car_params,
|
|
10
|
+
build_mc_params,
|
|
11
|
+
build_search_params,
|
|
12
|
+
)
|
|
13
|
+
from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
|
|
14
|
+
from .constants import (
|
|
15
|
+
BOAT_SEARCH_URL,
|
|
16
|
+
CAR_SEARCH_URL,
|
|
17
|
+
HEADERS,
|
|
18
|
+
MC_SEARCH_URL,
|
|
19
|
+
SEARCH_URL,
|
|
20
|
+
)
|
|
21
|
+
from .constants import (
|
|
22
|
+
BoatType,
|
|
23
|
+
CarColor,
|
|
24
|
+
CarModel,
|
|
25
|
+
CarSortOrder,
|
|
26
|
+
CarTransmission,
|
|
27
|
+
CarWheelDrive,
|
|
28
|
+
Category,
|
|
29
|
+
Location,
|
|
30
|
+
McModel,
|
|
31
|
+
McSortOrder,
|
|
32
|
+
McType,
|
|
33
|
+
SortOrder,
|
|
34
|
+
SubCategory,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AsyncBlocketAPI:
|
|
39
|
+
def __init__(self, client: httpx.AsyncClient | None = None) -> None:
|
|
40
|
+
self._client = client or httpx.AsyncClient()
|
|
41
|
+
self._owns_client = client is None
|
|
42
|
+
|
|
43
|
+
async def __aenter__(self) -> AsyncBlocketAPI:
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
async def __aexit__(self, *_: Any) -> None:
|
|
47
|
+
await self.aclose()
|
|
48
|
+
|
|
49
|
+
async def aclose(self) -> None:
|
|
50
|
+
await self._client.aclose()
|
|
51
|
+
|
|
52
|
+
async def search(
|
|
53
|
+
self,
|
|
54
|
+
query: str,
|
|
55
|
+
*,
|
|
56
|
+
page: int = 1,
|
|
57
|
+
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
58
|
+
locations: list[Location] = [],
|
|
59
|
+
category: Category | None = None,
|
|
60
|
+
sub_category: SubCategory | None = None,
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
params = build_search_params(
|
|
63
|
+
query,
|
|
64
|
+
page=page,
|
|
65
|
+
sort_order=sort_order,
|
|
66
|
+
locations=locations,
|
|
67
|
+
category=category,
|
|
68
|
+
sub_category=sub_category,
|
|
69
|
+
)
|
|
70
|
+
response = await self._client.get(SEARCH_URL, headers=HEADERS, params=params)
|
|
71
|
+
response.raise_for_status()
|
|
72
|
+
return response.json()
|
|
73
|
+
|
|
74
|
+
async def search_car(
|
|
75
|
+
self,
|
|
76
|
+
query: str | None = None,
|
|
77
|
+
*,
|
|
78
|
+
page: int = 1,
|
|
79
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
80
|
+
locations: list[Location] = [],
|
|
81
|
+
models: list[CarModel] = [],
|
|
82
|
+
price_from: int | None = None,
|
|
83
|
+
price_to: int | None = None,
|
|
84
|
+
year_from: int | None = None,
|
|
85
|
+
year_to: int | None = None,
|
|
86
|
+
milage_from: int | None = None,
|
|
87
|
+
milage_to: int | None = None,
|
|
88
|
+
colors: list[CarColor] = [],
|
|
89
|
+
transmissions: list[CarTransmission] = [],
|
|
90
|
+
horsepower_from: int | None = None,
|
|
91
|
+
horsepower_to: int | None = None,
|
|
92
|
+
wheel_drive: list[CarWheelDrive] = [],
|
|
93
|
+
org_id: int | None = None,
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
params = build_car_params(
|
|
96
|
+
query,
|
|
97
|
+
page=page,
|
|
98
|
+
sort_order=sort_order,
|
|
99
|
+
locations=locations,
|
|
100
|
+
models=models,
|
|
101
|
+
price_from=price_from,
|
|
102
|
+
price_to=price_to,
|
|
103
|
+
year_from=year_from,
|
|
104
|
+
year_to=year_to,
|
|
105
|
+
milage_from=milage_from,
|
|
106
|
+
milage_to=milage_to,
|
|
107
|
+
colors=colors,
|
|
108
|
+
transmissions=transmissions,
|
|
109
|
+
horsepower_from=horsepower_from,
|
|
110
|
+
horsepower_to=horsepower_to,
|
|
111
|
+
wheel_drive=wheel_drive,
|
|
112
|
+
org_id=org_id,
|
|
113
|
+
)
|
|
114
|
+
response = await self._client.get(
|
|
115
|
+
CAR_SEARCH_URL, headers=HEADERS, params=params
|
|
116
|
+
)
|
|
117
|
+
response.raise_for_status()
|
|
118
|
+
return response.json()
|
|
119
|
+
|
|
120
|
+
async def search_boat(
|
|
121
|
+
self,
|
|
122
|
+
query: str | None = None,
|
|
123
|
+
*,
|
|
124
|
+
page: int = 1,
|
|
125
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
126
|
+
types: list[BoatType] = [],
|
|
127
|
+
locations: list[Location] = [],
|
|
128
|
+
price_from: int | None = None,
|
|
129
|
+
price_to: int | None = None,
|
|
130
|
+
length_from: int | None = None,
|
|
131
|
+
length_to: int | None = None,
|
|
132
|
+
org_id: int | None = None,
|
|
133
|
+
) -> Any:
|
|
134
|
+
params = build_boat_params(
|
|
135
|
+
query,
|
|
136
|
+
page=page,
|
|
137
|
+
sort_order=sort_order,
|
|
138
|
+
types=types,
|
|
139
|
+
locations=locations,
|
|
140
|
+
price_from=price_from,
|
|
141
|
+
price_to=price_to,
|
|
142
|
+
length_from=length_from,
|
|
143
|
+
length_to=length_to,
|
|
144
|
+
org_id=org_id,
|
|
145
|
+
)
|
|
146
|
+
response = await self._client.get(
|
|
147
|
+
BOAT_SEARCH_URL, headers=HEADERS, params=params
|
|
148
|
+
)
|
|
149
|
+
response.raise_for_status()
|
|
150
|
+
return response.json()
|
|
151
|
+
|
|
152
|
+
async def search_mc(
|
|
153
|
+
self,
|
|
154
|
+
query: str | None = None,
|
|
155
|
+
*,
|
|
156
|
+
page: int = 1,
|
|
157
|
+
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
158
|
+
models: list[McModel] = [],
|
|
159
|
+
types: list[McType] = [],
|
|
160
|
+
locations: list[Location] = [],
|
|
161
|
+
price_from: int | None = None,
|
|
162
|
+
price_to: int | None = None,
|
|
163
|
+
engine_volume_from: int | None = None,
|
|
164
|
+
engine_volume_to: int | None = None,
|
|
165
|
+
org_id: int | None = None,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
params = build_mc_params(
|
|
168
|
+
query,
|
|
169
|
+
page=page,
|
|
170
|
+
sort_order=sort_order,
|
|
171
|
+
models=models,
|
|
172
|
+
types=types,
|
|
173
|
+
locations=locations,
|
|
174
|
+
price_from=price_from,
|
|
175
|
+
price_to=price_to,
|
|
176
|
+
engine_volume_from=engine_volume_from,
|
|
177
|
+
engine_volume_to=engine_volume_to,
|
|
178
|
+
org_id=org_id,
|
|
179
|
+
)
|
|
180
|
+
response = await self._client.get(MC_SEARCH_URL, headers=HEADERS, params=params)
|
|
181
|
+
response.raise_for_status()
|
|
182
|
+
return response.json()
|
|
183
|
+
|
|
184
|
+
async def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
185
|
+
response = await self._client.get(ad.url, headers=HEADERS)
|
|
186
|
+
response.raise_for_status()
|
|
187
|
+
return ad.parse(response)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ._params import (
|
|
8
|
+
_ParamValue,
|
|
9
|
+
build_boat_params,
|
|
10
|
+
build_car_params,
|
|
11
|
+
build_mc_params,
|
|
12
|
+
build_search_params,
|
|
13
|
+
)
|
|
14
|
+
from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
|
|
15
|
+
from .constants import (
|
|
16
|
+
BOAT_SEARCH_URL,
|
|
17
|
+
CAR_SEARCH_URL,
|
|
18
|
+
HEADERS,
|
|
19
|
+
MC_SEARCH_URL,
|
|
20
|
+
SEARCH_URL,
|
|
21
|
+
)
|
|
22
|
+
from .constants import (
|
|
23
|
+
BoatType,
|
|
24
|
+
CarColor,
|
|
25
|
+
CarModel,
|
|
26
|
+
CarSortOrder,
|
|
27
|
+
CarTransmission,
|
|
28
|
+
CarWheelDrive,
|
|
29
|
+
Category,
|
|
30
|
+
Location,
|
|
31
|
+
McModel,
|
|
32
|
+
McSortOrder,
|
|
33
|
+
McType,
|
|
34
|
+
SortOrder,
|
|
35
|
+
SubCategory,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _request(*, url: str, params: list[tuple[str, _ParamValue]]) -> httpx.Response:
|
|
40
|
+
response = httpx.get(url, headers=HEADERS, params=params)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
return response
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BlocketAPI:
|
|
46
|
+
def search(
|
|
47
|
+
self,
|
|
48
|
+
query: str,
|
|
49
|
+
*,
|
|
50
|
+
page: int = 1,
|
|
51
|
+
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
52
|
+
locations: list[Location] = [],
|
|
53
|
+
category: Category | None = None,
|
|
54
|
+
sub_category: SubCategory | None = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
params = build_search_params(
|
|
57
|
+
query,
|
|
58
|
+
page=page,
|
|
59
|
+
sort_order=sort_order,
|
|
60
|
+
locations=locations,
|
|
61
|
+
category=category,
|
|
62
|
+
sub_category=sub_category,
|
|
63
|
+
)
|
|
64
|
+
return _request(url=SEARCH_URL, params=params).json()
|
|
65
|
+
|
|
66
|
+
def search_car(
|
|
67
|
+
self,
|
|
68
|
+
query: str | None = None,
|
|
69
|
+
*,
|
|
70
|
+
page: int = 1,
|
|
71
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
72
|
+
locations: list[Location] = [],
|
|
73
|
+
models: list[CarModel] = [],
|
|
74
|
+
price_from: int | None = None,
|
|
75
|
+
price_to: int | None = None,
|
|
76
|
+
year_from: int | None = None,
|
|
77
|
+
year_to: int | None = None,
|
|
78
|
+
milage_from: int | None = None,
|
|
79
|
+
milage_to: int | None = None,
|
|
80
|
+
colors: list[CarColor] = [],
|
|
81
|
+
transmissions: list[CarTransmission] = [],
|
|
82
|
+
horsepower_from: int | None = None,
|
|
83
|
+
horsepower_to: int | None = None,
|
|
84
|
+
wheel_drive: list[CarWheelDrive] = [],
|
|
85
|
+
org_id: int | None = None,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
params = build_car_params(
|
|
88
|
+
query,
|
|
89
|
+
page=page,
|
|
90
|
+
sort_order=sort_order,
|
|
91
|
+
locations=locations,
|
|
92
|
+
models=models,
|
|
93
|
+
price_from=price_from,
|
|
94
|
+
price_to=price_to,
|
|
95
|
+
year_from=year_from,
|
|
96
|
+
year_to=year_to,
|
|
97
|
+
milage_from=milage_from,
|
|
98
|
+
milage_to=milage_to,
|
|
99
|
+
colors=colors,
|
|
100
|
+
transmissions=transmissions,
|
|
101
|
+
horsepower_from=horsepower_from,
|
|
102
|
+
horsepower_to=horsepower_to,
|
|
103
|
+
wheel_drive=wheel_drive,
|
|
104
|
+
org_id=org_id,
|
|
105
|
+
)
|
|
106
|
+
return _request(url=CAR_SEARCH_URL, params=params).json()
|
|
107
|
+
|
|
108
|
+
def search_boat(
|
|
109
|
+
self,
|
|
110
|
+
query: str | None = None,
|
|
111
|
+
*,
|
|
112
|
+
page: int = 1,
|
|
113
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
114
|
+
types: list[BoatType] = [],
|
|
115
|
+
locations: list[Location] = [],
|
|
116
|
+
price_from: int | None = None,
|
|
117
|
+
price_to: int | None = None,
|
|
118
|
+
length_from: int | None = None,
|
|
119
|
+
length_to: int | None = None,
|
|
120
|
+
org_id: int | None = None,
|
|
121
|
+
) -> Any:
|
|
122
|
+
params = build_boat_params(
|
|
123
|
+
query,
|
|
124
|
+
page=page,
|
|
125
|
+
sort_order=sort_order,
|
|
126
|
+
types=types,
|
|
127
|
+
locations=locations,
|
|
128
|
+
price_from=price_from,
|
|
129
|
+
price_to=price_to,
|
|
130
|
+
length_from=length_from,
|
|
131
|
+
length_to=length_to,
|
|
132
|
+
org_id=org_id,
|
|
133
|
+
)
|
|
134
|
+
return _request(url=BOAT_SEARCH_URL, params=params).json()
|
|
135
|
+
|
|
136
|
+
def search_mc(
|
|
137
|
+
self,
|
|
138
|
+
query: str | None = None,
|
|
139
|
+
*,
|
|
140
|
+
page: int = 1,
|
|
141
|
+
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
142
|
+
models: list[McModel] = [],
|
|
143
|
+
types: list[McType] = [],
|
|
144
|
+
locations: list[Location] = [],
|
|
145
|
+
price_from: int | None = None,
|
|
146
|
+
price_to: int | None = None,
|
|
147
|
+
engine_volume_from: int | None = None,
|
|
148
|
+
engine_volume_to: int | None = None,
|
|
149
|
+
org_id: int | None = None,
|
|
150
|
+
) -> dict[str, Any]:
|
|
151
|
+
params = build_mc_params(
|
|
152
|
+
query,
|
|
153
|
+
page=page,
|
|
154
|
+
sort_order=sort_order,
|
|
155
|
+
models=models,
|
|
156
|
+
types=types,
|
|
157
|
+
locations=locations,
|
|
158
|
+
price_from=price_from,
|
|
159
|
+
price_to=price_to,
|
|
160
|
+
engine_volume_from=engine_volume_from,
|
|
161
|
+
engine_volume_to=engine_volume_to,
|
|
162
|
+
org_id=org_id,
|
|
163
|
+
)
|
|
164
|
+
return _request(url=MC_SEARCH_URL, params=params).json()
|
|
165
|
+
|
|
166
|
+
def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
167
|
+
response = _request(url=ad.url, params=[])
|
|
168
|
+
return ad.parse(response)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
from enum import IntEnum, StrEnum
|
|
2
2
|
|
|
3
3
|
SITE_URL = "https://www.blocket.se"
|
|
4
|
+
SEARCH_URL = f"{SITE_URL}/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON"
|
|
5
|
+
CAR_SEARCH_URL = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
|
|
6
|
+
BOAT_SEARCH_URL = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_BOAT_USED"
|
|
7
|
+
MC_SEARCH_URL = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_MC_USED"
|
|
8
|
+
|
|
4
9
|
HEADERS = {
|
|
5
10
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
|
|
6
11
|
}
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A python API wrapper for blocket.se
|
|
5
5
|
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: httpx>=0.28.1
|
|
10
|
-
Requires-Dist: pre-commit>=4.5.1
|
|
11
|
-
Requires-Dist: pytest>=9.0.2
|
|
12
|
-
Requires-Dist: respx>=0.22.0
|
|
13
|
-
Requires-Dist: mypy>=1.19.1
|
|
14
|
-
Requires-Dist: mkdocs>=1.6.1
|
|
15
|
-
Requires-Dist: mkdocs-material>=9.7.1
|
|
16
|
-
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
17
10
|
Requires-Dist: beautifulsoup4>=4.14.3
|
|
18
|
-
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
15
|
+
Requires-Dist: respx>=0.22.0; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=1.19.1; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
18
|
+
Requires-Dist: flask>=3.1.3; extra == "dev"
|
|
19
|
+
Requires-Dist: htpy>=26.5.1; extra == "dev"
|
|
20
|
+
Requires-Dist: gunicorn>=26.0.0; extra == "dev"
|
|
19
21
|
Dynamic: license-file
|
|
20
22
|
|
|
21
|
-
<p align="center">
|
|
22
|
-
<img src="https://blocket-api.se/blocket-api.png" />
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
23
|
# BlocketAPI
|
|
26
24
|
|
|
27
|
-
[](https://pypi.org/project/blocket_api/) [](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE)  
|
|
28
26
|
|
|
29
27
|
BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
30
28
|
|
|
@@ -35,10 +33,13 @@ BlocketAPI allows users to search [blocket.se](https://blocket.se/) for ads.
|
|
|
35
33
|
Install BlocketAPI via PyPI...
|
|
36
34
|
|
|
37
35
|
```sh
|
|
38
|
-
pip install blocket-api
|
|
36
|
+
pip install blocket-api # using pip
|
|
37
|
+
uv add blocket-api # using uv
|
|
39
38
|
```
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
## 📜 Docs
|
|
41
|
+
|
|
42
|
+
The full documentation can be found at [blocket-api.se](https://blocket-api.se/).
|
|
42
43
|
|
|
43
44
|
## 💁♀️ Usage
|
|
44
45
|
|
|
@@ -119,6 +120,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
119
120
|
api.get_ad(McAd(12345678))
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
### Async support
|
|
124
|
+
|
|
125
|
+
```py
|
|
126
|
+
import asyncio
|
|
127
|
+
from blocket_api import AsyncBlocketAPI
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
async with AsyncBlocketAPI() as api:
|
|
131
|
+
results = await api.search("iPhone 15")
|
|
132
|
+
|
|
133
|
+
asyncio.run(main())
|
|
134
|
+
```
|
|
135
|
+
|
|
122
136
|
## 📝 Notes
|
|
123
137
|
|
|
124
138
|
- REST API: https://blocket-api.se
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
httpx>=0.28.1
|
|
2
|
+
beautifulsoup4>=4.14.3
|
|
3
|
+
|
|
4
|
+
[dev]
|
|
2
5
|
pre-commit>=4.5.1
|
|
3
6
|
pytest>=9.0.2
|
|
7
|
+
pytest-asyncio>=0.26.0
|
|
4
8
|
respx>=0.22.0
|
|
5
9
|
mypy>=1.19.1
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
ruff>=0.15.12
|
|
10
|
+
ruff>=0.9.0
|
|
11
|
+
flask>=3.1.3
|
|
12
|
+
htpy>=26.5.1
|
|
13
|
+
gunicorn>=26.0.0
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "blocket_api"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.2"
|
|
4
4
|
description = "A python API wrapper for blocket.se"
|
|
5
5
|
authors = [{name = "dunderrrrrr", email = "emil.bjorkroth@gmail.com"}]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"httpx>=0.28.1",
|
|
10
|
+
"beautifulsoup4>=4.14.3",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = [
|
|
10
15
|
"pre-commit>=4.5.1",
|
|
11
16
|
"pytest>=9.0.2",
|
|
17
|
+
"pytest-asyncio>=0.26.0",
|
|
12
18
|
"respx>=0.22.0",
|
|
13
19
|
"mypy>=1.19.1",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"ruff>=0.15.12",
|
|
20
|
+
"ruff>=0.9.0",
|
|
21
|
+
"flask>=3.1.3",
|
|
22
|
+
"htpy>=26.5.1",
|
|
23
|
+
"gunicorn>=26.0.0",
|
|
19
24
|
]
|
|
20
25
|
|
|
21
26
|
|
|
@@ -23,3 +28,4 @@ dependencies = [
|
|
|
23
28
|
testpaths = [
|
|
24
29
|
"tests/*",
|
|
25
30
|
]
|
|
31
|
+
asyncio_mode = "auto"
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
import httpx
|
|
7
|
-
from httpx import Response
|
|
8
|
-
|
|
9
|
-
from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
|
|
10
|
-
from .constants import (
|
|
11
|
-
HEADERS,
|
|
12
|
-
SITE_URL,
|
|
13
|
-
BoatType,
|
|
14
|
-
CarColor,
|
|
15
|
-
CarModel,
|
|
16
|
-
CarSortOrder,
|
|
17
|
-
CarTransmission,
|
|
18
|
-
CarWheelDrive,
|
|
19
|
-
Category,
|
|
20
|
-
Location,
|
|
21
|
-
McModel,
|
|
22
|
-
McSortOrder,
|
|
23
|
-
McType,
|
|
24
|
-
SortOrder,
|
|
25
|
-
SubCategory,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dataclass(frozen=True)
|
|
30
|
-
class QueryParam:
|
|
31
|
-
name: str
|
|
32
|
-
value: str | int
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _request(*, url: str, params: list[QueryParam]) -> Response:
|
|
36
|
-
response = httpx.get(
|
|
37
|
-
url,
|
|
38
|
-
headers=HEADERS,
|
|
39
|
-
params=[(param.name, param.value) for param in params],
|
|
40
|
-
)
|
|
41
|
-
response.raise_for_status()
|
|
42
|
-
return response
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@dataclass(frozen=True)
|
|
46
|
-
class BlocketAPI:
|
|
47
|
-
def search(
|
|
48
|
-
self,
|
|
49
|
-
query: str,
|
|
50
|
-
*,
|
|
51
|
-
page: int = 1,
|
|
52
|
-
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
53
|
-
locations: list[Location] = [],
|
|
54
|
-
category: Category | None = None,
|
|
55
|
-
sub_category: SubCategory | None = None,
|
|
56
|
-
) -> dict[str, Any]:
|
|
57
|
-
if category and sub_category:
|
|
58
|
-
raise AssertionError("Cannot specify both category and sub_categories")
|
|
59
|
-
|
|
60
|
-
url = f"{SITE_URL}/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON"
|
|
61
|
-
|
|
62
|
-
param_dict: dict[str, str | int | None] = {
|
|
63
|
-
"q": query,
|
|
64
|
-
"page": page,
|
|
65
|
-
"sort": sort_order.value,
|
|
66
|
-
"category": category.value if category else None,
|
|
67
|
-
"sub_category": sub_category.value if sub_category else None,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
71
|
-
|
|
72
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
73
|
-
|
|
74
|
-
return _request(url=url, params=params).json()
|
|
75
|
-
|
|
76
|
-
def search_car(
|
|
77
|
-
self,
|
|
78
|
-
query: str | None = None,
|
|
79
|
-
*,
|
|
80
|
-
page: int = 1,
|
|
81
|
-
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
82
|
-
locations: list[Location] = [],
|
|
83
|
-
models: list[CarModel] = [],
|
|
84
|
-
price_from: int | None = None,
|
|
85
|
-
price_to: int | None = None,
|
|
86
|
-
year_from: int | None = None,
|
|
87
|
-
year_to: int | None = None,
|
|
88
|
-
milage_from: int | None = None,
|
|
89
|
-
milage_to: int | None = None,
|
|
90
|
-
colors: list[CarColor] = [],
|
|
91
|
-
transmissions: list[CarTransmission] = [],
|
|
92
|
-
horsepower_from: int | None = None,
|
|
93
|
-
horsepower_to: int | None = None,
|
|
94
|
-
wheel_drive: list[CarWheelDrive] = [],
|
|
95
|
-
org_id: int | None = None,
|
|
96
|
-
) -> dict[str, Any]:
|
|
97
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
|
|
98
|
-
|
|
99
|
-
param_dict: dict[str, str | int | None] = {
|
|
100
|
-
"q": query,
|
|
101
|
-
"page": page,
|
|
102
|
-
"sort": sort_order.value,
|
|
103
|
-
"price_from": price_from,
|
|
104
|
-
"price_to": price_to,
|
|
105
|
-
"year_from": year_from,
|
|
106
|
-
"year_to": year_to,
|
|
107
|
-
"milage_from": milage_from,
|
|
108
|
-
"milage_to": milage_to,
|
|
109
|
-
"engine_effect_from": horsepower_from,
|
|
110
|
-
"engine_effect_to": horsepower_to,
|
|
111
|
-
"orgId": org_id,
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
115
|
-
|
|
116
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
117
|
-
params.extend(QueryParam("make", model.value) for model in models)
|
|
118
|
-
params.extend(QueryParam("exterior_colour", color.value) for color in colors)
|
|
119
|
-
params.extend(QueryParam("transmission", t.value) for t in transmissions)
|
|
120
|
-
params.extend(QueryParam("wheel_drive", w.value) for w in wheel_drive)
|
|
121
|
-
|
|
122
|
-
return _request(url=url, params=params).json()
|
|
123
|
-
|
|
124
|
-
def search_boat(
|
|
125
|
-
self,
|
|
126
|
-
query: str | None = None,
|
|
127
|
-
*,
|
|
128
|
-
page: int = 1,
|
|
129
|
-
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
130
|
-
types: list[BoatType] = [],
|
|
131
|
-
locations: list[Location] = [],
|
|
132
|
-
price_from: int | None = None,
|
|
133
|
-
price_to: int | None = None,
|
|
134
|
-
length_from: int | None = None,
|
|
135
|
-
length_to: int | None = None,
|
|
136
|
-
org_id: int | None = None,
|
|
137
|
-
) -> Any:
|
|
138
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_BOAT_USED"
|
|
139
|
-
|
|
140
|
-
param_dict: dict[str, str | int | None] = {
|
|
141
|
-
"q": query,
|
|
142
|
-
"page": page,
|
|
143
|
-
"sort": sort_order.value,
|
|
144
|
-
"price_from": price_from,
|
|
145
|
-
"price_to": price_to,
|
|
146
|
-
"length_feet_from": length_from,
|
|
147
|
-
"length_feet_to": length_to,
|
|
148
|
-
"orgId": org_id,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
152
|
-
|
|
153
|
-
params.extend(QueryParam("class", t.value) for t in types)
|
|
154
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
155
|
-
|
|
156
|
-
return _request(url=url, params=params).json()
|
|
157
|
-
|
|
158
|
-
def search_mc(
|
|
159
|
-
self,
|
|
160
|
-
query: str | None = None,
|
|
161
|
-
*,
|
|
162
|
-
page: int = 1,
|
|
163
|
-
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
164
|
-
models: list[McModel] = [],
|
|
165
|
-
types: list[McType] = [],
|
|
166
|
-
locations: list[Location] = [],
|
|
167
|
-
price_from: int | None = None,
|
|
168
|
-
price_to: int | None = None,
|
|
169
|
-
engine_volume_from: int | None = None,
|
|
170
|
-
engine_volume_to: int | None = None,
|
|
171
|
-
org_id: int | None = None,
|
|
172
|
-
) -> dict[str, Any]:
|
|
173
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_MC_USED"
|
|
174
|
-
|
|
175
|
-
param_dict: dict[str, str | int | None] = {
|
|
176
|
-
"q": query,
|
|
177
|
-
"page": page,
|
|
178
|
-
"sort": sort_order.value,
|
|
179
|
-
"price_from": price_from,
|
|
180
|
-
"price_to": price_to,
|
|
181
|
-
"engine_volume_from": engine_volume_from,
|
|
182
|
-
"engine_volume_to": engine_volume_to,
|
|
183
|
-
"orgId": org_id,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
187
|
-
|
|
188
|
-
params.extend(QueryParam("make", m.value) for m in models)
|
|
189
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
190
|
-
params.extend(QueryParam("type", t.value) for t in types)
|
|
191
|
-
|
|
192
|
-
return _request(url=url, params=params).json()
|
|
193
|
-
|
|
194
|
-
def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
195
|
-
response = _request(url=ad.url, params=[])
|
|
196
|
-
return ad.parse(response)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|