├── .coveragerc ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── requirements-dev.txt ├── setup.cfg ├── setup.py ├── testing ├── __init__.py ├── business_lookup_responses.py ├── error_responses.py ├── json │ ├── business_lookup_sacre_coeur_paris.json │ └── business_lookup_yelp_san_francisco.json ├── obj │ ├── __init__.py │ └── business.py └── util.py ├── tests ├── __init__.py ├── conftest.py ├── endpoint │ ├── __init__.py │ ├── business_test.py │ └── conftest.py ├── errors_test.py ├── integration │ ├── __init__.py │ └── business_integration_test.py └── obj │ ├── __init__.py │ └── response_object_test.py ├── tox.ini └── yelp ├── __init__.py ├── client.py ├── config.py ├── endpoint ├── __init__.py └── business.py ├── errors.py └── obj ├── __init__.py ├── attribute.py ├── business.py ├── category.py ├── coordinates.py ├── hours.py ├── location.py └── response_object.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = 4 | yelp/ 5 | tests/ 6 | omit = 7 | */tmp* 8 | setup.py 9 | # Don't complain if non-runnable code isn't run 10 | */__main__.py 11 | 12 | [report] 13 | show_missing = True 14 | skip_covered = True 15 | fail_under = 100 16 | 17 | exclude_lines = 18 | # Have to re-enable the standard pragma 19 | \#\s*pragma: no cover 20 | 21 | # Don't complain if tests don't hit defensive assertion code: 22 | ^\s*raise AssertionError\b 23 | ^\s*raise NotImplementedError\b 24 | ^\s*return NotImplemented\b 25 | ^\s*raise$ 26 | 27 | # Don't complain if non-runnable code isn't run: 28 | ^if __name__ == ['"]__main__['"]:$ 29 | 30 | [html] 31 | directory = coverage-html 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | .DS_Store 4 | .cache/ 5 | .coverage 6 | .tox/ 7 | MANIFEST 8 | __pycache__ 9 | build/ 10 | dist/ 11 | venv-* 12 | .venv.touch 13 | .pytest_cache/ 14 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: git://github.com/pre-commit/pre-commit-hooks 3 | rev: v1.4.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-merge-conflict 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-json 10 | - id: check-yaml 11 | - id: debug-statements 12 | - id: name-tests-test 13 | exclude: ^tests/testing.py$ 14 | - id: flake8 15 | - id: requirements-txt-fixer 16 | - id: pretty-format-json 17 | args: 18 | - --autofix 19 | - id: fix-encoding-pragma 20 | - repo: https://github.com/asottile/pyupgrade 21 | rev: v1.4.0 22 | hooks: 23 | - id: pyupgrade 24 | - repo: https://github.com/ambv/black 25 | rev: 18.6b4 26 | hooks: 27 | - id: black 28 | - repo: https://github.com/asottile/reorder_python_imports 29 | rev: v1.1.0 30 | hooks: 31 | - id: reorder-python-imports 32 | args: 33 | - --add-import 34 | - from __future__ import absolute_import 35 | - --add-import 36 | - from __future__ import unicode_literals 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | matrix: 3 | include: # These should match the tox env list 4 | - env: TOXENV=py27 5 | python: 2.7 6 | - env: TOXENV=py36 7 | python: 3.6 8 | - env: TOXENV=pre-commit 9 | python: 3.6 10 | install: pip install coveralls tox 11 | script: tox 12 | after_success: 13 | - coveralls 14 | cache: 15 | directories: 16 | - $HOME/.cache/pip 17 | - $HOME/.pre-commit 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Yelp Inc 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBUILD_FLAG = 2 | 3 | .PHONY: all 4 | all: venv test 5 | 6 | .PHONY: venv 7 | venv: .venv.touch 8 | tox -e venv $(REBUILD_FLAG) 9 | 10 | .PHONY: test 11 | test: .venv.touch 12 | tox $(REBUILD_FLAG) 13 | 14 | .venv.touch: setup.py requirements-dev.txt 15 | $(eval REBUILD_FLAG := --recreate) 16 | touch .venv.touch 17 | 18 | .PHONY: clean 19 | clean: 20 | find . -iname '*.pyc' -delete 21 | rm -rf .tox 22 | rm -rf ./venv-* 23 | rm -f .venv.touch 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Yelp/yelp-python.svg?branch=master)](https://travis-ci.org/Yelp/yelp-python) 2 | [![Coverage Status](https://img.shields.io/coveralls/Yelp/yelp-python.svg?branch=master)](https://coveralls.io/r/Yelp/yelp-python) 3 | 4 | # yelp-python 5 | 6 | A Python library for the [Yelp Fusion API](https://www.yelp.com/developers/documentation/v3/get_started). It simplifies the process of request construction, and response parsing for Python developers. This clientlib is built and tested on Python 2.7 and 3.6. 7 | 8 | Please file issues on this repository for bugs/feature requests for this python client. For bugs/features requests for the Yelp Fusion API itself, please open issues on the [dedicated Yelp Fusion repository](https://github.com/Yelp/yelp-fusion). 9 | 10 | 11 | ## Installation 12 | 13 | Install yelp-python from PyPI using: 14 | 15 | pip install yelp 16 | 17 | ## Usage 18 | 19 | ### Basics 20 | 21 | You must have a Yelp Fusion API key to make requests to the API. Sign up for an API key at https://www.yelp.com/developers/v3/manage_app. Instantiate `yelp.client.Client` with your API key, and start making requests! 22 | 23 | ``` 24 | from yelp.client import Client 25 | 26 | MY_API_KEY = "abcefghijklmnopqrstuvqwxy123456789" # Replace this with your real API key 27 | 28 | client = Client(MY_API_KEY) 29 | ``` 30 | 31 | Now you can use the client object to make requests. 32 | 33 | ### Business Details Endpoint 34 | 35 | Endpoint documentation: https://www.yelp.com/developers/documentation/v3/business 36 | 37 | To query the Business Details Endpoint use the `busines.get_by_id` function with a Yelp business alias (i.e. `yelp-san-francisco`) or ID (i.e. `4kMBvIEWPxWkWKFN__8SxQ`). You can also pass in the locale parameter as specified in the [Business Details Endpoint Documentation](https://www.yelp.com/developers/documentation/v3/business). 38 | 39 | ``` 40 | > business_response = client.business.get_by_id('yelp-san-francisco') 41 | 42 | > business_response 43 | Business(alias='yelp-san-francisco', attributes=None, categories=[Category(alias='massmedia', title='Mass Media')], coordinates=Coordinates(latitude=37.7867703362929, longitude=-122.399958372115), display_phone='(415) 908-3801', hours=[Hours(hours_type='REGULAR', is_open_now=True, open=[DayHours(day=0, end='1800', is_overnight=False, start='0800'), DayHours(day=1, end='1800', is_overnight=False, start='0800'), DayHours(day=2, end='1800', is_overnight=False, start='0800'), DayHours(day=3, end='1800', is_overnight=False, start='0800'), DayHours(day=4, end='1800', is_overnight=False, start='0800')])], id='4kMBvIEWPxWkWKFN__8SxQ', image_url='https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg', is_claimed=True, is_closed=False, location=Location(address1='140 New Montgomery St', address2='', address3='', city='San Francisco', country='US', cross_streets='Natoma St & Minna St', display_address=['140 New Montgomery St', 'San Francisco, CA 94105'], state='CA', zip_code='94105'), name='Yelp', phone='+14159083801', photos=['https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg', 'https://s3-media2.fl.yelpcdn.com/bphoto/yFHIb9gob4TzhKUemMOPww/o.jpg', 'https://s3-media1.fl.yelpcdn.com/bphoto/EHCfkEpZraIfPl8gvCo1tg/o.jpg'], rating=2.0, review_count=8421, transactions=[], url='https://www.yelp.com/biz/yelp-san-francisco?adjust_creative=wpr6gw4FnptTrk1CeT8POg&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=wpr6gw4FnptTrk1CeT8POg') 44 | ``` 45 | 46 | ## Contributing 47 | 48 | 1. Fork it (http://github.com/yelp/yelp-python/fork) 49 | 2. Setup your virtual environment 50 | ``` 51 | $ pip install tox 52 | $ tox -e venv 53 | $ . venv-yelp/bin/activate 54 | ``` 55 | 3. Create your feature branch (git checkout -b my-new-feature) 56 | 4. Commit your changes (git commit -am 'Add some feature') 57 | 5. Push to the branch (git push origin my-new-feature) 58 | 6. Create new Pull Request 59 | 60 | ### Testing 61 | 62 | Please write tests for any new features. We use pytest + tox so just run `tox` to run the full test suite. Full py.test documentation [here](http://pytest.org/latest/contents.html). 63 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | coverage==4.5.1 2 | mock==2.0.0 3 | pre-commit==1.10.4 4 | pytest==3.6.3 5 | responses==0.9.0 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from setuptools import find_packages 6 | from setuptools import setup 7 | 8 | setup( 9 | name="yelp", 10 | version="1.0.1", 11 | description="Python Clientlib for Yelp Public API", 12 | url="https://github.com/Yelp/yelp-python", 13 | author="Yelp", 14 | author_email="partnerships@yelp.com", 15 | classifiers=[ 16 | "License :: OSI Approved :: MIT License", 17 | "Programming Language :: Python :: 2", 18 | "Programming Language :: Python :: 2.7", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.6", 21 | "Programming Language :: Python :: Implementation :: CPython", 22 | ], 23 | license="MIT", 24 | keywords="yelp", 25 | packages=find_packages(exclude=("tests*",)), 26 | install_requires=["six", "requests"], 27 | ) 28 | -------------------------------------------------------------------------------- /testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/testing/__init__.py -------------------------------------------------------------------------------- /testing/business_lookup_responses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import responses 6 | 7 | from testing.util import read_json_file 8 | 9 | 10 | YELP_SAN_FRANCISCO = responses.Response( 11 | method="GET", 12 | url="https://api.yelp.com/v3/businesses/yelp-san-francisco", 13 | json=read_json_file("business_lookup_yelp_san_francisco.json"), 14 | status=200, 15 | ) 16 | 17 | 18 | SACRE_COEUR_PARIS = responses.Response( 19 | method="GET", 20 | url="https://api.yelp.com/v3/businesses/basilique-du-sacré-cœur-de-montmartre-paris-3", # noqa: E501 21 | json=read_json_file("business_lookup_sacre_coeur_paris.json"), 22 | status=200, 23 | ) 24 | -------------------------------------------------------------------------------- /testing/error_responses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import responses 6 | 7 | 8 | ERROR_RESPONSES = { 9 | "VALIDATION_ERROR": responses.Response( 10 | method="GET", 11 | url="https://api.yelp.com/v3/businesses/fake-business-alias?locale=en_USS", 12 | json={ 13 | "error": { 14 | "code": "VALIDATION_ERROR", 15 | "description": "'en_USS' does not match '^[a-z]{2,3}_[A-Z]{2}$'", 16 | "field": "locale", 17 | "instance": "en_USS", 18 | } 19 | }, 20 | status=400, 21 | ), 22 | "INVALID_LOCALE": responses.Response( 23 | method="GET", 24 | url="https://api.yelp.com/v3/businesses/fake-business-alias?locale=jp_US", 25 | json={ 26 | "error": { 27 | "code": "INVALID_LOCALE", 28 | "description": "The locale you specified is not valid.", 29 | } 30 | }, 31 | status=400, 32 | ), 33 | "INVALID_AUTHORIZATION_METHOD": responses.Response( 34 | method="GET", 35 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 36 | json={ 37 | "error": { 38 | "code": "INVALID_AUTHORIZATION_METHOD", 39 | "description": "Invalid authorization method supplied.", 40 | } 41 | }, 42 | status=400, 43 | ), 44 | "UNAUTHORIZED_ACCESS_TOKEN": responses.Response( 45 | method="GET", 46 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 47 | json={ 48 | "error": { 49 | "code": "UNAUTHORIZED_ACCESS_TOKEN", 50 | "description": ( 51 | "The access token provided is not currently able to query " 52 | "this endpoint." 53 | ), 54 | } 55 | }, 56 | status=401, 57 | ), 58 | "TOKEN_INVALID": responses.Response( 59 | method="GET", 60 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 61 | json={ 62 | "error": { 63 | "code": "TOKEN_INVALID", 64 | "description": "Invalid access token or authorization header.", 65 | } 66 | }, 67 | status=401, 68 | ), 69 | "BUSINESS_UNAVAILABLE": responses.Response( 70 | method="GET", 71 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 72 | json={ 73 | "error": { 74 | "code": "BUSINESS_UNAVAILABLE", 75 | "description": ( 76 | "We may not be able to provide details for certain " 77 | "businesses, for example if they do not have any reviews yet." 78 | ), 79 | } 80 | }, 81 | status=403, 82 | ), 83 | "BUSINESS_NOT_FOUND": responses.Response( 84 | method="GET", 85 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 86 | json={ 87 | "error": { 88 | "code": "BUSINESS_NOT_FOUND", 89 | "description": "The requested business could not be found.", 90 | } 91 | }, 92 | status=404, 93 | ), 94 | "TOO_MANY_REQUESTS_PER_SECOND": responses.Response( 95 | method="GET", 96 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 97 | json={ 98 | "error": { 99 | "code": "TOO_MANY_REQUESTS_PER_SECOND", 100 | "description": ( 101 | "You have exceeded the queries-per-second limit for this " 102 | "endpoint. Try reducing the rate at which you make queries." 103 | ), 104 | } 105 | }, 106 | status=429, 107 | ), 108 | "ACCESS_LIMIT_REACHED": responses.Response( 109 | method="GET", 110 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 111 | json={ 112 | "error": { 113 | "code": "ACCESS_LIMIT_REACHED", 114 | "description": ( 115 | "You've reached the access limit for this client. See " 116 | "instructions for requesting a higher access limit at " 117 | "https://www.yelp.com/developers/documentation/v3/rate_limiting" 118 | ), 119 | } 120 | }, 121 | status=429, 122 | ), 123 | "INTERNAL_ERROR": responses.Response( 124 | method="GET", 125 | url="https://api.yelp.com/v3/businesses/fake-business-alias", 126 | json={ 127 | "error": { 128 | "code": "INTERNAL_ERROR", 129 | "description": ( 130 | "Something went wrong internally, please try again later." 131 | ), 132 | } 133 | }, 134 | status=500, 135 | ), 136 | } 137 | -------------------------------------------------------------------------------- /testing/json/business_lookup_sacre_coeur_paris.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "basilique-du-sacr\u00e9-c\u0153ur-de-montmartre-paris-3", 3 | "categories": [ 4 | { 5 | "alias": "churches", 6 | "title": "\u00c9glise" 7 | }, 8 | { 9 | "alias": "landmarks", 10 | "title": "Lieu & B\u00e2timent historique" 11 | } 12 | ], 13 | "coordinates": { 14 | "latitude": 48.886720769013, 15 | "longitude": 2.3430021056794 16 | }, 17 | "display_phone": "01 53 41 89 00", 18 | "hours": [ 19 | { 20 | "hours_type": "REGULAR", 21 | "is_open_now": false, 22 | "open": [ 23 | { 24 | "day": 0, 25 | "end": "2230", 26 | "is_overnight": false, 27 | "start": "0600" 28 | }, 29 | { 30 | "day": 1, 31 | "end": "2230", 32 | "is_overnight": false, 33 | "start": "0600" 34 | }, 35 | { 36 | "day": 2, 37 | "end": "2230", 38 | "is_overnight": false, 39 | "start": "0600" 40 | }, 41 | { 42 | "day": 3, 43 | "end": "2230", 44 | "is_overnight": false, 45 | "start": "0600" 46 | }, 47 | { 48 | "day": 4, 49 | "end": "2230", 50 | "is_overnight": false, 51 | "start": "0600" 52 | }, 53 | { 54 | "day": 5, 55 | "end": "2230", 56 | "is_overnight": false, 57 | "start": "0600" 58 | }, 59 | { 60 | "day": 6, 61 | "end": "2230", 62 | "is_overnight": false, 63 | "start": "0600" 64 | } 65 | ] 66 | } 67 | ], 68 | "id": "spIGAtquYQ0S7xai5eJSuA", 69 | "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/_jdFMkxKj8ejkD2dOduC1A/o.jpg", 70 | "is_claimed": false, 71 | "is_closed": false, 72 | "location": { 73 | "address1": "35 rue du Chevalier de la Barre", 74 | "address2": "", 75 | "address3": "", 76 | "city": "Paris", 77 | "country": "FR", 78 | "cross_streets": "", 79 | "display_address": [ 80 | "35 rue du Chevalier de la Barre", 81 | "75018 Paris" 82 | ], 83 | "state": "75", 84 | "zip_code": "75018" 85 | }, 86 | "name": "Basilique du Sacr\u00e9-C\u0153ur de Montmartre", 87 | "phone": "+33153418900", 88 | "photos": [ 89 | "https://s3-media2.fl.yelpcdn.com/bphoto/_jdFMkxKj8ejkD2dOduC1A/o.jpg", 90 | "https://s3-media4.fl.yelpcdn.com/bphoto/xFFrnrUAPZYFvtZMLLWkvQ/o.jpg", 91 | "https://s3-media4.fl.yelpcdn.com/bphoto/70tWE7016eFJ-xjTup-YRA/o.jpg" 92 | ], 93 | "rating": 4.5, 94 | "review_count": 538, 95 | "transactions": [], 96 | "url": "https://www.yelp.fr/biz/basilique-du-sacr%C3%A9-c%C5%93ur-de-montmartre-paris-3" 97 | } 98 | -------------------------------------------------------------------------------- /testing/json/business_lookup_yelp_san_francisco.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "yelp-san-francisco", 3 | "categories": [ 4 | { 5 | "alias": "massmedia", 6 | "title": "Mass Media" 7 | } 8 | ], 9 | "coordinates": { 10 | "latitude": 37.7867703362929, 11 | "longitude": -122.399958372115 12 | }, 13 | "display_phone": "(415) 908-3801", 14 | "hours": [ 15 | { 16 | "hours_type": "REGULAR", 17 | "is_open_now": true, 18 | "open": [ 19 | { 20 | "day": 0, 21 | "end": "1800", 22 | "is_overnight": false, 23 | "start": "0800" 24 | }, 25 | { 26 | "day": 1, 27 | "end": "1800", 28 | "is_overnight": false, 29 | "start": "0800" 30 | }, 31 | { 32 | "day": 2, 33 | "end": "1800", 34 | "is_overnight": false, 35 | "start": "0800" 36 | }, 37 | { 38 | "day": 3, 39 | "end": "1800", 40 | "is_overnight": false, 41 | "start": "0800" 42 | }, 43 | { 44 | "day": 4, 45 | "end": "1800", 46 | "is_overnight": false, 47 | "start": "0800" 48 | } 49 | ] 50 | } 51 | ], 52 | "id": "4kMBvIEWPxWkWKFN__8SxQ", 53 | "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg", 54 | "is_claimed": true, 55 | "is_closed": false, 56 | "location": { 57 | "address1": "140 New Montgomery St", 58 | "address2": "", 59 | "address3": "", 60 | "city": "San Francisco", 61 | "country": "US", 62 | "cross_streets": "Natoma St & Minna St", 63 | "display_address": [ 64 | "140 New Montgomery St", 65 | "San Francisco, CA 94105" 66 | ], 67 | "state": "CA", 68 | "zip_code": "94105" 69 | }, 70 | "name": "Yelp", 71 | "phone": "+14159083801", 72 | "photos": [ 73 | "https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg", 74 | "https://s3-media2.fl.yelpcdn.com/bphoto/yFHIb9gob4TzhKUemMOPww/o.jpg", 75 | "https://s3-media1.fl.yelpcdn.com/bphoto/EHCfkEpZraIfPl8gvCo1tg/o.jpg" 76 | ], 77 | "rating": 2.0, 78 | "review_count": 8422, 79 | "transactions": [], 80 | "url": "https://www.yelp.com/biz/yelp-san-francisco" 81 | } 82 | -------------------------------------------------------------------------------- /testing/obj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/testing/obj/__init__.py -------------------------------------------------------------------------------- /testing/obj/business.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from yelp.obj.business import Business 6 | from yelp.obj.category import Category 7 | from yelp.obj.coordinates import Coordinates 8 | from yelp.obj.hours import DayHours 9 | from yelp.obj.hours import Hours 10 | from yelp.obj.location import Location 11 | 12 | 13 | yelp_san_francisco = Business( 14 | id="4kMBvIEWPxWkWKFN__8SxQ", 15 | alias="yelp-san-francisco", 16 | name="Yelp", 17 | image_url="https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg", 18 | is_claimed=True, 19 | is_closed=False, 20 | url="https://www.yelp.com/biz/yelp-san-francisco", 21 | phone="+14159083801", 22 | display_phone="(415) 908-3801", 23 | review_count=8422, 24 | categories=[Category(alias="massmedia", title="Mass Media")], 25 | rating=2.0, 26 | location=Location( 27 | display_address=["140 New Montgomery St", "San Francisco, CA 94105"], 28 | address1="140 New Montgomery St", 29 | address2="", 30 | address3="", 31 | city="San Francisco", 32 | state="CA", 33 | zip_code="94105", 34 | country="US", 35 | cross_streets="Natoma St & Minna St", 36 | ), 37 | coordinates=Coordinates(latitude=37.7867703362929, longitude=-122.399958372115), 38 | photos=[ 39 | "https://s3-media2.fl.yelpcdn.com/bphoto/nQK-6_vZMt5n88zsAS94ew/o.jpg", 40 | "https://s3-media2.fl.yelpcdn.com/bphoto/yFHIb9gob4TzhKUemMOPww/o.jpg", 41 | "https://s3-media1.fl.yelpcdn.com/bphoto/EHCfkEpZraIfPl8gvCo1tg/o.jpg", 42 | ], 43 | hours=[ 44 | Hours( 45 | open=[ 46 | DayHours(day=0, start="0800", end="1800", is_overnight=False), 47 | DayHours(day=1, start="0800", end="1800", is_overnight=False), 48 | DayHours(day=2, start="0800", end="1800", is_overnight=False), 49 | DayHours(day=3, start="0800", end="1800", is_overnight=False), 50 | DayHours(day=4, start="0800", end="1800", is_overnight=False), 51 | ], 52 | hours_type="REGULAR", 53 | is_open_now=True, 54 | ) 55 | ], 56 | transactions=[], 57 | attributes=None, 58 | ) 59 | 60 | 61 | sacre_coeur_paris = Business( 62 | alias="basilique-du-sacré-cœur-de-montmartre-paris-3", 63 | attributes=None, 64 | categories=[ 65 | Category(alias="churches", title="Église"), 66 | Category(alias="landmarks", title="Lieu & Bâtiment historique"), 67 | ], 68 | coordinates=Coordinates(latitude=48.886720769013, longitude=2.3430021056794), 69 | display_phone="01 53 41 89 00", 70 | hours=[ 71 | Hours( 72 | hours_type="REGULAR", 73 | is_open_now=False, 74 | open=[ 75 | DayHours(day=0, end="2230", is_overnight=False, start="0600"), 76 | DayHours(day=1, end="2230", is_overnight=False, start="0600"), 77 | DayHours(day=2, end="2230", is_overnight=False, start="0600"), 78 | DayHours(day=3, end="2230", is_overnight=False, start="0600"), 79 | DayHours(day=4, end="2230", is_overnight=False, start="0600"), 80 | DayHours(day=5, end="2230", is_overnight=False, start="0600"), 81 | DayHours(day=6, end="2230", is_overnight=False, start="0600"), 82 | ], 83 | ) 84 | ], 85 | id="spIGAtquYQ0S7xai5eJSuA", 86 | image_url="https://s3-media2.fl.yelpcdn.com/bphoto/_jdFMkxKj8ejkD2dOduC1A/o.jpg", 87 | is_claimed=False, 88 | is_closed=False, 89 | location=Location( 90 | address1="35 rue du Chevalier de la Barre", 91 | address2="", 92 | address3="", 93 | city="Paris", 94 | country="FR", 95 | cross_streets="", 96 | display_address=["35 rue du Chevalier de la Barre", "75018 Paris"], 97 | state="75", 98 | zip_code="75018", 99 | ), 100 | name="Basilique du Sacré-Cœur de Montmartre", 101 | phone="+33153418900", 102 | photos=[ 103 | "https://s3-media2.fl.yelpcdn.com/bphoto/_jdFMkxKj8ejkD2dOduC1A/o.jpg", 104 | "https://s3-media4.fl.yelpcdn.com/bphoto/xFFrnrUAPZYFvtZMLLWkvQ/o.jpg", 105 | "https://s3-media4.fl.yelpcdn.com/bphoto/70tWE7016eFJ-xjTup-YRA/o.jpg", 106 | ], 107 | rating=4.5, 108 | review_count=538, 109 | transactions=[], 110 | url="https://www.yelp.fr/biz/basilique-du-sacr%C3%A9-c%C5%93ur-de-montmartre-paris-3", # noqa: E501 111 | ) 112 | -------------------------------------------------------------------------------- /testing/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import json 6 | import os.path 7 | 8 | 9 | def read_json_file(filename): 10 | filepath = os.path.join(os.path.dirname(__file__), "json", filename) 11 | with open(filepath) as f: 12 | return json.load(f) 13 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import pytest 6 | import responses 7 | 8 | 9 | @pytest.fixture(autouse=True) 10 | def activate_response_mocking(): 11 | with responses.mock: 12 | yield 13 | -------------------------------------------------------------------------------- /tests/endpoint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/tests/endpoint/__init__.py -------------------------------------------------------------------------------- /tests/endpoint/business_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import mock 6 | import pytest 7 | 8 | import yelp.endpoint.business 9 | from yelp.endpoint.business import BusinessEndpoints 10 | 11 | 12 | @pytest.fixture 13 | def business_endpoints(mock_client): 14 | return BusinessEndpoints(mock_client) 15 | 16 | 17 | @pytest.fixture 18 | def mock_business_response_cls(): 19 | with mock.patch.object( 20 | yelp.endpoint.business, "Business" 21 | ) as mock_business_response_cls: 22 | yield mock_business_response_cls 23 | 24 | 25 | class TestBusiness: 26 | def test_no_url_params( 27 | self, business_endpoints, mock_client, mock_business_response_cls 28 | ): 29 | business_endpoints.get_by_id("test-id") 30 | 31 | mock_client._make_request.assert_called_once_with( 32 | "/v3/businesses/test-id", url_params={} 33 | ) 34 | assert mock_business_response_cls.called 35 | 36 | def test_with_url_params( 37 | self, business_endpoints, mock_client, mock_business_response_cls 38 | ): 39 | business_endpoints.get_by_id("test-id", locale="fr_FR") 40 | 41 | mock_client._make_request.assert_called_once_with( 42 | "/v3/businesses/test-id", url_params={"locale": "fr_FR"} 43 | ) 44 | assert mock_business_response_cls.called 45 | -------------------------------------------------------------------------------- /tests/endpoint/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import mock 6 | import pytest 7 | 8 | import yelp.client 9 | from yelp.client import Client 10 | 11 | 12 | @pytest.fixture 13 | def mock_requests(): 14 | with mock.patch.object(yelp.client, "requests") as mock_requests: 15 | yield mock_requests 16 | 17 | 18 | @pytest.fixture 19 | def mock_client(mock_requests): 20 | return mock.Mock(name="Mock Client", spec=Client) 21 | -------------------------------------------------------------------------------- /tests/errors_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import mock 6 | import pytest 7 | 8 | from yelp.errors import YelpError 9 | 10 | 11 | def test(): 12 | json_response = { 13 | "error": { 14 | "code": "AN_UNKNOWN_CODE_WHAT_COULD_IT_BE", 15 | "description": "A mystery description.", 16 | } 17 | } 18 | 19 | fake_response_with_unknown_error_code = mock.Mock(name="fake response") 20 | fake_response_with_unknown_error_code.json.return_value = json_response 21 | 22 | with pytest.raises(NotImplementedError): 23 | YelpError.from_response(fake_response_with_unknown_error_code) 24 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/business_integration_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import pytest 6 | import responses 7 | 8 | import testing.business_lookup_responses as response_fixtures 9 | import testing.obj.business as business_obj_fixtures 10 | from testing.error_responses import ERROR_RESPONSES 11 | from yelp import errors 12 | from yelp.client import Client 13 | 14 | 15 | class TestBusinessIntegration: 16 | @pytest.mark.parametrize( 17 | ["business_alias", "url_params", "mock_response", "expected_response"], 18 | [ 19 | ( 20 | "yelp-san-francisco", 21 | {}, 22 | response_fixtures.YELP_SAN_FRANCISCO, 23 | business_obj_fixtures.yelp_san_francisco, 24 | ), 25 | ( 26 | "basilique-du-sacré-cœur-de-montmartre-paris-3", 27 | {"locale": "fr_FR"}, 28 | response_fixtures.SACRE_COEUR_PARIS, 29 | business_obj_fixtures.sacre_coeur_paris, 30 | ), 31 | ], 32 | ) 33 | def test_success( 34 | self, business_alias, url_params, mock_response, expected_response 35 | ): 36 | responses.add(mock_response) 37 | client = Client("BOGUS API KEY") 38 | response = client.business.get_by_id(business_alias, **url_params) 39 | assert response.to_dict() == expected_response.to_dict() 40 | 41 | @pytest.mark.parametrize( 42 | ["url_params", "mock_response", "expected_error"], 43 | [ 44 | ( 45 | {"locale": "en_USS"}, 46 | ERROR_RESPONSES["VALIDATION_ERROR"], 47 | errors.ValidationError, 48 | ), 49 | ({}, ERROR_RESPONSES["INVALID_LOCALE"], errors.InvalidLocale), 50 | ( 51 | {}, 52 | ERROR_RESPONSES["INVALID_AUTHORIZATION_METHOD"], 53 | errors.InvalidAuthorizationMethod, 54 | ), 55 | ( 56 | {}, 57 | ERROR_RESPONSES["UNAUTHORIZED_ACCESS_TOKEN"], 58 | errors.UnauthorizedAccessToken, 59 | ), 60 | ({}, ERROR_RESPONSES["TOKEN_INVALID"], errors.TokenInvalid), 61 | ({}, ERROR_RESPONSES["BUSINESS_UNAVAILABLE"], errors.BusinessUnavailable), 62 | ({}, ERROR_RESPONSES["BUSINESS_NOT_FOUND"], errors.BusinessNotFound), 63 | ( 64 | {}, 65 | ERROR_RESPONSES["TOO_MANY_REQUESTS_PER_SECOND"], 66 | errors.TooManyRequestsPerSecond, 67 | ), 68 | ({}, ERROR_RESPONSES["ACCESS_LIMIT_REACHED"], errors.AccessLimitReached), 69 | ({}, ERROR_RESPONSES["INTERNAL_ERROR"], errors.InternalError), 70 | ], 71 | ) 72 | def test_errors(self, url_params, mock_response, expected_error): 73 | responses.add(mock_response) 74 | 75 | client = Client("BOGUS API KEY") 76 | with pytest.raises(expected_error): 77 | client.business.get_by_id("fake-business-alias", url_params=url_params) 78 | -------------------------------------------------------------------------------- /tests/obj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/tests/obj/__init__.py -------------------------------------------------------------------------------- /tests/obj/response_object_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from yelp.obj.response_object import ResponseObject 6 | 7 | 8 | class DogFood(ResponseObject): 9 | _schema = {"brand": str, "cost": float} 10 | 11 | 12 | class Place(ResponseObject): 13 | _schema = {"name": str} 14 | 15 | 16 | class Dog(ResponseObject): 17 | _schema = { 18 | "name": str, 19 | "aliases": [str], 20 | "favorite_food": DogFood, 21 | "favorite_places": [Place], 22 | } 23 | 24 | 25 | dog_obj = Dog( 26 | name="Master Peabody", 27 | aliases=["Woofus", "Dogmeat"], 28 | favorite_food=DogFood(brand="Kibbles", cost=9.99), 29 | favorite_places=[Place(name="dirt pile"), Place(name="fire hydrant")], 30 | ) 31 | 32 | 33 | def test_repr(): 34 | expected_py3 = ( 35 | "Dog(aliases=['Woofus', 'Dogmeat'], " 36 | "favorite_food=DogFood(brand='Kibbles', cost=9.99), " 37 | "favorite_places=[Place(name='dirt pile'), Place(name='fire hydrant')], " 38 | "name='Master Peabody')" 39 | ) 40 | expected_py27 = ( 41 | "Dog(aliases=[u'Woofus', u'Dogmeat'], " 42 | "favorite_food=DogFood(brand=u'Kibbles', cost=9.99), " 43 | "favorite_places=[Place(name=u'dirt pile'), Place(name=u'fire hydrant')], " 44 | "name=u'Master Peabody')" 45 | ) 46 | 47 | assert repr(dog_obj) in {expected_py27, expected_py3} 48 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | project = yelp 3 | envlist = py27,py36,pre-commit 4 | 5 | [testenv] 6 | deps = -rrequirements-dev.txt 7 | passenv = HOME SSH_AUTH_SOCK USER 8 | commands = 9 | coverage erase 10 | coverage run -m pytest {posargs:tests} 11 | coverage report 12 | 13 | [testenv:pre-commit] 14 | envdir = venv-{[tox]project} 15 | basepython=python3.6 16 | commands = 17 | pre-commit install -f --install-hooks 18 | pre-commit run --all-files 19 | 20 | [testenv:venv] 21 | envdir = venv-{[tox]project} 22 | basepython = /usr/bin/python3.6 23 | commands = 24 | 25 | [flake8] 26 | # Match max-line-length to ambv/black's default of 88. 27 | max-line-length = 88 28 | -------------------------------------------------------------------------------- /yelp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/yelp/__init__.py -------------------------------------------------------------------------------- /yelp/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import requests 6 | import six 7 | 8 | from yelp.config import API_ROOT_URL 9 | from yelp.endpoint.business import BusinessEndpoints 10 | from yelp.errors import YelpError 11 | 12 | 13 | class Client(object): 14 | def __init__(self, api_key): 15 | self._session = requests.Session() 16 | self._session.headers.update(self._get_auth_header(api_key)) 17 | 18 | # Add endpoints to this client. Then they will be accessed e.g. 19 | # client.business.get_by_id('yelp-san-francisco') 20 | self.business = BusinessEndpoints(self) 21 | 22 | def _make_request(self, path, url_params=None): 23 | url_params = url_params if url_params is not None else {} 24 | 25 | url = "{}{}".format( 26 | API_ROOT_URL, six.moves.urllib.parse.quote(path.encode("utf-8")) 27 | ) 28 | 29 | response = self._session.get(url, params=url_params) 30 | 31 | if response.status_code == 200: 32 | return response.json() 33 | else: 34 | raise YelpError.from_response(response) 35 | 36 | def _get_auth_header(self, api_key): 37 | return {"Authorization": "Bearer {api_key}".format(api_key=api_key)} 38 | -------------------------------------------------------------------------------- /yelp/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | API_ROOT_URL = "https://api.yelp.com" 6 | 7 | BUSINESS_PATH = "/v3/businesses/{business_id}" 8 | PHONE_SEARCH_PATH = "/v3/businesses/search/phone" 9 | SEARCH_PATH = "/v3/businesses/search" 10 | -------------------------------------------------------------------------------- /yelp/endpoint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/yelp/endpoint/__init__.py -------------------------------------------------------------------------------- /yelp/endpoint/business.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from yelp.config import BUSINESS_PATH 6 | from yelp.obj.business import Business 7 | 8 | 9 | class BusinessEndpoints(object): 10 | def __init__(self, client): 11 | self.client = client 12 | 13 | def get_by_id(self, business_id, **url_params): 14 | """Make a request to the business details endpoint. More info at 15 | https://www.yelp.com/developers/documentation/v3/business 16 | 17 | Args: 18 | business_id (str): The business alias (i.e. yelp-san-francisco) or 19 | ID (i.e. 4kMBvIEWPxWkWKFN__8SxQ. 20 | **url_params: Dict corresponding to business API params 21 | https://www.yelp.com/developers/documentation/v3/business 22 | 23 | Returns: 24 | yelp.obj.business.Business object that wraps the response. 25 | 26 | """ 27 | business_path = BUSINESS_PATH.format(business_id=business_id) 28 | response = self.client._make_request(business_path, url_params=url_params) 29 | return Business(response) 30 | -------------------------------------------------------------------------------- /yelp/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | 6 | class YelpError(Exception): 7 | required_fields = ["code", "description"] 8 | optional_fields = [] 9 | 10 | def __init__(self, raw_response, **error_info): 11 | for field_name in self.required_fields: 12 | setattr(self, field_name, error_info[field_name]) 13 | 14 | for field_name in self.optional_fields: 15 | setattr(self, field_name, error_info.get(field_name)) 16 | 17 | self.raw_response = raw_response 18 | self.http_status = raw_response.status_code 19 | 20 | @staticmethod 21 | def from_response(raw_response): 22 | """The Yelp Fusion API returns error messages with a json body 23 | like: 24 | { 25 | 'error': { 26 | 'code': 'ALL_CAPS_CODE', 27 | 'description': 'Human readable description.' 28 | } 29 | } 30 | 31 | Some errors may have additional fields. For example, a 32 | validation error: 33 | { 34 | 'error': { 35 | 'code': 'VALIDATION_ERROR', 36 | 'description': "'en_USS' does not match '^[a-z]{2,3}_[A-Z]{2}$'", 37 | 'field': 'locale', 38 | 'instance': 'en_USS' 39 | } 40 | } 41 | """ 42 | json_response = raw_response.json() 43 | error_info = json_response["error"] 44 | code = error_info["code"] 45 | 46 | try: 47 | error_cls = _error_map[code] 48 | except KeyError: 49 | raise NotImplementedError( 50 | "Unknown error code '{}' returned in Yelp API response. " 51 | "This code may have been newly added. Please ensure you are " 52 | "using the latest version of the yelp-python library, and if " 53 | "so, create a new issue at https://github.com/Yelp/yelp-python " 54 | "to add support for this error.".format(code) 55 | ) 56 | else: 57 | return error_cls(raw_response, **error_info) 58 | 59 | 60 | class ValidationError(YelpError): 61 | optional_fields = ["field", "instance"] 62 | 63 | 64 | class InvalidLocale(YelpError): 65 | pass 66 | 67 | 68 | class InvalidAuthorizationMethod(YelpError): 69 | pass 70 | 71 | 72 | class UnauthorizedAccessToken(YelpError): 73 | pass 74 | 75 | 76 | class TokenInvalid(YelpError): 77 | pass 78 | 79 | 80 | class BusinessUnavailable(YelpError): 81 | pass 82 | 83 | 84 | class BusinessNotFound(YelpError): 85 | pass 86 | 87 | 88 | class TooManyRequestsPerSecond(YelpError): 89 | pass 90 | 91 | 92 | class AccessLimitReached(YelpError): 93 | pass 94 | 95 | 96 | class InternalError(YelpError): 97 | pass 98 | 99 | 100 | _error_map = { 101 | "VALIDATION_ERROR": ValidationError, 102 | "INVALID_LOCALE": InvalidLocale, 103 | "INVALID_AUTHORIZATION_METHOD": InvalidAuthorizationMethod, 104 | "UNAUTHORIZED_ACCESS_TOKEN": UnauthorizedAccessToken, 105 | "TOKEN_INVALID": TokenInvalid, 106 | "BUSINESS_UNAVAILABLE": BusinessUnavailable, 107 | "BUSINESS_NOT_FOUND": BusinessNotFound, 108 | "TOO_MANY_REQUESTS_PER_SECOND": TooManyRequestsPerSecond, 109 | "ACCESS_LIMIT_REACHED": AccessLimitReached, 110 | "INTERNAL_ERROR": InternalError, 111 | } 112 | -------------------------------------------------------------------------------- /yelp/obj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yelp/yelp-python/12d611bc2344bbc1c93c83775aa71b7b01b36ad6/yelp/obj/__init__.py -------------------------------------------------------------------------------- /yelp/obj/attribute.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from six import text_type as str 6 | 7 | from yelp.obj.response_object import ResponseObject 8 | 9 | 10 | class Attribute(ResponseObject): 11 | 12 | _schema = {"name": str, "value": str} 13 | -------------------------------------------------------------------------------- /yelp/obj/business.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from six import text_type as str 6 | 7 | from yelp.obj.attribute import Attribute 8 | from yelp.obj.category import Category 9 | from yelp.obj.coordinates import Coordinates 10 | from yelp.obj.hours import Hours 11 | from yelp.obj.location import Location 12 | from yelp.obj.response_object import ResponseObject 13 | 14 | 15 | class Business(ResponseObject): 16 | 17 | _schema = { 18 | "id": str, 19 | "alias": str, 20 | "name": str, 21 | "image_url": str, 22 | "is_claimed": bool, 23 | "is_closed": bool, 24 | "url": str, 25 | "phone": str, 26 | "display_phone": str, 27 | "review_count": int, 28 | "categories": [Category], 29 | "rating": float, 30 | "location": Location, 31 | "coordinates": Coordinates, 32 | "photos": [str], 33 | "hours": [Hours], 34 | "transactions": [str], 35 | "attributes": [Attribute], 36 | } 37 | -------------------------------------------------------------------------------- /yelp/obj/category.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from six import text_type as str 6 | 7 | from yelp.obj.response_object import ResponseObject 8 | 9 | 10 | class Category(ResponseObject): 11 | 12 | _schema = {"alias": str, "title": str} 13 | -------------------------------------------------------------------------------- /yelp/obj/coordinates.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from yelp.obj.response_object import ResponseObject 6 | 7 | 8 | class Coordinates(ResponseObject): 9 | 10 | _schema = {"latitude": float, "longitude": float} 11 | -------------------------------------------------------------------------------- /yelp/obj/hours.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from six import text_type as str 6 | 7 | from yelp.obj.response_object import ResponseObject 8 | 9 | 10 | class DayHours(ResponseObject): 11 | 12 | _schema = {"day": int, "start": str, "end": str, "is_overnight": bool} 13 | 14 | 15 | class Hours(ResponseObject): 16 | 17 | _schema = {"open": [DayHours], "hours_type": str, "is_open_now": bool} 18 | -------------------------------------------------------------------------------- /yelp/obj/location.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | from six import text_type as str 6 | 7 | from yelp.obj.response_object import ResponseObject 8 | 9 | 10 | class Location(ResponseObject): 11 | 12 | _schema = { 13 | "display_address": [str], 14 | "address1": str, 15 | "address2": str, 16 | "address3": str, 17 | "city": str, 18 | "state": str, 19 | "zip_code": str, 20 | "country": str, 21 | "cross_streets": str, 22 | } 23 | -------------------------------------------------------------------------------- /yelp/obj/response_object.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | 6 | def dictify(obj): 7 | if isinstance(obj, ResponseObject): 8 | return { 9 | field_name: dictify(getattr(obj, field_name)) 10 | for field_name in obj._schema.keys() 11 | } 12 | elif isinstance(obj, list): 13 | return [dictify(element) for element in obj] 14 | else: 15 | return obj 16 | 17 | 18 | class ResponseObject(object): 19 | _schema = {} 20 | 21 | def __init__(self, response=None, **fields_to_values_override): 22 | self._field_names = sorted(self._schema) 23 | fields_to_values = {} 24 | 25 | if response is not None: 26 | for field_name, field_type in self._schema.items(): 27 | raw_value = response.get(field_name) 28 | 29 | if raw_value is None: 30 | value = None 31 | elif isinstance(field_type, list): 32 | # If the schema is defined as a list, then the response 33 | # data for this field is expected to be a list. For 34 | # example, if the field_type is [str] then the data is 35 | # expected to be a list of strings. 36 | assert isinstance(raw_value, list) 37 | element_type = field_type[0] 38 | value = [element_type(element) for element in raw_value] 39 | else: 40 | value = field_type(raw_value) 41 | 42 | fields_to_values[field_name] = value 43 | 44 | fields_to_values.update(fields_to_values_override) 45 | 46 | for field_name in self._field_names: 47 | field_value = fields_to_values[field_name] 48 | self.__setattr__(field_name, field_value) 49 | 50 | def __repr__(self): 51 | field_strings = [] 52 | 53 | for field_name in self._field_names: 54 | field_value = getattr(self, field_name) 55 | field_str = "{}={}".format(field_name, repr(field_value)) 56 | field_strings.append(field_str) 57 | 58 | return "{classname}({field_strings})".format( 59 | classname=type(self).__name__, field_strings=", ".join(field_strings) 60 | ) 61 | 62 | __str__ = __repr__ 63 | 64 | def to_dict(self): 65 | return dictify(self) 66 | --------------------------------------------------------------------------------