yelpapi 2.7.0__tar.gz → 2.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,15 +16,16 @@ jobs:
16
16
  steps:
17
17
  - uses: actions/checkout@v7
18
18
 
19
- - uses: actions/setup-python@v7
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v7
20
21
  with:
21
22
  python-version: '3.15'
22
23
 
23
24
  - name: Verify tag matches package version
24
25
  run: |
25
26
  version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
26
- if [ "v${version}" != "${{ github.ref_name }}" ]; then
27
- echo "Tag ${{ github.ref_name }} does not match pyproject.toml version ${version}" >&2
27
+ if [ "v${version}" != "${GITHUB_REF_NAME}" ]; then
28
+ echo "Tag ${GITHUB_REF_NAME} does not match pyproject.toml version ${version}" >&2
28
29
  exit 1
29
30
  fi
30
31
 
@@ -48,4 +49,4 @@ jobs:
48
49
  - name: Create GitHub release
49
50
  env:
50
51
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51
- run: gh release create "${{ github.ref_name }}" dist/* --title "YelpAPI ${{ github.ref_name }}" --notes-file release-notes.md
52
+ run: gh release create "${GITHUB_REF_NAME}" dist/* --title "YelpAPI ${GITHUB_REF_NAME}" --notes-file release-notes.md
@@ -2,12 +2,36 @@ name: Tests
2
2
 
3
3
  on:
4
4
  push:
5
+ branches: [master]
5
6
  pull_request:
6
7
 
7
8
  permissions:
8
9
  contents: read
9
10
 
11
+ # Cancel an in-progress run when a newer commit is pushed to the same branch or pull request.
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
10
16
  jobs:
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+
23
+ # Ruff targets requires-python in pyproject.toml regardless of the interpreter it runs on.
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v7
26
+ with:
27
+ python-version: '3.x'
28
+
29
+ - name: Install ruff
30
+ run: pip install ruff
31
+
32
+ - name: Lint with ruff
33
+ run: ruff check --output-format=github
34
+
11
35
  typecheck:
12
36
  runs-on: ubuntu-latest
13
37
 
@@ -31,6 +55,7 @@ jobs:
31
55
  test:
32
56
  runs-on: ubuntu-latest
33
57
  strategy:
58
+ fail-fast: false
34
59
  matrix:
35
60
  python-version: ['3.11', '3.12', '3.13', '3.14', '3.15']
36
61
 
@@ -24,19 +24,24 @@ Type-check (strict; configured in `[tool.mypy]` in `pyproject.toml`, which sets
24
24
  mypy
25
25
  ```
26
26
 
27
- CI (`.github/workflows/tests.yml`) runs `pytest` on every supported Python version and `mypy` once in a separate `typecheck` job. Both must pass.
27
+ Lint (configured in `[tool.ruff]` in `pyproject.toml`; `ruff check --fix` applies safe fixes):
28
+ ```
29
+ ruff check
30
+ ```
31
+
32
+ CI (`.github/workflows/tests.yml`) runs `pytest` on every supported Python version, plus `mypy` and `ruff check` once each in separate `typecheck` and `lint` jobs. All must pass.
28
33
 
29
34
  ## Architecture
30
35
 
31
- This is a thin Python wrapper around the [Yelp Fusion API](https://docs.developer.yelp.com/docs/fusion-intro). The entire implementation lives in `yelpapi/yelpapi.py` as a single `YelpAPI` class, re-exported from `yelpapi/__init__.py` via `__all__`.
36
+ This is a thin Python wrapper around Yelp's public REST APIs: the [Yelp Places API](https://docs.developer.yelp.com/docs/places-intro), the [Yelp AI API](https://docs.developer.yelp.com/docs/yelp-ai-api), [Yelp Insights](https://docs.developer.yelp.com/docs/yelp-insights), and the [Home Services API](https://docs.developer.yelp.com/docs/home-services-api). The entire implementation lives in `yelpapi/yelpapi.py` as a single `YelpAPI` class, re-exported from `yelpapi/__init__.py` via `__all__`.
32
37
 
33
38
  **Design philosophy:** The wrapper is intentionally minimal and extensible — all API parameters are passed through `**kwargs` directly to the HTTP request, so it doesn't break when Yelp adds new parameters. The only thing that would break it is a URL scheme change. Public methods return `dict[str, Any]`; don't introduce `TypedDict`s or response models, since they would break this pass-through design.
34
39
 
35
- **Request flow:** Each public method (e.g., `search_query`, `business_query`) validates required parameters, then delegates to `_query()`, which strips `None`-valued kwargs before issuing the GET request via a shared `requests.Session`. `_query()` raises `requests.HTTPError` for HTTP error statuses, and raises `YelpAPI.YelpAPIError` if the JSON body isn't an object or contains an `error` key. Methods that require a location share a `_require_location_or_lat_lng()` helper.
40
+ **Request flow:** Each public method (e.g., `search_query`, `business_query`) validates required parameters, then delegates to `_get()` (GET; kwargs become query string parameters) or `_post()` (POST; kwargs become the JSON body). Both strip `None`-valued kwargs before issuing the request via a shared `requests.Session`, then hand the response to `_parse_response()`, which raises `requests.HTTPError` for HTTP error statuses, returns `{}` for empty `202`/`204` responses, and raises `YelpAPI.YelpAPIError` if the JSON body isn't an object or contains an `error` key. Methods that require a location share a `_require_location_or_lat_lng()` helper.
36
41
 
37
42
  **Session management:** A single `requests.Session` is reused for all calls (performance benefit). Users should close it via context manager (`with YelpAPI(...) as api`) or explicitly via `api.close()`.
38
43
 
39
- **Tests** use `requests-mock` (via the `mock_request` autouse fixture in `conftest.py`) to intercept HTTP calls, and `Faker` for generating random test data. No real API calls are made in tests.
44
+ **Tests** use `requests-mock` (via the `mock_request` autouse fixture in `conftest.py`) to intercept HTTP calls, and a fixed-seed `Faker` for generating test data, so every run is deterministic. No real API calls are made in tests.
40
45
 
41
46
  ## Conventions
42
47
 
@@ -45,7 +50,8 @@ This is a thin Python wrapper around the [Yelp Fusion API](https://docs.develope
45
50
  - **Style:** Use single quotes for strings, and 120-character lines.
46
51
  - **Adding an endpoint** requires all of the following:
47
52
  - a `*_API_URL` constant
48
- - a method that validates required parameters and calls `_query()`
53
+ - a method that validates required parameters and calls `_get()` (GET) or `_post()` (POST); path parameters are positional arguments inserted with `quote(value, safe='')`
54
+ - an entry in the `GET_ENDPOINTS` or `POST_ENDPOINTS` table in `tests/test_yelpapi.py` (use `PATH_PARAM` for path parameters)
49
55
  - an entry in the `YelpAPI` class docstring's API list
50
56
  - an entry in the README "METHODS" list
51
57
  - tests covering both validation failures and success
@@ -1,5 +1,23 @@
1
1
  # Changes
2
2
 
3
+ ## 2.8.0 (2026-10-28)
4
+ * **Heads up for subclassers:** the private request helper `YelpAPI._query()` has been renamed to `YelpAPI._get()`, and the new POST helper is `YelpAPI._post()`.
5
+ * If your subclass *calls* `self._query(...)` to reach endpoints this library doesn't wrap, switch to `self._get(...)`. `_query()` still works for this release but emits a `FutureWarning`.
6
+ * If your subclass *overrides* `_query()` (e.g., for caching, retries, or logging), override `_get()` (and `_post()` for POST endpoints) instead. Public methods no longer call `_query()`, so an override of it is no longer used. Defining such a subclass emits a `FutureWarning`.
7
+ * `_query()` will be removed in 2.9.0.
8
+ * Added 8 new API endpoints: AI Chat (`ai_chat_query`), Business Insights (`business_insights_query`), Business Food & Drinks Insights (`business_food_and_drinks_insights_query`), Business Risk Signal Insights (`business_risk_signals_insights_query`), Business Request a Phone Call (`business_rapc_enabledness_query`), Jobs (`jobs_query`), Job Survey (`job_survey_query`), and Request a Quote (`request_a_quote_query`).
9
+ * Added support for POST endpoints, which send `**kwargs` as the JSON request body (with `None` values removed). Successful responses with no body (e.g., `202 Accepted`) return an empty dict.
10
+ * `business_service_offerings_query` now emits a `DeprecationWarning`, because Yelp has deprecated the endpoint in favor of "Jobs offered" on the Business API.
11
+ * Fixed: a latitude or longitude of `0` (the equator or prime meridian) is now accepted by `search_query`, `featured_event_query`, and `transaction_search_query` instead of raising `ValueError`.
12
+ * Fixed: `business_match_query` now accepts an empty `address1`, which Yelp allows for service businesses with no street address.
13
+ * Fixed: a malformed `error` object in a response (e.g., a plain string, or one missing `code` or `description`) now raises `YelpAPI.YelpAPIError` instead of a `TypeError` or `KeyError`.
14
+ * Path parameters (business IDs, aliases, event IDs, job aliases, and transaction types) are now percent-encoded, so characters like `/`, `?`, and `#` can no longer change the URL that is requested.
15
+ * Clarified the `YelpAPI.YelpAPIError` docs: HTTP error statuses raise `requests.HTTPError`, while `YelpAPIError` covers error objects and non-object bodies in successful responses.
16
+ * Updated docstrings to match Yelp's current docs (plan and access requirements, `match_threshold`, the 20-ID limit for `business_ids`, and required fields for Request a Quote).
17
+ * Fixed the examples so they catch `requests.HTTPError` and keep going when an endpoint needs a paid plan or extra access.
18
+ * Strengthened the test suite: every endpoint's URL, HTTP method, and parameters are now checked, and test data is deterministic.
19
+ * Added [Ruff](https://docs.astral.sh/ruff/) linting, run in CI.
20
+
3
21
  ## 2.7.0 (2026-10-28)
4
22
  * Dropped support for end-of-life Python 3.9 and 3.10; added Python 3.15. Minimum supported version is now 3.11.
5
23
  * `YelpAPI.__enter__` now returns `Self`, so subclasses are typed correctly when used as context managers.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: yelpapi
3
- Version: 2.7.0
4
- Summary: yelpapi is a pure Python implementation of the Yelp Fusion API.
3
+ Version: 2.8.0
4
+ Summary: yelpapi is a pure Python implementation of Yelp's public REST APIs (Places, AI, Insights, and Home Services).
5
5
  Project-URL: Homepage, https://github.com/lanl/yelpapi
6
6
  Project-URL: Changelog, https://github.com/lanl/yelpapi/blob/master/CHANGES.md
7
7
  Author-email: Geoffrey Fairchild <mail@gfairchild.com>
@@ -49,6 +49,7 @@ Requires-Dist: mypy; extra == 'dev'
49
49
  Requires-Dist: pytest; extra == 'dev'
50
50
  Requires-Dist: pytest-cov; extra == 'dev'
51
51
  Requires-Dist: requests-mock; extra == 'dev'
52
+ Requires-Dist: ruff; extra == 'dev'
52
53
  Requires-Dist: types-requests; extra == 'dev'
53
54
  Description-Content-Type: text/markdown
54
55
 
@@ -60,7 +61,7 @@ Description-Content-Type: text/markdown
60
61
  This software is licensed under the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause). Please refer to the separate [LICENSE](LICENSE) file for the exact text of the license. You are obligated to give attribution if you use this code.
61
62
 
62
63
  ## ABOUT
63
- yelpapi is a pure Python implementation of the [Yelp Fusion API](https://docs.developer.yelp.com/docs/fusion-intro). It is simple, fast, and robust to any changes Yelp may make to the API in the future.
64
+ yelpapi is a pure Python implementation of Yelp's public REST APIs, including the [Yelp Places API](https://docs.developer.yelp.com/docs/places-intro), the [Yelp AI API](https://docs.developer.yelp.com/docs/yelp-ai-api), [Yelp Insights](https://docs.developer.yelp.com/docs/yelp-insights), and the [Home Services API](https://docs.developer.yelp.com/docs/home-services-api). It is simple, fast, and robust to any changes Yelp may make to its APIs in the future.
64
65
 
65
66
  ## REQUIREMENTS
66
67
  This code requires Python 3.11 or higher and [requests](https://github.com/psf/requests).
@@ -101,18 +102,28 @@ finally:
101
102
  ```
102
103
 
103
104
  ## METHODS
105
+ * [AI Chat API](https://docs.developer.yelp.com/reference/v2_ai_chat) - `ai_chat_query(...)`
104
106
  * [Autocomplete API](https://docs.developer.yelp.com/reference/v3_autocomplete) - `autocomplete_query(...)`
105
107
  * [Business API](https://docs.developer.yelp.com/reference/v3_business_info) - `business_query(...)`
106
108
  * [Business Engagement Metrics API](https://docs.developer.yelp.com/reference/v3_get_businesses_engagement) - `business_engagement_query(...)`
109
+ * [Business Food & Drinks Insights API](https://docs.developer.yelp.com/reference/v3_get_business_food_and_drinks_insights) - `business_food_and_drinks_insights_query(...)`
110
+ * [Business Insights API](https://docs.developer.yelp.com/reference/v3_businesses_insights) - `business_insights_query(...)`
107
111
  * [Business Match API](https://docs.developer.yelp.com/reference/v3_business_match) - `business_match_query(...)`
108
- * [Business Service Offerings API](https://docs.developer.yelp.com/reference/v3_business_service_offerings) - `business_service_offerings_query(...)`
112
+ * [Business Request a Phone Call API](https://docs.developer.yelp.com/reference/v3_business_rapc_enabledness) - `business_rapc_enabledness_query(...)`
113
+ * [Business Risk Signal Insights API](https://docs.developer.yelp.com/reference/v3_get_business_risk_signals_insights) - `business_risk_signals_insights_query(...)`
114
+ * [Business Service Offerings API](https://docs.developer.yelp.com/reference/v3_business_service_offerings) - `business_service_offerings_query(...)` (deprecated by Yelp in favor of "Jobs offered" on the Business API)
109
115
  * [Categories API](https://docs.developer.yelp.com/reference/v3_all_categories) - `categories_query(...)`
110
116
  * [Category API](https://docs.developer.yelp.com/reference/v3_categories) - `category_query(...)`
111
117
  * [Event Lookup API](https://docs.developer.yelp.com/reference/v3_event) - `event_lookup_query(...)`
112
118
  * [Event Search API](https://docs.developer.yelp.com/reference/v3_events_search) - `event_search_query(...)`
113
119
  * [Featured Event API](https://docs.developer.yelp.com/reference/v3_featured_event) - `featured_event_query(...)`
120
+ * [Job Survey API](https://docs.developer.yelp.com/reference/v3_job_survey) - `job_survey_query(...)`
121
+ * [Jobs API](https://docs.developer.yelp.com/reference/v3_get_jobs) - `jobs_query(...)`
114
122
  * [Phone Search API](https://docs.developer.yelp.com/reference/v3_business_phone_search) - `phone_search_query(...)`
123
+ * [Request a Quote API](https://docs.developer.yelp.com/reference/v3_raq_submit_project) - `request_a_quote_query(...)`
115
124
  * [Review Highlights API](https://docs.developer.yelp.com/reference/v3_business_review_highlights) - `review_highlights_query(...)`
116
125
  * [Reviews API](https://docs.developer.yelp.com/reference/v3_business_reviews) - `reviews_query(...)`
117
126
  * [Search API](https://docs.developer.yelp.com/reference/v3_business_search) - `search_query(...)`
118
127
  * [Transaction Search API](https://docs.developer.yelp.com/reference/v3_transaction_search) - `transaction_search_query(...)`
128
+
129
+ Yelp's [GraphQL API](https://docs.developer.yelp.com/docs/graphql-intro) is not wrapped, since it uses GraphQL queries rather than REST parameters.
@@ -6,7 +6,7 @@
6
6
  This software is licensed under the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause). Please refer to the separate [LICENSE](LICENSE) file for the exact text of the license. You are obligated to give attribution if you use this code.
7
7
 
8
8
  ## ABOUT
9
- yelpapi is a pure Python implementation of the [Yelp Fusion API](https://docs.developer.yelp.com/docs/fusion-intro). It is simple, fast, and robust to any changes Yelp may make to the API in the future.
9
+ yelpapi is a pure Python implementation of Yelp's public REST APIs, including the [Yelp Places API](https://docs.developer.yelp.com/docs/places-intro), the [Yelp AI API](https://docs.developer.yelp.com/docs/yelp-ai-api), [Yelp Insights](https://docs.developer.yelp.com/docs/yelp-insights), and the [Home Services API](https://docs.developer.yelp.com/docs/home-services-api). It is simple, fast, and robust to any changes Yelp may make to its APIs in the future.
10
10
 
11
11
  ## REQUIREMENTS
12
12
  This code requires Python 3.11 or higher and [requests](https://github.com/psf/requests).
@@ -47,18 +47,28 @@ finally:
47
47
  ```
48
48
 
49
49
  ## METHODS
50
+ * [AI Chat API](https://docs.developer.yelp.com/reference/v2_ai_chat) - `ai_chat_query(...)`
50
51
  * [Autocomplete API](https://docs.developer.yelp.com/reference/v3_autocomplete) - `autocomplete_query(...)`
51
52
  * [Business API](https://docs.developer.yelp.com/reference/v3_business_info) - `business_query(...)`
52
53
  * [Business Engagement Metrics API](https://docs.developer.yelp.com/reference/v3_get_businesses_engagement) - `business_engagement_query(...)`
54
+ * [Business Food & Drinks Insights API](https://docs.developer.yelp.com/reference/v3_get_business_food_and_drinks_insights) - `business_food_and_drinks_insights_query(...)`
55
+ * [Business Insights API](https://docs.developer.yelp.com/reference/v3_businesses_insights) - `business_insights_query(...)`
53
56
  * [Business Match API](https://docs.developer.yelp.com/reference/v3_business_match) - `business_match_query(...)`
54
- * [Business Service Offerings API](https://docs.developer.yelp.com/reference/v3_business_service_offerings) - `business_service_offerings_query(...)`
57
+ * [Business Request a Phone Call API](https://docs.developer.yelp.com/reference/v3_business_rapc_enabledness) - `business_rapc_enabledness_query(...)`
58
+ * [Business Risk Signal Insights API](https://docs.developer.yelp.com/reference/v3_get_business_risk_signals_insights) - `business_risk_signals_insights_query(...)`
59
+ * [Business Service Offerings API](https://docs.developer.yelp.com/reference/v3_business_service_offerings) - `business_service_offerings_query(...)` (deprecated by Yelp in favor of "Jobs offered" on the Business API)
55
60
  * [Categories API](https://docs.developer.yelp.com/reference/v3_all_categories) - `categories_query(...)`
56
61
  * [Category API](https://docs.developer.yelp.com/reference/v3_categories) - `category_query(...)`
57
62
  * [Event Lookup API](https://docs.developer.yelp.com/reference/v3_event) - `event_lookup_query(...)`
58
63
  * [Event Search API](https://docs.developer.yelp.com/reference/v3_events_search) - `event_search_query(...)`
59
64
  * [Featured Event API](https://docs.developer.yelp.com/reference/v3_featured_event) - `featured_event_query(...)`
65
+ * [Job Survey API](https://docs.developer.yelp.com/reference/v3_job_survey) - `job_survey_query(...)`
66
+ * [Jobs API](https://docs.developer.yelp.com/reference/v3_get_jobs) - `jobs_query(...)`
60
67
  * [Phone Search API](https://docs.developer.yelp.com/reference/v3_business_phone_search) - `phone_search_query(...)`
68
+ * [Request a Quote API](https://docs.developer.yelp.com/reference/v3_raq_submit_project) - `request_a_quote_query(...)`
61
69
  * [Review Highlights API](https://docs.developer.yelp.com/reference/v3_business_review_highlights) - `review_highlights_query(...)`
62
70
  * [Reviews API](https://docs.developer.yelp.com/reference/v3_business_reviews) - `reviews_query(...)`
63
71
  * [Search API](https://docs.developer.yelp.com/reference/v3_business_search) - `search_query(...)`
64
72
  * [Transaction Search API](https://docs.developer.yelp.com/reference/v3_transaction_search) - `transaction_search_query(...)`
73
+
74
+ Yelp's [GraphQL API](https://docs.developer.yelp.com/docs/graphql-intro) is not wrapped, since it uses GraphQL queries rather than REST parameters.
@@ -25,19 +25,47 @@
25
25
 
26
26
  # Example call:
27
27
  # ./examples.py "[API Key]"
28
+ #
29
+ # Some endpoints below require a paid plan or access that must be requested from Yelp. Those calls are made via
30
+ # run_restricted(), which prints the HTTP error and moves on instead of aborting the script.
28
31
 
29
32
  import argparse
33
+ import contextlib
34
+ from collections.abc import Callable
30
35
  from pprint import pprint
36
+ from typing import Any
37
+
38
+ import requests
31
39
 
32
40
  from yelpapi import YelpAPI
33
41
 
34
42
  SEP = '\n-------------------------------------------------------------------------\n'
35
43
 
44
+
45
+ def print_http_error(e: requests.HTTPError) -> None:
46
+ """Print an HTTP error, including Yelp's error object if the response has one."""
47
+ print(e)
48
+ if e.response is not None:
49
+ with contextlib.suppress(ValueError):
50
+ print(e.response.json().get('error'))
51
+
52
+
53
+ def run_restricted(call: Callable[[], dict[str, Any]]) -> dict[str, Any] | None:
54
+ """Run a call that may fail for API keys without the required plan or access."""
55
+ try:
56
+ response = call()
57
+ except requests.HTTPError as e:
58
+ print('(this endpoint may require a paid plan or extra access for your API key)')
59
+ print_http_error(e)
60
+ return None
61
+ pprint(response)
62
+ return response
63
+
36
64
  argparser = argparse.ArgumentParser(
37
65
  description='Example Yelp queries using yelpapi. '
38
- 'Visit https://docs.developer.yelp.com/docs/fusion-intro to get the necessary API keys.',
66
+ 'Visit https://docs.developer.yelp.com/docs/places-intro to get the necessary API keys.',
39
67
  )
40
- argparser.add_argument('api_key', type=str, help='Yelp Fusion API Key')
68
+ argparser.add_argument('api_key', type=str, help='Yelp API key')
41
69
  args = argparser.parse_args()
42
70
 
43
71
  with YelpAPI(args.api_key) as yelp_api:
@@ -74,6 +102,7 @@ with YelpAPI(args.api_key) as yelp_api:
74
102
  print(SEP)
75
103
 
76
104
  # Transaction Search API: https://docs.developer.yelp.com/reference/v3_transaction_search
105
+ # NOTE: 'delivery' is the only supported transaction type, and only US locations are supported
77
106
  print("***** businesses in Dallas supporting delivery transactions *****\nyelp_api.transaction_search_query(transaction_type='delivery', location='dallas, tx')\n")
78
107
  response = yelp_api.transaction_search_query(transaction_type='delivery', location='dallas, tx')
79
108
  pprint(response)
@@ -85,19 +114,6 @@ with YelpAPI(args.api_key) as yelp_api:
85
114
  pprint(response)
86
115
  print(SEP)
87
116
 
88
- # Reviews API: https://docs.developer.yelp.com/reference/v3_business_reviews
89
- print("***** selected reviews for Amy's on 6th St. *****\nyelp_api.reviews_query(id='amys-ice-creams-austin-3')\n")
90
- response = yelp_api.reviews_query(id='amys-ice-creams-austin-3')
91
- pprint(response)
92
- print(SEP)
93
-
94
- # Review Highlights API: https://docs.developer.yelp.com/reference/v3_business_review_highlights
95
- # NOTE: requires a Premium Plan
96
- print("***** review highlights for Amy's on 6th St. *****\nyelp_api.review_highlights_query(id='amys-ice-creams-austin-3')\n")
97
- response = yelp_api.review_highlights_query(id='amys-ice-creams-austin-3')
98
- pprint(response)
99
- print(SEP)
100
-
101
117
  # Autocomplete API: https://docs.developer.yelp.com/reference/v3_autocomplete
102
118
  # centroid: https://www.flickr.com/places/info/2427422
103
119
  print("***** autocomplete results for 'Hambur' in Iowa City *****\nyelp_api.autocomplete_query(text='Hambur', longitude=-91.5327, latitude=41.6560)\n")
@@ -106,7 +122,7 @@ with YelpAPI(args.api_key) as yelp_api:
106
122
  print(SEP)
107
123
 
108
124
  # Categories API: https://docs.developer.yelp.com/reference/v3_all_categories
109
- print("***** all Yelp categories *****\nyelp_api.categories_query()\n")
125
+ print('***** all Yelp categories *****\nyelp_api.categories_query()\n')
110
126
  response = yelp_api.categories_query()
111
127
  pprint(response)
112
128
  print(SEP)
@@ -118,16 +134,17 @@ with YelpAPI(args.api_key) as yelp_api:
118
134
  print(SEP)
119
135
 
120
136
  # Event Search API: https://docs.developer.yelp.com/reference/v3_events_search
121
- print("***** event search result *****\nyelp_api.event_search_query()\n")
122
- response = yelp_api.event_search_query()
137
+ print("***** event search result for New York City, NY *****\nyelp_api.event_search_query(location='New York City, NY')\n")
138
+ response = yelp_api.event_search_query(location='New York City, NY')
123
139
  pprint(response)
124
140
  print(SEP)
125
141
 
126
142
  # Event Lookup API: https://docs.developer.yelp.com/reference/v3_event
127
- print("***** event lookup result using previous search's first event *****\nyelp_api.event_lookup_query(id=response['events'][0]['id'])\n")
128
- response = yelp_api.event_lookup_query(id=response['events'][0]['id'])
129
- pprint(response)
130
- print(SEP)
143
+ if response.get('events'):
144
+ print("***** event lookup result using previous search's first event *****\nyelp_api.event_lookup_query(id=response['events'][0]['id'])\n")
145
+ response = yelp_api.event_lookup_query(id=response['events'][0]['id'])
146
+ pprint(response)
147
+ print(SEP)
131
148
 
132
149
  # Featured Event API: https://docs.developer.yelp.com/reference/v3_featured_event
133
150
  print("***** featured event lookup result for New York City, NY *****\nyelp_api.featured_event_query(location='New York City, NY')\n")
@@ -135,32 +152,91 @@ with YelpAPI(args.api_key) as yelp_api:
135
152
  pprint(response)
136
153
  print(SEP)
137
154
 
155
+ # AI Chat API: https://docs.developer.yelp.com/reference/v2_ai_chat
156
+ print("***** AI chat about ice cream in Austin, TX *****\nyelp_api.ai_chat_query(query='Where can I get great ice cream in Austin, TX?')\n")
157
+ run_restricted(lambda: yelp_api.ai_chat_query(query='Where can I get great ice cream in Austin, TX?'))
158
+ print(SEP)
159
+
160
+ # ----- The endpoints below require a paid plan or extra access. -----
161
+
162
+ # Reviews API: https://docs.developer.yelp.com/reference/v3_business_reviews
163
+ # NOTE: requires an Enhanced or Premium Plan
164
+ print("***** selected reviews for Amy's on 6th St. *****\nyelp_api.reviews_query(id='amys-ice-creams-austin-3')\n")
165
+ run_restricted(lambda: yelp_api.reviews_query(id='amys-ice-creams-austin-3'))
166
+ print(SEP)
167
+
168
+ # Review Highlights API: https://docs.developer.yelp.com/reference/v3_business_review_highlights
169
+ # NOTE: requires a Premium Plan
170
+ print("***** review highlights for Amy's on 6th St. *****\nyelp_api.review_highlights_query(id='amys-ice-creams-austin-3')\n")
171
+ run_restricted(lambda: yelp_api.review_highlights_query(id='amys-ice-creams-austin-3'))
172
+ print(SEP)
173
+
138
174
  # Business Engagement Metrics API: https://docs.developer.yelp.com/reference/v3_get_businesses_engagement
139
- # NOTE: requires special permissions on the Yelp Places API key
175
+ # NOTE: part of Yelp Insights; requires Yelp Insights access for your API key
140
176
  print("***** engagement metrics for Amy's on 6th St. *****\nyelp_api.business_engagement_query(business_ids='amys-ice-creams-austin-3')\n")
141
- response = yelp_api.business_engagement_query(business_ids='amys-ice-creams-austin-3')
142
- pprint(response)
177
+ run_restricted(lambda: yelp_api.business_engagement_query(business_ids='amys-ice-creams-austin-3'))
143
178
  print(SEP)
144
179
 
145
180
  # Business Service Offerings API: https://docs.developer.yelp.com/reference/v3_business_service_offerings
146
- # NOTE: requires special permissions on the Yelp Places API key
181
+ # NOTE: requires special permissions on the Yelp API key
182
+ # DEPRECATED: Yelp recommends "Jobs offered" on the Business API instead
147
183
  print("***** service offerings for Amy's on 6th St. *****\nyelp_api.business_service_offerings_query(id='amys-ice-creams-austin-3')\n")
148
- response = yelp_api.business_service_offerings_query(id='amys-ice-creams-austin-3')
149
- pprint(response)
184
+ run_restricted(lambda: yelp_api.business_service_offerings_query(id='amys-ice-creams-austin-3'))
185
+ print(SEP)
186
+
187
+ # Business Insights API: https://docs.developer.yelp.com/reference/v3_businesses_insights
188
+ # NOTE: part of Yelp Insights; requires Yelp Insights access for your API key
189
+ # date_range_start/date_range_end are months in YYYYMM format
190
+ print("***** business insights for Amy's on 6th St. *****\nyelp_api.business_insights_query(business_ids='amys-ice-creams-austin-3', date_range_start='202601', date_range_end='202603')\n")
191
+ run_restricted(lambda: yelp_api.business_insights_query(
192
+ business_ids='amys-ice-creams-austin-3',
193
+ date_range_start='202601',
194
+ date_range_end='202603',
195
+ ))
150
196
  print(SEP)
151
197
 
198
+ # Business Food & Drinks Insights API: https://docs.developer.yelp.com/reference/v3_get_business_food_and_drinks_insights
199
+ # NOTE: part of Yelp Insights; requires Yelp Insights access for your API key
200
+ print("***** food & drinks insights for Amy's on 6th St. *****\nyelp_api.business_food_and_drinks_insights_query(id='amys-ice-creams-austin-3')\n")
201
+ run_restricted(lambda: yelp_api.business_food_and_drinks_insights_query(id='amys-ice-creams-austin-3'))
202
+ print(SEP)
203
+
204
+ # Business Risk Signal Insights API: https://docs.developer.yelp.com/reference/v3_get_business_risk_signals_insights
205
+ # NOTE: part of Yelp Insights; requires Yelp Insights access for your API key
206
+ print("***** risk signal insights for Amy's on 6th St. *****\nyelp_api.business_risk_signals_insights_query(id='amys-ice-creams-austin-3')\n")
207
+ run_restricted(lambda: yelp_api.business_risk_signals_insights_query(id='amys-ice-creams-austin-3'))
208
+ print(SEP)
209
+
210
+ # Jobs API: https://docs.developer.yelp.com/reference/v3_get_jobs
211
+ # NOTE: part of the Home Services API; access is disabled by default and must be requested from Yelp
212
+ print("***** home service jobs matching a leaky faucet *****\nyelp_api.jobs_query(query='I need someone to fix a leaky faucet')\n")
213
+ jobs = run_restricted(lambda: yelp_api.jobs_query(query='I need someone to fix a leaky faucet'))
214
+ print(SEP)
215
+
216
+ # Job Survey API: https://docs.developer.yelp.com/reference/v3_job_survey
217
+ # NOTE: part of the Home Services API; access is disabled by default and must be requested from Yelp
218
+ if jobs and jobs.get('jobs'):
219
+ job_alias = jobs['jobs'][0]['job_alias']
220
+ print(f"***** survey for the best-matching job from the previous query *****\nyelp_api.job_survey_query(job_alias='{job_alias}')\n")
221
+ run_restricted(lambda: yelp_api.job_survey_query(job_alias=job_alias))
222
+ print(SEP)
223
+
224
+ # ----- Error handling -----
225
+ # HTTP error statuses (e.g., 400 for bad parameters, 404 for unknown IDs) raise requests.HTTPError.
226
+ # YelpAPI.YelpAPIError is raised when a successful HTTP response contains a Yelp error object or isn't a JSON object.
227
+
152
228
  # Example erroneous search query
153
229
  print("***** sample erroneous search query *****\nyelp_api.search_query(term='ice cream', location='austin, tx', sort_by='BAD_SORT')\n")
154
230
  try:
155
231
  # sort can only take on values "best_match", "rating", "review_count", or "distance"
156
232
  yelp_api.search_query(term='ice cream', location='austin, tx', sort_by='BAD_SORT')
157
- except YelpAPI.YelpAPIError as e:
158
- print(e)
233
+ except requests.HTTPError as e:
234
+ print_http_error(e)
159
235
  print(SEP)
160
236
 
161
237
  # Example erroneous business query
162
238
  print("***** sample erroneous business query *****\nyelp_api.business_query(id='fake-business')\n")
163
239
  try:
164
240
  yelp_api.business_query(id='fake-business')
165
- except YelpAPI.YelpAPIError as e:
166
- print(e)
241
+ except requests.HTTPError as e:
242
+ print_http_error(e)
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "yelpapi"
7
- version = "2.7.0"
8
- description = "yelpapi is a pure Python implementation of the Yelp Fusion API."
7
+ version = "2.8.0"
8
+ description = "yelpapi is a pure Python implementation of Yelp's public REST APIs (Places, AI, Insights, and Home Services)."
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
11
11
  authors = [
@@ -39,6 +39,7 @@ dev = [
39
39
  "pytest",
40
40
  "pytest-cov",
41
41
  "requests-mock",
42
+ "ruff",
42
43
  "types-requests",
43
44
  ]
44
45
 
@@ -49,6 +50,29 @@ Changelog = "https://github.com/lanl/yelpapi/blob/master/CHANGES.md"
49
50
  [tool.pytest.ini_options]
50
51
  addopts = "-vl --cov=yelpapi --import-mode=importlib"
51
52
 
53
+ [tool.ruff]
54
+ line-length = 120
55
+
56
+ [tool.ruff.lint]
57
+ select = [
58
+ "E", # pycodestyle errors
59
+ "W", # pycodestyle warnings
60
+ "F", # pyflakes
61
+ "I", # isort
62
+ "B", # flake8-bugbear
63
+ "UP", # pyupgrade
64
+ "SIM", # flake8-simplify
65
+ "Q", # flake8-quotes
66
+ "RUF", # ruff-specific rules
67
+ ]
68
+
69
+ [tool.ruff.lint.flake8-quotes]
70
+ inline-quotes = "single"
71
+
72
+ [tool.ruff.lint.per-file-ignores]
73
+ # The examples print each call verbatim on one line.
74
+ "examples/*" = ["E501"]
75
+
52
76
  [tool.mypy]
53
77
  strict = true
54
78
  python_version = "3.11"
@@ -0,0 +1,17 @@
1
+ * **Heads up for subclassers:** the private request helper `YelpAPI._query()` has been renamed to `YelpAPI._get()`, and the new POST helper is `YelpAPI._post()`.
2
+ * If your subclass *calls* `self._query(...)` to reach endpoints this library doesn't wrap, switch to `self._get(...)`. `_query()` still works for this release but emits a `FutureWarning`.
3
+ * If your subclass *overrides* `_query()` (e.g., for caching, retries, or logging), override `_get()` (and `_post()` for POST endpoints) instead. Public methods no longer call `_query()`, so an override of it is no longer used. Defining such a subclass emits a `FutureWarning`.
4
+ * `_query()` will be removed in 2.9.0.
5
+ * Added 8 new API endpoints: AI Chat (`ai_chat_query`), Business Insights (`business_insights_query`), Business Food & Drinks Insights (`business_food_and_drinks_insights_query`), Business Risk Signal Insights (`business_risk_signals_insights_query`), Business Request a Phone Call (`business_rapc_enabledness_query`), Jobs (`jobs_query`), Job Survey (`job_survey_query`), and Request a Quote (`request_a_quote_query`).
6
+ * Added support for POST endpoints, which send `**kwargs` as the JSON request body (with `None` values removed). Successful responses with no body (e.g., `202 Accepted`) return an empty dict.
7
+ * `business_service_offerings_query` now emits a `DeprecationWarning`, because Yelp has deprecated the endpoint in favor of "Jobs offered" on the Business API.
8
+ * Fixed: a latitude or longitude of `0` (the equator or prime meridian) is now accepted by `search_query`, `featured_event_query`, and `transaction_search_query` instead of raising `ValueError`.
9
+ * Fixed: `business_match_query` now accepts an empty `address1`, which Yelp allows for service businesses with no street address.
10
+ * Fixed: a malformed `error` object in a response (e.g., a plain string, or one missing `code` or `description`) now raises `YelpAPI.YelpAPIError` instead of a `TypeError` or `KeyError`.
11
+ * Path parameters (business IDs, aliases, event IDs, job aliases, and transaction types) are now percent-encoded, so characters like `/`, `?`, and `#` can no longer change the URL that is requested.
12
+ * Clarified the `YelpAPI.YelpAPIError` docs: HTTP error statuses raise `requests.HTTPError`, while `YelpAPIError` covers error objects and non-object bodies in successful responses.
13
+ * Updated docstrings to match Yelp's current docs (plan and access requirements, `match_threshold`, the 20-ID limit for `business_ids`, and required fields for Request a Quote).
14
+ * Fixed the examples so they catch `requests.HTTPError` and keep going when an endpoint needs a paid plan or extra access.
15
+ * Strengthened the test suite: every endpoint's URL, HTTP method, and parameters are now checked, and test data is deterministic.
16
+ * Added [Ruff](https://docs.astral.sh/ruff/) linting, run in CI.
17
+
@@ -11,4 +11,7 @@ def mock_request():
11
11
 
12
12
  @pytest.fixture
13
13
  def faker():
14
- return Faker()
14
+ # Seeded so that every run uses the same data.
15
+ fake = Faker()
16
+ fake.seed_instance(0)
17
+ return fake