blocket-api 0.5.0__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.0 → blocket_api-0.5.2}/PKG-INFO +32 -16
- {blocket_api-0.5.0 → blocket_api-0.5.2}/README.md +21 -7
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api/__init__.py +4 -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.0 → blocket_api-0.5.2}/blocket_api/constants.py +12 -0
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api.egg-info/PKG-INFO +32 -16
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api.egg-info/SOURCES.txt +2 -0
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api.egg-info/requires.txt +8 -5
- {blocket_api-0.5.0 → blocket_api-0.5.2}/pyproject.toml +12 -6
- blocket_api-0.5.0/blocket_api/blocket.py +0 -193
- {blocket_api-0.5.0 → blocket_api-0.5.2}/LICENSE +0 -0
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api/ad_parser.py +0 -0
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api.egg-info/dependency_links.txt +0 -0
- {blocket_api-0.5.0 → blocket_api-0.5.2}/blocket_api.egg-info/top_level.txt +0 -0
- {blocket_api-0.5.0 → 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
|
|
|
@@ -50,6 +51,7 @@ from blocket_api import (
|
|
|
50
51
|
CarModel,
|
|
51
52
|
CarSortOrder,
|
|
52
53
|
CarTransmission,
|
|
54
|
+
CarWheelDrive,
|
|
53
55
|
Location,
|
|
54
56
|
)
|
|
55
57
|
|
|
@@ -72,6 +74,7 @@ api.search_car(
|
|
|
72
74
|
price_from=10000,
|
|
73
75
|
price_to=50000,
|
|
74
76
|
transmissions=[CarTransmission.MANUAL],
|
|
77
|
+
wheel_drive=[CarWheelDrive.RWD],
|
|
75
78
|
locations=[Location.STOCKHOLM],
|
|
76
79
|
horsepower_from=200,
|
|
77
80
|
horsepower_to=300,
|
|
@@ -117,6 +120,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
117
120
|
api.get_ad(McAd(12345678))
|
|
118
121
|
```
|
|
119
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
|
+
|
|
120
136
|
## 📝 Notes
|
|
121
137
|
|
|
122
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
|
|
|
@@ -30,6 +29,7 @@ from blocket_api import (
|
|
|
30
29
|
CarModel,
|
|
31
30
|
CarSortOrder,
|
|
32
31
|
CarTransmission,
|
|
32
|
+
CarWheelDrive,
|
|
33
33
|
Location,
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -52,6 +52,7 @@ api.search_car(
|
|
|
52
52
|
price_from=10000,
|
|
53
53
|
price_to=50000,
|
|
54
54
|
transmissions=[CarTransmission.MANUAL],
|
|
55
|
+
wheel_drive=[CarWheelDrive.RWD],
|
|
55
56
|
locations=[Location.STOCKHOLM],
|
|
56
57
|
horsepower_from=200,
|
|
57
58
|
horsepower_to=300,
|
|
@@ -97,6 +98,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
97
98
|
api.get_ad(McAd(12345678))
|
|
98
99
|
```
|
|
99
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
|
+
|
|
100
114
|
## 📝 Notes
|
|
101
115
|
|
|
102
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,
|
|
@@ -7,6 +8,7 @@ from .constants import (
|
|
|
7
8
|
CarModel,
|
|
8
9
|
CarSortOrder,
|
|
9
10
|
CarTransmission,
|
|
11
|
+
CarWheelDrive,
|
|
10
12
|
Category,
|
|
11
13
|
McModel,
|
|
12
14
|
McSortOrder,
|
|
@@ -16,6 +18,7 @@ from .constants import (
|
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
21
|
+
"AsyncBlocketAPI",
|
|
19
22
|
"BlocketAPI",
|
|
20
23
|
"Location",
|
|
21
24
|
"BoatAd",
|
|
@@ -26,6 +29,7 @@ __all__ = [
|
|
|
26
29
|
"CarModel",
|
|
27
30
|
"CarSortOrder",
|
|
28
31
|
"CarTransmission",
|
|
32
|
+
"CarWheelDrive",
|
|
29
33
|
"Category",
|
|
30
34
|
"SortOrder",
|
|
31
35
|
"SubCategory",
|
|
@@ -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
|
}
|
|
@@ -381,6 +386,13 @@ class CarTransmission(IntEnum):
|
|
|
381
386
|
MANUAL = 1
|
|
382
387
|
|
|
383
388
|
|
|
389
|
+
class CarWheelDrive(IntEnum):
|
|
390
|
+
FWD = 3
|
|
391
|
+
RWD = 1
|
|
392
|
+
FOUR = 2
|
|
393
|
+
TWO = 1729
|
|
394
|
+
|
|
395
|
+
|
|
384
396
|
class BoatType(IntEnum):
|
|
385
397
|
BOWRIDER = 7961
|
|
386
398
|
DAYCRUISER = 2184
|
|
@@ -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
|
|
|
@@ -50,6 +51,7 @@ from blocket_api import (
|
|
|
50
51
|
CarModel,
|
|
51
52
|
CarSortOrder,
|
|
52
53
|
CarTransmission,
|
|
54
|
+
CarWheelDrive,
|
|
53
55
|
Location,
|
|
54
56
|
)
|
|
55
57
|
|
|
@@ -72,6 +74,7 @@ api.search_car(
|
|
|
72
74
|
price_from=10000,
|
|
73
75
|
price_to=50000,
|
|
74
76
|
transmissions=[CarTransmission.MANUAL],
|
|
77
|
+
wheel_drive=[CarWheelDrive.RWD],
|
|
75
78
|
locations=[Location.STOCKHOLM],
|
|
76
79
|
horsepower_from=200,
|
|
77
80
|
horsepower_to=300,
|
|
@@ -117,6 +120,19 @@ api.get_ad(BoatAd(12345678))
|
|
|
117
120
|
api.get_ad(McAd(12345678))
|
|
118
121
|
```
|
|
119
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
|
+
|
|
120
136
|
## 📝 Notes
|
|
121
137
|
|
|
122
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,193 +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
|
-
Category,
|
|
19
|
-
Location,
|
|
20
|
-
McModel,
|
|
21
|
-
McSortOrder,
|
|
22
|
-
McType,
|
|
23
|
-
SortOrder,
|
|
24
|
-
SubCategory,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass(frozen=True)
|
|
29
|
-
class QueryParam:
|
|
30
|
-
name: str
|
|
31
|
-
value: str | int
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _request(*, url: str, params: list[QueryParam]) -> Response:
|
|
35
|
-
response = httpx.get(
|
|
36
|
-
url,
|
|
37
|
-
headers=HEADERS,
|
|
38
|
-
params=[(param.name, param.value) for param in params],
|
|
39
|
-
)
|
|
40
|
-
response.raise_for_status()
|
|
41
|
-
return response
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@dataclass(frozen=True)
|
|
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
|
-
if category and sub_category:
|
|
57
|
-
raise AssertionError("Cannot specify both category and sub_categories")
|
|
58
|
-
|
|
59
|
-
url = f"{SITE_URL}/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON"
|
|
60
|
-
|
|
61
|
-
param_dict: dict[str, str | int | None] = {
|
|
62
|
-
"q": query,
|
|
63
|
-
"page": page,
|
|
64
|
-
"sort": sort_order.value,
|
|
65
|
-
"category": category.value if category else None,
|
|
66
|
-
"sub_category": sub_category.value if sub_category else None,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
70
|
-
|
|
71
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
72
|
-
|
|
73
|
-
return _request(url=url, params=params).json()
|
|
74
|
-
|
|
75
|
-
def search_car(
|
|
76
|
-
self,
|
|
77
|
-
query: str | None = None,
|
|
78
|
-
*,
|
|
79
|
-
page: int = 1,
|
|
80
|
-
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
81
|
-
locations: list[Location] = [],
|
|
82
|
-
models: list[CarModel] = [],
|
|
83
|
-
price_from: int | None = None,
|
|
84
|
-
price_to: int | None = None,
|
|
85
|
-
year_from: int | None = None,
|
|
86
|
-
year_to: int | None = None,
|
|
87
|
-
milage_from: int | None = None,
|
|
88
|
-
milage_to: int | None = None,
|
|
89
|
-
colors: list[CarColor] = [],
|
|
90
|
-
transmissions: list[CarTransmission] = [],
|
|
91
|
-
horsepower_from: int | None = None,
|
|
92
|
-
horsepower_to: int | None = None,
|
|
93
|
-
org_id: int | None = None,
|
|
94
|
-
) -> dict[str, Any]:
|
|
95
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
|
|
96
|
-
|
|
97
|
-
param_dict: dict[str, str | int | None] = {
|
|
98
|
-
"q": query,
|
|
99
|
-
"page": page,
|
|
100
|
-
"sort": sort_order.value,
|
|
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
|
-
"engine_effect_from": horsepower_from,
|
|
108
|
-
"engine_effect_to": horsepower_to,
|
|
109
|
-
"orgId": org_id,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
113
|
-
|
|
114
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
115
|
-
params.extend(QueryParam("make", model.value) for model in models)
|
|
116
|
-
params.extend(QueryParam("exterior_colour", color.value) for color in colors)
|
|
117
|
-
params.extend(QueryParam("transmission", t.value) for t in transmissions)
|
|
118
|
-
|
|
119
|
-
return _request(url=url, params=params).json()
|
|
120
|
-
|
|
121
|
-
def search_boat(
|
|
122
|
-
self,
|
|
123
|
-
query: str | None = None,
|
|
124
|
-
*,
|
|
125
|
-
page: int = 1,
|
|
126
|
-
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
127
|
-
types: list[BoatType] = [],
|
|
128
|
-
locations: list[Location] = [],
|
|
129
|
-
price_from: int | None = None,
|
|
130
|
-
price_to: int | None = None,
|
|
131
|
-
length_from: int | None = None,
|
|
132
|
-
length_to: int | None = None,
|
|
133
|
-
org_id: int | None = None,
|
|
134
|
-
) -> Any:
|
|
135
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_BOAT_USED"
|
|
136
|
-
|
|
137
|
-
param_dict: dict[str, str | int | None] = {
|
|
138
|
-
"q": query,
|
|
139
|
-
"page": page,
|
|
140
|
-
"sort": sort_order.value,
|
|
141
|
-
"price_from": price_from,
|
|
142
|
-
"price_to": price_to,
|
|
143
|
-
"length_feet_from": length_from,
|
|
144
|
-
"length_feet_to": length_to,
|
|
145
|
-
"orgId": org_id,
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
149
|
-
|
|
150
|
-
params.extend(QueryParam("class", t.value) for t in types)
|
|
151
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
152
|
-
|
|
153
|
-
return _request(url=url, params=params).json()
|
|
154
|
-
|
|
155
|
-
def search_mc(
|
|
156
|
-
self,
|
|
157
|
-
query: str | None = None,
|
|
158
|
-
*,
|
|
159
|
-
page: int = 1,
|
|
160
|
-
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
161
|
-
models: list[McModel] = [],
|
|
162
|
-
types: list[McType] = [],
|
|
163
|
-
locations: list[Location] = [],
|
|
164
|
-
price_from: int | None = None,
|
|
165
|
-
price_to: int | None = None,
|
|
166
|
-
engine_volume_from: int | None = None,
|
|
167
|
-
engine_volume_to: int | None = None,
|
|
168
|
-
org_id: int | None = None,
|
|
169
|
-
) -> dict[str, Any]:
|
|
170
|
-
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_MC_USED"
|
|
171
|
-
|
|
172
|
-
param_dict: dict[str, str | int | None] = {
|
|
173
|
-
"q": query,
|
|
174
|
-
"page": page,
|
|
175
|
-
"sort": sort_order.value,
|
|
176
|
-
"price_from": price_from,
|
|
177
|
-
"price_to": price_to,
|
|
178
|
-
"engine_volume_from": engine_volume_from,
|
|
179
|
-
"engine_volume_to": engine_volume_to,
|
|
180
|
-
"orgId": org_id,
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
|
|
184
|
-
|
|
185
|
-
params.extend(QueryParam("make", m.value) for m in models)
|
|
186
|
-
params.extend(QueryParam("location", loc.value) for loc in locations)
|
|
187
|
-
params.extend(QueryParam("type", t.value) for t in types)
|
|
188
|
-
|
|
189
|
-
return _request(url=url, params=params).json()
|
|
190
|
-
|
|
191
|
-
def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
192
|
-
response = _request(url=ad.url, params=[])
|
|
193
|
-
return ad.parse(response)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|