diff options
author | Jordan Cook <JWCook@users.noreply.github.com> | 2021-08-07 19:32:42 -0500 |
---|---|---|
committer | Jordan Cook <jordan.cook@pioneer.com> | 2021-08-07 20:29:01 -0500 |
commit | 5455bddcaeec4a274e15cfa194b382aa6793d060 (patch) | |
tree | 774ed2914cdcf090b0cfb269a35a44f5b3fb87b7 | |
parent | 7b80eb209365cdd8b7cf566d4d56b1cf40d1344f (diff) | |
parent | 308cf7216db87a2f4f8ce9ad95a7dbae31bffe57 (diff) | |
download | requests-cache-5455bddcaeec4a274e15cfa194b382aa6793d060.tar.gz |
Re-enable pre-deploy tests for all supported requests versions, and pin optional dependency versions compatible with requests<2.22
-rw-r--r-- | .github/workflows/deploy.yml | 6 | ||||
-rw-r--r-- | docs/advanced_usage.md | 112 | ||||
-rw-r--r-- | docs/user_guide.md | 1 | ||||
-rw-r--r-- | poetry.lock | 68 | ||||
-rw-r--r-- | pyproject.toml | 12 | ||||
-rw-r--r-- | requests_cache/session.py | 2 | ||||
-rwxr-xr-x | runtests.sh | 2 | ||||
-rw-r--r-- | tests/compat/README.md | 3 | ||||
-rw-r--r-- | tests/compat/httpbin_sample.test-db | bin | 0 -> 24576 bytes | |||
-rw-r--r-- | tests/compat/test_requests_mock_combine_cache.py | 35 | ||||
-rw-r--r-- | tests/compat/test_requests_mock_disable_cache.py | 24 | ||||
-rw-r--r-- | tests/compat/test_requests_mock_load_cache.py | 70 | ||||
-rw-r--r-- | tests/compat/test_responses_load_cache.py | 61 |
13 files changed, 323 insertions, 73 deletions
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b5c4934..accf1da 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,8 +26,8 @@ jobs: runs-on: ubuntu-18.04 strategy: matrix: - python-version: [3.6] - requests-version: [latest] #[2.17, 2.18, 2.19, 2.20.1, 2.21, 2.22, 2.23, 2.24, 2.25, latest] + python-version: [3.7] + requests-version: [2.17, 2.18, 2.19, 2.20.1, 2.21, 2.22, 2.23, 2.24, 2.25, latest] fail-fast: false services: nginx: @@ -65,8 +65,6 @@ jobs: - name: Install dependencies if: steps.cache.outputs.cache-hit != 'true' run: | - # botocore has a urllib3 version conflict with older versions of requests - # poetry add boto3@1.15 botocore@1.18 --optional --lock poetry add requests@${{ matrix.requests-version }} --lock poetry install -v -E all diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 0109072..fd65db3 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -331,9 +331,8 @@ Or if you are using {py:func}`.install_cache`, you can use the `session_factory` The same approach can be used with other libraries that subclass {py:class}`requests.Session`. ### Requests-futures -Example with [requests-futures](https://github.com/ross/requests-futures): - -Some libraries, including `requests-futures`, support wrapping an existing session object: +Some libraries, including [requests-futures](https://github.com/ross/requests-futures), +support wrapping an existing session object: ```python >>> session = FutureSession(session=CachedSession()) ``` @@ -342,39 +341,9 @@ In this case, `FutureSession` must wrap `CachedSession` rather than the other wa `FutureSession` returns (as you might expect) futures rather than response objects. See [issue #135](https://github.com/reclosedev/requests-cache/issues/135) for more notes on this. -### Requests-mock -Example with [requests-mock](https://github.com/jamielennox/requests-mock): - -Requests-mock works a bit differently. It has multiple methods of mocking requests, and the -method most compatible with requests-cache is attaching its -[adapter](https://requests-mock.readthedocs.io/en/latest/adapter.html) to a CachedSession: -```python ->>> import requests ->>> from requests_mock import Adapter ->>> from requests_cache import CachedSession ->>> ->>> # Set up a CachedSession that will make mock requests where it would normally make real requests ->>> adapter = Adapter() ->>> adapter.register_uri( -... 'GET', -... 'mock://some_test_url', -... headers={'Content-Type': 'text/plain'}, -... text='mock response', -... status_code=200, -... ) ->>> session = CachedSession() ->>> session.mount('mock://', adapter) ->>> ->>> session.get('mock://some_test_url', text='mock_response') ->>> response = session.get('mock://some_test_url') ->>> print(response.text) -``` - ### Internet Archive - -Example with [internetarchive](https://github.com/jjjake/internetarchive): - -Usage is the same as other libraries that subclass `requests.Session`: +Usage with [internetarchive](https://github.com/jjjake/internetarchive) is the same as other libraries +that subclass `requests.Session`: :::{admonition} Example code :class: toggle ```python @@ -385,3 +354,76 @@ Usage is the same as other libraries that subclass `requests.Session`: ... """Session with features from both CachedSession and ArchiveSession""" ``` ::: + +### Requests-mock +[requests-mock](https://github.com/jamielennox/requests-mock) has multiple methods for mocking +requests, including a contextmanager, decorator, fixture, and adapter. There are a few different +options for using it with requests-cache, depending on how you want your tests to work. + +#### Disabling requests-cache +If you have an application that uses requests-cache and you just want to use requests-mock in +your tests, the easiest thing to do is to disable requests-cache. + +For example, if you are using {py:func}`.install_cache` in your application and the +requests-mock [pytest fixture](https://requests-mock.readthedocs.io/en/latest/pytest.html) in your +tests, you could wrap it in another fixture that uses {py:func}`.uninstall_cache` or {py:func}`.disabled`: +:::{admonition} Example code +:class: toggle +```{literalinclude} ../tests/compat/test_requests_mock_disable_cache.py +``` +::: + +Or if you use a `CachedSession` object, you could replace it with a regular `Session`, for example: +:::{admonition} Example code +:class: toggle +```python +import unittest +import pytest +import requests + +@pytest.fixure(scope='function', autouse=True) +def disable_requests_cache(): + """Replace CachedSession with a regular Session for all test functions""" + with unittest.mock.patch('requests_cache.CachedSession', requests.Session): + yield +``` +::: + +#### Combining requests-cache with requests-mock +If you want both caching and mocking features at the same time, you can attach requests-mock's +[adapter](https://requests-mock.readthedocs.io/en/latest/adapter.html) to a `CachedSession`: + +:::{admonition} Example code +:class: toggle +```{literalinclude} ../tests/compat/test_requests_mock_combine_cache.py +``` +::: + +#### Building a mocker using requests-cache data +Another approach is to use cached data to dynamically define mock requests + responses. +This has the advantage of only using request-mock's behavior for +[request matching](https://requests-mock.readthedocs.io/en/latest/matching.html). + +:::{admonition} Example code +:class: toggle +```{literalinclude} ../tests/compat/test_requests_mock_load_cache.py +:lines: 21-40 +``` +::: + +To turn that into a complete example: +:::{admonition} Example code +:class: toggle +```{literalinclude} ../tests/compat/test_requests_mock_load_cache.py +``` +::: + +### Responses +Usage with the [responses](https://github.com/getsentry/responses) library is similar to the +requests-mock examples above. + +:::{admonition} Example code +:class: toggle +```{literalinclude} ../tests/compat/test_responses_load_cache.py +``` +:::
\ No newline at end of file diff --git a/docs/user_guide.md b/docs/user_guide.md index 4698a9a..ac42691 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -1,3 +1,4 @@ +(user-guide)= # User Guide This section covers the main features of requests-cache. diff --git a/poetry.lock b/poetry.lock index e643b9e..f1579fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -104,35 +104,29 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.18.16" +version = "1.15.18" description = "The AWS SDK for Python" category = "main" optional = true -python-versions = ">= 3.6" +python-versions = "*" [package.dependencies] -botocore = ">=1.21.16,<1.22.0" +botocore = ">=1.18.18,<1.19.0" jmespath = ">=0.7.1,<1.0.0" -s3transfer = ">=0.5.0,<0.6.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] +s3transfer = ">=0.3.0,<0.4.0" [[package]] name = "botocore" -version = "1.21.16" +version = "1.18.18" description = "Low-level, data-driven core of boto 3." category = "main" optional = true -python-versions = ">= 3.6" +python-versions = "*" [package.dependencies] jmespath = ">=0.7.1,<1.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = ">=1.25.4,<1.27" - -[package.extras] -crt = ["awscrt (==0.11.24)"] +urllib3 = {version = ">=1.20,<1.26", markers = "python_version != \"3.4\""} [[package]] name = "bson" @@ -891,6 +885,21 @@ fixture = ["fixtures"] test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] [[package]] +name = "responses" +version = "0.10.15" +description = "A utility library for mocking out the `requests` Python library." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +requests = ">=2.0" +six = "*" + +[package.extras] +tests = ["coverage (>=3.7.1,<5.0.0)", "pytest-cov", "pytest-localserver", "flake8", "pytest (>=4.6,<5.0)", "pytest"] + +[[package]] name = "rich" version = "10.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" @@ -910,18 +919,15 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "s3transfer" -version = "0.5.0" +version = "0.3.7" description = "An Amazon S3 Transfer Manager" category = "main" optional = true -python-versions = ">= 3.6" +python-versions = "*" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" -[package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] - [[package]] name = "six" version = "1.16.0" @@ -1209,7 +1215,7 @@ six = "*" [[package]] name = "urllib3" -version = "1.26.6" +version = "1.25.11" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1257,7 +1263,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes all = ["boto3", "cattrs", "pymongo", "redis", "ujson"] bson = ["cattrs", "bson"] docs = ["furo", "linkify-it-py", "myst-parser", "Sphinx", "sphinx-autodoc-typehints", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxcontrib-apidoc"] -dynamodb = ["boto3"] +dynamodb = ["boto3", "botocore"] json = ["cattrs", "ujson"] mongodb = ["cattrs", "pymongo"] redis = ["redis"] @@ -1266,7 +1272,7 @@ yaml = ["cattrs"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "b50d2aa5beb53dc09e67119f21e1371fe359491baa38e9045d2fd3b533df9af8" +content-hash = "abd8060cb3c6ab0d629e4714117a216239facdbc6ac8d183487231035c564573" [metadata.files] alabaster = [ @@ -1303,12 +1309,12 @@ black = [ {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"}, ] boto3 = [ - {file = "boto3-1.18.16-py3-none-any.whl", hash = "sha256:23e55b7cde2b35c79c63d4d52c761fdc2141f70f02df76f68882776a33dfcb63"}, - {file = "boto3-1.18.16.tar.gz", hash = "sha256:806111acfb70715dfbe9d9f0d6089e7a9661a6d6bb422b17e035fc32e17f3f37"}, + {file = "boto3-1.15.18-py2.py3-none-any.whl", hash = "sha256:9ab957090f7893172768bb8b8d2c5cce0afd36a9d36d73a9fb14168f72d75a8b"}, + {file = "boto3-1.15.18.tar.gz", hash = "sha256:f56148e2c6b9a2d704218da42f07d72f00270bfddb13bc1bdea20d3327daa51e"}, ] botocore = [ - {file = "botocore-1.21.16-py3-none-any.whl", hash = "sha256:697b577d62a8893bce56c74ee53e54f04e69b14e42a6591e109c49b5675c19ed"}, - {file = "botocore-1.21.16.tar.gz", hash = "sha256:b0e342b8c554f34f9f1cb028fbc20aff535fefe0c86a4e2cae7201846cd6aa4a"}, + {file = "botocore-1.18.18-py2.py3-none-any.whl", hash = "sha256:de5f9fc0c7e88ee7ba831fa27475be258ae09ece99143ed623d3618a3c84ee2c"}, + {file = "botocore-1.18.18.tar.gz", hash = "sha256:e224754230e7e015836ba20037cac6321e8e2ce9b8627c14d579fcb37249decd"}, ] bson = [ {file = "bson-0.5.10.tar.gz", hash = "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"}, @@ -1851,13 +1857,17 @@ requests-mock = [ {file = "requests-mock-1.9.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"}, {file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"}, ] +responses = [ + {file = "responses-0.10.15-py2.py3-none-any.whl", hash = "sha256:af94d28cdfb48ded0ad82a5216616631543650f440334a693479b8991a6594a2"}, + {file = "responses-0.10.15.tar.gz", hash = "sha256:7bb697a5fedeb41d81e8b87f152d453d5cab42dcd1691b6a7d6097e94d33f373"}, +] rich = [ {file = "rich-10.7.0-py3-none-any.whl", hash = "sha256:517b4e0efd064dd1fe821ca93dd3095d73380ceac1f0a07173d507d9b18f1396"}, {file = "rich-10.7.0.tar.gz", hash = "sha256:13ac80676e12cf528dc4228dc682c8402f82577c2aa67191e294350fa2c3c4e9"}, ] s3transfer = [ - {file = "s3transfer-0.5.0-py3-none-any.whl", hash = "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"}, - {file = "s3transfer-0.5.0.tar.gz", hash = "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c"}, + {file = "s3transfer-0.3.7-py2.py3-none-any.whl", hash = "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246"}, + {file = "s3transfer-0.3.7.tar.gz", hash = "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -2011,8 +2021,8 @@ url-normalize = [ {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, ] urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, + {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, ] virtualenv = [ {file = "virtualenv-20.7.0-py2.py3-none-any.whl", hash = "sha256:fdfdaaf0979ac03ae7f76d5224a05b58165f3c804f8aa633f3dd6f22fbd435d5"}, diff --git a/pyproject.toml b/pyproject.toml index 2042465..48c0785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,8 @@ cattrs = {version = "^1.7", python = "^3.7", optional = true} ujson = {version = ">=4.0", optional = true} # Optional backend dependencies -boto3 = { version = "^1.17", optional = true } +boto3 = { version = "<1.16", optional = true } +botocore = { version = "<1.19", optional = true } pymongo = { version = "^3.0", optional = true } pyyaml = {version = ">=5.4", optional = true} redis = { version = "^3.0", optional = true } @@ -53,7 +54,7 @@ all = ["boto3", "cattrs", "pymongo", "redis", "ujson"] bson = ["cattrs", "bson"] # BSON comes with pymongo, and can also be used as a standalone codec json = ["cattrs", "ujson"] yaml = ["cattrs", "yaml"] -dynamodb = ["boto3"] +dynamodb = ["boto3", "botocore"] mongodb = ["cattrs", "pymongo"] redis = ["redis"] docs = ["furo", "linkify-it-py", "myst-parser", "sphinx", "sphinx-autodoc-typehints", @@ -74,6 +75,7 @@ pytest-order = "1.0" pytest-xdist = ">=2.2" radon = "^5.0" requests-mock = "^1.8" +responses = "0.10.15" rich = ">=10.0" timeout-decorator = "^0.5" types-pyyaml = ">=5.4.3" @@ -107,7 +109,11 @@ omit = [ profile = 'black' line_length = 105 skip_gitignore = true -skip = ['examples/', 'requests_cache/__init__.py'] +skip = [ + 'examples/', + 'requests_cache/__init__.py', + 'tests/compat/', +] known_first_party = ['tests'] # Things that are common enough they may as well be grouped with stdlib imports extra_standard_library = ['pytest', 'setuptools'] diff --git a/requests_cache/session.py b/requests_cache/session.py index b4d4d65..32b03c2 100644 --- a/requests_cache/session.py +++ b/requests_cache/session.py @@ -259,7 +259,7 @@ class CachedSession(CacheMixin, OriginalSession): """Class that extends :py:class:`requests.Session` with caching features. See individual :py:mod:`backend classes <requests_cache.backends>` for additional backend-specific arguments. - Also see :ref:`advanced_usage` for more details and examples on how the following arguments + Also see :ref:`user-guide` for more details and examples on how the following arguments affect cache behavior. Args: diff --git a/runtests.sh b/runtests.sh index 31e1e95..b1a47c0 100755 --- a/runtests.sh +++ b/runtests.sh @@ -4,5 +4,5 @@ COVERAGE_ARGS='--cov --cov-report=term --cov-report=html' export STRESS_TEST_MULTIPLIER=2 # Run unit tests first (and with multiprocessing) to fail quickly if there are issues -pytest tests/unit --numprocesses=auto $COVERAGE_ARGS +pytest tests/unit tests/compat --numprocesses=auto $COVERAGE_ARGS pytest tests/integration --cov-append $COVERAGE_ARGS diff --git a/tests/compat/README.md b/tests/compat/README.md new file mode 100644 index 0000000..9c76979 --- /dev/null +++ b/tests/compat/README.md @@ -0,0 +1,3 @@ +# Library compatibility tests +This folder contains tests for compatibility with other requests-based libraries, to at least ensure +that documented examples do not break. diff --git a/tests/compat/httpbin_sample.test-db b/tests/compat/httpbin_sample.test-db Binary files differnew file mode 100644 index 0000000..9d3161a --- /dev/null +++ b/tests/compat/httpbin_sample.test-db diff --git a/tests/compat/test_requests_mock_combine_cache.py b/tests/compat/test_requests_mock_combine_cache.py new file mode 100644 index 0000000..e6fba2c --- /dev/null +++ b/tests/compat/test_requests_mock_combine_cache.py @@ -0,0 +1,35 @@ +"""Example of using requests-cache with the requests-mock library""" +import pytest +from requests_mock import Adapter +from requests_cache import CachedSession + +URL = 'https://some_test_url' + + +@pytest.fixture(scope='function') +def mock_session(): + """Fixture that provides a CachedSession that will make mock requests where it would normally + make real requests""" + adapter = Adapter() + adapter.register_uri( + 'GET', + URL, + headers={'Content-Type': 'text/plain'}, + text='Mock response!', + status_code=200, + ) + + session = CachedSession(backend='memory') + session.mount('https://', adapter) + yield session + + +def test_mock_session(mock_session): + """Test that the mock_session fixture is working as expected""" + response_1 = mock_session.get(URL) + assert response_1.text == 'Mock response!' + assert getattr(response_1, 'from_cache', False) is False + + response_2 = mock_session.get(URL) + assert response_2.text == 'Mock response!' + assert response_2.from_cache is True diff --git a/tests/compat/test_requests_mock_disable_cache.py b/tests/compat/test_requests_mock_disable_cache.py new file mode 100644 index 0000000..71d0404 --- /dev/null +++ b/tests/compat/test_requests_mock_disable_cache.py @@ -0,0 +1,24 @@ +"""Example of using requests-cache with the requests-mock library""" +import pytest +import requests +import requests_cache + + +@pytest.fixture(scope='function') +def requests_cache_mock(requests_mock): + with requests_cache.disabled(): + yield requests_mock + + +def test_requests_cache_mock(requests_cache_mock): + """Within this test function, requests will be mocked and not cached""" + url = 'https://example.com' + requests_cache_mock.get(url, text='Mock response!') + + # Make sure the mocker is used + response_1 = requests.get(url) + assert response_1.text == 'Mock response!' + + # Make sure the cache is not used + response_2 = requests.get(url) + assert getattr(response_2, 'from_cache', False) is False diff --git a/tests/compat/test_requests_mock_load_cache.py b/tests/compat/test_requests_mock_load_cache.py new file mode 100644 index 0000000..dea7bb3 --- /dev/null +++ b/tests/compat/test_requests_mock_load_cache.py @@ -0,0 +1,70 @@ +"""Example of using requests-cache with the requests-mock library""" +from os.path import dirname, join +from unittest.mock import patch + +import pytest + +import requests +from requests import Session +from requests_cache import CachedSession +from requests_mock import Adapter, NoMockAddress + +TEST_DB = join(dirname(__file__), 'httpbin_sample.test-db') +TEST_URLS = [ + 'https://httpbin.org/get', + 'https://httpbin.org/html', + 'https://httpbin.org/json', +] +UNMOCKED_URL = 'https://httpbin.org/ip' + + +@pytest.fixture(scope='session') +def mock_session(): + """Fixture that provides a session with mocked URLs and responses based on cache data""" + adapter = Adapter() + cache = CachedSession(TEST_DB).cache + + for response in cache.values(): + adapter.register_uri( + response.request.method, + response.request.url, + content=response.content, + headers=response.headers, + status_code=response.status_code, + ) + print(f'Added mock response: {response}') + + session = Session() + session.mount('http://', adapter) + session.mount('https://', adapter) + yield session + + +@patch.object(requests.adapters.HTTPAdapter, 'send', side_effect=ValueError('Real request made!')) +def test_mock_session(mock_http_adapter, mock_session): + """Test that the mock_session fixture is working as expected""" + # An error will be raised if a real request is made + with pytest.raises(ValueError): + requests.get(TEST_URLS[0]) + + # All mocked URLs will return a response based on requests-cache data + for url in TEST_URLS: + response = mock_session.get(url) + assert getattr(response, 'from_cache', False) is False + + # requests-mock will raise an error for an unmocked URL, as usual + with pytest.raises(NoMockAddress): + mock_session.get(UNMOCKED_URL) + + +def save_test_data(): + """Run once to save data to reuse for tests, for demo purposes. + In practice, you could just run your application or tests with requests-cache installed. + """ + session = CachedSession(TEST_DB) + for url in TEST_URLS: + session.get(url) + + +if __name__ == '__main__': + save_test_data() diff --git a/tests/compat/test_responses_load_cache.py b/tests/compat/test_responses_load_cache.py new file mode 100644 index 0000000..d9a35b7 --- /dev/null +++ b/tests/compat/test_responses_load_cache.py @@ -0,0 +1,61 @@ +"""Example of using requests-cache with the responses library""" +from contextlib import contextmanager +from os.path import dirname, join +from unittest.mock import patch + +import pytest +import requests +from responses import RequestsMock, Response +from requests.exceptions import ConnectionError +from requests_cache import CachedSession + +TEST_DB = join(dirname(__file__), 'httpbin_sample.test-db') +TEST_URLS = [ + 'https://httpbin.org/get', + 'https://httpbin.org/html', + 'https://httpbin.org/json', +] +PASSTHRU_URL = 'https://httpbin.org/gzip' +UNMOCKED_URL = 'https://httpbin.org/ip' + + +@contextmanager +def get_responses(): + """Contextmanager that provides a RequestsMock object mocked URLs and responses + based on cache data + """ + with RequestsMock() as mocker: + cache = CachedSession(TEST_DB).cache + for response in cache.values(): + mocker.add( + Response( + response.request.method, + response.request.url, + body=response.content, + headers=response.headers, + status=response.status_code, + ) + ) + mocker.add_passthru(PASSTHRU_URL) + yield mocker + + +# responses patches HTTPAdapter.send(), so we need to patch one level lower to verify request mocking +@patch.object( + requests.adapters.HTTPAdapter, 'get_connection', side_effect=ValueError('Real request made!') +) +def test_mock_session(mock_http_adapter): + """Test that the mock_session fixture is working as expected""" + with get_responses(): + # An error will be raised if a real request is made + with pytest.raises(ValueError): + requests.get(PASSTHRU_URL) + + # All mocked URLs will return a response based on requests-cache data + for url in TEST_URLS: + response = requests.get(url) + assert getattr(response, 'from_cache', False) is False + + # responses will raise an error for an unmocked URL, as usual + with pytest.raises(ConnectionError): + requests.get(UNMOCKED_URL) |