blocket-api 0.5.1__tar.gz → 0.5.3__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.3}/PKG-INFO +32 -18
- {blocket_api-0.5.1 → blocket_api-0.5.3}/README.md +20 -8
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api/__init__.py +8 -6
- blocket_api-0.5.3/blocket_api/_params.py +166 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api/ad_parser.py +4 -5
- blocket_api-0.5.3/blocket_api/async_blocket.py +185 -0
- blocket_api-0.5.3/blocket_api/blocket.py +166 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api/constants.py +5 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api.egg-info/PKG-INFO +32 -18
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api.egg-info/SOURCES.txt +2 -0
- blocket_api-0.5.3/blocket_api.egg-info/requires.txt +13 -0
- blocket_api-0.5.3/pyproject.toml +34 -0
- blocket_api-0.5.1/blocket_api/blocket.py +0 -196
- blocket_api-0.5.1/blocket_api.egg-info/requires.txt +0 -10
- blocket_api-0.5.1/pyproject.toml +0 -25
- {blocket_api-0.5.1 → blocket_api-0.5.3}/LICENSE +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api.egg-info/dependency_links.txt +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/blocket_api.egg-info/top_level.txt +0 -0
- {blocket_api-0.5.1 → blocket_api-0.5.3}/setup.cfg +0 -0
|
@@ -1,44 +1,45 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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:
|
|
11
|
-
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
10
|
+
Requires-Dist: beautifulsoup4>=4.15.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pre-commit>=4.6.2; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
|
|
15
|
+
Requires-Dist: respx>=0.23.1; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=2.3.1; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.16.6; 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.2.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
|
|
|
31
|
-
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach and user-friendly interface
|
|
29
|
+
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach ~~and user-friendly interface~~, making it a popular choice for Swedes looking to purchase or sell items quickly and efficiently.
|
|
32
30
|
|
|
33
31
|
## 🧑💻️ Install
|
|
34
32
|
|
|
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,24 +1,23 @@
|
|
|
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
|
|
|
11
|
-
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach and user-friendly interface
|
|
7
|
+
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach ~~and user-friendly interface~~, making it a popular choice for Swedes looking to purchase or sell items quickly and efficiently.
|
|
12
8
|
|
|
13
9
|
## 🧑💻️ Install
|
|
14
10
|
|
|
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,23 +18,24 @@ from .constants import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
21
|
+
"AsyncBlocketAPI",
|
|
20
22
|
"BlocketAPI",
|
|
21
|
-
"Location",
|
|
22
23
|
"BoatAd",
|
|
23
|
-
"CarAd",
|
|
24
24
|
"BoatSortOrder",
|
|
25
25
|
"BoatType",
|
|
26
|
+
"CarAd",
|
|
26
27
|
"CarColor",
|
|
27
28
|
"CarModel",
|
|
28
29
|
"CarSortOrder",
|
|
29
30
|
"CarTransmission",
|
|
30
31
|
"CarWheelDrive",
|
|
31
32
|
"Category",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"RecommerceAd",
|
|
33
|
+
"Location",
|
|
34
|
+
"McAd",
|
|
35
35
|
"McModel",
|
|
36
36
|
"McSortOrder",
|
|
37
37
|
"McType",
|
|
38
|
-
"
|
|
38
|
+
"RecommerceAd",
|
|
39
|
+
"SortOrder",
|
|
40
|
+
"SubCategory",
|
|
39
41
|
]
|
|
@@ -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)
|
|
@@ -100,7 +100,7 @@ class MobilityAd:
|
|
|
100
100
|
return
|
|
101
101
|
|
|
102
102
|
def _extract_specifications(self, grid: Tag, data: dict) -> None:
|
|
103
|
-
specs_section = grid.find("section", class_="key-info
|
|
103
|
+
specs_section = grid.find("section", class_=lambda x: x and "key-info" in x)
|
|
104
104
|
if not specs_section:
|
|
105
105
|
return
|
|
106
106
|
dl = specs_section.find("dl")
|
|
@@ -108,9 +108,8 @@ class MobilityAd:
|
|
|
108
108
|
return
|
|
109
109
|
|
|
110
110
|
specifications = {}
|
|
111
|
-
for
|
|
112
|
-
|
|
113
|
-
dd = div.find("dd")
|
|
111
|
+
for dt in dl.find_all("dt"):
|
|
112
|
+
dd = dt.find_next_sibling("dd")
|
|
114
113
|
if dt and dd:
|
|
115
114
|
specifications[dt.get_text(strip=True)] = dd.get_text(strip=True)
|
|
116
115
|
|
|
@@ -193,7 +192,7 @@ class McAd(MobilityAd):
|
|
|
193
192
|
|
|
194
193
|
def extend(self, data: dict, soup: BeautifulSoup, grid: Tag) -> None:
|
|
195
194
|
if place_h2 := soup.find(
|
|
196
|
-
"h2",
|
|
195
|
+
"h2", string=lambda t: isinstance(t, NavigableString) and "Plats" in t
|
|
197
196
|
):
|
|
198
197
|
if link := place_h2.find_parent():
|
|
199
198
|
data["location"] = link.get_text(strip=True)
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
BoatType,
|
|
21
|
+
CarColor,
|
|
22
|
+
CarModel,
|
|
23
|
+
CarSortOrder,
|
|
24
|
+
CarTransmission,
|
|
25
|
+
CarWheelDrive,
|
|
26
|
+
Category,
|
|
27
|
+
Location,
|
|
28
|
+
McModel,
|
|
29
|
+
McSortOrder,
|
|
30
|
+
McType,
|
|
31
|
+
SortOrder,
|
|
32
|
+
SubCategory,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncBlocketAPI:
|
|
37
|
+
def __init__(self, client: httpx.AsyncClient | None = None) -> None:
|
|
38
|
+
self._client = client or httpx.AsyncClient()
|
|
39
|
+
self._owns_client = client is None
|
|
40
|
+
|
|
41
|
+
async def __aenter__(self) -> AsyncBlocketAPI:
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
async def __aexit__(self, *_: object) -> None:
|
|
45
|
+
await self.aclose()
|
|
46
|
+
|
|
47
|
+
async def aclose(self) -> None:
|
|
48
|
+
await self._client.aclose()
|
|
49
|
+
|
|
50
|
+
async def search(
|
|
51
|
+
self,
|
|
52
|
+
query: str,
|
|
53
|
+
*,
|
|
54
|
+
page: int = 1,
|
|
55
|
+
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
56
|
+
locations: list[Location] = [],
|
|
57
|
+
category: Category | None = None,
|
|
58
|
+
sub_category: SubCategory | None = None,
|
|
59
|
+
) -> dict[str, Any]:
|
|
60
|
+
params = build_search_params(
|
|
61
|
+
query,
|
|
62
|
+
page=page,
|
|
63
|
+
sort_order=sort_order,
|
|
64
|
+
locations=locations,
|
|
65
|
+
category=category,
|
|
66
|
+
sub_category=sub_category,
|
|
67
|
+
)
|
|
68
|
+
response = await self._client.get(SEARCH_URL, headers=HEADERS, params=params)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response.json()
|
|
71
|
+
|
|
72
|
+
async def search_car(
|
|
73
|
+
self,
|
|
74
|
+
query: str | None = None,
|
|
75
|
+
*,
|
|
76
|
+
page: int = 1,
|
|
77
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
78
|
+
locations: list[Location] = [],
|
|
79
|
+
models: list[CarModel] = [],
|
|
80
|
+
price_from: int | None = None,
|
|
81
|
+
price_to: int | None = None,
|
|
82
|
+
year_from: int | None = None,
|
|
83
|
+
year_to: int | None = None,
|
|
84
|
+
milage_from: int | None = None,
|
|
85
|
+
milage_to: int | None = None,
|
|
86
|
+
colors: list[CarColor] = [],
|
|
87
|
+
transmissions: list[CarTransmission] = [],
|
|
88
|
+
horsepower_from: int | None = None,
|
|
89
|
+
horsepower_to: int | None = None,
|
|
90
|
+
wheel_drive: list[CarWheelDrive] = [],
|
|
91
|
+
org_id: int | None = None,
|
|
92
|
+
) -> dict[str, Any]:
|
|
93
|
+
params = build_car_params(
|
|
94
|
+
query,
|
|
95
|
+
page=page,
|
|
96
|
+
sort_order=sort_order,
|
|
97
|
+
locations=locations,
|
|
98
|
+
models=models,
|
|
99
|
+
price_from=price_from,
|
|
100
|
+
price_to=price_to,
|
|
101
|
+
year_from=year_from,
|
|
102
|
+
year_to=year_to,
|
|
103
|
+
milage_from=milage_from,
|
|
104
|
+
milage_to=milage_to,
|
|
105
|
+
colors=colors,
|
|
106
|
+
transmissions=transmissions,
|
|
107
|
+
horsepower_from=horsepower_from,
|
|
108
|
+
horsepower_to=horsepower_to,
|
|
109
|
+
wheel_drive=wheel_drive,
|
|
110
|
+
org_id=org_id,
|
|
111
|
+
)
|
|
112
|
+
response = await self._client.get(
|
|
113
|
+
CAR_SEARCH_URL, headers=HEADERS, params=params
|
|
114
|
+
)
|
|
115
|
+
response.raise_for_status()
|
|
116
|
+
return response.json()
|
|
117
|
+
|
|
118
|
+
async def search_boat(
|
|
119
|
+
self,
|
|
120
|
+
query: str | None = None,
|
|
121
|
+
*,
|
|
122
|
+
page: int = 1,
|
|
123
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
124
|
+
types: list[BoatType] = [],
|
|
125
|
+
locations: list[Location] = [],
|
|
126
|
+
price_from: int | None = None,
|
|
127
|
+
price_to: int | None = None,
|
|
128
|
+
length_from: int | None = None,
|
|
129
|
+
length_to: int | None = None,
|
|
130
|
+
org_id: int | None = None,
|
|
131
|
+
) -> Any:
|
|
132
|
+
params = build_boat_params(
|
|
133
|
+
query,
|
|
134
|
+
page=page,
|
|
135
|
+
sort_order=sort_order,
|
|
136
|
+
types=types,
|
|
137
|
+
locations=locations,
|
|
138
|
+
price_from=price_from,
|
|
139
|
+
price_to=price_to,
|
|
140
|
+
length_from=length_from,
|
|
141
|
+
length_to=length_to,
|
|
142
|
+
org_id=org_id,
|
|
143
|
+
)
|
|
144
|
+
response = await self._client.get(
|
|
145
|
+
BOAT_SEARCH_URL, headers=HEADERS, params=params
|
|
146
|
+
)
|
|
147
|
+
response.raise_for_status()
|
|
148
|
+
return response.json()
|
|
149
|
+
|
|
150
|
+
async def search_mc(
|
|
151
|
+
self,
|
|
152
|
+
query: str | None = None,
|
|
153
|
+
*,
|
|
154
|
+
page: int = 1,
|
|
155
|
+
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
156
|
+
models: list[McModel] = [],
|
|
157
|
+
types: list[McType] = [],
|
|
158
|
+
locations: list[Location] = [],
|
|
159
|
+
price_from: int | None = None,
|
|
160
|
+
price_to: int | None = None,
|
|
161
|
+
engine_volume_from: int | None = None,
|
|
162
|
+
engine_volume_to: int | None = None,
|
|
163
|
+
org_id: int | None = None,
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
params = build_mc_params(
|
|
166
|
+
query,
|
|
167
|
+
page=page,
|
|
168
|
+
sort_order=sort_order,
|
|
169
|
+
models=models,
|
|
170
|
+
types=types,
|
|
171
|
+
locations=locations,
|
|
172
|
+
price_from=price_from,
|
|
173
|
+
price_to=price_to,
|
|
174
|
+
engine_volume_from=engine_volume_from,
|
|
175
|
+
engine_volume_to=engine_volume_to,
|
|
176
|
+
org_id=org_id,
|
|
177
|
+
)
|
|
178
|
+
response = await self._client.get(MC_SEARCH_URL, headers=HEADERS, params=params)
|
|
179
|
+
response.raise_for_status()
|
|
180
|
+
return response.json()
|
|
181
|
+
|
|
182
|
+
async def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
183
|
+
response = await self._client.get(ad.url, headers=HEADERS)
|
|
184
|
+
response.raise_for_status()
|
|
185
|
+
return ad.parse(response)
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
BoatType,
|
|
22
|
+
CarColor,
|
|
23
|
+
CarModel,
|
|
24
|
+
CarSortOrder,
|
|
25
|
+
CarTransmission,
|
|
26
|
+
CarWheelDrive,
|
|
27
|
+
Category,
|
|
28
|
+
Location,
|
|
29
|
+
McModel,
|
|
30
|
+
McSortOrder,
|
|
31
|
+
McType,
|
|
32
|
+
SortOrder,
|
|
33
|
+
SubCategory,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _request(*, url: str, params: list[tuple[str, _ParamValue]]) -> httpx.Response:
|
|
38
|
+
response = httpx.get(url, headers=HEADERS, params=params)
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
return response
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BlocketAPI:
|
|
44
|
+
def search(
|
|
45
|
+
self,
|
|
46
|
+
query: str,
|
|
47
|
+
*,
|
|
48
|
+
page: int = 1,
|
|
49
|
+
sort_order: SortOrder = SortOrder.RELEVANCE,
|
|
50
|
+
locations: list[Location] = [],
|
|
51
|
+
category: Category | None = None,
|
|
52
|
+
sub_category: SubCategory | None = None,
|
|
53
|
+
) -> dict[str, Any]:
|
|
54
|
+
params = build_search_params(
|
|
55
|
+
query,
|
|
56
|
+
page=page,
|
|
57
|
+
sort_order=sort_order,
|
|
58
|
+
locations=locations,
|
|
59
|
+
category=category,
|
|
60
|
+
sub_category=sub_category,
|
|
61
|
+
)
|
|
62
|
+
return _request(url=SEARCH_URL, params=params).json()
|
|
63
|
+
|
|
64
|
+
def search_car(
|
|
65
|
+
self,
|
|
66
|
+
query: str | None = None,
|
|
67
|
+
*,
|
|
68
|
+
page: int = 1,
|
|
69
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
70
|
+
locations: list[Location] = [],
|
|
71
|
+
models: list[CarModel] = [],
|
|
72
|
+
price_from: int | None = None,
|
|
73
|
+
price_to: int | None = None,
|
|
74
|
+
year_from: int | None = None,
|
|
75
|
+
year_to: int | None = None,
|
|
76
|
+
milage_from: int | None = None,
|
|
77
|
+
milage_to: int | None = None,
|
|
78
|
+
colors: list[CarColor] = [],
|
|
79
|
+
transmissions: list[CarTransmission] = [],
|
|
80
|
+
horsepower_from: int | None = None,
|
|
81
|
+
horsepower_to: int | None = None,
|
|
82
|
+
wheel_drive: list[CarWheelDrive] = [],
|
|
83
|
+
org_id: int | None = None,
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
params = build_car_params(
|
|
86
|
+
query,
|
|
87
|
+
page=page,
|
|
88
|
+
sort_order=sort_order,
|
|
89
|
+
locations=locations,
|
|
90
|
+
models=models,
|
|
91
|
+
price_from=price_from,
|
|
92
|
+
price_to=price_to,
|
|
93
|
+
year_from=year_from,
|
|
94
|
+
year_to=year_to,
|
|
95
|
+
milage_from=milage_from,
|
|
96
|
+
milage_to=milage_to,
|
|
97
|
+
colors=colors,
|
|
98
|
+
transmissions=transmissions,
|
|
99
|
+
horsepower_from=horsepower_from,
|
|
100
|
+
horsepower_to=horsepower_to,
|
|
101
|
+
wheel_drive=wheel_drive,
|
|
102
|
+
org_id=org_id,
|
|
103
|
+
)
|
|
104
|
+
return _request(url=CAR_SEARCH_URL, params=params).json()
|
|
105
|
+
|
|
106
|
+
def search_boat(
|
|
107
|
+
self,
|
|
108
|
+
query: str | None = None,
|
|
109
|
+
*,
|
|
110
|
+
page: int = 1,
|
|
111
|
+
sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
|
|
112
|
+
types: list[BoatType] = [],
|
|
113
|
+
locations: list[Location] = [],
|
|
114
|
+
price_from: int | None = None,
|
|
115
|
+
price_to: int | None = None,
|
|
116
|
+
length_from: int | None = None,
|
|
117
|
+
length_to: int | None = None,
|
|
118
|
+
org_id: int | None = None,
|
|
119
|
+
) -> Any:
|
|
120
|
+
params = build_boat_params(
|
|
121
|
+
query,
|
|
122
|
+
page=page,
|
|
123
|
+
sort_order=sort_order,
|
|
124
|
+
types=types,
|
|
125
|
+
locations=locations,
|
|
126
|
+
price_from=price_from,
|
|
127
|
+
price_to=price_to,
|
|
128
|
+
length_from=length_from,
|
|
129
|
+
length_to=length_to,
|
|
130
|
+
org_id=org_id,
|
|
131
|
+
)
|
|
132
|
+
return _request(url=BOAT_SEARCH_URL, params=params).json()
|
|
133
|
+
|
|
134
|
+
def search_mc(
|
|
135
|
+
self,
|
|
136
|
+
query: str | None = None,
|
|
137
|
+
*,
|
|
138
|
+
page: int = 1,
|
|
139
|
+
sort_order: McSortOrder = McSortOrder.RELEVANCE,
|
|
140
|
+
models: list[McModel] = [],
|
|
141
|
+
types: list[McType] = [],
|
|
142
|
+
locations: list[Location] = [],
|
|
143
|
+
price_from: int | None = None,
|
|
144
|
+
price_to: int | None = None,
|
|
145
|
+
engine_volume_from: int | None = None,
|
|
146
|
+
engine_volume_to: int | None = None,
|
|
147
|
+
org_id: int | None = None,
|
|
148
|
+
) -> dict[str, Any]:
|
|
149
|
+
params = build_mc_params(
|
|
150
|
+
query,
|
|
151
|
+
page=page,
|
|
152
|
+
sort_order=sort_order,
|
|
153
|
+
models=models,
|
|
154
|
+
types=types,
|
|
155
|
+
locations=locations,
|
|
156
|
+
price_from=price_from,
|
|
157
|
+
price_to=price_to,
|
|
158
|
+
engine_volume_from=engine_volume_from,
|
|
159
|
+
engine_volume_to=engine_volume_to,
|
|
160
|
+
org_id=org_id,
|
|
161
|
+
)
|
|
162
|
+
return _request(url=MC_SEARCH_URL, params=params).json()
|
|
163
|
+
|
|
164
|
+
def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
|
|
165
|
+
response = _request(url=ad.url, params=[])
|
|
166
|
+
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,44 +1,45 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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:
|
|
11
|
-
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
10
|
+
Requires-Dist: beautifulsoup4>=4.15.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pre-commit>=4.6.2; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
|
|
15
|
+
Requires-Dist: respx>=0.23.1; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=2.3.1; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff>=0.16.6; 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.2.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
|
|
|
31
|
-
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach and user-friendly interface
|
|
29
|
+
> Blocket is one of Sweden's largest online marketplaces. It was founded in 1996 and allows users to buy and sell a wide range of items, including cars, real estate, jobs, services, and second-hand goods. The platform is known for its extensive reach ~~and user-friendly interface~~, making it a popular choice for Swedes looking to purchase or sell items quickly and efficiently.
|
|
32
30
|
|
|
33
31
|
## 🧑💻️ Install
|
|
34
32
|
|
|
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
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "blocket_api"
|
|
3
|
+
version = "0.5.3"
|
|
4
|
+
description = "A python API wrapper for blocket.se"
|
|
5
|
+
authors = [{name = "dunderrrrrr", email = "emil.bjorkroth@gmail.com"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"httpx>=0.28.1",
|
|
10
|
+
"beautifulsoup4>=4.15.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = [
|
|
15
|
+
"pre-commit>=4.6.2",
|
|
16
|
+
"pytest>=9.1.1",
|
|
17
|
+
"pytest-asyncio>=1.4.0",
|
|
18
|
+
"respx>=0.23.1",
|
|
19
|
+
"mypy>=2.3.1",
|
|
20
|
+
"ruff>=0.16.6",
|
|
21
|
+
"flask>=3.1.3",
|
|
22
|
+
"htpy>=26.5.1",
|
|
23
|
+
"gunicorn>=26.2.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
testpaths = [
|
|
29
|
+
"tests/*",
|
|
30
|
+
]
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
ignore = ["B006", "RUF012", "SIM102", "PYI034"]
|
|
@@ -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)
|
blocket_api-0.5.1/pyproject.toml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "blocket_api"
|
|
3
|
-
version = "0.5.1"
|
|
4
|
-
description = "A python API wrapper for blocket.se"
|
|
5
|
-
authors = [{name = "dunderrrrrr", email = "emil.bjorkroth@gmail.com"}]
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
requires-python = ">=3.10"
|
|
8
|
-
dependencies = [
|
|
9
|
-
"httpx>=0.28.1",
|
|
10
|
-
"pre-commit>=4.5.1",
|
|
11
|
-
"pytest>=9.0.2",
|
|
12
|
-
"respx>=0.22.0",
|
|
13
|
-
"mypy>=1.19.1",
|
|
14
|
-
"mkdocs>=1.6.1",
|
|
15
|
-
"mkdocs-material>=9.7.1",
|
|
16
|
-
"mkdocs-swagger-ui-tag>=0.7.2",
|
|
17
|
-
"beautifulsoup4>=4.14.3",
|
|
18
|
-
"ruff>=0.15.12",
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[tool.pytest.ini_options]
|
|
23
|
-
testpaths = [
|
|
24
|
-
"tests/*",
|
|
25
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|