A utility for mocking out the Python Requests library.

Overview

Responses

https://travis-ci.org/getsentry/responses.svg?branch=master

A utility library for mocking out the requests Python library.

Note

Responses requires Python 2.7 or newer, and requests >= 2.0

Installing

pip install responses

Basics

The core of responses comes from registering mock responses:

import responses
import requests

@responses.activate
def test_simple():
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
                  json={'error': 'not found'}, status=404)

    resp = requests.get('http://twitter.com/api/1/foobar')

    assert resp.json() == {"error": "not found"}

    assert len(responses.calls) == 1
    assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
    assert responses.calls[0].response.text == '{"error": "not found"}'

If you attempt to fetch a url which doesn't hit a match, responses will raise a ConnectionError:

import responses
import requests

from requests.exceptions import ConnectionError

@responses.activate
def test_simple():
    with pytest.raises(ConnectionError):
        requests.get('http://twitter.com/api/1/foobar')

Lastly, you can pass an Exception as the body to trigger an error on the request:

import responses
import requests

@responses.activate
def test_simple():
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
                  body=Exception('...'))
    with pytest.raises(Exception):
        requests.get('http://twitter.com/api/1/foobar')

Response Parameters

Responses are automatically registered via params on add, but can also be passed directly:

import responses

responses.add(
    responses.Response(
        method='GET',
        url='http://example.com',
    )
)

The following attributes can be passed to a Response mock:

method (str)
The HTTP method (GET, POST, etc).
url (str or compiled regular expression)
The full resource URL.
match_querystring (bool)
Include the query string when matching requests. Enabled by default if the response URL contains a query string, disabled if it doesn't or the URL is a regular expression.
body (str or BufferedReader)
The response body.
json
A Python object representing the JSON response body. Automatically configures the appropriate Content-Type.
status (int)
The HTTP status code.
content_type (content_type)
Defaults to text/plain.
headers (dict)
Response headers.
stream (bool)
Disabled by default. Indicates the response should use the streaming API.
match (list)
A list of callbacks to match requests based on request body contents.

Matching Request Parameters

When adding responses for endpoints that are sent request data you can add matchers to ensure your code is sending the right parameters and provide different responses based on the request body contents. Responses provides matchers for JSON and URLencoded request bodies and you can supply your own for other formats.

import responses
import requests

@responses.activate
def test_calc_api():
    responses.add(
        responses.POST,
        url='http://calc.com/sum',
        body="4",
        match=[
            responses.urlencoded_params_matcher({"left": "1", "right": "3"})
        ]
    )
    requests.post("http://calc.com/sum", data={"left": 1, "right": 3})

Matching JSON encoded data can be done with responses.json_params_matcher(). If your application uses other encodings you can build your own matcher that returns True or False if the request parameters match. Your matcher can expect a request_body parameter to be provided by responses.

Dynamic Responses

You can utilize callbacks to provide dynamic responses. The callback must return a tuple of (status, headers, body).

import json

import responses
import requests

@responses.activate
def test_calc_api():

    def request_callback(request):
        payload = json.loads(request.body)
        resp_body = {'value': sum(payload['numbers'])}
        headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
        return (200, headers, json.dumps(resp_body))

    responses.add_callback(
        responses.POST, 'http://calc.com/sum',
        callback=request_callback,
        content_type='application/json',
    )

    resp = requests.post(
        'http://calc.com/sum',
        json.dumps({'numbers': [1, 2, 3]}),
        headers={'content-type': 'application/json'},
    )

    assert resp.json() == {'value': 6}

    assert len(responses.calls) == 1
    assert responses.calls[0].request.url == 'http://calc.com/sum'
    assert responses.calls[0].response.text == '{"value": 6}'
    assert (
        responses.calls[0].response.headers['request-id'] ==
        '728d329e-0e86-11e4-a748-0c84dc037c13'
    )

You can also pass a compiled regex to add_callback to match multiple urls:

import re, json

from functools import reduce

import responses
import requests

operators = {
  'sum': lambda x, y: x+y,
  'prod': lambda x, y: x*y,
  'pow': lambda x, y: x**y
}

@responses.activate
def test_regex_url():

    def request_callback(request):
        payload = json.loads(request.body)
        operator_name = request.path_url[1:]

        operator = operators[operator_name]

        resp_body = {'value': reduce(operator, payload['numbers'])}
        headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
        return (200, headers, json.dumps(resp_body))

    responses.add_callback(
        responses.POST,
        re.compile('http://calc.com/(sum|prod|pow|unsupported)'),
        callback=request_callback,
        content_type='application/json',
    )

    resp = requests.post(
        'http://calc.com/prod',
        json.dumps({'numbers': [2, 3, 4]}),
        headers={'content-type': 'application/json'},
    )
    assert resp.json() == {'value': 24}

test_regex_url()

If you want to pass extra keyword arguments to the callback function, for example when reusing a callback function to give a slightly different result, you can use functools.partial:

from functools import partial

...

    def request_callback(request, id=None):
        payload = json.loads(request.body)
        resp_body = {'value': sum(payload['numbers'])}
        headers = {'request-id': id}
        return (200, headers, json.dumps(resp_body))

    responses.add_callback(
        responses.POST, 'http://calc.com/sum',
        callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'),
        content_type='application/json',
    )

You can see params passed in the original request in responses.calls[].request.params:

import responses
import requests

@responses.activate
def test_request_params():
    responses.add(
        method=responses.GET,
        url="http://example.com?hello=world",
        body="test",
        match_querystring=False,
    )

    resp = requests.get('http://example.com', params={"hello": "world"})
    assert responses.calls[0].request.params == {"hello": "world"}

Responses as a context manager

import responses
import requests

def test_my_api():
    with responses.RequestsMock() as rsps:
        rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
                 body='{}', status=200,
                 content_type='application/json')
        resp = requests.get('http://twitter.com/api/1/foobar')

        assert resp.status_code == 200

    # outside the context manager requests will hit the remote server
    resp = requests.get('http://twitter.com/api/1/foobar')
    resp.status_code == 404

Responses as a pytest fixture

@pytest.fixture
def mocked_responses():
    with responses.RequestsMock() as rsps:
        yield rsps

def test_api(mocked_responses):
    mocked_responses.add(
        responses.GET, 'http://twitter.com/api/1/foobar',
        body='{}', status=200,
        content_type='application/json')
    resp = requests.get('http://twitter.com/api/1/foobar')
    assert resp.status_code == 200

Assertions on declared responses

When used as a context manager, Responses will, by default, raise an assertion error if a url was registered but not accessed. This can be disabled by passing the assert_all_requests_are_fired value:

import responses
import requests

def test_my_api():
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
        rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
                 body='{}', status=200,
                 content_type='application/json')

assert_call_count

Assert that the request was called exactly n times.

import responses
import requests

@responses.activate
def test_assert_call_count():
    responses.add(responses.GET, "http://example.com")

    requests.get("http://example.com")
    assert responses.assert_call_count("http://example.com", 1) is True

    requests.get("http://example.com")
    with pytest.raises(AssertionError) as excinfo:
        responses.assert_call_count("http://example.com", 1)
    assert "Expected URL 'http://example.com' to be called 1 times. Called 2 times." in str(excinfo.value)

Multiple Responses

You can also add multiple responses for the same url:

import responses
import requests

@responses.activate
def test_my_api():
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
    responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
                  body='{}', status=200,
                  content_type='application/json')

    resp = requests.get('http://twitter.com/api/1/foobar')
    assert resp.status_code == 500
    resp = requests.get('http://twitter.com/api/1/foobar')
    assert resp.status_code == 200

Using a callback to modify the response

If you use customized processing in requests via subclassing/mixins, or if you have library tools that interact with requests at a low level, you may need to add extended processing to the mocked Response object to fully simulate the environment for your tests. A response_callback can be used, which will be wrapped by the library before being returned to the caller. The callback accepts a response as it's single argument, and is expected to return a single response object.

import responses
import requests

def response_callback(resp):
    resp.callback_processed = True
    return resp

with responses.RequestsMock(response_callback=response_callback) as m:
    m.add(responses.GET, 'http://example.com', body=b'test')
    resp = requests.get('http://example.com')
    assert resp.text == "test"
    assert hasattr(resp, 'callback_processed')
    assert resp.callback_processed is True

Passing through real requests

In some cases you may wish to allow for certain requests to pass through responses and hit a real server. This can be done with the add_passthru methods:

import responses

@responses.activate
def test_my_api():
    responses.add_passthru('https://percy.io')

This will allow any requests matching that prefix, that is otherwise not registered as a mock response, to passthru using the standard behavior.

Regex can be used like:

responses.add_passthru(re.compile('https://percy.io/\\w+'))

Viewing/Modifying registered responses

Registered responses are available as a private attribute of the RequestMock instance. It is sometimes useful for debugging purposes to view the stack of registered responses which can be accessed via responses.mock._matches.

The replace function allows a previously registered response to be changed. The method signature is identical to add. response s are identified using method and url. Only the first matched response is replaced.

import responses
import requests

@responses.activate
def test_replace():

    responses.add(responses.GET, 'http://example.org', json={'data': 1})
    responses.replace(responses.GET, 'http://example.org', json={'data': 2})

    resp = requests.get('http://example.org')

    assert resp.json() == {'data': 2}

The upsert function allows a previously registered response to be changed like replace. If the response is registered, the upsert function will registered it like add.

remove takes a method and url argument and will remove all matched responses from the registered list.

Finally, reset will reset all registered responses.

Contributing

Responses uses several linting and autoformatting utilities, so it's important that when submitting patches you use the appropriate toolchain:

Clone the repository:

git clone https://github.com/getsentry/responses.git

Create an environment (e.g. with virtualenv):

virtualenv .env && source .env/bin/activate

Configure development requirements:

make develop

Responses uses Pytest for testing. You can run all tests by:

pytest

And run a single test by:

pytest -k '<test_function_name>'
Comments
  • Test Max Retries

    Test Max Retries

    I made an HTTP adapter:

    session = requests.Session()
    session.verify = False
    
    # Try max of 3 times before erroring out
    retry_adapter = requests.adapters.HTTPAdapter(max_retries=3)
    session.mount('https://', retry_adapter)
    

    I wanted to make sure it will automatically retry, but it seems it's not getting triggered right:

    exception = ConnectionError('Bad connection')
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsp:
       rsp.add(responses.POST, url, body=exception)
       rsp.add(responses.POST, url, body=exception)
       rsp.add(responses.POST, url, body='{}')
       session.post(url=url, json={})
    

    It just keeps raising the exception.

    enhancement Status: In Progress 
    opened by allenwyma 26
  • Cookie handling regression from 0.14.0 to 0.15.0

    Cookie handling regression from 0.14.0 to 0.15.0

    Environment

    Using the responses library under Python 3.9.7

    Steps to Reproduce

    Run this test against responses 0.14.0 and against responses 0.15.0

    #!/usr/bin/env python3
    import sys
    import requests
    import responses
    
    
    
    def main():
        with responses.RequestsMock() as fakes:
            fakes.add(responses.GET, 'https://example.com', status=200,
                adding_headers=[
                      ('Set-Cookie', 'mycookie=myvalue; path=/; secure'),
                ],
            body="Returned body",
            )
    
            session = requests.session()
            response = session.get('https://example.com')
    
            tests = {}
            tests['response-cookie'] = 'mycookie' in response.cookies
            tests['session-cookie'] = 'mycookie' in session.cookies
    
            failures = 0
            for testname, result in tests.items():
                if not result:
                    failures += 1
                sys.stdout.write(f"Test {testname} result {result}\n")
            if failures:
                sys.stdout.write(f"{failures} of {len(tests)} failed.\n")
            else:
                sys.stdout.write(f"All {len(tests)} tests passed.\n")
    
            return 1 if failures else 0
    
    if __name__ == '__main__':
        sys.exit(main())
    

    Expected Result

    Expected 'mycookie' to appear in both the response.cookies and the session.cookies

    Actual Result

    'mycookie' appears in both the response.cookies and the session.cookies for 0.14.0 'mycookie' appears in the response.cookies but not in the session.cookies for 0.15.0

    bug Status: Backlog 
    opened by retracile 25
  • responses 0.21.0 broke mypy check

    responses 0.21.0 broke mypy check

    Describe the bug

    We use response in our tests, and from responses 0.21.0 we've got this error:

    $mypy tests/test_download.py 
    venv/lib/python3.10/site-packages/mypy/typeshed/stdlib/unittest/mock.pyi: error: Source file found twice under different module names: "unittest.mock" and "mypy.typeshed.stdlib.unittest.mock"
    Found 1 error in 1 file (errors prevented further checking)
    

    Returning mypy version did not resolve problem, but returning responses to 0.20.0 does.

    I try to find problem in your changes, and it could be probably this line in init.py:

    from mypy.typeshed.stdlib.unittest.mock import _patcher as _mock_patcher
    

    Additional context

    No response

    Version of responses

    0.21.0

    Steps to Reproduce

    # content of crash.py
    
    import responses
    import unittest.mock
    

    and than run

    mypy crash.py
    

    Expected Result

    Success: no issues found in 1 source file

    Actual Result

    venv/lib/python3.10/site-packages/mypy/typeshed/stdlib/unittest/mock.pyi: error: Source file found twice under different module names: "unittest.mock" and "mypy.typeshed.stdlib.unittest.mock" Found 1 error in 1 file (errors prevented further checking)

    bug Status: In Progress 
    opened by ondratu 19
  • TypeError: cannot unpack non-iterable CallbackResponse object

    TypeError: cannot unpack non-iterable CallbackResponse object

    After upgrading from responses 0.12.0 to 0.12.1 our unit tests fail with the following error:

    ...
    
    ../../../../venv/lib/python3.8/site-packages/requests/sessions.py:590: in post
        return self.request('POST', url, data=data, json=json, **kwargs)
    ../../../../venv/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py:515: in request
        return super(OAuth2Session, self).request(
    ../../../../venv/lib/python3.8/site-packages/aws_xray_sdk/ext/requests/patch.py:27: in _xray_traced_requests
        return xray_recorder.record_subsegment(
    ../../../../venv/lib/python3.8/site-packages/aws_xray_sdk/core/recorder.py:424: in record_subsegment
        return_value = wrapped(*args, **kwargs)
    ../../../../venv/lib/python3.8/site-packages/requests/sessions.py:542: in request
        resp = self.send(prep, **send_kwargs)
    ../../../../venv/lib/python3.8/site-packages/requests/sessions.py:655: in send
        r = adapter.send(request, **kwargs)
    ../../../../venv/lib/python3.8/site-packages/responses.py:733: in unbound_on_send
        return self._on_request(adapter, request, *a, **kwargs)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <responses.RequestsMock object at 0x104e06880>
    adapter = <requests.adapters.HTTPAdapter object at 0x10a7a8a00>
    request = <PreparedRequest [POST]>
    kwargs = {'cert': None, 'proxies': OrderedDict(), 'stream': False, 'timeout': 10, ...}
    
        def _on_request(self, adapter, request, **kwargs):
    >       match, match_failed_reasons = self._find_match(request)
    E       TypeError: cannot unpack non-iterable CallbackResponse object
    
    ../../../../venv/lib/python3.8/site-packages/responses.py:680: TypeError
    

    This seems to have been caused by the refactoring of the Response._find_match() method.

    opened by philhoff-edeka 19
  • Allow applying @responses.activate to a class

    Allow applying @responses.activate to a class

    Right now I have to apply the decorator to every method I want to mock responses in; I'd prefer to apply the decorator only to the TestCase declaration as my TestCase does http in every test method. This would keep requests consistent with the mock.patch behaviour.

    E.g.:

    @responses.activate
    class CommandHandlerTestCase(unittest.TestCase):
    
        def test_function_1(self):
            responses.add(...)
    
        def test_function_2(self):
            responses.add(...)
    
        def test_function_3(self):
            responses.add(...)
    
    Status: In Progress 
    opened by piotrkilczuk 18
  • Responses 0.19.0 seems to break some s3 mocks

    Responses 0.19.0 seems to break some s3 mocks

    The 0.19.0 Responses seems to break some s3 mocks.

    We noticed a lot of failing test cases in our CI after today's 0.19.0 upgrade. All of them are S3-mock related: https://github.com/apache/airflow/runs/5447285682?check_suite_focus=true#step:8:18784

    Environment

    Apache Airflow development environment.

    Which SDK and version?

    • responses 0.19.0

    Steps to Reproduce

    1. This is the fastest way to bring the development environment of Airflow to demonstrate the error:
    docker run --env "AIRFLOW__CORE__EXECUTOR=SequentialExecutor" \
       --env "AIRFLOW__CORE__SQL_ALCHEMY_CONN=sqlite:////root/airflow/airflow.db" \
       -it \
       ghcr.io/apache/airflow/main/ci/python3.7:8bb092fb6bbafbdca5a08d5f30329044fdd9794c
    
    1. Once the above command succeds, you should see this:
    Airflow home: /root/airflow                                                                                                                                                                                                                                                                                      
    Airflow sources: /opt/airflow                                                                                                                                                                                                                                                                                    
    Airflow core SQL connection: sqlite:////root/airflow/airflow.db                                                                                         
                                                                                                                                                                                                                                                                                                                     
                                                                                                                                                            
    Using already installed airflow version                                                                                                                                                                                                                                                                          
                                                                                                                                                            
                                                                                                                                                            
    No need for www assets recompilation.                                                                                                                   
                                                                                                                                                                                                                                                                                                                     
    ===============================================================================================                                                         
                 Checking integrations and backends                                                                                                         
    ===============================================================================================                                                         
    -----------------------------------------------------------------------------------------------                                                                                                                                                                                                                  
                                                                                                                                                            
    Disabled integrations: kerberos mongo redis cassandra openldap trino pinot rabbitmq                                                                     
                                                                                                                                                            
    Enable them via --integration <INTEGRATION_NAME> flags (you can use 'all' for all)                                                                                                                                                                                                                               
                                                                                                                                                            
    Your dags for webserver and scheduler are read from /root/airflow/dags directory                                                                        
                                                                                                                                                                                                                                                                                                                     
    You can add /files/airflow-breeze-config directory and place variables.env                                                                                                                                                                                                                                       
    In it to make breeze source the variables automatically for you                                                                                                                                                                                                                                                  
                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                     
    You can add /files/airflow-breeze-config directory and place .tmux.conf                                                                                                                                                                                                                                          
    in it to make breeze use your local .tmux.conf for tmux                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                            
    You can add /files/airflow-breeze-config directory and place init.sh                                                                                                                                                                                                                                             
    In it to make breeze source an initialization script automatically for you                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                     
    root@6a1ac9c2648f:/opt/airflow# 
    
    1. Run example test (in the container):
    pytest tests/providers/amazon/aws/hooks/test_s3.py::TestAwsS3Hook::test_delete_bucket_if_not_bucket_exist
    
    1. It fails with error inside moto library (but apparently related to responses library:
    /usr/local/lib/python3.7/site-packages/moto/core/models.py:118: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    /usr/local/lib/python3.7/site-packages/moto/core/models.py:96: in start
        self.enable_patching(reset)
    /usr/local/lib/python3.7/site-packages/moto/core/models.py:362: in enable_patching
        callback=convert_flask_to_responses_response(value),
    /usr/local/lib/python3.7/site-packages/responses/__init__.py:603: in add
        self._registry.add(method)
    /usr/local/lib/python3.7/site-packages/responses/registries.py:53: in add
    ....
    
    1. Downgrade responses to 0.18.0
    root@6a1ac9c2648f:/opt/airflow# pip install responses==0.18.0                                                                                                                                                                                                                                                    
    install responses==0.18.0                                                                                                                                                                                                                                                    
    Collecting responses==0.18.0                                                                                                                                                                                                                                                                                     
      Downloading responses-0.18.0-py3-none-any.whl (38 kB)                                                                                                 
    Requirement already satisfied: urllib3>=1.25.10 in /usr/local/lib/python3.7/site-packages (from responses==0.18.0) (1.26.8)                             
    Requirement already satisfied: requests<3.0,>=2.0 in /usr/local/lib/python3.7/site-packages (from responses==0.18.0) (2.27.1)                                                                                                                                                                                    
    Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.7/site-packages (from requests<3.0,>=2.0->responses==0.18.0) (2.0.12)                                                                                                                                                         
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/site-packages (from requests<3.0,>=2.0->responses==0.18.0) (2020.12.5)    
    Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.7/site-packages (from requests<3.0,>=2.0->responses==0.18.0) (3.3)                                                                                                                                                                         
    Installing collected packages: responses                                                                                                                                                                                                                                                                         
      Attempting uninstall: responses                                                                                                                                                                                                                                                                                
        Found existing installation: responses 0.19.0                                                                                                                                                                                                                                                                
        Uninstalling responses-0.19.0:                                                                                                                      
          Successfully uninstalled responses-0.19.0                                                                                                                                                                                                                                                                  
    Successfully installed responses-0.18.0                                                                                                                       
    
    1. Rerun the example test. This time it will succeed:
    root@6a1ac9c2648f:/opt/airflow# pytest tests/providers/amazon/aws/hooks/test_s3.py::TestAwsS3Hook::test_delete_bucket_if_not_bucket_exist                                                                                                                                                                        
    ============================================================================================================================================== test session starts ==============================================================================================================================================
    platform linux -- Python 3.7.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /usr/local/bin/python                                                         
    cachedir: .pytest_cache                                                                                                                                                                                                                                                                                          
    rootdir: /opt/airflow, configfile: pytest.ini                                                                                                                                                                                                                                                                    
    plugins: anyio-3.5.0, instafail-0.4.2, timeouts-1.2.1, requests-mock-1.9.3, httpx-0.20.0, forked-1.4.0, xdist-2.5.0, asyncio-0.18.2, flaky-3.7.0, rerunfailures-9.1.1, cov-3.0.0                                                                                                                                 
    asyncio: mode=strict                                                                                                                                                                                                                                                                                             
    setup timeout: 0.0s, execution timeout: 0.0s, teardown timeout: 0.0s                                                                                                                                                                                                                                             
    collected 1 item                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                     
    tests/providers/amazon/aws/hooks/test_s3.py::TestAwsS3Hook::test_delete_bucket_if_not_bucket_exist PASSED                                                                                                                                                                                                 [100%] 
                                                                                                                                                                                                                                                                                                                     
    =============================================================================================================================================== warnings summary ================================================================================================================================================
    airflow/configuration.py:376                                                                                                                            
      /opt/airflow/airflow/configuration.py:376: FutureWarning: The 'log_filename_template' setting in [logging] has the old default value of '{{ ti.dag_id }}/{{ ti.task_id }}/{{ ts }}/{{ try_number }}.log'. This value has been changed to 'dag_id={{ ti.dag_id }}/run_id={{ ti.run_id }}/task_id={{ ti.task_id }
    }/{%% if ti.map_index >= 0 %%}map_index={{ ti.map_index }}/{%% endif %%}attempt={{ try_number }}.log' in the running config, but please update your config before Apache Airflow 3.0.                                                                                                                            
        FutureWarning,                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                     
    -- Docs: https://docs.pytest.org/en/stable/warnings.html                                                                                                                                                                                                                                                         
    ========================================================================================================================================= 1 passed, 1 warning in 0.60s ==========================================================================================================================================
    root@6a1ac9c2648f:/opt/airflow# 
    

    Expected Result

    I expect that the 0.19.0 release will not break moto's s3 mocking :).

    Actual Result

    It breaks it

    bug Status: In Progress 
    opened by potiuk 16
  • Update inspection to be compatible with newer Python versions

    Update inspection to be compatible with newer Python versions

    getargspec and formatargspec are deprecated since Python 3.5 and produce deprecation warnings. Since Python 3.3, a new inspection API has been available. We use it starting from Python 3.4, so we can make use of the existing six.PY34 constant.

    Fixes #134.

    I welcome any hints to make this code cleaner, I don't have a lot of experience with inspection.

    opened by Timvde 15
  • Feature Passthrough Flag

    Feature Passthrough Flag

    This PR closes #399 by adding a passthrough flag to BaseResponse, which can be used to let requests pass through instead of returning the defined mock values.

    It may be discussed, in how far it also closes other issues mentioned in #399.

    Status: In Progress 
    opened by carloshorn 14
  • Use tomllib/tomli/tomli-w instead of unmaintained toml [WIP]

    Use tomllib/tomli/tomli-w instead of unmaintained toml [WIP]

    Replace the toml dependency that is unmaintained (last release in 2020) and does not implement TOML 1.0 with more modern libraries. Use the built-in tomllib module to load .toml files on Python 3.11, and its drop-in replacement tomli package on older versions of Python. For writing .toml files, use the tomli-w package.

    enhancement 
    opened by mgorny 12
  • mbeliaev/registry

    mbeliaev/registry

    I went through #411 and, sincerely, I had absolutely the same need and was thinking how would be better to implement it. thanks to @carloshorn, who inspired with the idea of Registry

    I propose fully backwards compatible implementation. Short description:

    1. User is provided with registries.BaseRegistry as abstract class that must be implemented
    2. in custom class use can define any logic of finding match (user just redefines find function that must return found_match, match_failed_reasons), eg examples mentioned in #411
    • strict: provide every response only once.
    • very strict: only provide the next response.
    • insertion: always provide responses in insertion order.
    • rotating: move found matches to the end, i.e. if they are already in order it performs a rotation (might play well with the replace method if the other responses stay the same.)
    • fall-back: the current selection order
    1. then user can replace registry for default mock by running responses.set_registry(CustomRegistry) or just set as argument in RequestsMock

    idea is the same as with matchers. Once we shift to registries, then we can populate them with some predefined, eg like in #411 or some from point 2 above. Then, users will have easy access to new types of response registries out of the box with single line of code

    Note: once we agreed to proceed with implementation, then I will polish this PR: docs, test, typing, changelog

    Status: In Progress 
    opened by beliaev-maksim 12
  • Add support for removing and replacing existing mocked URLs

    Add support for removing and replacing existing mocked URLs

    This PR adds support for responses.replace(). The problem it is solving is the following: we usually have a base fixture of mocked API calls which we use like below in tests.

    @pytest.mark.usefixtures('mock_api_calls')
    def some_test():
       pass
    

    The fixture itself is defined as:

    @pytest.fixture()
    def mock_api_calls():
        responses.add(responses.GET, 'http://someurl.com/one')
        responses.add(responses.GET, 'http://someurl.com/two')
        responses.add(responses.GET, 'http://someurl.com/three')
        responses.add(responses.GET, 'http://someurl.com/four')
    

    However, in the specific test we then want to change one of the API calls to return a different response. This wasn't possible previously, as responses.add() always appends to _urls. With replace(), we can now do:

    @pytest.mark.usefixtures('mock_api_calls')
    def some_test():
      responses.replace(responses.GET, 'http://someurl.com/two', body='different')
    

    Implementation-wise I broke out the creation of the entry into _build_entry() to be able to reuse it from replace(). I also reused _find_match() by adding an extra keyword for exact_match (as I figured we don't want to replace based on regex).

    Thoughts?


    This change is Reviewable

    opened by joscarsson 12
  • YAML vs TOML recorder format

    YAML vs TOML recorder format

    considering expansion of recorder to include matchers (I am currently working on it) I would like to reopen the discussion about recorded format

    initially in #545 implementation was done via yaml. However, following thread in https://github.com/getsentry/responses/pull/545#discussion_r859059586 we decided to go for TOML.

    I would like to understand where we will face these issues with ambiguity in YAML. The reason is following, TOML becomes completely unreadable for nested structures, compare YAML output below to following TOML.

    @markstory my proposal, make dumper exposed in user interface, allowing to switch between JSON/YAML/TOML/etc (considering that our data is serializable, and we use only built-in types: Dict, List, Str, Bool, Int, so, we should be serializable) and we switch default output to YAML

    responses:
      - response:
          method: GET
          url: 'http://localhost:37677/404'
          body: 404 Not Found
          status: 404
          content_type: text/plain
          auto_calculate_content_length: false
          matchers:
            - query_param_matcher:
                matcher_import_path: responses.matchers
                args:
                  strict_match: true
                  params: {}
      - response:
          method: GET
          url: 'http://localhost:37677/status/wrong'
          body: Invalid status code
          status: 400
          content_type: text/plain
          auto_calculate_content_length: false
          matchers:
            - query_param_matcher:
                matcher_import_path: responses.matchers
                args:
                  strict_match: true
                  params: {}
      - response:
          method: GET
          url: 'http://localhost:37677/500?query=smth'
          body: 500 Internal Server Error
          status: 500
          content_type: text/plain
          auto_calculate_content_length: false
          matchers:
            - query_param_matcher:
                matcher_import_path: responses.matchers
                args:
                  strict_match: true
                  params:
                    query: smth
            - query_string_matcher:
                matcher_import_path: responses.matchers
                args:
                  query: query=smth
      - response:
          method: PUT
          url: 'http://localhost:37677/202'
          body: OK
          status: 202
          content_type: text/plain
          auto_calculate_content_length: false
          matchers:
            - query_param_matcher:
                matcher_import_path: responses.matchers
                args:
                  strict_match: true
                  params: {}
    
    
    [[responses]]
    
    [responses.response]
    method = "GET"
    url = "http://localhost:37677/404"
    body = "404 Not Found"
    status = 404
    content_type = "text/plain"
    auto_calculate_content_length = false
    
    [[responses.response.matchers]]
    
    [responses.response.matchers.query_param_matcher]
    matcher_import_path = "responses.matchers"
    
    [responses.response.matchers.query_param_matcher.args]
    strict_match = true
    
    [responses.response.matchers.query_param_matcher.args.params]
    
    [[responses]]
    
    [responses.response]
    method = "GET"
    url = "http://localhost:37677/status/wrong"
    body = "Invalid status code"
    status = 400
    content_type = "text/plain"
    auto_calculate_content_length = false
    
    [[responses.response.matchers]]
    
    [responses.response.matchers.query_param_matcher]
    matcher_import_path = "responses.matchers"
    
    [responses.response.matchers.query_param_matcher.args]
    strict_match = true
    
    [responses.response.matchers.query_param_matcher.args.params]
    
    [[responses]]
    
    [responses.response]
    method = "GET"
    url = "http://localhost:37677/500?query=smth"
    body = "500 Internal Server Error"
    status = 500
    content_type = "text/plain"
    auto_calculate_content_length = false
    
    [[responses.response.matchers]]
    
    [responses.response.matchers.query_param_matcher]
    matcher_import_path = "responses.matchers"
    
    [responses.response.matchers.query_param_matcher.args]
    strict_match = true
    
    [responses.response.matchers.query_param_matcher.args.params]
    query = "smth"
    
    [[responses.response.matchers]]
    
    [responses.response.matchers.query_string_matcher]
    matcher_import_path = "responses.matchers"
    
    [responses.response.matchers.query_string_matcher.args]
    query = "query=smth"
    
    [[responses]]
    
    [responses.response]
    method = "PUT"
    url = "http://localhost:37677/202"
    body = "OK"
    status = 202
    content_type = "text/plain"
    auto_calculate_content_length = false
    
    [[responses.response.matchers]]
    
    [responses.response.matchers.query_param_matcher]
    matcher_import_path = "responses.matchers"
    
    [responses.response.matchers.query_param_matcher.args]
    strict_match = true
    
    [responses.response.matchers.query_param_matcher.args.params]
    
    
    enhancement 
    opened by beliaev-maksim 3
  • Type checking calls[] is insufficient

    Type checking calls[] is insufficient

    Describe the bug

    this bit of code

    import requests
    import responses
    
    def get_something():
        return requests.get('http://example.com')
    
    
    def test_get_something():
        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, 'http://example.com', body='foo')
            foo = get_something()
            assert rsps.calls[0].request.url == 'http://example.com/'
    

    results in a type checking failure in mypy and pyright. The Pyright message is:

    Cannot access member "request" for type "List[Call]"
      Member "request" is unknown
    

    Additional context

    No response

    Version of responses

    0.22.0

    Steps to Reproduce

    Example in bug description.

    Expected Result

    The example code should pass type checking. Members of List[Call] should have their attributes properly annotated.

    Actual Result

    Cannot access member "request" for type "List[Call]"   Member "request" is unknown

    bug duplicate 
    opened by evilensky 4
Releases(0.22.0)
  • 0.22.0(Oct 11, 2022)

    • Update requests dependency to the version of 2.22.0 or higher. See #584.
    • [BETA] Added possibility to record responses to TOML files via @_recorder.record(file_path="out.toml") decorator.
    • [BETA] Added possibility to replay responses (populate registry) from TOML files via responses._add_from_file(file_path="out.toml") method.
    • Fix type for the mock's patcher object. See #556
    • Fix type annotation for CallList
    • Add passthrough argument to BaseResponse object. See #557
    • Fix registries leak. See #563
    • OriginalResponseShim is removed. See #585
    • Add support for the loose version of json_params_matcher via named argument strict_match. See #551
    • Add lists support as JSON objects in json_params_matcher. See #559
    • Added project links to pypi listing.
    • delete, get, head, options, patch, post, put shortcuts are now implemented using functools.partialmethod.
    • Fix MaxRetryError exception. Replace exception by RetryError according to requests implementation. See #572.
    • Adjust error message when Retry is exhausted. See #580.
    Source code(tar.gz)
    Source code(zip)
    responses-0.22.0-py3-none-any.whl(49.94 KB)
    responses-0.22.0.tar.gz(70.10 KB)
  • 0.21.0(May 25, 2022)

    • Add threading.Lock() to allow responses working with threading module.
    • Add urllib3 Retry mechanism. See #135
    • Removed internal _cookies_from_headers function
    • Now add, upsert, replace methods return registered response. remove method returns list of removed responses.
    • Added null value support in urlencoded_params_matcher via allow_blank keyword argument
    • Added strict version of decorator. Now you can apply @responses.activate(assert_all_requests_are_fired=True) to your function to validate that all requests were executed in the wrapped function. See #183
    Source code(tar.gz)
    Source code(zip)
    responses-0.21.0-py3-none-any.whl(44.90 KB)
    responses-0.21.0.tar.gz(63.96 KB)
  • 0.20.0(Mar 18, 2022)

  • 0.19.0(Mar 7, 2022)

    • Added a registry that provides more strict ordering based on the invocation index. See responses.registries.OrderedRegistry.
    • Added shortcuts for each request method: delete, get, head, options, patch, post, put. For example, to add response for POST request you can use responses.post() instead of responses.add(responses.POST).
    • Prevent responses.activate decorator to leak, if wrapped function called from within another wrapped function. Also, allow calling of above mentioned chain. See #481 for more details.
    • Expose get_registry() method of RequestsMock object. Replaces internal _get_registry().
    • query_param_matcher can now accept dictionaries with int and float values.
    • Add support for the loose version of query_param_matcher via named argument strict_match.
    • Added support for async/await functions.
    • response_callback is no longer executed on exceptions raised by failed Responses
    • Change logic of _get_url_and_path to comply with RFC 3986. Now URL match occurs by matching schema, authority and path, where path is terminated by the first question mark ("?") or number sign ("#") character, or by the end of the URI.
    • An error is now raised when both content_type and headers[content-type] are provided as parameters.
    • When a request isn't matched the passthru prefixes are now included in error messages.
    Source code(tar.gz)
    Source code(zip)
    responses-0.19.0-py3-none-any.whl(40.70 KB)
    responses-0.19.0.tar.gz(48.85 KB)
  • 0.18.0(Feb 2, 2022)

    • Dropped support of Python 2.7, 3.5, 3.6
    • Fixed issue with type annotation for responses.activate decorator. See #468
    • Removed internal _is_string and _ensure_str functions
    • Removed internal _quote from test_responses.py
    • Removed internal _matches attribute of RequestsMock object.
    • Generated decorator wrapper now uses stdlib features instead of strings and exec
    • Fix issue when Deprecation Warning was raised with default arguments in responses.add_callback due to match_querystring. See #464
    Source code(tar.gz)
    Source code(zip)
    responses-0.18.0-py3-none-any.whl(37.82 KB)
    responses-0.18.0.tar.gz(44.80 KB)
  • 0.17.0(Jan 10, 2022)

    • This release is the last to support Python 2.7.
    • Fixed issue when response.iter_content when chunk_size=None entered infinite loop
    • Fixed issue when passthru_prefixes persisted across tests. Now add_passthru is valid only within a context manager or for a single function and cleared on exit
    • Deprecate match_querystring argument in Response`` andCallbackResponse. Useresponses.matchers.query_param_matcherorresponses.matchers.query_string_matcher`
    • Added support for non-UTF-8 bytes in responses.matchers.multipart_matcher
    • Added responses.registries. Now user can create custom registries to manipulate the order of responses in the match algorithm responses.activate(registry=CustomRegistry)
    • Fixed issue with response match when requests were performed between adding responses with same URL. See Issue #212
    Source code(tar.gz)
    Source code(zip)
    responses-0.17.0-py2.py3-none-any.whl(37.71 KB)
    responses-0.17.0.tar.gz(44.00 KB)
  • 0.16.0(Nov 16, 2021)

    • Fixed regression with stream parameter deprecation, requests.session() and cookie handling.
    • Replaced adhoc URL parsing with urllib.parse.
    • Added match parameter to add_callback method
    • Added responses.matchers.fragment_identifier_matcher. This matcher allows you to match request URL fragment identifier.
    • Improved test coverage.
    • Fixed failing test in python 2.7 when python-future is also installed.
    Source code(tar.gz)
    Source code(zip)
    responses-0.16.0-py2.py3-none-any.whl(34.84 KB)
    responses-0.16.0.tar.gz(40.95 KB)
  • 0.15.0(Oct 26, 2021)

    • Added responses.PassthroughResponse and reponses.BaseResponse.passthrough. These features make building passthrough responses more compatible with dynamcially generated response objects.
    • Removed the unused _is_redirect() function from responses internals.
    • Added responses.matchers.request_kwargs_matcher. This matcher allows you to match additional request arguments like stream.
    • Added responses.matchers.multipart_matcher. This matcher allows you to match request body and headers for multipart/form-data data
    • Added responses.matchers.query_string_matcher. This matcher allows you to match request query string, similar to responses.matchers.query_param_matcher.
    • Added responses.matchers.header_matcher(). This matcher allows you to match request headers. By default only headers supplied to header_matcher() are checked. You can make header matching exhaustive by passing strict_match=True to header_matcher().
    • Changed all matchers output message in case of mismatch. Now message is aligned between Python2 and Python3 versions
    • Deprecate stream argument in Response and CallbackResponse
    • Added Python 3.10 support
    Source code(tar.gz)
    Source code(zip)
    responses-0.15.0-py2.py3-none-any.whl(32.02 KB)
    responses-0.15.0.tar.gz(37.07 KB)
  • 0.14.0(Sep 10, 2021)

    • Added responses.matchers.
    • Moved responses.json_params_matcher to responses.matchers.json_param_matcher
    • Moved responses.urlencoded_params_matcher to responses.matchers.urlencoded_param_matcher
    • Added responses.query_params_matcher. This matcher allows you to match query strings with a dictionary.
    • Added auto_calculate_content_length option to responses.add(). When enabled, this option will generate a Content-Length header based on the number of bytes in the response body.
    Source code(tar.gz)
    Source code(zip)
    responses-0.14.0-py2.py3-none-any.whl(26.76 KB)
    responses-0.14.0.tar.gz(31.90 KB)
  • 0.13.4(Aug 9, 2021)

  • 0.13.2(Mar 29, 2021)

  • 0.13.1(Mar 17, 2021)

  • 0.13.0(Mar 17, 2021)

    • responses.upsert() was added. This method will add() a response if one has not already been registered for a URL, or replace() an existing response.
    • responses.registered() was added. The method allows you to get a list of the currently registered responses. This formalizes the previously private responses.mock._matches method.
    • A more useful __repr__ has been added to Response.
    • Error messages have been improved.
    Source code(tar.gz)
    Source code(zip)
  • 0.12.1(Nov 12, 2020)

    • responses.urlencoded_params_matcher and responses.json_params_matcher now accept None to match empty requests.
    • Fixed imports to work with new urllib3 versions.
    • request.params now allows parameters to have multiple values for the same key.
    • Improved ConnectionError messages.
    Source code(tar.gz)
    Source code(zip)
  • 0.12.0(Aug 30, 2020)

  • 0.11.0(Aug 24, 2020)

  • 0.10.16(Aug 11, 2020)

  • 0.10.15(Jun 12, 2020)

    • Added assert_call_count to improve ergonomics around ensuring a mock was called.
    • Fix incorrect handling of paths with query strings.
    • Add Python 3.9 support to CI matrix.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.14(Apr 20, 2020)

  • 0.10.13(Apr 20, 2020)

    • Improved README examples.
    • Improved handling of unicode bodies. The inferred content-type for unicode bodies is now text/plain; charset=utf-8.
    • Streamlined querysting matching code.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.12(Mar 3, 2020)

  • 0.10.11(Feb 25, 2020)

  • 0.10.10(Jan 30, 2020)

    • Added Python 3.8 support
    • Remove Python 3.4 from test suite matrix.
    • The response.request object now has a params attribute that contains the query string parameters from the request that was captured.
    • add_passthru now supports re pattern objects to match URLs.
    • ConnectionErrors raised by responses now include more details on the request that was attempted and the mocks registered.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.9(Dec 20, 2019)

  • 0.10.8(Dec 12, 2019)

  • 0.10.7(Nov 25, 2019)

    Fixes

    • Improved formatting of project description in pypi.
    • Unicode cookie values are now normalized to URL quoted encoding.
    • Module exports are statically defined improving code completion and IDE navigation.
    • Improved compatibility with pytest 5
    Source code(tar.gz)
    Source code(zip)
  • 0.10.6(Mar 15, 2019)

    • Improved documentation.
    • Improved installation requirements for py3
    • ConnectionError's raised by responses now indicate which request path/method failed to match a mock.
    • test_responses.py is no longer part of the installation targets.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.5(Dec 17, 2018)

  • 0.10.4(Nov 15, 2018)

Owner
Sentry
Real-time crash reporting for your web apps, mobile apps, and games.
Sentry
HTTP client mocking tool for Python - inspired by Fakeweb for Ruby

HTTPretty 1.0.5 HTTP Client mocking tool for Python created by Gabriel Falcão . It provides a full fake TCP socket module. Inspired by FakeWeb Github

Gabriel Falcão 2k Jan 6, 2023
Coroutine-based concurrency library for Python

gevent Read the documentation online at http://www.gevent.org. Post issues on the bug tracker, discuss and ask open ended questions on the mailing lis

gevent 5.9k Dec 28, 2022
Hypothesis is a powerful, flexible, and easy to use library for property-based testing.

Hypothesis Hypothesis is a family of testing libraries which let you write tests parametrized by a source of examples. A Hypothesis implementation the

Hypothesis 6.4k Jan 1, 2023
Green is a clean, colorful, fast python test runner.

Green -- A clean, colorful, fast python test runner. Features Clean - Low redundancy in output. Result statistics for each test is vertically aligned.

Nathan Stocks 756 Dec 22, 2022
Scalable user load testing tool written in Python

Locust Locust is an easy to use, scriptable and scalable performance testing tool. You define the behaviour of your users in regular Python code, inst

Locust.io 20.4k Jan 8, 2023
A cross-platform GUI automation Python module for human beings. Used to programmatically control the mouse & keyboard.

PyAutoGUI PyAutoGUI is a cross-platform GUI automation Python module for human beings. Used to programmatically control the mouse & keyboard. pip inst

Al Sweigart 7.6k Jan 1, 2023
splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Cobra Team 2.6k Dec 27, 2022
Let your Python tests travel through time

FreezeGun: Let your Python tests travel through time FreezeGun is a library that allows your Python tests to travel through time by mocking the dateti

Steve Pulec 3.5k Jan 9, 2023
A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

FactoryBoy project 3k Jan 5, 2023
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Kirill Klenov 871 Dec 25, 2022
Faker is a Python package that generates fake data for you.

Faker is a Python package that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in yo

Daniele Faraglia 15.2k Jan 1, 2023
Mimesis is a high-performance fake data generator for Python, which provides data for a variety of purposes in a variety of languages.

Mimesis - Fake Data Generator Description Mimesis is a high-performance fake data generator for Python, which provides data for a variety of purposes

Isaak Uchakaev 3.8k Jan 1, 2023
Radically simplified static file serving for Python web apps

WhiteNoise Radically simplified static file serving for Python web apps With a couple of lines of config WhiteNoise allows your web app to serve its o

Dave Evans 2.1k Jan 8, 2023
livereload server in python (MAINTAINERS NEEDED)

LiveReload Reload webpages on changes, without hitting refresh in your browser. Installation python-livereload is for web developers who know Python,

Hsiaoming Yang 977 Dec 14, 2022
A screamingly fast Python 2/3 WSGI server written in C.

bjoern: Fast And Ultra-Lightweight HTTP/1.1 WSGI Server A screamingly fast, ultra-lightweight WSGI server for CPython 2 and CPython 3, written in C us

Jonas Haag 2.9k Dec 21, 2022
Waitress - A WSGI server for Python 2 and 3

Waitress Waitress is a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones which live in t

Pylons Project 1.2k Dec 30, 2022
Python HTTP Server

Python HTTP Server Preview Languange and Code Editor: How to run? Download the zip first. Open the http.py and wait 1-2 seconds. You will see __pycach

SonLyte 16 Oct 21, 2021
PyQaver is a PHP like WebServer for Python.

PyQaver is a PHP like WebServer for Python.

Dev Bash 7 Apr 25, 2022
Robyn is an async Python backend server with a runtime written in Rust, btw.

Robyn is an async Python backend server with a runtime written in Rust, btw. Python server running on top of of Rust Async RunTime. Installation

Sanskar Jethi 1.8k Dec 30, 2022