summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Cook <JWCook@users.noreply.github.com>2021-08-07 19:32:42 -0500
committerJordan Cook <jordan.cook@pioneer.com>2021-08-07 20:29:01 -0500
commit5455bddcaeec4a274e15cfa194b382aa6793d060 (patch)
tree774ed2914cdcf090b0cfb269a35a44f5b3fb87b7
parent7b80eb209365cdd8b7cf566d4d56b1cf40d1344f (diff)
parent308cf7216db87a2f4f8ce9ad95a7dbae31bffe57 (diff)
downloadrequests-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.yml6
-rw-r--r--docs/advanced_usage.md112
-rw-r--r--docs/user_guide.md1
-rw-r--r--poetry.lock68
-rw-r--r--pyproject.toml12
-rw-r--r--requests_cache/session.py2
-rwxr-xr-xruntests.sh2
-rw-r--r--tests/compat/README.md3
-rw-r--r--tests/compat/httpbin_sample.test-dbbin0 -> 24576 bytes
-rw-r--r--tests/compat/test_requests_mock_combine_cache.py35
-rw-r--r--tests/compat/test_requests_mock_disable_cache.py24
-rw-r--r--tests/compat/test_requests_mock_load_cache.py70
-rw-r--r--tests/compat/test_responses_load_cache.py61
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
new file mode 100644
index 0000000..9d3161a
--- /dev/null
+++ b/tests/compat/httpbin_sample.test-db
Binary files differ
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)