blocket-api 0.2.9__tar.gz → 0.3.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.2.9
3
+ Version: 0.3.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
@@ -12,14 +12,14 @@ Requires-Dist: pytest>=8.2.2
12
12
  Requires-Dist: respx>=0.21.1
13
13
  Requires-Dist: mypy>=1.15.0
14
14
  Requires-Dist: pydantic>=2.11.7
15
- Requires-Dist: ruff>=0.13.0
15
+ Requires-Dist: ruff>=0.13.1
16
16
  Dynamic: license-file
17
17
 
18
18
  # BlocketAPI
19
19
 
20
20
  [![PyPI version](https://img.shields.io/pypi/v/blocket_api?style=for-the-badge)](https://pypi.org/project/blocket_api/) [![License](https://img.shields.io/badge/license-WTFPL-green?style=for-the-badge)](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE) ![Python](https://img.shields.io/badge/Python-3.10-blue?style=for-the-badge) ![PyPI - Downloads](https://img.shields.io/pypi/dm/blocket_api?style=for-the-badge&color=%23dbce58)
21
21
 
22
- BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format.
22
+ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format or as cleaned pydantic objects.
23
23
 
24
24
  > 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.
25
25
 
@@ -34,12 +34,14 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
34
34
 
35
35
  ## 🧑‍💻️ Install
36
36
 
37
- BlocketAPI is available on PyPI.
37
+ Install BlocketAPI via PyPI...
38
38
 
39
39
  ```sh
40
40
  pip install blocket-api
41
41
  ```
42
42
 
43
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
44
+
43
45
  ## 💁‍♀️ Usage
44
46
 
45
47
  ```py
@@ -63,9 +65,11 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
63
65
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
64
66
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
65
67
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
66
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
68
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
67
69
  | [`api.store_search()`](#store_search) | 👏 No | Yes | Search for a store.
68
- | [`api.get_store_listings()`](#get_store_listings) | 👏 No | - | Get listings from a specific store.
70
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
71
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
72
+
69
73
 
70
74
  ## 🤓 Detailed usage
71
75
 
@@ -133,7 +137,7 @@ Parameters:
133
137
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
134
138
 
135
139
  ```py
136
- >>> from blocket_api import Region
140
+ >>> from blocket_api.blocket import Region, Category
137
141
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
138
142
  {
139
143
  "data":[
@@ -215,6 +219,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
215
219
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
216
220
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
217
221
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
222
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
218
223
 
219
224
  ```py
220
225
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -254,6 +259,8 @@ Get listings from a specific store.
254
259
 
255
260
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
256
261
  - `page` (`int`, optional) - The page number to return.
262
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
263
+
257
264
 
258
265
  ```py
259
266
  >>> api.get_store_listings(1234)
@@ -271,6 +278,30 @@ Get listings from a specific store.
271
278
  }
272
279
  ```
273
280
 
281
+ ### get_ad_by_id
282
+ Get ad data from a specific ad id.
283
+
284
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
285
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
286
+
287
+
288
+ ```py
289
+ >>> api.get_ad_by_id(1234)
290
+ {
291
+ "data": {
292
+ "ad_id": "1234",
293
+ "ad_status": "active",
294
+ "advertiser": {...},
295
+ "body": "A nice ad.",
296
+ "category": [
297
+ {"id": "4000", "name": "Personligt"},
298
+ {"id": "4080", "name": "Kläder & skor"},
299
+ ],
300
+ ...
301
+ }
302
+ }
303
+ ```
304
+
274
305
  ## 🔐 Blocket API token
275
306
 
276
307
  There are two ways to acquire your token:
@@ -295,5 +326,6 @@ My token has never expired or changed during this project. However, if your're m
295
326
 
296
327
  ## 📝 Notes
297
328
 
329
+ - REST API: https://blocket-api.se
298
330
  - Source repo: https://github.com/dunderrrrrr/blocket_api
299
331
  - PyPI: https://pypi.org/project/blocket-api/
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/blocket_api?style=for-the-badge)](https://pypi.org/project/blocket_api/) [![License](https://img.shields.io/badge/license-WTFPL-green?style=for-the-badge)](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE) ![Python](https://img.shields.io/badge/Python-3.10-blue?style=for-the-badge) ![PyPI - Downloads](https://img.shields.io/pypi/dm/blocket_api?style=for-the-badge&color=%23dbce58)
4
4
 
5
- BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format.
5
+ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format or as cleaned pydantic objects.
6
6
 
7
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.
8
8
 
@@ -17,12 +17,14 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
17
17
 
18
18
  ## 🧑‍💻️ Install
19
19
 
20
- BlocketAPI is available on PyPI.
20
+ Install BlocketAPI via PyPI...
21
21
 
22
22
  ```sh
23
23
  pip install blocket-api
24
24
  ```
25
25
 
26
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
27
+
26
28
  ## 💁‍♀️ Usage
27
29
 
28
30
  ```py
@@ -46,9 +48,11 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
46
48
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
47
49
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
48
50
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
49
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
51
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
50
52
  | [`api.store_search()`](#store_search) | 👏 No | Yes | Search for a store.
51
- | [`api.get_store_listings()`](#get_store_listings) | 👏 No | - | Get listings from a specific store.
53
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
54
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
55
+
52
56
 
53
57
  ## 🤓 Detailed usage
54
58
 
@@ -116,7 +120,7 @@ Parameters:
116
120
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
117
121
 
118
122
  ```py
119
- >>> from blocket_api import Region
123
+ >>> from blocket_api.blocket import Region, Category
120
124
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
121
125
  {
122
126
  "data":[
@@ -198,6 +202,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
198
202
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
199
203
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
200
204
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
205
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
201
206
 
202
207
  ```py
203
208
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -237,6 +242,8 @@ Get listings from a specific store.
237
242
 
238
243
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
239
244
  - `page` (`int`, optional) - The page number to return.
245
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
246
+
240
247
 
241
248
  ```py
242
249
  >>> api.get_store_listings(1234)
@@ -254,6 +261,30 @@ Get listings from a specific store.
254
261
  }
255
262
  ```
256
263
 
264
+ ### get_ad_by_id
265
+ Get ad data from a specific ad id.
266
+
267
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
268
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
269
+
270
+
271
+ ```py
272
+ >>> api.get_ad_by_id(1234)
273
+ {
274
+ "data": {
275
+ "ad_id": "1234",
276
+ "ad_status": "active",
277
+ "advertiser": {...},
278
+ "body": "A nice ad.",
279
+ "category": [
280
+ {"id": "4000", "name": "Personligt"},
281
+ {"id": "4080", "name": "Kläder & skor"},
282
+ ],
283
+ ...
284
+ }
285
+ }
286
+ ```
287
+
257
288
  ## 🔐 Blocket API token
258
289
 
259
290
  There are two ways to acquire your token:
@@ -278,5 +309,6 @@ My token has never expired or changed during this project. However, if your're m
278
309
 
279
310
  ## 📝 Notes
280
311
 
312
+ - REST API: https://blocket-api.se
281
313
  - Source repo: https://github.com/dunderrrrrr/blocket_api
282
314
  - PyPI: https://pypi.org/project/blocket-api/
@@ -10,8 +10,11 @@ from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union, ov
10
10
  import httpx
11
11
 
12
12
  from blocket_api.models import (
13
+ AdByIdResults,
13
14
  CustomSearchResults,
15
+ HomeSearchResults,
14
16
  MotorSearchResults,
17
+ StoreListings,
15
18
  StoreSearchResults,
16
19
  )
17
20
  from blocket_api.qasa import HOME_SEARCH_ORDERING, HomeType, OrderBy, Qasa
@@ -403,6 +406,7 @@ class BlocketAPI:
403
406
  url = f"{BYTBIL_URL}/blocket-basedata-api/v3/vehicle-data/{registration_number}"
404
407
  return _make_request(url=f"{url}", token=None).json()
405
408
 
409
+ @overload
406
410
  def home_search(
407
411
  self,
408
412
  city: str,
@@ -410,19 +414,45 @@ class BlocketAPI:
410
414
  order_by: OrderBy = OrderBy.published_at,
411
415
  ordering: HOME_SEARCH_ORDERING = "descending",
412
416
  offset: int = 0,
413
- ) -> dict:
417
+ *,
418
+ as_objects: Literal[True],
419
+ ) -> HomeSearchResults: ...
420
+
421
+ @overload
422
+ def home_search(
423
+ self,
424
+ city: str,
425
+ type: HomeType,
426
+ order_by: OrderBy = OrderBy.published_at,
427
+ ordering: HOME_SEARCH_ORDERING = "descending",
428
+ offset: int = 0,
429
+ *,
430
+ as_objects: Literal[False] = False,
431
+ ) -> dict: ...
432
+
433
+ def home_search(
434
+ self,
435
+ city: str,
436
+ type: HomeType,
437
+ order_by: OrderBy = OrderBy.published_at,
438
+ ordering: HOME_SEARCH_ORDERING = "descending",
439
+ offset: int = 0,
440
+ *,
441
+ as_objects: bool = False,
442
+ ) -> dict | HomeSearchResults:
414
443
  """
415
444
  This returns all available home listings available at
416
445
  https://bostad.blocket.se/. Specify offset to get next page. Each page contains
417
446
  60 items, which is max items returned per api query.
418
447
  """
419
- return Qasa(
448
+ response = Qasa(
420
449
  city=city,
421
450
  home_type=type,
422
451
  order_by=order_by,
423
452
  ordering=ordering,
424
453
  offset=offset,
425
454
  ).search()
455
+ return HomeSearchResults.model_validate(response) if as_objects else response
426
456
 
427
457
  @overload
428
458
  def search_store(
@@ -473,12 +503,32 @@ class BlocketAPI:
473
503
  response = _make_request(url=f"{url}", token=self.token).json()
474
504
  return StoreSearchResults.model_validate(response) if as_objects else response
475
505
 
506
+ @overload
507
+ def get_store_listings(
508
+ self,
509
+ store_id: int,
510
+ page: int = 0,
511
+ *,
512
+ as_objects: Literal[True],
513
+ ) -> StoreListings: ...
514
+
515
+ @overload
516
+ def get_store_listings(
517
+ self,
518
+ store_id: int,
519
+ page: int = 0,
520
+ *,
521
+ as_objects: Literal[False] = False,
522
+ ) -> dict: ...
523
+
476
524
  @public_token
477
525
  def get_store_listings(
478
526
  self,
479
527
  store_id: int,
480
528
  page: int = 0,
481
- ) -> dict:
529
+ *,
530
+ as_objects: bool = False,
531
+ ) -> dict | StoreListings:
482
532
  """
483
533
  Return all listings from a specific store from https://www.blocket.se/butik/<store>.
484
534
  The store_id can be found by searching for the store with search_store().
@@ -487,4 +537,32 @@ class BlocketAPI:
487
537
  f"{BASE_URL}/search_bff/v2/content?lim=60&page={page}&sort=rel&store_id={store_id}"
488
538
  "&status=active&gl=3&include=extend_with_shipping"
489
539
  )
490
- return _make_request(url=f"{url}", token=self.token).json()
540
+ response = _make_request(url=f"{url}", token=self.token).json()
541
+ return StoreListings.model_validate(response) if as_objects else response
542
+
543
+ @overload
544
+ def get_ad_by_id(
545
+ self,
546
+ ad_id: int,
547
+ *,
548
+ as_objects: Literal[True],
549
+ ) -> AdByIdResults: ...
550
+
551
+ @overload
552
+ def get_ad_by_id(
553
+ self,
554
+ ad_id: int,
555
+ *,
556
+ as_objects: Literal[False] = False,
557
+ ) -> dict: ...
558
+
559
+ @public_token
560
+ def get_ad_by_id(
561
+ self,
562
+ ad_id: int,
563
+ *,
564
+ as_objects: bool = False,
565
+ ) -> dict | AdByIdResults:
566
+ url = f"{BASE_URL}/search_bff/v2/content/{ad_id}"
567
+ response = _make_request(url=f"{url}", token=self.token).json()
568
+ return AdByIdResults.model_validate(response) if as_objects else response
@@ -0,0 +1,536 @@
1
+ import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+ ### CustomSearch ###
7
+
8
+
9
+ class CustomSearchPrice(BaseModel):
10
+ suffix: str
11
+ value: int
12
+
13
+
14
+ class CustomSearchLocation(BaseModel):
15
+ image_url: str
16
+ label: str
17
+ query_key: str | None = None
18
+
19
+
20
+ class CustomSearchImage(BaseModel):
21
+ height: int
22
+ type: str
23
+ url: str
24
+ width: int
25
+
26
+
27
+ class CustomSearchCategory(BaseModel):
28
+ id: str
29
+ name: str
30
+
31
+
32
+ class CustomSearchResult(BaseModel):
33
+ ad_id: str
34
+ ad_status: str
35
+ body: str
36
+ category: list[CustomSearchCategory]
37
+ images: list[CustomSearchImage] = []
38
+ list_id: str
39
+ list_time: datetime.datetime
40
+ map: CustomSearchLocation | None = None
41
+ map_url: str | None = None
42
+ price: CustomSearchPrice
43
+ share_url: str
44
+ state_id: str
45
+ subject: str
46
+ type: str
47
+ zipcode: str | None = None
48
+
49
+
50
+ class CustomSearchResults(BaseModel):
51
+ data: list[CustomSearchResult]
52
+ total_count: int
53
+
54
+
55
+ ### MotorSearch ###
56
+
57
+
58
+ class MotorSearchCarEquipment(BaseModel):
59
+ label: str
60
+
61
+
62
+ class MotorSearchCarLocation(BaseModel):
63
+ region: str
64
+ municipality: str
65
+ area: str
66
+
67
+
68
+ class MotorSearchCarImage(BaseModel):
69
+ height: int
70
+ width: int
71
+ image: str
72
+
73
+
74
+ class MotorSearchCar(BaseModel):
75
+ images: list[MotorSearchCarImage]
76
+ location: MotorSearchCarLocation
77
+ fuel: str
78
+ gearbox: str
79
+ regDate: int
80
+ mileage: int
81
+ equipment: list[MotorSearchCarEquipment] | None = None
82
+
83
+
84
+ class MotorSearchPrice(BaseModel):
85
+ amount: str
86
+ billingPeriod: str
87
+ oldPrice: str | None = None
88
+
89
+
90
+ class MotorSearchSeller(BaseModel):
91
+ type: str
92
+ name: str
93
+ id: str
94
+
95
+
96
+ class MotorSearchResult(BaseModel):
97
+ dealId: str
98
+ link: str
99
+ listTime: datetime.datetime
100
+ originalListTime: datetime.datetime
101
+ seller: MotorSearchSeller
102
+ heading: str
103
+ price: MotorSearchPrice
104
+ thumbnail: str
105
+ car: MotorSearchCar
106
+ description: str
107
+
108
+
109
+ class MotorSearchResults(BaseModel):
110
+ cars: list[MotorSearchResult]
111
+ hits: int
112
+ pages: int
113
+
114
+
115
+ ### StoreSearch ###
116
+
117
+
118
+ class StoreSearchLocation(BaseModel):
119
+ longitude: float
120
+ latitude: float
121
+
122
+
123
+ class StoreSearchImage(BaseModel):
124
+ height: int
125
+ width: int
126
+ url: str
127
+
128
+
129
+ class StoreSearchLogotype(BaseModel):
130
+ height: int
131
+ width: int
132
+ url: str
133
+
134
+
135
+ class StoreSearchResult(BaseModel):
136
+ address: str | None = None
137
+ id: str
138
+ logotype: StoreSearchLogotype
139
+ image: StoreSearchImage | None = None
140
+ description: str
141
+ external_store_url: str | None = None
142
+ location: StoreSearchLocation | None = None
143
+ name: str
144
+ title: str
145
+ ad_count: int
146
+ category: str
147
+
148
+
149
+ class StoreSearchResults(BaseModel):
150
+ data: list[StoreSearchResult]
151
+ total_count: int
152
+ total_page_count: int
153
+
154
+
155
+ ### HomeSearch ###
156
+
157
+
158
+ class HomeSearchGeoPoint(BaseModel):
159
+ lat: float
160
+ lon: float
161
+
162
+
163
+ class HomeSearchLocation(BaseModel):
164
+ id: int
165
+ locality: str
166
+ countryCode: str
167
+ streetNumber: str | None = None
168
+ point: HomeSearchGeoPoint
169
+ route: str
170
+
171
+
172
+ class HomeSearchUpload(BaseModel):
173
+ id: int
174
+ order: int | None = None
175
+ type: str
176
+ url: str
177
+
178
+
179
+ class HomeSearchResult(BaseModel):
180
+ bedroomCount: int | None = None
181
+ blockListing: bool
182
+ rentalLengthSeconds: float | None = None
183
+ householdSize: int
184
+ corporateHome: bool
185
+ description: str
186
+ endDate: str | None = None
187
+ firstHand: bool
188
+ furnished: bool
189
+ homeType: str
190
+ id: str
191
+ instantSign: bool
192
+ market: str
193
+ lastBumpedAt: str | None = None
194
+ monthlyCost: int
195
+ petsAllowed: bool
196
+ platform: str
197
+ publishedAt: str
198
+ publishedOrBumpedAt: str
199
+ earlyAccessEndsAt: str | None = None
200
+ rent: int
201
+ currency: str
202
+ roomCount: int
203
+ seniorHome: bool
204
+ shared: bool
205
+ shortcutHome: bool
206
+ smokingAllowed: bool
207
+ sortingScore: float
208
+ squareMeters: int
209
+ startDate: str
210
+ studentHome: bool
211
+ tenantBaseFee: int
212
+ title: str | None = None
213
+ wheelchairAccessible: bool
214
+ location: HomeSearchLocation
215
+ displayStreetNumber: bool
216
+ uploads: list[HomeSearchUpload]
217
+
218
+
219
+ class Documents(BaseModel):
220
+ hasNextPage: bool
221
+ hasPreviousPage: bool
222
+ nodes: list[HomeSearchResult]
223
+ pagesCount: int
224
+ totalCount: int
225
+
226
+
227
+ class HomeIndexSearch(BaseModel):
228
+ documents: Documents
229
+
230
+
231
+ class DataModel(BaseModel):
232
+ homeIndexSearch: HomeIndexSearch
233
+
234
+
235
+ class HomeSearchResults(BaseModel):
236
+ data: DataModel
237
+
238
+
239
+ ### StoreListings ###
240
+
241
+
242
+ class StoreListingContactMethods(BaseModel):
243
+ phone: bool
244
+
245
+
246
+ class StoreListingAdvertiser(BaseModel):
247
+ contact_methods: StoreListingContactMethods
248
+ name: str
249
+ store_name: str | None = None
250
+ type: str
251
+
252
+
253
+ class StoreListingAttribute(BaseModel):
254
+ header: str
255
+ id: str
256
+ items: list[str]
257
+
258
+
259
+ class StoreListingCategory(BaseModel):
260
+ id: str
261
+ name: str
262
+
263
+
264
+ class StoreListingImage(BaseModel):
265
+ height: int
266
+ type: str
267
+ url: str
268
+ width: int
269
+
270
+
271
+ class StorListingLocationItem(BaseModel):
272
+ id: str
273
+ name: str
274
+ query_key: str
275
+
276
+
277
+ class StoreListingParameter(BaseModel):
278
+ id: str
279
+ label: str
280
+ value: str
281
+ suffix: str | None = None
282
+
283
+
284
+ class ParameterGroup(BaseModel):
285
+ label: str
286
+ parameters: list[StoreListingParameter]
287
+ type: str
288
+
289
+
290
+ class StoreListingPartnerInfo(BaseModel):
291
+ external_id: str
292
+ name: str
293
+
294
+
295
+ class StoreListingPrice(BaseModel):
296
+ suffix: str
297
+ value: int
298
+
299
+
300
+ class StoreListing(BaseModel):
301
+ ad_id: str
302
+ ad_status: str
303
+ advertiser: StoreListingAdvertiser
304
+ attributes: list[StoreListingAttribute]
305
+ body: str
306
+ category: list[StoreListingCategory]
307
+ images: list[StoreListingImage]
308
+ license_plate: str | None = None
309
+ list_id: str
310
+ list_time: str
311
+ location: list[StorListingLocationItem]
312
+ map_url: str | None = None
313
+ parameter_groups: list[ParameterGroup]
314
+ partner_info: StoreListingPartnerInfo
315
+ price: StoreListingPrice
316
+ share_url: str
317
+ state_id: str
318
+ subject: str
319
+ type: str
320
+
321
+
322
+ class StoreListings(BaseModel):
323
+ data: list[StoreListing]
324
+
325
+
326
+ ### GetAdById ###
327
+
328
+
329
+ class AdByIdReviews(BaseModel):
330
+ overall_score: int
331
+ reviews_received_count: int
332
+
333
+
334
+ class AdByIdPublicProfile(BaseModel):
335
+ account_created_at: str
336
+ badges: list[Any]
337
+ blocket_account_id: str
338
+ display_name: str
339
+ number_of_active_ads: int
340
+ reviews: AdByIdReviews
341
+ verified: bool
342
+ verified_label: str
343
+
344
+
345
+ class AdByIdContactMethods(BaseModel):
346
+ mc: bool
347
+
348
+
349
+ class AdByIdAdvertiser(BaseModel):
350
+ account_id: str
351
+ contact_methods: AdByIdContactMethods
352
+ name: str
353
+ public_profile: AdByIdPublicProfile
354
+ type: str
355
+
356
+
357
+ class AdByIdCategory(BaseModel):
358
+ id: str
359
+ name: str
360
+
361
+
362
+ class AdByIdImage(BaseModel):
363
+ height: int
364
+ type: str
365
+ url: str
366
+ width: int
367
+
368
+
369
+ class AdByIdLocation(BaseModel):
370
+ id: str
371
+ name: str
372
+ query_key: str
373
+
374
+
375
+ class AdByIdMap(BaseModel):
376
+ image_url: str
377
+ label: str
378
+ type: str
379
+
380
+
381
+ class AdByIdParameter(BaseModel):
382
+ id: str
383
+ label: str
384
+ value: str
385
+ suffix: str | None = None
386
+
387
+
388
+ class AdByIdParameterGroup(BaseModel):
389
+ label: str
390
+ parameters: list[AdByIdParameter]
391
+ type: str
392
+
393
+
394
+ class AdByIdPrice(BaseModel):
395
+ suffix: str
396
+ value: int
397
+
398
+
399
+ class AdByIdFeatures(BaseModel):
400
+ is_part_of_merge: bool
401
+
402
+
403
+ class AdByIdUrls(BaseModel):
404
+ seller_activate: str
405
+
406
+
407
+ class AdByIdBlocketPackage(BaseModel):
408
+ activated_providers: list[Any]
409
+ ad_id: str
410
+ blocket_payment_backwards_compatability: Any
411
+ buyers_protection_fee: Any
412
+ eligible_providers: list[Any]
413
+ fail_code: Any
414
+ failure: Any
415
+ feature_swish: bool
416
+ features: AdByIdFeatures
417
+ is_in_dispute: bool
418
+ partner: str
419
+ payment_methods: list[Any]
420
+ payment_request_entrance: Any
421
+ payment_url: Any
422
+ service_data: Any
423
+ shipping_buy_now_entrance: Any
424
+ state: str
425
+ transaction_dates: Any
426
+ urls: AdByIdUrls
427
+
428
+
429
+ class AdByIdGamApiKeywords(BaseModel):
430
+ adid: list[str]
431
+ adtitle: list[str]
432
+ adtype: list[str]
433
+ appmode: list[str]
434
+ blocket_section: list[str]
435
+ companyad: list[str]
436
+ country: list[str]
437
+ country_code: list[str]
438
+ county: list[str]
439
+ id: list[str]
440
+ itemimage: list[str]
441
+ itemsecuredthumb: list[str]
442
+ itemthumb: list[str]
443
+ livingarea: list[str] | None = None
444
+ lkf: list[str]
445
+ municipality: list[str]
446
+ nmp_vertical: list[str]
447
+ page: list[str]
448
+ price: list[str]
449
+ price_range_2: list[str] | None = None
450
+ publisher: list[str]
451
+ services: list[Any]
452
+
453
+
454
+ class AdByIdClientKeywords(BaseModel):
455
+ cmp_advertising: str
456
+ logged_in: str
457
+ page_gen: str
458
+ ppid_1: str
459
+ ppid_2: str
460
+ screen_height: str
461
+ screen_width: str
462
+ site_mode: str
463
+ supply_type: str
464
+ viewport_height: str
465
+ viewport_width: str
466
+
467
+
468
+ class AdByIdGamContext(BaseModel):
469
+ api_keywords: AdByIdGamApiKeywords
470
+ client_keywords: AdByIdClientKeywords
471
+
472
+
473
+ class AdByIdContext(BaseModel):
474
+ gam: AdByIdGamContext
475
+ network_id: str | None = None
476
+
477
+
478
+ class AdByIdInventory(BaseModel):
479
+ context: AdByIdContext
480
+
481
+
482
+ class AdByIdGamKeywords(BaseModel):
483
+ adformat: list[str]
484
+ adsize: list[str]
485
+
486
+
487
+ class AdByIdGamMediaType(BaseModel):
488
+ height: int | None = None
489
+ type: str
490
+ width: int | None = None
491
+
492
+
493
+ class AdByIdGamPlacement(BaseModel):
494
+ keywords: AdByIdGamKeywords
495
+ media_types: list[AdByIdGamMediaType]
496
+ path: str
497
+ target_id: str
498
+
499
+
500
+ class AdByIdPlacement(BaseModel):
501
+ gam: AdByIdGamPlacement | None = None
502
+ type: str
503
+ vendor: str
504
+ viewport: str
505
+ safe_purchase: dict[str, Any] | None = None
506
+
507
+
508
+ class AdByIdData(BaseModel):
509
+ ad_id: str
510
+ ad_status: str
511
+ advertiser: AdByIdAdvertiser
512
+ body: str
513
+ category: list[AdByIdCategory]
514
+ images: list[AdByIdImage]
515
+ latitude: float | None = None
516
+ list_id: str
517
+ list_time: str
518
+ location: list[AdByIdLocation]
519
+ longitude: float | None = None
520
+ map: AdByIdMap
521
+ map_url: str
522
+ parameter_groups: list[AdByIdParameterGroup]
523
+ price: AdByIdPrice
524
+ saved: bool
525
+ share_url: str
526
+ state_id: str
527
+ subject: str
528
+ type: str
529
+ zipcode: str
530
+ blocket_package: AdByIdBlocketPackage
531
+
532
+
533
+ class AdByIdResults(BaseModel):
534
+ data: AdByIdData
535
+ inventory: AdByIdInventory
536
+ placements: list[AdByIdPlacement] | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.2.9
3
+ Version: 0.3.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
@@ -12,14 +12,14 @@ Requires-Dist: pytest>=8.2.2
12
12
  Requires-Dist: respx>=0.21.1
13
13
  Requires-Dist: mypy>=1.15.0
14
14
  Requires-Dist: pydantic>=2.11.7
15
- Requires-Dist: ruff>=0.13.0
15
+ Requires-Dist: ruff>=0.13.1
16
16
  Dynamic: license-file
17
17
 
18
18
  # BlocketAPI
19
19
 
20
20
  [![PyPI version](https://img.shields.io/pypi/v/blocket_api?style=for-the-badge)](https://pypi.org/project/blocket_api/) [![License](https://img.shields.io/badge/license-WTFPL-green?style=for-the-badge)](https://github.com/dunderrrrrr/blocket_api/blob/main/LICENSE) ![Python](https://img.shields.io/badge/Python-3.10-blue?style=for-the-badge) ![PyPI - Downloads](https://img.shields.io/pypi/dm/blocket_api?style=for-the-badge&color=%23dbce58)
21
21
 
22
- BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format.
22
+ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blocket.se](https://blocket.se/). This means you can either retrieve results from a specific saved search or list all listings/ads across all saved searches. The results from these queries are returned in a `json` format or as cleaned pydantic objects.
23
23
 
24
24
  > 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.
25
25
 
@@ -34,12 +34,14 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
34
34
 
35
35
  ## 🧑‍💻️ Install
36
36
 
37
- BlocketAPI is available on PyPI.
37
+ Install BlocketAPI via PyPI...
38
38
 
39
39
  ```sh
40
40
  pip install blocket-api
41
41
  ```
42
42
 
43
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
44
+
43
45
  ## 💁‍♀️ Usage
44
46
 
45
47
  ```py
@@ -63,9 +65,11 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
63
65
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
64
66
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
65
67
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
66
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
68
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
67
69
  | [`api.store_search()`](#store_search) | 👏 No | Yes | Search for a store.
68
- | [`api.get_store_listings()`](#get_store_listings) | 👏 No | - | Get listings from a specific store.
70
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
71
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
72
+
69
73
 
70
74
  ## 🤓 Detailed usage
71
75
 
@@ -133,7 +137,7 @@ Parameters:
133
137
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
134
138
 
135
139
  ```py
136
- >>> from blocket_api import Region
140
+ >>> from blocket_api.blocket import Region, Category
137
141
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
138
142
  {
139
143
  "data":[
@@ -215,6 +219,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
215
219
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
216
220
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
217
221
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
222
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
218
223
 
219
224
  ```py
220
225
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -254,6 +259,8 @@ Get listings from a specific store.
254
259
 
255
260
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
256
261
  - `page` (`int`, optional) - The page number to return.
262
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
263
+
257
264
 
258
265
  ```py
259
266
  >>> api.get_store_listings(1234)
@@ -271,6 +278,30 @@ Get listings from a specific store.
271
278
  }
272
279
  ```
273
280
 
281
+ ### get_ad_by_id
282
+ Get ad data from a specific ad id.
283
+
284
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
285
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
286
+
287
+
288
+ ```py
289
+ >>> api.get_ad_by_id(1234)
290
+ {
291
+ "data": {
292
+ "ad_id": "1234",
293
+ "ad_status": "active",
294
+ "advertiser": {...},
295
+ "body": "A nice ad.",
296
+ "category": [
297
+ {"id": "4000", "name": "Personligt"},
298
+ {"id": "4080", "name": "Kläder & skor"},
299
+ ],
300
+ ...
301
+ }
302
+ }
303
+ ```
304
+
274
305
  ## 🔐 Blocket API token
275
306
 
276
307
  There are two ways to acquire your token:
@@ -295,5 +326,6 @@ My token has never expired or changed during this project. However, if your're m
295
326
 
296
327
  ## 📝 Notes
297
328
 
329
+ - REST API: https://blocket-api.se
298
330
  - Source repo: https://github.com/dunderrrrrr/blocket_api
299
331
  - PyPI: https://pypi.org/project/blocket-api/
@@ -4,4 +4,4 @@ pytest>=8.2.2
4
4
  respx>=0.21.1
5
5
  mypy>=1.15.0
6
6
  pydantic>=2.11.7
7
- ruff>=0.13.0
7
+ ruff>=0.13.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "blocket_api"
3
- version = "0.2.9"
3
+ version = "0.3.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"
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "respx>=0.21.1",
13
13
  "mypy>=1.15.0",
14
14
  "pydantic>=2.11.7",
15
- "ruff>=0.13.0",
15
+ "ruff>=0.13.1",
16
16
  ]
17
17
 
18
18
 
@@ -1,151 +0,0 @@
1
- import datetime
2
-
3
- from pydantic import BaseModel
4
-
5
- ### CustomSearch ###
6
-
7
-
8
- class CustomSearchPrice(BaseModel):
9
- suffix: str
10
- value: int
11
-
12
-
13
- class CustomSearchLocation(BaseModel):
14
- image_url: str
15
- label: str
16
- query_key: str | None = None
17
-
18
-
19
- class CustomSearchImage(BaseModel):
20
- height: int
21
- type: str
22
- url: str
23
- width: int
24
-
25
-
26
- class CustomSearchCategory(BaseModel):
27
- id: str
28
- name: str
29
-
30
-
31
- class CustomSearchResult(BaseModel):
32
- ad_id: str
33
- ad_status: str
34
- body: str
35
- category: list[CustomSearchCategory]
36
- images: list[CustomSearchImage] = []
37
- list_id: str
38
- list_time: datetime.datetime
39
- map: CustomSearchLocation | None = None
40
- map_url: str | None = None
41
- price: CustomSearchPrice
42
- share_url: str
43
- state_id: str
44
- subject: str
45
- type: str
46
- zipcode: str | None = None
47
-
48
-
49
- class CustomSearchResults(BaseModel):
50
- data: list[CustomSearchResult]
51
- total_count: int
52
-
53
-
54
- ### MotorSearch ###
55
-
56
-
57
- class MotorSearchCarEquipment(BaseModel):
58
- label: str
59
-
60
-
61
- class MotorSearchCarLocation(BaseModel):
62
- region: str
63
- municipality: str
64
- area: str
65
-
66
-
67
- class MotorSearchCarImage(BaseModel):
68
- height: int
69
- width: int
70
- image: str
71
-
72
-
73
- class MotorSearchCar(BaseModel):
74
- images: list[MotorSearchCarImage]
75
- location: MotorSearchCarLocation
76
- fuel: str
77
- gearbox: str
78
- regDate: int
79
- mileage: int
80
- equipment: list[MotorSearchCarEquipment] | None = None
81
-
82
-
83
- class MotorSearchPrice(BaseModel):
84
- amount: str
85
- billingPeriod: str
86
- oldPrice: str | None = None
87
-
88
-
89
- class MotorSearchSeller(BaseModel):
90
- type: str
91
- name: str
92
- id: str
93
-
94
-
95
- class MotorSearchResult(BaseModel):
96
- dealId: str
97
- link: str
98
- listTime: datetime.datetime
99
- originalListTime: datetime.datetime
100
- seller: MotorSearchSeller
101
- heading: str
102
- price: MotorSearchPrice
103
- thumbnail: str
104
- car: MotorSearchCar
105
- description: str
106
-
107
-
108
- class MotorSearchResults(BaseModel):
109
- cars: list[MotorSearchResult]
110
- hits: int
111
- pages: int
112
-
113
-
114
- ### StoreSearch ###
115
-
116
-
117
- class StoreSearchLocation(BaseModel):
118
- longitude: float
119
- latitude: float
120
-
121
-
122
- class StoreSearchImage(BaseModel):
123
- height: int
124
- width: int
125
- url: str
126
-
127
-
128
- class StoreSearchLogotype(BaseModel):
129
- height: int
130
- width: int
131
- url: str
132
-
133
-
134
- class StoreSearchResult(BaseModel):
135
- address: str | None = None
136
- id: str
137
- logotype: StoreSearchLogotype
138
- image: StoreSearchImage | None = None
139
- description: str
140
- external_store_url: str | None = None
141
- location: StoreSearchLocation | None = None
142
- name: str
143
- title: str
144
- ad_count: int
145
- category: str
146
-
147
-
148
- class StoreSearchResults(BaseModel):
149
- data: list[StoreSearchResult]
150
- total_count: int
151
- total_page_count: int
File without changes
File without changes