blocket-api 0.2.9__tar.gz → 0.3.1__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.1
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
 
@@ -31,15 +31,18 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
31
31
  - Get vehicle information, price evaluation and search for vehicles with multiple filters.
32
32
  - Search for homes with filters for city, home type and more.
33
33
  - Search for stores and get a specific store's listings.
34
+ - ...and more!
34
35
 
35
36
  ## 🧑‍💻️ Install
36
37
 
37
- BlocketAPI is available on PyPI.
38
+ Install BlocketAPI via PyPI...
38
39
 
39
40
  ```sh
40
41
  pip install blocket-api
41
42
  ```
42
43
 
44
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
45
+
43
46
  ## 💁‍♀️ Usage
44
47
 
45
48
  ```py
@@ -63,9 +66,12 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
63
66
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
64
67
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
65
68
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
66
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
69
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
67
70
  | [`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.
71
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
72
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
73
+ | [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
74
+
69
75
 
70
76
  ## 🤓 Detailed usage
71
77
 
@@ -133,7 +139,7 @@ Parameters:
133
139
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
134
140
 
135
141
  ```py
136
- >>> from blocket_api import Region
142
+ >>> from blocket_api.blocket import Region, Category
137
143
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
138
144
  {
139
145
  "data":[
@@ -215,6 +221,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
215
221
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
216
222
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
217
223
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
224
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
218
225
 
219
226
  ```py
220
227
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -254,6 +261,8 @@ Get listings from a specific store.
254
261
 
255
262
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
256
263
  - `page` (`int`, optional) - The page number to return.
264
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
265
+
257
266
 
258
267
  ```py
259
268
  >>> api.get_store_listings(1234)
@@ -271,6 +280,53 @@ Get listings from a specific store.
271
280
  }
272
281
  ```
273
282
 
283
+ ### get_ad_by_id
284
+ Get ad data from a specific ad id.
285
+
286
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
287
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
288
+
289
+
290
+ ```py
291
+ >>> api.get_ad_by_id(1234)
292
+ {
293
+ "data": {
294
+ "ad_id": "1234",
295
+ "ad_status": "active",
296
+ "advertiser": {...},
297
+ "body": "A nice ad.",
298
+ "category": [
299
+ {"id": "4000", "name": "Personligt"},
300
+ {"id": "4080", "name": "Kläder & skor"},
301
+ ],
302
+ ...
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### get_user_by_id
308
+ Get user data from a user id. User id can be found in ads, searches and in blocket url `https://www.blocket.se/profil/<user_id>`.
309
+
310
+ - `user_id` (`int`, required) - The blocket user id.
311
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
312
+
313
+
314
+ ```py
315
+ >>> api.get_user_by_id(1234)
316
+ {
317
+ 'account': {
318
+ 'blocket_account_id': '1234',
319
+ 'created_at': '2020-10-10T15:45:06.407660',
320
+ 'is_verified': True,
321
+ 'name': 'Janne',
322
+ ...
323
+ },
324
+ 'active_ads': {[
325
+ ...
326
+ ]}
327
+ }
328
+ ```
329
+
274
330
  ## 🔐 Blocket API token
275
331
 
276
332
  There are two ways to acquire your token:
@@ -295,5 +351,6 @@ My token has never expired or changed during this project. However, if your're m
295
351
 
296
352
  ## 📝 Notes
297
353
 
354
+ - REST API: https://blocket-api.se
298
355
  - Source repo: https://github.com/dunderrrrrr/blocket_api
299
356
  - 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
 
@@ -14,15 +14,18 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
14
14
  - Get vehicle information, price evaluation and search for vehicles with multiple filters.
15
15
  - Search for homes with filters for city, home type and more.
16
16
  - Search for stores and get a specific store's listings.
17
+ - ...and more!
17
18
 
18
19
  ## 🧑‍💻️ Install
19
20
 
20
- BlocketAPI is available on PyPI.
21
+ Install BlocketAPI via PyPI...
21
22
 
22
23
  ```sh
23
24
  pip install blocket-api
24
25
  ```
25
26
 
27
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
28
+
26
29
  ## 💁‍♀️ Usage
27
30
 
28
31
  ```py
@@ -46,9 +49,12 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
46
49
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
47
50
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
48
51
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
49
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
52
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
50
53
  | [`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.
54
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
55
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
56
+ | [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
57
+
52
58
 
53
59
  ## 🤓 Detailed usage
54
60
 
@@ -116,7 +122,7 @@ Parameters:
116
122
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
117
123
 
118
124
  ```py
119
- >>> from blocket_api import Region
125
+ >>> from blocket_api.blocket import Region, Category
120
126
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
121
127
  {
122
128
  "data":[
@@ -198,6 +204,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
198
204
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
199
205
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
200
206
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
207
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
201
208
 
202
209
  ```py
203
210
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -237,6 +244,8 @@ Get listings from a specific store.
237
244
 
238
245
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
239
246
  - `page` (`int`, optional) - The page number to return.
247
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
248
+
240
249
 
241
250
  ```py
242
251
  >>> api.get_store_listings(1234)
@@ -254,6 +263,53 @@ Get listings from a specific store.
254
263
  }
255
264
  ```
256
265
 
266
+ ### get_ad_by_id
267
+ Get ad data from a specific ad id.
268
+
269
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
270
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
271
+
272
+
273
+ ```py
274
+ >>> api.get_ad_by_id(1234)
275
+ {
276
+ "data": {
277
+ "ad_id": "1234",
278
+ "ad_status": "active",
279
+ "advertiser": {...},
280
+ "body": "A nice ad.",
281
+ "category": [
282
+ {"id": "4000", "name": "Personligt"},
283
+ {"id": "4080", "name": "Kläder & skor"},
284
+ ],
285
+ ...
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### get_user_by_id
291
+ Get user data from a user id. User id can be found in ads, searches and in blocket url `https://www.blocket.se/profil/<user_id>`.
292
+
293
+ - `user_id` (`int`, required) - The blocket user id.
294
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
295
+
296
+
297
+ ```py
298
+ >>> api.get_user_by_id(1234)
299
+ {
300
+ 'account': {
301
+ 'blocket_account_id': '1234',
302
+ 'created_at': '2020-10-10T15:45:06.407660',
303
+ 'is_verified': True,
304
+ 'name': 'Janne',
305
+ ...
306
+ },
307
+ 'active_ads': {[
308
+ ...
309
+ ]}
310
+ }
311
+ ```
312
+
257
313
  ## 🔐 Blocket API token
258
314
 
259
315
  There are two ways to acquire your token:
@@ -278,5 +334,6 @@ My token has never expired or changed during this project. However, if your're m
278
334
 
279
335
  ## 📝 Notes
280
336
 
337
+ - REST API: https://blocket-api.se
281
338
  - Source repo: https://github.com/dunderrrrrr/blocket_api
282
339
  - PyPI: https://pypi.org/project/blocket-api/
@@ -10,8 +10,12 @@ 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,
14
+ BlocketUser,
13
15
  CustomSearchResults,
16
+ HomeSearchResults,
14
17
  MotorSearchResults,
18
+ StoreListings,
15
19
  StoreSearchResults,
16
20
  )
17
21
  from blocket_api.qasa import HOME_SEARCH_ORDERING, HomeType, OrderBy, Qasa
@@ -403,6 +407,7 @@ class BlocketAPI:
403
407
  url = f"{BYTBIL_URL}/blocket-basedata-api/v3/vehicle-data/{registration_number}"
404
408
  return _make_request(url=f"{url}", token=None).json()
405
409
 
410
+ @overload
406
411
  def home_search(
407
412
  self,
408
413
  city: str,
@@ -410,36 +415,54 @@ class BlocketAPI:
410
415
  order_by: OrderBy = OrderBy.published_at,
411
416
  ordering: HOME_SEARCH_ORDERING = "descending",
412
417
  offset: int = 0,
413
- ) -> dict:
418
+ *,
419
+ as_objects: Literal[True],
420
+ ) -> HomeSearchResults: ...
421
+
422
+ @overload
423
+ def home_search(
424
+ self,
425
+ city: str,
426
+ type: HomeType,
427
+ order_by: OrderBy = OrderBy.published_at,
428
+ ordering: HOME_SEARCH_ORDERING = "descending",
429
+ offset: int = 0,
430
+ *,
431
+ as_objects: Literal[False] = False,
432
+ ) -> dict: ...
433
+
434
+ def home_search(
435
+ self,
436
+ city: str,
437
+ type: HomeType,
438
+ order_by: OrderBy = OrderBy.published_at,
439
+ ordering: HOME_SEARCH_ORDERING = "descending",
440
+ offset: int = 0,
441
+ *,
442
+ as_objects: bool = False,
443
+ ) -> dict | HomeSearchResults:
414
444
  """
415
445
  This returns all available home listings available at
416
446
  https://bostad.blocket.se/. Specify offset to get next page. Each page contains
417
447
  60 items, which is max items returned per api query.
418
448
  """
419
- return Qasa(
449
+ response = Qasa(
420
450
  city=city,
421
451
  home_type=type,
422
452
  order_by=order_by,
423
453
  ordering=ordering,
424
454
  offset=offset,
425
455
  ).search()
456
+ return HomeSearchResults.model_validate(response) if as_objects else response
426
457
 
427
458
  @overload
428
459
  def search_store(
429
- self,
430
- search_query: str,
431
- page: int = 0,
432
- *,
433
- as_objects: Literal[True],
460
+ self, search_query: str, page: int = 0, *, as_objects: Literal[True]
434
461
  ) -> StoreSearchResults: ...
435
462
 
436
463
  @overload
437
464
  def search_store(
438
- self,
439
- search_query: str,
440
- page: int = 0,
441
- *,
442
- as_objects: Literal[False] = False,
465
+ self, search_query: str, page: int = 0, *, as_objects: Literal[False] = False
443
466
  ) -> dict: ...
444
467
 
445
468
  @overload
@@ -473,12 +496,24 @@ class BlocketAPI:
473
496
  response = _make_request(url=f"{url}", token=self.token).json()
474
497
  return StoreSearchResults.model_validate(response) if as_objects else response
475
498
 
499
+ @overload
500
+ def get_store_listings(
501
+ self, store_id: int, page: int = 0, *, as_objects: Literal[True]
502
+ ) -> StoreListings: ...
503
+
504
+ @overload
505
+ def get_store_listings(
506
+ self, store_id: int, page: int = 0, *, as_objects: Literal[False] = False
507
+ ) -> dict: ...
508
+
476
509
  @public_token
477
510
  def get_store_listings(
478
511
  self,
479
512
  store_id: int,
480
513
  page: int = 0,
481
- ) -> dict:
514
+ *,
515
+ as_objects: bool = False,
516
+ ) -> dict | StoreListings:
482
517
  """
483
518
  Return all listings from a specific store from https://www.blocket.se/butik/<store>.
484
519
  The store_id can be found by searching for the store with search_store().
@@ -487,4 +522,49 @@ class BlocketAPI:
487
522
  f"{BASE_URL}/search_bff/v2/content?lim=60&page={page}&sort=rel&store_id={store_id}"
488
523
  "&status=active&gl=3&include=extend_with_shipping"
489
524
  )
490
- return _make_request(url=f"{url}", token=self.token).json()
525
+ response = _make_request(url=f"{url}", token=self.token).json()
526
+ return StoreListings.model_validate(response) if as_objects else response
527
+
528
+ @overload
529
+ def get_ad_by_id(
530
+ self, ad_id: int, *, as_objects: Literal[True]
531
+ ) -> AdByIdResults: ...
532
+
533
+ @overload
534
+ def get_ad_by_id(
535
+ self, ad_id: int, *, as_objects: Literal[False] = False
536
+ ) -> dict: ...
537
+
538
+ @public_token
539
+ def get_ad_by_id(
540
+ self, ad_id: int, *, as_objects: bool = False
541
+ ) -> dict | AdByIdResults:
542
+ """
543
+ Return an ad with it's content. The ad_id can be found by searching or
544
+ from the blocket ad url.
545
+ """
546
+ url = f"{BASE_URL}/search_bff/v2/content/{ad_id}"
547
+ response = _make_request(url=f"{url}", token=self.token).json()
548
+ return AdByIdResults.model_validate(response) if as_objects else response
549
+
550
+ @overload
551
+ def get_user_by_id(
552
+ self, user_id: int, *, as_objects: Literal[True]
553
+ ) -> BlocketUser: ...
554
+
555
+ @overload
556
+ def get_user_by_id(
557
+ self, user_id: int, *, as_objects: Literal[False] = False
558
+ ) -> dict: ...
559
+
560
+ @public_token
561
+ def get_user_by_id(
562
+ self, user_id: int, *, as_objects: bool = False
563
+ ) -> dict | BlocketUser:
564
+ """
565
+ Returns a user's profile data. Contains account information, ads and more.
566
+ Blocket url is https://www.blocket.se/profil/<user_id>.
567
+ """
568
+ url = f"{BASE_URL}/profile-be/v1/public-profiles/{user_id}"
569
+ response = _make_request(url=f"{url}", token=self.token).json()
570
+ return BlocketUser.model_validate(response) if as_objects else response
@@ -0,0 +1,592 @@
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
537
+
538
+
539
+ ### GetUserById ###
540
+
541
+
542
+ class BlocketUserAccount(BaseModel):
543
+ blocket_account_id: str
544
+ created_at: datetime.datetime
545
+ is_verified: bool
546
+ name: str
547
+ on_blocket_since: str
548
+ verified_with: str
549
+
550
+
551
+ class BlocketUserAd(BaseModel):
552
+ ad_id: str
553
+ ad_url: str
554
+ image_url: str
555
+ price: str
556
+ region: str
557
+ subject: str
558
+
559
+
560
+ class BlocketUserActiveAds(BaseModel):
561
+ ads: list[BlocketUserAd]
562
+ label_primary: str
563
+ label_secondary: str
564
+
565
+
566
+ class BlocketUserBadge(BaseModel):
567
+ description: str
568
+ icon: str
569
+ icon_blocket_ui: str
570
+ id: str
571
+ label: str
572
+
573
+
574
+ class BlocketUserCO2Savings(BaseModel):
575
+ date_range: str
576
+ equivalent: str
577
+ text: str
578
+
579
+
580
+ class BlocketUserProfileInfo(BaseModel):
581
+ description: str | None = None
582
+ has_reviews: bool
583
+ name: str
584
+
585
+
586
+ class BlocketUser(BaseModel):
587
+ account: BlocketUserAccount
588
+ active_ads: BlocketUserActiveAds
589
+ badges: list[BlocketUserBadge]
590
+ co2_savings: BlocketUserCO2Savings
591
+ profile_info: BlocketUserProfileInfo
592
+ review_scores: dict | 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.1
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
 
@@ -31,15 +31,18 @@ BlocketAPI allows users to query saved searches, known as "Bevakningar", on [blo
31
31
  - Get vehicle information, price evaluation and search for vehicles with multiple filters.
32
32
  - Search for homes with filters for city, home type and more.
33
33
  - Search for stores and get a specific store's listings.
34
+ - ...and more!
34
35
 
35
36
  ## 🧑‍💻️ Install
36
37
 
37
- BlocketAPI is available on PyPI.
38
+ Install BlocketAPI via PyPI...
38
39
 
39
40
  ```sh
40
41
  pip install blocket-api
41
42
  ```
42
43
 
44
+ or use [blocket-api.se](https://blocket-api.se) without installing anything!
45
+
43
46
  ## 💁‍♀️ Usage
44
47
 
45
48
  ```py
@@ -63,9 +66,12 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
63
66
  | [`api.custom_search()`](#custom_search) | 👏 No | Yes | Search for everything on Blocket and filter by region |
64
67
  | [`api.motor_search()`](#motor_search) | 👏 No | Yes | Advanced search for car-listings. |
65
68
  | [`api.price_eval()`](#price_eval) | 👏 No | - | Vehicle purchase valuation and details. |
66
- | [`api.home_search()`](#home_search) | 👏 No | - | Query home listings.
69
+ | [`api.home_search()`](#home_search) | 👏 No | Yes | Query home listings.
67
70
  | [`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.
71
+ | [`api.get_store_listings()`](#get_store_listings) | 👏 No | Yes | Get listings from a specific store.
72
+ | [`api.get_ad_by_id()`](#get_ad_by_id) | 👏 No | Yes | Get ad data from id.
73
+ | [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
74
+
69
75
 
70
76
  ## 🤓 Detailed usage
71
77
 
@@ -133,7 +139,7 @@ Parameters:
133
139
  - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
134
140
 
135
141
  ```py
136
- >>> from blocket_api import Region
142
+ >>> from blocket_api.blocket import Region, Category
137
143
  >>> api.custom_search("saab", Region.blekinge) # search for term "saab" in region of "Blekinge"
138
144
  {
139
145
  "data":[
@@ -215,6 +221,7 @@ Query home listings from [bostad.blocket.se](https://bostad.blocket.se/).
215
221
  - `order_by` (`OrderBy`, optional) - Sorting order, ex. `OrderBy.price`.
216
222
  - `ordering` (`HOME_SEARCH_ORDERING`, optional) - Sorting order, ex. `"descending"`.
217
223
  - `offset` (`int`, optional) - Offset for results, ex. `60`.
224
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is False.
218
225
 
219
226
  ```py
220
227
  >>> from blocket_api.qasa import HomeType, OrderBy
@@ -254,6 +261,8 @@ Get listings from a specific store.
254
261
 
255
262
  - `store_id` (`int`, required) - The store id. Can be obtained by calling `store_search()`.
256
263
  - `page` (`int`, optional) - The page number to return.
264
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
265
+
257
266
 
258
267
  ```py
259
268
  >>> api.get_store_listings(1234)
@@ -271,6 +280,53 @@ Get listings from a specific store.
271
280
  }
272
281
  ```
273
282
 
283
+ ### get_ad_by_id
284
+ Get ad data from a specific ad id.
285
+
286
+ - `ad_id` (`int`, required) - The ad id. Can be found by calling any other method or from ad url.
287
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
288
+
289
+
290
+ ```py
291
+ >>> api.get_ad_by_id(1234)
292
+ {
293
+ "data": {
294
+ "ad_id": "1234",
295
+ "ad_status": "active",
296
+ "advertiser": {...},
297
+ "body": "A nice ad.",
298
+ "category": [
299
+ {"id": "4000", "name": "Personligt"},
300
+ {"id": "4080", "name": "Kläder & skor"},
301
+ ],
302
+ ...
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### get_user_by_id
308
+ Get user data from a user id. User id can be found in ads, searches and in blocket url `https://www.blocket.se/profil/<user_id>`.
309
+
310
+ - `user_id` (`int`, required) - The blocket user id.
311
+ - `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
312
+
313
+
314
+ ```py
315
+ >>> api.get_user_by_id(1234)
316
+ {
317
+ 'account': {
318
+ 'blocket_account_id': '1234',
319
+ 'created_at': '2020-10-10T15:45:06.407660',
320
+ 'is_verified': True,
321
+ 'name': 'Janne',
322
+ ...
323
+ },
324
+ 'active_ads': {[
325
+ ...
326
+ ]}
327
+ }
328
+ ```
329
+
274
330
  ## 🔐 Blocket API token
275
331
 
276
332
  There are two ways to acquire your token:
@@ -295,5 +351,6 @@ My token has never expired or changed during this project. However, if your're m
295
351
 
296
352
  ## 📝 Notes
297
353
 
354
+ - REST API: https://blocket-api.se
298
355
  - Source repo: https://github.com/dunderrrrrr/blocket_api
299
356
  - 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.1"
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