diff --git a/serpapi/core.py b/serpapi/core.py index f24e089..be245f3 100644 --- a/serpapi/core.py +++ b/serpapi/core.py @@ -39,8 +39,7 @@ def search(self, params: dict = None, **kwargs): **Learn more**: https://serpapi.com/search-api """ - if params is None: - params = {} + params = dict(params) if params is not None else {} # These are arguments that should be passed to the underlying requests.request call. request_kwargs = {} @@ -63,8 +62,7 @@ def search_archive(self, params: dict = None, **kwargs): **Learn more**: https://serpapi.com/search-archive-api """ - if params is None: - params = {} + params = dict(params) if params is not None else {} # These are arguments that should be passed to the underlying requests.request call. request_kwargs = {} @@ -141,8 +139,7 @@ def locations(self, params: dict = None, **kwargs): **Learn more**: https://serpapi.com/locations-api """ - if params is None: - params = {} + params = dict(params) if params is not None else {} # These are arguments that should be passed to the underlying requests.request call. request_kwargs = {} @@ -171,8 +168,7 @@ def account(self, params: dict = None, **kwargs): **Learn more**: https://serpapi.com/account-api """ - if params is None: - params = {} + params = dict(params) if params is not None else {} # These are arguments that should be passed to the underlying requests.request call. request_kwargs = {} diff --git a/serpapi/http.py b/serpapi/http.py index 4ea1a33..c6afd81 100644 --- a/serpapi/http.py +++ b/serpapi/http.py @@ -22,6 +22,9 @@ def __init__(self, *, api_key=None, timeout=None): self.session = requests.Session() def request(self, method, path, params, *, assert_200=True, **kwargs): + # Keep API key injection local to this request. + params = dict(params) + # Inject the API Key into the params. request_data = kwargs.get("data") api_key_in_data = isinstance(request_data, dict) and "api_key" in request_data diff --git a/tests/test_parameter_reuse.py b/tests/test_parameter_reuse.py new file mode 100644 index 0000000..8842b54 --- /dev/null +++ b/tests/test_parameter_reuse.py @@ -0,0 +1,79 @@ +import copy +from urllib.parse import parse_qs, urlsplit + +import pytest +import requests + +from serpapi import Client + + +PARAMS = { + "search": {"q": "coffee"}, + "search_archive": {"search_id": "example-search"}, + "locations": {"q": "Austin"}, + "account": {}, +} + + +def capture_requests(monkeypatch, client): + sent = [] + + def send(request, **kwargs): + sent.append(request) + response = requests.Response() + response.status_code = 200 + response.headers["Content-Type"] = "application/json" + response._content = b'{}' + response.request = request + return response + + monkeypatch.setattr(client.session, "send", send) + return sent + + +@pytest.mark.parametrize("method", list(PARAMS)) +@pytest.mark.parametrize("explicit_key", [False, True]) +def test_reused_params_keep_each_clients_key(monkeypatch, method, explicit_key): + first = Client(api_key="client-one") + second = Client(api_key="client-two") + first_sent = capture_requests(monkeypatch, first) + second_sent = capture_requests(monkeypatch, second) + params = dict(PARAMS[method]) + if explicit_key: + params["api_key"] = "caller-key" + original = copy.deepcopy(params) + getattr(first, method)(params) + getattr(second, method)(params) + expected = ["caller-key", "caller-key"] if explicit_key else ["client-one", "client-two"] + actual = [parse_qs(urlsplit(sent[0].url).query)["api_key"][0] for sent in (first_sent, second_sent)] + assert actual == expected + assert params == original + + +@pytest.mark.parametrize("method,override", [ + ("search", {"q": "tea"}), + ("search_archive", {"output": "json"}), + ("locations", {"limit": 2}), + ("account", {"api_key": "caller-key"}), +]) +def test_keyword_overrides_do_not_mutate_params(monkeypatch, method, override): + client = Client(api_key="client-key") + sent = capture_requests(monkeypatch, client) + params = dict(PARAMS[method]) + original = copy.deepcopy(params) + getattr(client, method)(params, **override) + query = parse_qs(urlsplit(sent[0].url).query) + for key, value in override.items(): + assert query[key] == [str(value)] + assert params == original + + +@pytest.mark.parametrize("explicit_key", [False, True]) +def test_direct_request_does_not_mutate_params(monkeypatch, explicit_key): + client = Client(api_key="client-key") + sent = capture_requests(monkeypatch, client) + params = {"api_key": "caller-key"} if explicit_key else {} + original = dict(params) + client.request("GET", "/search", params) + assert parse_qs(urlsplit(sent[0].url).query)["api_key"] == ["caller-key" if explicit_key else "client-key"] + assert params == original