├── tests ├── __init__.py ├── conftest.py └── tests_zoopla.py ├── zoopla ├── __version__.py ├── __init__.py ├── fields.py ├── exceptions.py ├── enums.py ├── api.py └── schemas.py ├── .gitignore ├── pytest.ini ├── examples ├── average_sale_price.py ├── richlist.py ├── area_value_graphs.py ├── properties.py ├── zed_indicies.py └── arrange_viewing.py ├── .travis.yml ├── requirements.txt ├── setup.py └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zoopla/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0" 2 | -------------------------------------------------------------------------------- /zoopla/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import Zoopla 2 | from .enums import * -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | .cache/ 3 | *.pyc 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files=tests_*.py 3 | python_classes=Test 4 | python_functions=test_* 5 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | def pytest_addoption(parser): 2 | parser.addoption("--api_key", action="store", help="Zoopla API Key") 3 | -------------------------------------------------------------------------------- /examples/average_sale_price.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='your_api_key') 4 | 5 | average = zoopla.average_area_sold_price({'area': 'SW11'}) 6 | print(average.average_sold_price_7year) 7 | print(average.average_sold_price_5year) 8 | -------------------------------------------------------------------------------- /examples/richlist.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='') 4 | 5 | rl = zoopla.property_rich_list({'area': 'SW11'}) 6 | 7 | for l in rl.highest: 8 | print(l.name) 9 | print(l.zed_index) 10 | print(l.details_url) 11 | -------------------------------------------------------------------------------- /examples/area_value_graphs.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='your_api_key') 4 | 5 | area_graphs = zoopla.area_value_graphs({'area': 'SW11'}) 6 | 7 | print(area_graphs.average_values_graph_url) 8 | print(area_graphs.value_trend_graph_url) 9 | -------------------------------------------------------------------------------- /zoopla/fields.py: -------------------------------------------------------------------------------- 1 | from marshmallow.fields import String 2 | 3 | 4 | class StrippedString(String): 5 | def deserialize(self, value, attr=None, data=None): 6 | result = super(StrippedString, self).deserialize(value, attr, data) 7 | return result.strip() 8 | -------------------------------------------------------------------------------- /zoopla/exceptions.py: -------------------------------------------------------------------------------- 1 | class RequestFormatException(Exception): 2 | pass 3 | 4 | 5 | class ResponseFormatException(Exception): 6 | pass 7 | 8 | 9 | class ZooplaAPIException(Exception): 10 | def __init__(self, text): 11 | self.text = text 12 | 13 | def __str__(self): 14 | return "Zoopla returned an error: %s" % self.text 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.6" 6 | 7 | # command to install dependencies 8 | install: 9 | - pip install -r requirements.txt 10 | - pip install pytest-cov 11 | - pip install codecov 12 | 13 | # command to run tests 14 | script: 15 | - pytest --api_key=$api_key --cov=./ --cov-report xml 16 | 17 | after_success: 18 | - codecov 19 | -------------------------------------------------------------------------------- /examples/properties.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='your_api_key') 4 | 5 | search = zoopla.property_listings({ 6 | 'maximum_beds': 2, 7 | 'page_size': 100, 8 | 'listing_status': 'sale', 9 | 'area': 'Blackley, Greater Manchester' 10 | }) 11 | 12 | for result in search.listing: 13 | print(result.price) 14 | print(result.description) 15 | print(result.image_url) 16 | -------------------------------------------------------------------------------- /examples/zed_indicies.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='your_api_key') 4 | 5 | zed_indices = zoopla.area_zed_indices({ 6 | 'area': 'Blackley, Greater Manchester', 7 | 'output_type': 'area', 8 | 'area_type': 'streets', 9 | 'order': 'ascending', 10 | 'page_number': 1, 11 | 'page_size': 10 12 | }) 13 | 14 | print(zed_indices.town) 15 | print(zed_indices.results_url) 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bleach==3.3.0 2 | certifi==2018.8.24 3 | cffi==1.11.5 4 | chardet==3.0.4 5 | cmarkgfm==0.4.2 6 | docutils==0.14 7 | enum34==1.1.6 8 | future==0.16.0 9 | idna==2.7 10 | marshmallow==2.16.1 11 | pkginfo==1.4.2 12 | py==1.10.0 13 | pycparser==2.19 14 | Pygments==2.7.4 15 | pytest==3.2.2 16 | readme-renderer==22.0 17 | requests==2.20.0 18 | requests-toolbelt==0.8.0 19 | six==1.11.0 20 | tqdm==4.19.6 21 | twine==1.12.1 22 | urllib3==1.26.5 23 | webencodings==0.5.1 24 | -------------------------------------------------------------------------------- /examples/arrange_viewing.py: -------------------------------------------------------------------------------- 1 | from zoopla import Zoopla 2 | 3 | zoopla = Zoopla(api_key='your_api_key') 4 | 5 | session = zoopla.get_session_id() 6 | 7 | arrange_viewing = zoopla.arrange_viewing({ 8 | 'session_id': session, 9 | 'listing_id': 44863256, 10 | 'name': 'Tester', 11 | 'email': "zoopla_developer@mashery.com", 12 | 'phone': '01010101', 13 | 'phone_type': 'work', 14 | 'best_time_to_call': 'anytime', 15 | 'message': 'Hi, I seen your listing on zoopla.co.uk and I would love to arrange a viewing!' 16 | 17 | }) 18 | 19 | if 'success' in arrange_viewing and arrange_viewing.success == 1: 20 | print("Advertiser contacted successfully.") 21 | -------------------------------------------------------------------------------- /zoopla/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class AreaType(Enum): 5 | STREETS = 'streets' 6 | POSTCODES = 'postcodes' 7 | OUTCODES = 'outcodes' 8 | AREAS = 'areas' 9 | TOWNS = 'towns' 10 | COUNTIES = 'counties' 11 | 12 | def __str__(self): 13 | return self._value_ 14 | 15 | def __repr__(self): 16 | return "" % self 17 | 18 | 19 | class OutputType(Enum): 20 | STREET = 'street' 21 | POSTCODE = 'postcode' 22 | OUTCODE = 'outcode' 23 | AREA = 'area' 24 | TOWN = 'town' 25 | COUNTY = 'county' 26 | 27 | def __str__(self): 28 | return self._value_ 29 | 30 | def __repr__(self): 31 | return "" % self 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, Command 4 | import os 5 | import sys 6 | from shutil import rmtree 7 | from zoopla.__version__ import __version__ 8 | 9 | here = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | with open("README.rst", "rb") as f: 12 | long_descr = f.read().decode("utf-8") 13 | 14 | 15 | class PublishCommand(Command): 16 | """Support setup.py publish.""" 17 | 18 | description = 'Build and publish the package.' 19 | user_options = [] 20 | 21 | @staticmethod 22 | def status(s): 23 | """Prints things in bold.""" 24 | print('\033[1m{0}\033[0m'.format(s)) 25 | 26 | def initialize_options(self): 27 | pass 28 | 29 | def finalize_options(self): 30 | pass 31 | 32 | def run(self): 33 | try: 34 | self.status('Removing previous builds…') 35 | rmtree(os.path.join(here, 'dist')) 36 | except: 37 | pass 38 | 39 | self.status('Building Source and Wheel (universal) distribution…') 40 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 41 | 42 | self.status('Uploading the package to PyPi via Twine…') 43 | os.system('twine upload dist/*') 44 | 45 | sys.exit() 46 | 47 | 48 | setup(name='zoopla', 49 | version=__version__, 50 | description='A Python wrapper for the Zoopla API', 51 | long_description=long_descr, 52 | url='https://github.com/anthonybloomer/zoopla', 53 | author='Anthony Bloomer, Gabriele Alese', 54 | keywords=['zoopla', 'api'], 55 | author_email='ant0@protonmail.ch, gabriele@alese.it', 56 | license='MIT', 57 | packages=['zoopla'], 58 | install_requires=[ 59 | 'marshmallow>=2.13.6,<3', 60 | 'requests>=2.11.1,<3', 61 | 'enum34>=1.1.6,<2' 62 | ], 63 | classifiers=[ 64 | 'Intended Audience :: Developers', 65 | 'License :: OSI Approved :: MIT License', 66 | "Topic :: Software Development :: Libraries", 67 | 'Programming Language :: Python :: 2.7', 68 | 'Programming Language :: Python :: 3.6' 69 | ], 70 | cmdclass={ 71 | 'publish': PublishCommand, 72 | }, 73 | zip_safe=False) 74 | -------------------------------------------------------------------------------- /tests/tests_zoopla.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zoopla.api import Zoopla, AreaType, OutputType 4 | 5 | 6 | @pytest.fixture 7 | def client(request): 8 | api_key = request.config.getoption("--api_key") 9 | return Zoopla(api_key=api_key) 10 | 11 | 12 | def test_area_value_graphs(client): 13 | area_graphs = client.area_value_graphs(params={ 14 | 'area': 'SW11', 15 | 'size': 'medium' 16 | }) 17 | 18 | assert area_graphs.area_name == 'SW11' 19 | # assert area_graphs.area_values_url == 'http://www.zoopla.co.uk/home-values/london/sw11/battersea-clapham-junction' # noqa 20 | 21 | 22 | def test_get_average_area_sold_price(client): 23 | averages = client.average_area_sold_price(params={ 24 | 'postcode': 'SW11', 25 | 'output_type': OutputType.COUNTY 26 | }) 27 | assert averages.number_of_sales_7year is not None 28 | 29 | 30 | def test_search_property_listings(client): 31 | result = client.property_listings({ 32 | 'maximum_beds': 2, 33 | 'page_size': 100, 34 | 'listing_status': 'sale', 35 | 'area': 'Blackley, Greater Manchester' 36 | }) 37 | 38 | first = result.listing[0] 39 | assert first.listing_status == 'sale' 40 | 41 | 42 | def test_local_info_graphs(client): 43 | local_graphs = client.local_info_graphs(params={ 44 | 'area': 'SW11'} 45 | ) 46 | assert 'people_graph_url' in local_graphs 47 | assert local_graphs.country == 'England' 48 | assert local_graphs.area_name == 'SW11' 49 | 50 | 51 | def test_zed_index(client): 52 | zed_result = client.zed_index(params={ 53 | 'area': 'SW11', 54 | 'output_type': 'outcode' 55 | }) 56 | assert zed_result.country == 'England' 57 | 58 | 59 | def test_area_zed_indices(client): 60 | area_zed_indices = client.area_zed_indices(params={ 61 | 'area': 'Blackley, Greater Manchester', 62 | 'output_type': 'area', 63 | 'area_type': 'streets', 64 | 'order': 'ascending', 65 | 'page_number': 1, 66 | 'page_size': 10 67 | }) 68 | assert area_zed_indices.town == 'Manchester' 69 | 70 | 71 | def test_auto_complete(client): 72 | auto_complete = client.geo_autocomplete(params={ 73 | 'search_term': 'SW11', 74 | 'search_type': 'properties' 75 | }) 76 | assert auto_complete.suggestions[0].value == 'SW11 1AD' # noqa 77 | 78 | 79 | def test_arrange_viewing(client): 80 | session = client.get_session_id() 81 | 82 | arrange_viewing = client.arrange_viewing({ 83 | 'session_id': session, 84 | 'listing_id': 44863256, 85 | 'name': 'Tester', 86 | 'email': "zoopla_developer@mashery.com", 87 | 'phone': '01010101', 88 | 'phone_type': 'work', 89 | 'best_time_to_call': 'anytime', 90 | 'message': 'Hi, I seen your listing on zoopla.co.uk and I would love to arrange a viewing!' 91 | 92 | }) 93 | 94 | assert 'success' in arrange_viewing and arrange_viewing.success == 1 95 | 96 | 97 | def test_richlist(client): 98 | rl = client.property_rich_list({'area': 'SW11'}) 99 | assert rl is not None 100 | assert 'highest' in rl 101 | for l in rl.highest: 102 | assert 'name' in l 103 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | zoopla 2 | ====== 3 | 4 | |Build Status| |codecov| 5 | 6 | A python wrapper for the Zoopla API. 7 | 8 | Zoopla has launched an open API to allow developers to create 9 | applications using hyper local data on 27m homes, over 1m sale and 10 | rental listings, and 15 years of sold price data. 11 | 12 | `Register`_ for a user account and apply for an instant API key. 13 | 14 | Browse the `documentation`_ to understand how to use the API and the 15 | specifications for the individual APIs. 16 | 17 | Installation 18 | ------------ 19 | 20 | :: 21 | 22 | $ pip install zoopla 23 | 24 | Tests 25 | ----- 26 | 27 | Install the dev requirements: 28 | 29 | .. code:: sh 30 | 31 | $ pip install -r requirements.txt 32 | 33 | | Run py.test with your developer key (otherwise you won’t be able to 34 | hit the live 35 | | API upon which these tests depend). 36 | 37 | .. code:: sh 38 | 39 | $ py.test --api_key= tests/ # pytest under Python 3+ 40 | 41 | Examples 42 | -------- 43 | 44 | Retrieve property listings for a given area. 45 | 46 | .. code:: python 47 | 48 | from zoopla import Zoopla 49 | 50 | zoopla = Zoopla(api_key='your_api_key') 51 | 52 | search = zoopla.property_listings({ 53 | 'maximum_beds': 2, 54 | 'page_size': 100, 55 | 'listing_status': 'sale', 56 | 'area': 'Blackley, Greater Manchester' 57 | }) 58 | 59 | for result in search.listing: 60 | print(result.price) 61 | print(result.description) 62 | print(result.image_url) 63 | 64 | 65 | Retrieve a list of house price estimates for the requested area. 66 | 67 | .. code:: python 68 | 69 | zed_indices = zoopla.area_zed_indices({ 70 | 'area': 'Blackley, Greater Manchester', 71 | 'output_type': 'area', 72 | 'area_type': 'streets', 73 | 'order': 'ascending', 74 | 'page_number': 1, 75 | 'page_size': 10 76 | }) 77 | 78 | print(zed_indices.town) 79 | print(zed_indices.results_url) 80 | 81 | Generate a graph of values for an outcode over the previous 3 months and 82 | return the URL to the generated image. 83 | 84 | .. code:: python 85 | 86 | area_graphs = zoopla.area_value_graphs({'area': 'SW11'}) 87 | 88 | print(area_graphs.average_values_graph_url) 89 | print(area_graphs.value_trend_graph_url) 90 | 91 | Retrieve the average sale price for houses in a particular area. 92 | 93 | .. code:: python 94 | 95 | average = zoopla.average_area_sold_price({'area': 'SW11'}) 96 | 97 | print(average.average_sold_price_7year) 98 | print(average.average_sold_price_5year) 99 | 100 | 101 | Submit a viewing request to an agent regarding a particular listing. 102 | 103 | .. code:: python 104 | 105 | session_id = zoopla.get_session_id() 106 | 107 | arrange_viewing = zoopla.arrange_viewing({ 108 | 'session_id': session_id, 109 | 'listing_id': 44863256, 110 | 'name': 'Tester', 111 | 'email': "zoopla_developer@mashery.com", 112 | 'phone': '01010101', 113 | 'phone_type': 'work', 114 | 'best_time_to_call': 'anytime', 115 | 'message': 'Hi, I seen your listing on zoopla.co.uk and I would love to arrange a viewing!' 116 | 117 | }) 118 | 119 | Contributing 120 | ------------ 121 | 122 | - Fork the project and clone locally. 123 | - Create a new branch for what you're going to work on. 124 | - Push to your origin repository. 125 | - Include tests and update documentation if necessary. 126 | - Create a new pull request in GitHub. 127 | 128 | .. _Register: http://developer.zoopla.com/member/register/ 129 | .. _documentation: http://developer.zoopla.com/docs/ 130 | 131 | 132 | .. |Build Status| image:: https://travis-ci.org/AnthonyBloomer/zoopla.svg?branch=master 133 | :target: https://travis-ci.org/AnthonyBloomer/zoopla 134 | 135 | .. |codecov| image:: https://codecov.io/gh/AnthonyBloomer/zoopla/branch/master/graph/badge.svg 136 | :target: https://codecov.io/gh/AnthonyBloomer/zoopla 137 | -------------------------------------------------------------------------------- /zoopla/api.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import requests 4 | 5 | from .exceptions import ( 6 | ZooplaAPIException, ResponseFormatException, RequestFormatException) 7 | from .schemas import ( 8 | PropertyListingResultSchema, LocalInfoGraphsResultSchema, ZedIndexResultSchema, 9 | AreaZedIndicesResultSchema, AutoCompleteResultSchema, 10 | AreaValueGraphsResultSchema, 11 | AverageSoldPricesBaseResultSchema, AverageAreaSoldPriceResultSchema, 12 | BaseRequestSchema, SearchPropertyListingRequestSchema, ZedIndexRequestSchema, 13 | AreaZedIndicesRequestSchema, AutocompleteRequestSchema, 14 | AreaValueGraphsRequestSchema, AverageSoldPriceRequestSchema, RefineEstimateSchema, RefineEstimateResultSchema, 15 | ArrangeViewingSchema, ArrangeViewingResultSchema, RichlistResultSchema 16 | ) 17 | 18 | from .enums import AreaType, OutputType 19 | 20 | import pprint 21 | 22 | logging.basicConfig() 23 | logger = logging.getLogger(__file__) 24 | 25 | 26 | class Zoopla(object): 27 | API_URL = 'http://api.zoopla.co.uk/api/v1/' 28 | 29 | def __init__(self, api_key, verbose=False): 30 | self.api_key = api_key 31 | self.verbose = verbose 32 | 33 | def _api_call(self, action, params=None): 34 | if not params: 35 | params = {} 36 | 37 | params.update({'api_key': self.api_key}) 38 | response = requests.get( 39 | self.API_URL + action, params) 40 | if self.verbose: 41 | print('Request: %s' % response.url) 42 | print('Status Code: %s' % response.status_code) 43 | if response.ok: 44 | json = response.json() 45 | if self.verbose: 46 | pprint.pprint(json) 47 | if 'error_string' in json: 48 | raise ZooplaAPIException(text=json['error_string']) 49 | return response.json() 50 | else: 51 | raise ZooplaAPIException(response.reason) 52 | 53 | def _base_call(self, action, request_schema, result_schema, parameters): 54 | request_errors = request_schema().validate(parameters) 55 | 56 | if request_errors: 57 | logger.warning(parameters) 58 | raise RequestFormatException(request_errors) 59 | 60 | response = self._api_call(action, parameters) 61 | result, errors = result_schema().load(response) 62 | 63 | if errors: 64 | logger.warning(response) 65 | raise ResponseFormatException(errors) 66 | 67 | return result 68 | 69 | def zed_index(self, params): 70 | """ 71 | Retrieve the Zoopla.co.uk Zed-Index! for a requested area. 72 | """ 73 | return self._base_call( 74 | action='zed_index.json', 75 | request_schema=ZedIndexRequestSchema, 76 | result_schema=ZedIndexResultSchema, 77 | parameters=params 78 | ) 79 | 80 | def area_value_graphs(self, params): 81 | """ 82 | Generate a graph of values for an outcode over the previous 3 months 83 | and return the URL to the generated image. Please note that 84 | the output type must always be "outcode" for this method 85 | and therefore an area sufficient to produce an outcode is required. 86 | """ 87 | return self._base_call( 88 | action='area_value_graphs.json', 89 | request_schema=AreaValueGraphsRequestSchema, 90 | result_schema=AreaValueGraphsResultSchema, 91 | parameters=params 92 | ) 93 | 94 | def property_rich_list(self, params): 95 | """ 96 | Retrieve richlist values for a specific area. 97 | """ 98 | params.update({ 99 | 'area_type': str(AreaType.STREETS) if 'area_type' not in params else str(params['area_type']), 100 | 'output_type': str(OutputType.COUNTY) if 'output_type' not in params else str(params['output_type']) 101 | 102 | }) 103 | return self._base_call( 104 | action='richlist.json', 105 | request_schema=BaseRequestSchema, 106 | result_schema=RichlistResultSchema, 107 | parameters=params 108 | ) 109 | 110 | def average_area_sold_price(self, params): 111 | """ 112 | Retrieve the average sale price for houses in a particular area. 113 | """ 114 | 115 | if 'area_type' in params and not isinstance(params['area_type'], AreaType): 116 | raise ZooplaAPIException('area_type should be an instance of AreaType') 117 | 118 | if 'output_type' in params and not isinstance(params['output_type'], OutputType): 119 | raise ZooplaAPIException('output_type should be an instance of OutputType') 120 | 121 | params.update({ 122 | 'area_type': str(AreaType.STREETS) if 'area_type' not in params else str(params['area_type']), 123 | 'output_type': str(OutputType.COUNTY) if 'output_type' not in params else str(params['output_type']) 124 | 125 | }) 126 | 127 | return self._base_call( 128 | action='average_area_sold_price.json', 129 | request_schema=BaseRequestSchema, 130 | result_schema=AverageAreaSoldPriceResultSchema, 131 | parameters=params 132 | ) 133 | 134 | def area_zed_indices(self, params): 135 | """ 136 | Retrieve a list of house price estimates for the requested area. 137 | """ 138 | return self._base_call( 139 | action='zed_indices.json', 140 | request_schema=AreaZedIndicesRequestSchema, 141 | result_schema=AreaZedIndicesResultSchema, 142 | parameters=params 143 | ) 144 | 145 | def average_sold_prices(self, params): 146 | """ 147 | Retrieve the average sale price for a particular sub-area 148 | type within a particular area. 149 | """ 150 | return self._base_call( 151 | action='average_sold_prices.json', 152 | request_schema=AverageSoldPriceRequestSchema, 153 | result_schema=AverageSoldPricesBaseResultSchema, 154 | parameters=params 155 | ) 156 | 157 | def property_listings(self, params): 158 | """ 159 | Retrieve property listings for a given area. 160 | """ 161 | return self._base_call( 162 | action='property_listings.json', 163 | request_schema=SearchPropertyListingRequestSchema, 164 | result_schema=PropertyListingResultSchema, 165 | parameters=params 166 | ) 167 | 168 | def refine_estimate(self, params): 169 | """ 170 | Request a more accurate Zoopla.co.uk Zed-Index 171 | based on extra data provided. 172 | """ 173 | return self._base_call( 174 | action='refine_estimate.json', 175 | request_schema=RefineEstimateSchema, 176 | result_schema=RefineEstimateResultSchema, 177 | parameters=params 178 | ) 179 | 180 | def arrange_viewing(self, params): 181 | """ 182 | Submit a viewing request to an agent regarding a particular listing. 183 | """ 184 | return self._base_call( 185 | action='arrange_viewing.json', 186 | request_schema=ArrangeViewingSchema, 187 | result_schema=ArrangeViewingResultSchema, 188 | parameters=params 189 | ) 190 | 191 | def get_session_id(self): 192 | """ 193 | Obtain a session ID parameter for use with associated method calls. 194 | """ 195 | response = self._api_call('get_session_id.json?api_key=' + self.api_key) 196 | return response['session_id'] 197 | 198 | def local_info_graphs(self, params): 199 | """ 200 | Generate a set of graphs of local info for an outcode 201 | (and optional incode) and return the URL to the generated image. 202 | """ 203 | return self._base_call( 204 | action='local_info_graphs.js', 205 | request_schema=BaseRequestSchema, 206 | result_schema=LocalInfoGraphsResultSchema, 207 | parameters=params 208 | ) 209 | 210 | def geo_autocomplete(self, params): 211 | """ 212 | This method is for showing the auto suggestion for locations. 213 | """ 214 | return self._base_call( 215 | action='geo_autocomplete.json', 216 | request_schema=AutocompleteRequestSchema, 217 | result_schema=AutoCompleteResultSchema, 218 | parameters=params 219 | ) 220 | -------------------------------------------------------------------------------- /zoopla/schemas.py: -------------------------------------------------------------------------------- 1 | from marshmallow import Schema, fields, validates, post_dump, missing 2 | from marshmallow.validate import OneOf 3 | from .fields import StrippedString 4 | 5 | 6 | class AttributeDict(dict): 7 | __getattr__ = dict.get 8 | __setattr__ = dict.__setitem__ 9 | __delattr__ = dict.__delitem__ 10 | 11 | 12 | class BaseSchema(Schema): 13 | 14 | def on_bind_field(self, field_name, field_obj): 15 | if field_obj.missing == missing: 16 | field_obj.missing = None 17 | field_obj.allow_none = True 18 | 19 | @post_dump 20 | def clean_missing(self, data): 21 | ret = data.copy() 22 | for key in filter(lambda key: data[key] is None, data): 23 | del ret[key] 24 | return ret 25 | 26 | @property 27 | def dict_class(self): 28 | return AttributeDict 29 | 30 | 31 | class BoundingBoxSchema(BaseSchema): 32 | latitude_min = fields.Float() 33 | latitude_max = fields.Float() 34 | longitude_min = fields.Float() 35 | longitude_max = fields.Float() 36 | 37 | 38 | class BaseRequestSchema(BaseSchema): 39 | area = fields.String() 40 | street = fields.String() 41 | town = fields.String() 42 | postcode = fields.String() 43 | county = fields.String() 44 | country = fields.String() 45 | latitude = fields.String() 46 | longitude = fields.String() 47 | lat_min = fields.String() 48 | lat_max = fields.String() 49 | lon_min = fields.String() 50 | lon_max = fields.String() 51 | output_type = fields.String(allow_none=False) 52 | area_type = fields.String(validate=OneOf(choices=( 53 | 'streets', 'postcodes', 'outcodes', 'areas', 'towns', 'counties' 54 | )), allow_none=False) 55 | 56 | 57 | class BaseResultSchema(BaseSchema): 58 | area_name = StrippedString() 59 | street = fields.String() 60 | town = fields.String() 61 | postcode = fields.String() 62 | country = fields.String() 63 | county = fields.String() 64 | bounding_box = fields.Nested(BoundingBoxSchema) 65 | 66 | 67 | class SearchPropertyListingRequestSchema(BaseRequestSchema): 68 | radius = fields.Float() 69 | order_by = fields.String(validate=OneOf(choices=('price', 'age'))) 70 | ordering = fields.String(validate=OneOf(choices=('descending', 'ascending'))) 71 | listing_status = fields.String(validate=OneOf(choices=('rent', 'sale'))) 72 | include_sold = fields.String(validate=OneOf(choices=('0', '1'))) 73 | include_rented = fields.String(validate=OneOf(choices=('0', '1'))) 74 | minimum_price = fields.Float() 75 | maximum_price = fields.Float() 76 | minimum_beds = fields.Integer() 77 | maximum_beds = fields.Integer() 78 | furnished = fields.String() 79 | property_type = fields.String(validate=OneOf(choices=('houses', 'flats'))) 80 | new_homes = fields.String() 81 | chain_free = fields.String() 82 | keywords = fields.String() 83 | listing_id = fields.String() 84 | branch_id = fields.String() 85 | page_number = fields.Integer() 86 | page_size = fields.Integer() 87 | summarised = fields.String(validate=OneOf(choices=('yes', 'no'))) 88 | 89 | @validates('radius') 90 | def validate_radius(self, value): 91 | if value is not None: 92 | return 0.1 < value < 40 93 | 94 | 95 | class PropertyListingSchema(BaseSchema): 96 | class Meta: 97 | dateformat = '%Y-%m-%d %H:%M:%S' 98 | 99 | listing_id = fields.String() 100 | listing_status = fields.String() 101 | 102 | price = fields.Float() 103 | price_modifier = fields.String() 104 | 105 | agent_address = fields.String() 106 | agent_logo = fields.URL() 107 | agent_name = fields.String() 108 | agent_phone = fields.String() 109 | 110 | category = fields.String() 111 | country = fields.String() 112 | country_code = fields.String() 113 | county = fields.String() 114 | 115 | description = fields.String() 116 | details_url = fields.URL() 117 | displayable_address = fields.String() 118 | first_published_date = fields.DateTime() 119 | last_published_date = fields.DateTime() 120 | 121 | image_url = fields.String() 122 | floor_plan = fields.List(fields.URL()) 123 | 124 | latitude = fields.Float() 125 | longitude = fields.Float() 126 | 127 | num_bathrooms = fields.Integer() 128 | num_bedrooms = fields.Integer() 129 | num_recepts = fields.Integer() 130 | outcode = fields.String() 131 | post_town = fields.String() 132 | property_type = fields.String() 133 | street_name = fields.String() 134 | 135 | 136 | class PropertyListingResultSchema(BaseResultSchema): 137 | listing = fields.Nested(PropertyListingSchema, many=True) 138 | result_count = fields.Integer() 139 | 140 | 141 | class LocalInfoGraphsResultSchema(BaseResultSchema): 142 | people_graph_url = fields.Url() 143 | crime_graph_url = fields.Url() 144 | council_tax_graph_url = fields.Url() 145 | education_graph_url = fields.Url() 146 | 147 | 148 | class ZedIndexRequestSchema(BaseRequestSchema): 149 | output_type = fields.String(validate=OneOf(choices=( 150 | 'town', 'outcode', 'county', 'country' 151 | ))) 152 | 153 | 154 | class ZedIndexResultSchema(BaseResultSchema): 155 | area_url = fields.String() 156 | zed_index = fields.Integer() 157 | zed_index_3month = fields.Integer() 158 | zed_index_6month = fields.Integer() 159 | zed_index_1year = fields.Integer() 160 | zed_index_2year = fields.Integer() 161 | zed_index_3year = fields.Integer() 162 | zed_index_4year = fields.Integer() 163 | zed_index_5year = fields.Integer() 164 | 165 | 166 | class ZedIndicesResultSchema(BaseSchema): 167 | name = fields.String() 168 | latitude = fields.Float() 169 | longitude = fields.Float() 170 | zed_index = fields.Integer() 171 | 172 | 173 | class AreaZedIndicesRequestSchema(BaseRequestSchema): 174 | ordering = fields.String( 175 | validate=OneOf(choices=('descending', 'ascending'))) 176 | page_number = fields.Integer() 177 | page_size = fields.Integer() 178 | area_type = fields.String(validate=OneOf(choices=( 179 | 'streets', 'postcodes', 'outcodes', 'areas', 'towns', 'counties' 180 | ))) 181 | 182 | 183 | class AreaZedIndicesResultSchema(BaseResultSchema): 184 | results_url = fields.Url() 185 | result_count = fields.Integer() 186 | results = fields.Nested(ZedIndicesResultSchema, many=True) 187 | 188 | 189 | class SuggestionSchema(BaseSchema): 190 | value = fields.String() 191 | identifier = fields.String(allow_none=True) 192 | 193 | 194 | class AutocompleteRequestSchema(BaseRequestSchema): 195 | search_term = fields.String() 196 | search_type = fields.String(validate=OneOf(choices=( 197 | 'listings', 'properties' 198 | ))) 199 | 200 | 201 | class AutoCompleteResultSchema(BaseResultSchema): 202 | suggestions = fields.Nested(SuggestionSchema, many=True) 203 | 204 | 205 | class AreaValueGraphsRequestSchema(BaseRequestSchema): 206 | size = fields.String() 207 | output_type = fields.Constant('outcode') 208 | 209 | 210 | class AreaValueGraphsResultSchema(BaseResultSchema): 211 | area_values_url = fields.Url() 212 | home_values_graph_url = fields.Url() 213 | value_trend_graph_url = fields.Url() 214 | value_ranges_graph_url = fields.Url() 215 | average_values_graph_url = fields.Url() 216 | 217 | 218 | class AverageAreaSoldPriceResultSchema(BaseResultSchema): 219 | average_sold_price_1year = fields.Float() 220 | average_sold_price_3year = fields.Float() 221 | average_sold_price_5year = fields.Float() 222 | average_sold_price_7year = fields.Float() 223 | number_of_sales_1year = fields.Integer() 224 | number_of_sales_3year = fields.Integer() 225 | number_of_sales_5year = fields.Integer() 226 | number_of_sales_7year = fields.Integer() 227 | turnover = fields.Float() 228 | prices_url = fields.Url() 229 | 230 | 231 | class AverageSoldPriceRequestSchema(BaseRequestSchema): 232 | area_type = fields.String(validate=OneOf(choices=( 233 | 'streets', 'postcodes', 'outcodes', 'areas', 'towns', 'counties' 234 | ))) 235 | page_number = fields.Integer() 236 | page_size = fields.Integer() 237 | ordering = fields.String( 238 | validate=OneOf(choices=('descending', 'ascending'))) 239 | 240 | 241 | class AverageSoldPricesBaseResultSchema(BaseResultSchema): 242 | result_count = fields.Integer() 243 | result = fields.Nested(AverageAreaSoldPriceResultSchema, many=True) 244 | 245 | 246 | class RefineEstimateSchema(BaseSchema): 247 | property_id = fields.Integer() 248 | property_type = fields.String(validate=OneOf(choices=( 249 | 'detached', 250 | 'link_detached', 251 | 'semi_detached', 252 | 'terraced', 253 | 'flat', 254 | 'end_terrace', 255 | 'maisonette', 256 | 'mews', 257 | 'town_house', 258 | 'cottage', 259 | 'bungalow', 260 | 'farm_barn', 261 | 'park_home' 262 | ))) 263 | tenure = fields.String(validate=OneOf(choices=( 264 | 'freehold', 265 | 'leasehold', 266 | 'share_of_freehold' 267 | ))) 268 | num_bedrooms = fields.Integer() 269 | num_bathrooms = fields.Integer() 270 | num_receptions = fields.Integer() 271 | session_id = fields.String() 272 | 273 | 274 | class RefineEstimateResultSchema(BaseSchema): 275 | estimate = fields.String() 276 | upper_estimate = fields.String() 277 | lower_estimate = fields.String() 278 | confidence = fields.Integer() 279 | 280 | 281 | class ArrangeViewingSchema(BaseSchema): 282 | session_id = fields.String() 283 | message = fields.String() 284 | best_time_to_call = fields.String(validate=OneOf(choices=( 285 | 'anytime', 286 | 'afternoon', 287 | 'evening', 288 | 'morning' 289 | ))) 290 | phone_type = fields.String(validate=OneOf(choices=( 291 | 'mobile', 292 | 'work', 293 | 'home', 294 | 'morning' 295 | ))) 296 | phone = fields.String() 297 | email = fields.Email() 298 | name = fields.String() 299 | listing_id = fields.Integer() 300 | 301 | 302 | class ArrangeViewingResultSchema(BaseSchema): 303 | success = fields.Integer() 304 | error = fields.String() 305 | 306 | 307 | class RichList(BaseSchema): 308 | name = fields.String() 309 | zed_index = fields.String() 310 | details_url = fields.String() 311 | 312 | 313 | class RichlistResultSchema(BaseRequestSchema): 314 | area_name = fields.String() 315 | bounding_box = fields.Nested(BoundingBoxSchema) 316 | country = fields.String() 317 | latitude = fields.Float() 318 | longitude = fields.Float() 319 | richlist_url = fields.String() 320 | highest = fields.Nested(RichList, many=True) 321 | lowest = fields.Nested(RichList, many=True) 322 | --------------------------------------------------------------------------------