pyfunda 3.1.0__tar.gz → 3.1.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.
- {pyfunda-3.1.0 → pyfunda-3.1.1}/PKG-INFO +13 -5
- {pyfunda-3.1.0 → pyfunda-3.1.1}/README.md +10 -2
- pyfunda-3.1.0/funda/autocomplete.py → pyfunda-3.1.1/funda/_autocomplete.py +2 -2
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/funda.py +4 -4
- {pyfunda-3.1.0 → pyfunda-3.1.1}/pyproject.toml +22 -3
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_autocomplete.py +5 -5
- {pyfunda-3.1.0 → pyfunda-3.1.1}/.dockerignore +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/.github/FUNDING.yml +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/.github/workflows/publish.yml +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/.gitignore +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/Dockerfile +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/LICENSE +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/docs/API.md +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/docs/ARCHITECTURE.md +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/docs/DEVELOPMENT.md +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/docs/EXAMPLES.md +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/docs/README.md +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/almere_age_rank.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/analysis.ipynb +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/batch_details.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/broker_due_diligence.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/enrichment_export.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/export_to_csv.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/full_api_walkthrough.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/neighborhood_market_snapshot.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/new_listings_alert.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/poll_new_listings.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/price_history.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/price_tracker.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/search_sold.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/examples/similar_sales_comp.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/__init__.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_autocomplete_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_detail_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_enrichment_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_parallel.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_parse_helpers.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_price_history_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_search_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/_transport.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/constants.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/exceptions.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/headers.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/listing.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/models.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/parsing.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/py.typed +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/funda/search.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/__init__.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_client.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_enrichment_parser.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_live.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_models.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_search.py +0 -0
- {pyfunda-3.1.0 → pyfunda-3.1.1}/tests/test_transport_parallel.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyfunda
|
|
3
|
-
Version: 3.1.
|
|
4
|
-
Summary: Python API for Funda.nl real estate
|
|
3
|
+
Version: 3.1.1
|
|
4
|
+
Summary: Python API wrapper for Funda.nl, the Dutch real estate platform. Reverse-engineered mobile API client, no scraping, no Selenium, no CAPTCHA.
|
|
5
5
|
Project-URL: Homepage, https://github.com/0xMH/pyfunda
|
|
6
6
|
Project-URL: Repository, https://github.com/0xMH/pyfunda
|
|
7
7
|
Project-URL: Issues, https://github.com/0xMH/pyfunda/issues
|
|
8
8
|
Author: 0xMH
|
|
9
9
|
License-Expression: AGPL-3.0-or-later
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Keywords: api,funda,housing,netherlands,real-estate,scraper
|
|
11
|
+
Keywords: api,api-wrapper,client,dutch,funda,funda-api,funda-nl,funda-scraper,housing,housing-api,mobile-api,netherlands,real-estate,real-estate-api,rest-api,reverse-engineering,scraper,sdk
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
@@ -29,9 +29,17 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
[](https://pypi.org/project/pyfunda/)
|
|
30
30
|
[](https://github.com/0xMH/pyfunda/blob/main/LICENSE)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**pyfunda** is a Python API wrapper for [Funda.nl](https://www.funda.nl), the Netherlands' largest Dutch real estate platform. It talks to Funda's reverse engineered mobile JSON API, so you get clean typed objects for listings, prices, media, brokers, and price history without HTML scraping, Selenium, a headless browser, or CAPTCHA solving.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Funda has no public developer API. pyfunda is the only working opensource Python client that hits Funda's app facing `*.funda.io` endpoints directly, the same ones the official Android app uses.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install pyfunda
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use pyfunda as a Funda API client or Funda Python SDK to fetch Dutch property listings, run search queries by city and price, pull listing detail and media, get broker info and reviews, read price history, and stream new listings. If you have been looking for a working fundascraper alternative or a Netherlands housing API, this is the only Python wrapper hitting the same `*.funda.io` endpoints the Funda Android app uses.
|
|
41
|
+
|
|
42
|
+
> If you find this useful, consider giving it a star, it helps others discover the project.
|
|
35
43
|
|
|
36
44
|
[](https://star-history.com/#0xMH/pyfunda&Date)
|
|
37
45
|
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
[](https://pypi.org/project/pyfunda/)
|
|
5
5
|
[](https://github.com/0xMH/pyfunda/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**pyfunda** is a Python API wrapper for [Funda.nl](https://www.funda.nl), the Netherlands' largest Dutch real estate platform. It talks to Funda's reverse engineered mobile JSON API, so you get clean typed objects for listings, prices, media, brokers, and price history without HTML scraping, Selenium, a headless browser, or CAPTCHA solving.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Funda has no public developer API. pyfunda is the only working opensource Python client that hits Funda's app facing `*.funda.io` endpoints directly, the same ones the official Android app uses.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install pyfunda
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Use pyfunda as a Funda API client or Funda Python SDK to fetch Dutch property listings, run search queries by city and price, pull listing detail and media, get broker info and reviews, read price history, and stream new listings. If you have been looking for a working fundascraper alternative or a Netherlands housing API, this is the only Python wrapper hitting the same `*.funda.io` endpoints the Funda Android app uses.
|
|
16
|
+
|
|
17
|
+
> If you find this useful, consider giving it a star, it helps others discover the project.
|
|
10
18
|
|
|
11
19
|
[](https://star-history.com/#0xMH/pyfunda&Date)
|
|
12
20
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Internal autocomplete payload construction."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ from funda.models import JsonDict
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass(frozen=True, slots=True)
|
|
17
|
-
class
|
|
17
|
+
class LocationAutocomplete:
|
|
18
18
|
value: str
|
|
19
19
|
timeout: str = "3s"
|
|
20
20
|
size: int = 10
|
|
@@ -28,7 +28,7 @@ from funda.constants import (
|
|
|
28
28
|
PAGE_SIZE,
|
|
29
29
|
)
|
|
30
30
|
from funda.exceptions import FundaRequestError, ListingNotFound, PriceHistoryError, SearchError
|
|
31
|
-
from funda.
|
|
31
|
+
from funda._autocomplete import LocationAutocomplete
|
|
32
32
|
from funda.listing import Listing, LocationSuggestion, PriceHistory
|
|
33
33
|
from funda.models import JsonDict
|
|
34
34
|
from funda.parsing import (
|
|
@@ -84,7 +84,7 @@ class Funda:
|
|
|
84
84
|
def __enter__(self) -> "Funda":
|
|
85
85
|
return self
|
|
86
86
|
|
|
87
|
-
def __exit__(self, *
|
|
87
|
+
def __exit__(self, *_) -> None:
|
|
88
88
|
self.close()
|
|
89
89
|
|
|
90
90
|
def listing(self, listing_id: int | str) -> Listing:
|
|
@@ -130,7 +130,7 @@ class Funda:
|
|
|
130
130
|
sort: Sequence[Any] | None = None,
|
|
131
131
|
) -> list[LocationSuggestion]:
|
|
132
132
|
"""Suggest Funda location identifiers for search-box text."""
|
|
133
|
-
autocomplete =
|
|
133
|
+
autocomplete = LocationAutocomplete(
|
|
134
134
|
value=value,
|
|
135
135
|
size=size,
|
|
136
136
|
timeout=timeout,
|
|
@@ -434,7 +434,7 @@ class Funda:
|
|
|
434
434
|
|
|
435
435
|
raise SearchError("Search failed without a response")
|
|
436
436
|
|
|
437
|
-
def _autocomplete(self, autocomplete:
|
|
437
|
+
def _autocomplete(self, autocomplete: LocationAutocomplete) -> JsonDict:
|
|
438
438
|
payload = autocomplete.to_payload()
|
|
439
439
|
for attempt in range(3):
|
|
440
440
|
response = self._transport.post(
|
|
@@ -4,15 +4,34 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyfunda"
|
|
7
|
-
version = "3.1.
|
|
8
|
-
description = "Python API for Funda.nl real estate
|
|
7
|
+
version = "3.1.1"
|
|
8
|
+
description = "Python API wrapper for Funda.nl, the Dutch real estate platform. Reverse-engineered mobile API client, no scraping, no Selenium, no CAPTCHA."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "0xMH" }
|
|
14
14
|
]
|
|
15
|
-
keywords = [
|
|
15
|
+
keywords = [
|
|
16
|
+
"funda",
|
|
17
|
+
"funda-api",
|
|
18
|
+
"funda-nl",
|
|
19
|
+
"funda-scraper",
|
|
20
|
+
"real-estate",
|
|
21
|
+
"real-estate-api",
|
|
22
|
+
"netherlands",
|
|
23
|
+
"dutch",
|
|
24
|
+
"housing",
|
|
25
|
+
"housing-api",
|
|
26
|
+
"api",
|
|
27
|
+
"api-wrapper",
|
|
28
|
+
"rest-api",
|
|
29
|
+
"client",
|
|
30
|
+
"sdk",
|
|
31
|
+
"mobile-api",
|
|
32
|
+
"reverse-engineering",
|
|
33
|
+
"scraper",
|
|
34
|
+
]
|
|
16
35
|
classifiers = [
|
|
17
36
|
"Development Status :: 4 - Beta",
|
|
18
37
|
"Intended Audience :: Developers",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
|
-
from funda.
|
|
3
|
+
from funda._autocomplete import LocationAutocomplete
|
|
4
4
|
from funda.constants import (
|
|
5
5
|
LOCATION_AUTOCOMPLETE_AREA_TYPES,
|
|
6
6
|
LOCATION_AUTOCOMPLETE_TEMPLATE_ID,
|
|
@@ -45,7 +45,7 @@ AUTOCOMPLETE_RESPONSE = {
|
|
|
45
45
|
|
|
46
46
|
class AutocompleteTests(unittest.TestCase):
|
|
47
47
|
def test_payload_matches_funda_searchbox_template(self) -> None:
|
|
48
|
-
payload =
|
|
48
|
+
payload = LocationAutocomplete("almere poor").to_payload()
|
|
49
49
|
|
|
50
50
|
self.assertEqual(payload["id"], LOCATION_AUTOCOMPLETE_TEMPLATE_ID)
|
|
51
51
|
self.assertEqual(payload["params"]["value"], "almere poor")
|
|
@@ -60,7 +60,7 @@ class AutocompleteTests(unittest.TestCase):
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
def test_payload_can_limit_area_types_for_vague_area_searches(self) -> None:
|
|
63
|
-
payload =
|
|
63
|
+
payload = LocationAutocomplete(
|
|
64
64
|
"amsterdam west",
|
|
65
65
|
area_types=("city", "municipality", "neighborhood", "wijk"),
|
|
66
66
|
size=5,
|
|
@@ -74,9 +74,9 @@ class AutocompleteTests(unittest.TestCase):
|
|
|
74
74
|
|
|
75
75
|
def test_invalid_payload_values_fail_before_network(self) -> None:
|
|
76
76
|
with self.assertRaises(ValueError):
|
|
77
|
-
|
|
77
|
+
LocationAutocomplete("").to_payload()
|
|
78
78
|
with self.assertRaises(ValueError):
|
|
79
|
-
|
|
79
|
+
LocationAutocomplete("amsterdam", size=0).to_payload()
|
|
80
80
|
|
|
81
81
|
def test_parse_location_suggestions_maps_geo_hits(self) -> None:
|
|
82
82
|
suggestions = parse_location_suggestions(AUTOCOMPLETE_RESPONSE)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|