blocket-api 0.3.93__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.3.93
3
+ Version: 0.4.0
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
@@ -17,7 +17,7 @@ Requires-Dist: mkdocs-material>=9.6.21
17
17
  Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
18
18
  Requires-Dist: websockets>=15.0.1
19
19
  Requires-Dist: beautifulsoup4>=4.14.2
20
- Requires-Dist: ruff>=0.14.6
20
+ Requires-Dist: ruff>=0.14.10
21
21
  Dynamic: license-file
22
22
 
23
23
  <p align="center">
@@ -90,13 +90,28 @@ api.search_boat(
90
90
  price_to=90000,
91
91
  )
92
92
 
93
+ # search for motorcycles
94
+ from blocket_api import McType, McModel
95
+
96
+ api.search_mc(
97
+ "TC 150", # query is optional
98
+ types=[McType.SPORT],
99
+ locations=[Location.STOCKHOLM],
100
+ models=[McModel.DUCATI],
101
+ price_from=20000,
102
+ price_to=90000,
103
+ engine_volume_from=100,
104
+ engine_volume_to=200,
105
+ )
106
+
93
107
 
94
108
  # get ad details
95
- from blocket_api import CarAd, RecommerceAd, BoatAd
109
+ from blocket_api import CarAd, RecommerceAd, BoatAd, McAd
96
110
 
97
111
  api.get_ad(RecommerceAd(12345678))
98
112
  api.get_ad(CarAd(12345678))
99
113
  api.get_ad(BoatAd(12345678))
114
+ api.get_ad(McAd(12345678))
100
115
  ```
101
116
 
102
117
  ## 📝 Notes
@@ -68,13 +68,28 @@ api.search_boat(
68
68
  price_to=90000,
69
69
  )
70
70
 
71
+ # search for motorcycles
72
+ from blocket_api import McType, McModel
73
+
74
+ api.search_mc(
75
+ "TC 150", # query is optional
76
+ types=[McType.SPORT],
77
+ locations=[Location.STOCKHOLM],
78
+ models=[McModel.DUCATI],
79
+ price_from=20000,
80
+ price_to=90000,
81
+ engine_volume_from=100,
82
+ engine_volume_to=200,
83
+ )
84
+
71
85
 
72
86
  # get ad details
73
- from blocket_api import CarAd, RecommerceAd, BoatAd
87
+ from blocket_api import CarAd, RecommerceAd, BoatAd, McAd
74
88
 
75
89
  api.get_ad(RecommerceAd(12345678))
76
90
  api.get_ad(CarAd(12345678))
77
91
  api.get_ad(BoatAd(12345678))
92
+ api.get_ad(McAd(12345678))
78
93
  ```
79
94
 
80
95
  ## 📝 Notes
@@ -0,0 +1,37 @@
1
+ from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
2
+ from .blocket import BlocketAPI, Location
3
+ from .constants import (
4
+ BoatSortOrder,
5
+ BoatType,
6
+ CarColor,
7
+ CarModel,
8
+ CarSortOrder,
9
+ CarTransmission,
10
+ Category,
11
+ McModel,
12
+ McSortOrder,
13
+ McType,
14
+ SortOrder,
15
+ SubCategory,
16
+ )
17
+
18
+ __all__ = [
19
+ "BlocketAPI",
20
+ "Location",
21
+ "BoatAd",
22
+ "CarAd",
23
+ "BoatSortOrder",
24
+ "BoatType",
25
+ "CarColor",
26
+ "CarModel",
27
+ "CarSortOrder",
28
+ "CarTransmission",
29
+ "Category",
30
+ "SortOrder",
31
+ "SubCategory",
32
+ "RecommerceAd",
33
+ "McModel",
34
+ "McSortOrder",
35
+ "McType",
36
+ "McAd",
37
+ ]
@@ -17,7 +17,7 @@ class RecommerceAd:
17
17
  def url(self) -> str:
18
18
  return f"{SITE_URL}/recommerce/forsale/item/{self.id}"
19
19
 
20
- def parse(self, response: Response) -> Any:
20
+ def parse(self, response: Response) -> dict[str, Any]:
21
21
  soup = BeautifulSoup(response.content, "html.parser")
22
22
  json_script_tag = soup.select_one(
23
23
  'script:-soup-contains("window.__staticRouterHydrationData")'
@@ -41,7 +41,7 @@ class MobilityAd:
41
41
  def url(self) -> str:
42
42
  return f"{SITE_URL}/mobility/item/{self.id}"
43
43
 
44
- def parse(self, response: Response) -> dict:
44
+ def parse(self, response: Response) -> dict[str, Any]:
45
45
  soup = BeautifulSoup(response.content, "html.parser")
46
46
  grid = soup.find("div", class_="grid grid-cols-1 md:grid-cols-3 md:gap-x-32")
47
47
 
@@ -182,3 +182,18 @@ class BoatAd(MobilityAd):
182
182
  ):
183
183
  if link := place_h2.find_parent():
184
184
  data["location"] = link.get_text(strip=True)
185
+
186
+
187
+ class McAd(MobilityAd):
188
+ quick_spec_mapping = {
189
+ "Modellår": "model_year",
190
+ "Motorvolym": "engine_volume",
191
+ "Typ": "type",
192
+ }
193
+
194
+ def extend(self, data: dict, soup: BeautifulSoup, grid: Tag) -> None:
195
+ if place_h2 := soup.find(
196
+ "h2", text=lambda t: isinstance(t, NavigableString) and "Plats" in t
197
+ ):
198
+ if link := place_h2.find_parent():
199
+ data["location"] = link.get_text(strip=True)
@@ -6,7 +6,7 @@ from typing import Any
6
6
  import httpx
7
7
  from httpx import Response
8
8
 
9
- from .ad_parser import BoatAd, CarAd, RecommerceAd
9
+ from .ad_parser import BoatAd, CarAd, McAd, RecommerceAd
10
10
  from .constants import (
11
11
  HEADERS,
12
12
  SITE_URL,
@@ -17,6 +17,9 @@ from .constants import (
17
17
  CarTransmission,
18
18
  Category,
19
19
  Location,
20
+ McModel,
21
+ McSortOrder,
22
+ McType,
20
23
  SortOrder,
21
24
  SubCategory,
22
25
  )
@@ -44,18 +47,20 @@ class BlocketAPI:
44
47
  self,
45
48
  query: str,
46
49
  *,
50
+ page: int = 1,
47
51
  sort_order: SortOrder = SortOrder.RELEVANCE,
48
52
  locations: list[Location] = [],
49
53
  category: Category | None = None,
50
54
  sub_category: SubCategory | None = None,
51
- ) -> Any:
55
+ ) -> dict[str, Any]:
52
56
  if category and sub_category:
53
57
  raise AssertionError("Cannot specify both category and sub_categories")
54
58
 
55
- url = f"{SITE_URL}/recommerce-search-page/api/search/SEARCH_ID_BAP_COMMON"
59
+ url = f"{SITE_URL}/recommerce/forsale/search/api/search/SEARCH_ID_BAP_COMMON"
56
60
 
57
- param_dict = {
61
+ param_dict: dict[str, str | int | None] = {
58
62
  "q": query,
63
+ "page": page,
59
64
  "sort": sort_order.value,
60
65
  "category": category.value if category else None,
61
66
  "sub_category": sub_category.value if sub_category else None,
@@ -71,6 +76,7 @@ class BlocketAPI:
71
76
  self,
72
77
  query: str | None = None,
73
78
  *,
79
+ page: int = 1,
74
80
  sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
75
81
  locations: list[Location] = [],
76
82
  models: list[CarModel] = [],
@@ -82,11 +88,12 @@ class BlocketAPI:
82
88
  milage_to: int | None = None,
83
89
  colors: list[CarColor] = [],
84
90
  transmissions: list[CarTransmission] = [],
85
- ) -> Any:
91
+ ) -> dict[str, Any]:
86
92
  url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
87
93
 
88
- param_dict = {
94
+ param_dict: dict[str, str | int | None] = {
89
95
  "q": query,
96
+ "page": page,
90
97
  "sort": sort_order.value,
91
98
  "price_from": price_from,
92
99
  "price_to": price_to,
@@ -110,6 +117,7 @@ class BlocketAPI:
110
117
  self,
111
118
  query: str | None = None,
112
119
  *,
120
+ page: int = 1,
113
121
  sort_order: CarSortOrder = CarSortOrder.RELEVANCE,
114
122
  types: list[BoatType] = [],
115
123
  locations: list[Location] = [],
@@ -120,8 +128,9 @@ class BlocketAPI:
120
128
  ) -> Any:
121
129
  url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_BOAT_USED"
122
130
 
123
- param_dict = {
131
+ param_dict: dict[str, str | int | None] = {
124
132
  "q": query,
133
+ "page": page,
125
134
  "sort": sort_order.value,
126
135
  "price_from": price_from,
127
136
  "price_to": price_to,
@@ -136,6 +145,40 @@ class BlocketAPI:
136
145
 
137
146
  return _request(url=url, params=params).json()
138
147
 
139
- def get_ad(self, ad: RecommerceAd | CarAd | BoatAd) -> dict:
148
+ def search_mc(
149
+ self,
150
+ query: str | None = None,
151
+ *,
152
+ page: int = 1,
153
+ sort_order: McSortOrder = McSortOrder.RELEVANCE,
154
+ models: list[McModel] = [],
155
+ types: list[McType] = [],
156
+ locations: list[Location] = [],
157
+ price_from: int | None = None,
158
+ price_to: int | None = None,
159
+ engine_volume_from: int | None = None,
160
+ engine_volume_to: int | None = None,
161
+ ) -> dict[str, Any]:
162
+ url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_MC_USED"
163
+
164
+ param_dict: dict[str, str | int | None] = {
165
+ "q": query,
166
+ "page": page,
167
+ "sort": sort_order.value,
168
+ "price_from": price_from,
169
+ "price_to": price_to,
170
+ "engine_volume_from": engine_volume_from,
171
+ "engine_volume_to": engine_volume_to,
172
+ }
173
+
174
+ params = [QueryParam(k, v) for k, v in param_dict.items() if v is not None]
175
+
176
+ params.extend(QueryParam("make", m.value) for m in models)
177
+ params.extend(QueryParam("location", loc.value) for loc in locations)
178
+ params.extend(QueryParam("type", t.value) for t in types)
179
+
180
+ return _request(url=url, params=params).json()
181
+
182
+ def get_ad(self, ad: RecommerceAd | CarAd | BoatAd | McAd) -> dict[str, Any]:
140
183
  response = _request(url=ad.url, params=[])
141
184
  return ad.parse(response)
@@ -29,6 +29,21 @@ class CarSortOrder(StrEnum):
29
29
  YEAR_ASC = "YEAR_ASC"
30
30
 
31
31
 
32
+ class McSortOrder(StrEnum):
33
+ RELEVANCE = "RELEVANCE"
34
+ PRICE_DESC = "PRICE_DESC"
35
+ PRICE_ASC = "PRICE_ASC"
36
+ PUBLISHED_DESC = "PUBLISHED_DESC"
37
+ PUBLISHED_ASC = "PUBLISHED_ASC"
38
+
39
+ # mc specific
40
+ MILAGE_DESC = "MILAGE_DESC"
41
+ MILAGE_ASC = "MILAGE_ASC"
42
+ MODEL = "MODEL"
43
+ YEAR_DESC = "YEAR_DESC"
44
+ YEAR_ASC = "YEAR_ASC"
45
+
46
+
32
47
  class BoatSortOrder(StrEnum):
33
48
  RELEVANCE = "RELEVANCE"
34
49
  PRICE_DESC = "PRICE_DESC"
@@ -381,3 +396,122 @@ class BoatType(IntEnum):
381
396
  VATTENSKOTER = 2190
382
397
  YACHT = 7959
383
398
  ANNAT = 2258
399
+
400
+
401
+ class McModel(IntEnum):
402
+ ADLY = 7902
403
+ AGIRRA = 8306
404
+ AIXAM = 8257
405
+ AJP = 8411
406
+ AJS = 1528
407
+ AMERICAN_IRON_HORSE = 8337
408
+ APRILIA = 2998
409
+ BAJAJ = 6722
410
+ BAOTIAN = 1546
411
+ BAROSSA = 1534
412
+ BENDA = 482
413
+ BENGHE = 7904
414
+ BETA = 8254
415
+ BIG_DOG = 8335
416
+ BIMOTA = 2997
417
+ BMW = 1481
418
+ BOSS_HOSS = 8008
419
+ BSA = 8114
420
+ BUELL = 7333
421
+ BULTACO = 7907
422
+ CAGIVA = 4328
423
+ CAKE = 8500
424
+ CAN_AM = 8245
425
+ CFMOTO = 8251
426
+ COBRA = 8004
427
+ DUCATI = 1484
428
+ ENDURO = 7918
429
+ ENERGICA = 8368
430
+ EUROSCOOTER = 7927
431
+ FANTIC = 1522
432
+ GASGAS = 8256
433
+ GILERA = 7354
434
+ GOES = 8310
435
+ HARLEY_DAVIDSON = 1486
436
+ HERO = 7903
437
+ HONDA = 1487
438
+ HUSABERG = 7356
439
+ HUSQVARNA = 7357
440
+ HYOSUNG = 6725
441
+ INDIAN = 8313
442
+ ITALJET = 8369
443
+ JAWA_CZ = 1488
444
+ KAWASAKI = 1489
445
+ KTM = 7771
446
+ KYMCO = 7363
447
+ LAMBRETTA = 8360
448
+ LIFAN = 8357
449
+ LIGIER = 8331
450
+ LYNX = 7920
451
+ MOTO_GUZZI = 1492
452
+ MV_AGUSTA = 7365
453
+ MZ = 8085
454
+ NIU = 8385
455
+ NORTON = 7367
456
+ OSET = 8401
457
+ PEUGEOT = 1494
458
+ PIAGGIO = 1495
459
+ POLARIS = 7922
460
+ QINGQI = 8236
461
+ QUADRO = 8378
462
+ REGAL_RAPTOR = 8210
463
+ RENAULT = 481
464
+ RIEJU = 7912
465
+ ROMET = 8362
466
+ ROYAL_ENFIELD = 8336
467
+ SEGWAY = 8407
468
+ SHERCO = 8255
469
+ SILENCE = 8508
470
+ SKI_DOO = 7926
471
+ SMC = 8242
472
+ STARK = 1192
473
+ SUPER_SOCO = 8388
474
+ SUR_RON = 8379
475
+ SUZUKI = 1497
476
+ SWM = 8394
477
+ SYM = 7915
478
+ TALARIA = 8519
479
+ TEN7 = 8502
480
+ TGB = 8205
481
+ THUNDER_BY_CITY_WHEELS = 8400
482
+ TM = 1516
483
+ TOMOS = 8235
484
+ TRIUMPH = 1500
485
+ URAL = 8512
486
+ V8_CHOPPERS = 8397
487
+ VERTIGO = 8408
488
+ VESPA = 7901
489
+ VIARELLI = 8366
490
+ VICTORY = 8317
491
+ VOGE = 8390
492
+ X_PRO = 8404
493
+ YADEA = 8503
494
+ YAMAHA = 1502
495
+ ZERO = 8351
496
+ ZONTES = 8516
497
+ ZUNDAPP = 7911
498
+ OVRIGA = 2045
499
+
500
+
501
+ class McType(IntEnum):
502
+ ADVENTURE = 20
503
+ CHOPPER = 17
504
+ CROSS_ENDURO_TRIAL = 3
505
+ CRUISER = 16
506
+ CUSTOM = 4
507
+ KLASSISK_NAKEN = 2
508
+ LATT_MC = 5
509
+ MC_SCOOTER = 18
510
+ OFFROAD_MOTARD = 7
511
+ SPORT = 11
512
+ SUPERMOTO = 1547
513
+ TOURING = 12
514
+ TREHJULIG = 1549
515
+ TRIKE = 13
516
+ VETERAN = 14
517
+ ANNAT = 15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.3.93
3
+ Version: 0.4.0
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
@@ -17,7 +17,7 @@ Requires-Dist: mkdocs-material>=9.6.21
17
17
  Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
18
18
  Requires-Dist: websockets>=15.0.1
19
19
  Requires-Dist: beautifulsoup4>=4.14.2
20
- Requires-Dist: ruff>=0.14.6
20
+ Requires-Dist: ruff>=0.14.10
21
21
  Dynamic: license-file
22
22
 
23
23
  <p align="center">
@@ -90,13 +90,28 @@ api.search_boat(
90
90
  price_to=90000,
91
91
  )
92
92
 
93
+ # search for motorcycles
94
+ from blocket_api import McType, McModel
95
+
96
+ api.search_mc(
97
+ "TC 150", # query is optional
98
+ types=[McType.SPORT],
99
+ locations=[Location.STOCKHOLM],
100
+ models=[McModel.DUCATI],
101
+ price_from=20000,
102
+ price_to=90000,
103
+ engine_volume_from=100,
104
+ engine_volume_to=200,
105
+ )
106
+
93
107
 
94
108
  # get ad details
95
- from blocket_api import CarAd, RecommerceAd, BoatAd
109
+ from blocket_api import CarAd, RecommerceAd, BoatAd, McAd
96
110
 
97
111
  api.get_ad(RecommerceAd(12345678))
98
112
  api.get_ad(CarAd(12345678))
99
113
  api.get_ad(BoatAd(12345678))
114
+ api.get_ad(McAd(12345678))
100
115
  ```
101
116
 
102
117
  ## 📝 Notes
@@ -9,4 +9,4 @@ mkdocs-material>=9.6.21
9
9
  mkdocs-swagger-ui-tag>=0.7.2
10
10
  websockets>=15.0.1
11
11
  beautifulsoup4>=4.14.2
12
- ruff>=0.14.6
12
+ ruff>=0.14.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "blocket_api"
3
- version = "0.3.93"
3
+ version = "0.4.0"
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"
@@ -17,7 +17,7 @@ dependencies = [
17
17
  "mkdocs-swagger-ui-tag>=0.7.2",
18
18
  "websockets>=15.0.1",
19
19
  "beautifulsoup4>=4.14.2",
20
- "ruff>=0.14.6",
20
+ "ruff>=0.14.10",
21
21
  ]
22
22
 
23
23
 
@@ -1,13 +0,0 @@
1
- from .ad_parser import BoatAd as BoatAd
2
- from .ad_parser import CarAd as CarAd
3
- from .ad_parser import RecommerceAd as RecommerceAd
4
- from .blocket import BlocketAPI as BlocketAPI
5
- from .blocket import Location as Location
6
- from .constants import BoatType as BoatType
7
- from .constants import CarColor as CarColor
8
- from .constants import CarModel as CarModel
9
- from .constants import CarSortOrder as CarSortOrder
10
- from .constants import CarTransmission as CarTransmission
11
- from .constants import Category as Category
12
- from .constants import SortOrder as SortOrder
13
- from .constants import SubCategory as SubCategory
File without changes
File without changes