diff --git a/.travis.yml b/.travis.yml index 53abd63..f1e02ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,23 +17,11 @@ matrix: # Only run lint on a single 3.x - env: TOX_SUFFIX="lint" python: "3.7" - allow_failures: - - env: TOX_SUFFIX="aiohttp" - python: "pypy3" - - python: "3.8-dev" - exclude: - # Exclude aiohttp support - - env: TOX_SUFFIX="aiohttp" - python: "2.7" - - env: TOX_SUFFIX="aiohttp" - python: "pypy" python: - - "2.7" - "3.5" - "3.6" - "3.7" - - "3.8-dev" - - "pypy" + - "3.8" - "pypy3" install: - pip install tox-travis codecov diff --git a/README.rst b/README.rst index 1794ad2..190a3de 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,17 @@ + +######### +VCR.py 📼 +######### + + |PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| |CodeStyleBlack| -VCR.py -====== +---- .. image:: https://raw.github.com/kevin1024/vcrpy/master/vcr.png - :alt: vcr.py + :alt: vcr.py logo + :align: right + This is a Python version of `Ruby's VCR library `__. @@ -42,7 +49,7 @@ all HTTP interactions, which will update them to correspond to the new API. License -======= +------- This library uses the MIT license. See `LICENSE.txt `__ for more details diff --git a/docs/_static/vcr.png b/docs/_static/vcr.png new file mode 100644 index 0000000..3c3c9a3 Binary files /dev/null and b/docs/_static/vcr.png differ diff --git a/docs/_static/vcr.svg b/docs/_static/vcr.svg new file mode 100644 index 0000000..061b5b1 --- /dev/null +++ b/docs/_static/vcr.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/advanced.rst b/docs/advanced.rst index 9345ef7..5536055 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -130,11 +130,11 @@ Finally, register your method with VCR to use your new request matcher. Register your own cassette persister ------------------------------------ -Create your own persistence class, see the :ref:`persister_example`. +Create your own persistence class, see the example below: Your custom persister must implement both ``load_cassette`` and ``save_cassette`` methods. The ``load_cassette`` method must return a deserialized cassette or raise - ``ValueError`` if no cassette is found. +``ValueError`` if no cassette is found. Once the persister class is defined, register with VCR like so... @@ -143,7 +143,7 @@ Once the persister class is defined, register with VCR like so... import vcr my_vcr = vcr.VCR() - class CustomerPersister(object): + class CustomerPersister: # implement Persister methods... my_vcr.register_persister(CustomPersister) diff --git a/docs/changelog.rst b/docs/changelog.rst index e4a6a20..17e90db 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,204 +7,222 @@ For a full list of triaged issues, bugs and PRs and what release they are target All help in providing PRs to close out bug issues is appreciated. Even if that is providing a repo that fully replicates issues. We have very generous contributors that have added these to bug issues which meant another contributor picked up the bug and closed it out. -- 4.0.0 (UNRELEASED) - - Remove Python2 support - - Add Python 3.8 TravisCI support +- UNRELEASED + - ... + + +- 4.0.0 + - Remove Python2 support (@hugovk) + - Add Python 3.8 TravisCI support (@neozenith) + - Updated the logo to a modern material design (@sean0x42) + - 3.0.0 - - This release is a breaking change as it changes how aiohttp follows redirects and your cassettes may need to be re-recorded with this update. - - Fix multiple requests being replayed per single request in aiohttp stub #495 (@nickdirienzo) - - Add support for `request_info` on mocked responses in aiohttp stub #495 (@nickdirienzo) - - doc: fixed variable name (a -> cass) in an example for rewind #492 (@yarikoptic) + - This release is a breaking change as it changes how aiohttp follows redirects and your cassettes may need to be re-recorded with this update. + - Fix multiple requests being replayed per single request in aiohttp stub #495 (@nickdirienzo) + - Add support for `request_info` on mocked responses in aiohttp stub #495 (@nickdirienzo) + - doc: fixed variable name (a -> cass) in an example for rewind #492 (@yarikoptic) + - 2.1.1 - - Format code with black (@neozenith) - - Use latest pypy3 in Travis (@hugovk) - - Improve documentation about custom matchers (@gward) - - Fix exception when body is empty (@keithprickett) - - Add `pytest-recording` to the documentation as an alternative Pytest plugin (@Stranger6667) - - Fix yarl and python3.5 version issue (@neozenith) - - Fix header matcher for boto3 - fixes #474 (@simahawk) -- 2.1.0 - Add a `rewind` method to reset a cassette (thanks @khamidou) - New error message with more details on why the cassette failed to play a request (thanks @arthurHamon2, @neozenith) - Handle connect tunnel URI (thanks @jeking3) - Add code coverage to the project (thanks @neozenith) - Drop support to python 3.4 - Add deprecation warning on python 2.7, next major release will drop python 2.7 support - Fix build problems on requests tests (thanks to @dunossauro) - Fix matching on 'body' failing when Unicode symbols are present in them (thanks @valgur) - Fix bugs on aiohttp integration (thanks @graingert, @steinnes, @stj, @lamenezes, @lmazuel) - Fix Biopython incompatibility (thanks @rishab121) - Fix Boto3 integration (thanks @1oglop1, @arthurHamon2) -- 2.0.1 - Fix bug when using vcrpy with python 3.4 -- 2.0.0 - Support python 3.7 (fix httplib2 and urllib2, thanks @felixonmars) - [#356] Fixes `before_record_response` so the original response isn't changed (thanks @kgraves) - Fix requests stub when using proxy (thanks @samuelfekete @daneoshiga) - (only for aiohttp stub) Drop support to python 3.4 asyncio.coroutine (aiohttp doesn't support python it anymore) - Fix aiohttp stub to work with aiohttp client (thanks @stj) - Fix aiohttp stub to accept content type passed - Improve docs (thanks @adamchainz) -- 1.13.0 - Fix support to latest aiohttp version (3.3.2). Fix content-type bug in aiohttp stub. Save URL with query params properly when using aiohttp. -- 1.12.0 - Fix support to latest aiohttp version (3.2.1), Adapted setup to PEP508, Support binary responses on aiohttp, Dropped support for EOL python versions (2.6 and 3.3) -- 1.11.1 Fix compatibility with newest requests and urllib3 releases -- 1.11.0 Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison), - Support python 3.6 + CI tests (thanks @derekbekoe and @graingert), - Support pytest-asyncio coroutines (thanks @graingert) -- 1.10.5 Added a fix to httplib2 (thanks @carlosds730), Fix an issue with - aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes), - Remove duplicate mock triple (thanks @FooBarQuaxx) -- 1.10.4 Fix an issue with asyncio aiohttp (thanks @madninja) -- 1.10.3 Fix some issues with asyncio and params (thanks @anovikov1984 and - @lamenezes), Fix some issues with cassette serialize / deserialize and empty - response bodies (thanks @gRoussac and @dz0ny) -- 1.10.2 Fix 1.10.1 release - add aiohttp support back in -- 1.10.1 [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes) -- 1.10.0 Add support for aiohttp (thanks @lamenezes) -- 1.9.0 Add support for boto3 (thanks @desdm, @foorbarna). Fix deepcopy issue - for response headers when `decode_compressed_response` is enabled (thanks - @nickdirienzo) -- 1.8.0 Fix for Serialization errors with JSON adapter (thanks - @aliaksandrb). Avoid concatenating bytes with strings (thanks - @jaysonsantos). Exclude __pycache__ dirs & compiled files in sdist - (thanks @koobs). Fix Tornado support behavior for Tornado 3 (thanks - @abhinav). decode_compressed_response option and filter (thanks - @jayvdb). -- 1.7.4 [#217] Make use_cassette decorated functions actually return a - value (thanks @bcen). [#199] Fix path transfromation defaults. - Better headers dictionary management. -- 1.7.3 [#188] ``additional_matchers`` kwarg on ``use_cassette``. - [#191] Actually support passing multiple before_record_request - functions (thanks @agriffis). -- 1.7.2 [#186] Get effective_url in tornado (thanks @mvschaik), [#187] - Set request_time on Response object in tornado (thanks @abhinav). -- 1.7.1 [#183] Patch ``fetch_impl`` instead of the entire HTTPClient - class for Tornado (thanks @abhinav). -- 1.7.0 [#177] Properly support coroutine/generator decoration. [#178] - Support distribute (thanks @graingert). [#163] Make compatibility - between python2 and python3 recorded cassettes more robust (thanks - @gward). -- 1.6.1 [#169] Support conditional requirements in old versions of - pip, Fix RST parse errors generated by pandoc, [Tornado] Fix - unsupported features exception not being raised, [#166] - content-aware body matcher. -- 1.6.0 [#120] Tornado support (thanks @abhinav), [#147] packaging fixes - (thanks @graingert), [#158] allow filtering post params in requests - (thanks @MrJohz), [#140] add xmlrpclib support (thanks @Diaoul). -- 1.5.2 Fix crash when cassette path contains cassette library - directory (thanks @gazpachoking). -- 1.5.0 Automatic cassette naming and 'application/json' post data - filtering (thanks @marco-santamaria). -- 1.4.2 Fix a bug caused by requests 2.7 and chunked transfer encoding -- 1.4.1 Include README, tests, LICENSE in package. Thanks @ralphbean. -- 1.4.0 Filter post data parameters (thanks @eadmundo), support for - posting files through requests, inject\_cassette kwarg to access - cassette from ``use_cassette`` decorated function, - ``with_current_defaults`` actually works (thanks @samstav). -- 1.3.0 Fix/add support for urllib3 (thanks @aisch), fix default port - for https (thanks @abhinav). -- 1.2.0 Add custom\_patches argument to VCR/Cassette objects to allow - users to stub custom classes when cassettes become active. -- 1.1.4 Add force reset around calls to actual connection from stubs, - to ensure compatibility with the version of httplib/urlib2 in python - 2.7.9. -- 1.1.3 Fix python3 headers field (thanks @rtaboada), fix boto test - (thanks @telaviv), fix new\_episodes record mode (thanks @jashugan), - fix Windows connectionpool stub bug (thanks @gazpachoking), add - support for requests 2.5 -- 1.1.2 Add urllib==1.7.1 support. Make json serialize error handling - correct Improve logging of match failures. -- 1.1.1 Use function signature preserving ``wrapt.decorator`` to write - the decorator version of use\_cassette in order to ensure - compatibility with py.test fixtures and python 2. Move all request - filtering into the ``before_record_callable``. -- 1.1.0 Add ``before_record_response``. Fix several bugs related to the - context management of cassettes. -- 1.0.3: Fix an issue with requests 2.4 and make sure case sensitivity - is consistent across python versions -- 1.0.2: Fix an issue with requests 2.3 -- 1.0.1: Fix a bug with the new ignore requests feature and the once - record mode -- 1.0.0: *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in - the README. Take a look at the matcher section as well, you might - want to update your ``match_on`` settings. Add support for filtering - sensitive data from requests, matching query strings after the order - changes and improving the built-in matchers, (thanks to @mshytikov), - support for ignoring requests to certain hosts, bump supported - Python3 version to 3.4, fix some bugs with Boto support (thanks - @marusich), fix error with URL field capitalization in README (thanks - @simon-weber), added some log messages to help with debugging, added - ``all_played`` property on cassette (thanks @mshytikov) -- 0.7.0: VCR.py now supports Python 3! (thanks @asundg) Also I - refactored the stub connections quite a bit to add support for the - putrequest and putheader calls. This version also adds support for - httplib2 (thanks @nilp0inter). I have added a couple tests for boto - since it is an http client in its own right. Finally, this version - includes a fix for a bug where requests wasn't being patched properly - (thanks @msabramo). -- 0.6.0: Store response headers as a list since a HTTP response can - have the same header twice (happens with set-cookie sometimes). This - has the added benefit of preserving the order of headers. Thanks - @smallcode for the bug report leading to this change. I have made an - effort to ensure backwards compatibility with the old cassettes' - header storage mechanism, but if you want to upgrade to the new - header storage, you should delete your cassettes and re-record them. - Also this release adds better error messages (thanks @msabramo) and - adds support for using VCR as a decorator (thanks @smallcode for the - motivation) -- 0.5.0: Change the ``response_of`` method to ``responses_of`` since - cassettes can now contain more than one response for a request. Since - this changes the API, I'm bumping the version. Also includes 2 - bugfixes: a better error message when attempting to overwrite a - cassette file, and a fix for a bug with requests sessions (thanks - @msabramo) -- 0.4.0: Change default request recording behavior for multiple - requests. If you make the same request multiple times to the same - URL, the response might be different each time (maybe the response - has a timestamp in it or something), so this will make the same - request multiple times and save them all. Then, when you are - replaying the cassette, the responses will be played back in the same - order in which they were received. If you were making multiple - requests to the same URL in a cassette before version 0.4.0, you - might need to regenerate your cassette files. Also, removes support - for the cassette.play\_count counter API, since individual requests - aren't unique anymore. A cassette might contain the same request - several times. Also removes secure overwrite feature since that was - breaking overwriting files in Windows, and fixes a bug preventing - request's automatic body decompression from working. -- 0.3.5: Fix compatibility with requests 2.x -- 0.3.4: Bugfix: close file before renaming it. This fixes an issue on - Windows. Thanks @smallcode for the fix. -- 0.3.3: Bugfix for error message when an unreigstered custom matcher - was used -- 0.3.2: Fix issue with new config syntax and the ``match_on`` - parameter. Thanks, @chromy! -- 0.3.1: Fix issue causing full paths to be sent on the HTTP request - line. -- 0.3.0: *Backwards incompatible release* - Added support for record - modes, and changed the default recording behavior to the "once" - record mode. Please see the documentation on record modes for more. - Added support for custom request matching, and changed the default - request matching behavior to match only on the URL and method. Also, - improved the httplib mocking to add support for the - ``HTTPConnection.send()`` method. This means that requests won't - actually be sent until the response is read, since I need to record - the entire request in order to match up the appropriate response. I - don't think this should cause any issues unless you are sending - requests without ever loading the response (which none of the - standard httplib wrappers do, as far as I know. Thanks to @fatuhoku - for some of the ideas and the motivation behind this release. -- 0.2.1: Fixed missing modules in setup.py -- 0.2.0: Added configuration API, which lets you configure some - settings on VCR (see the README). Also, VCR no longer saves cassettes - if they haven't changed at all and supports JSON as well as YAML - (thanks @sirpengi). Added amazing new skeumorphic logo, thanks - @hairarrow. -- 0.1.0: *backwards incompatible release - delete your old cassette - files*: This release adds the ability to access the cassette to make - assertions on it, as well as a major code refactor thanks to - @dlecocq. It also fixes a couple longstanding bugs with redirects and - HTTPS. [#3 and #4] -- 0.0.4: If you have libyaml installed, vcrpy will use the c bindings - instead. Speed up your tests! Thanks @dlecocq -- 0.0.3: Add support for requests 1.2.3. Support for older versions of - requests dropped (thanks @vitormazzi and @bryanhelmig) -- 0.0.2: Add support for requests / urllib3 -- 0.0.1: Initial Release + - Format code with black (@neozenith) + - Use latest pypy3 in Travis (@hugovk) + - Improve documentation about custom matchers (@gward) + - Fix exception when body is empty (@keithprickett) + - Add `pytest-recording` to the documentation as an alternative Pytest plugin (@Stranger6667) + - Fix yarl and python3.5 version issue (@neozenith) + - Fix header matcher for boto3 - fixes #474 (@simahawk) +- 2.1.0 + - Add a `rewind` method to reset a cassette (thanks @khamidou) + - New error message with more details on why the cassette failed to play a request (thanks @arthurHamon2, @neozenith) + - Handle connect tunnel URI (thanks @jeking3) + - Add code coverage to the project (thanks @neozenith) + - Drop support to python 3.4 + - Add deprecation warning on python 2.7, next major release will drop python 2.7 support + - Fix build problems on requests tests (thanks to @dunossauro) + - Fix matching on 'body' failing when Unicode symbols are present in them (thanks @valgur) + - Fix bugs on aiohttp integration (thanks @graingert, @steinnes, @stj, @lamenezes, @lmazuel) + - Fix Biopython incompatibility (thanks @rishab121) + - Fix Boto3 integration (thanks @1oglop1, @arthurHamon2) +- 2.0.1 + - Fix bug when using vcrpy with python 3.4 +- 2.0.0 + - Support python 3.7 (fix httplib2 and urllib2, thanks @felixonmars) + - [#356] Fixes `before_record_response` so the original response isn't changed (thanks @kgraves) + - Fix requests stub when using proxy (thanks @samuelfekete @daneoshiga) + - (only for aiohttp stub) Drop support to python 3.4 asyncio.coroutine (aiohttp doesn't support python it anymore) + - Fix aiohttp stub to work with aiohttp client (thanks @stj) + - Fix aiohttp stub to accept content type passed + - Improve docs (thanks @adamchainz) + + +- 1.13.0 + - Fix support to latest aiohttp version (3.3.2). Fix content-type bug in aiohttp stub. Save URL with query params properly when using aiohttp. +- 1.12.0 + - Fix support to latest aiohttp version (3.2.1), Adapted setup to PEP508, Support binary responses on aiohttp, Dropped support for EOL python versions (2.6 and 3.3) +- 1.11.1 + - Fix compatibility with newest requests and urllib3 releases +- 1.11.0 + - Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison), + - Support python 3.6 + CI tests (thanks @derekbekoe and @graingert), + - Support pytest-asyncio coroutines (thanks @graingert) +- 1.10.5 + - Added a fix to httplib2 (thanks @carlosds730), Fix an issue with + - aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes), + - Remove duplicate mock triple (thanks @FooBarQuaxx) +- 1.10.4 + - Fix an issue with asyncio aiohttp (thanks @madninja) +- 1.10.3 + - Fix some issues with asyncio and params (thanks @anovikov1984 and @lamenezes) + - Fix some issues with cassette serialize / deserialize and empty response bodies (thanks @gRoussac and @dz0ny) +- 1.10.2 + - Fix 1.10.1 release - add aiohttp support back in +- 1.10.1 + - [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes) +- 1.10.0 + - Add support for aiohttp (thanks @lamenezes) +- 1.9.0 + - Add support for boto3 (thanks @desdm, @foorbarna). + - Fix deepcopy issue for response headers when `decode_compressed_response` is enabled (thanks @nickdirienzo) +- 1.8.0 + - Fix for Serialization errors with JSON adapter (thanks @aliaksandrb). + - Avoid concatenating bytes with strings (thanks @jaysonsantos). + - Exclude __pycache__ dirs & compiled files in sdist (thanks @koobs). + - Fix Tornado support behavior for Tornado 3 (thanks @abhinav). + - decode_compressed_response option and filter (thanks @jayvdb). +- 1.7.4 [#217] + - Make use_cassette decorated functions actually return a value (thanks @bcen). + - [#199] Fix path transfromation defaults. + - Better headers dictionary management. +- 1.7.3 [#188] + - ``additional_matchers`` kwarg on ``use_cassette``. + - [#191] Actually support passing multiple before_record_request functions (thanks @agriffis). +- 1.7.2 + - [#186] Get effective_url in tornado (thanks @mvschaik) + - [#187] Set request_time on Response object in tornado (thanks @abhinav). +- 1.7.1 + - [#183] Patch ``fetch_impl`` instead of the entire HTTPClient class for Tornado (thanks @abhinav). +- 1.7.0 + - [#177] Properly support coroutine/generator decoration. + - [#178] Support distribute (thanks @graingert). [#163] Make compatibility between python2 and python3 recorded cassettes more robust (thanks @gward). +- 1.6.1 + - [#169] Support conditional requirements in old versions of pip + - Fix RST parse errors generated by pandoc + - [Tornado] Fix unsupported features exception not being raised + - [#166] content-aware body matcher. +- 1.6.0 + - [#120] Tornado support (thanks @abhinav) + - [#147] packaging fixes (thanks @graingert) + - [#158] allow filtering post params in requests (thanks @MrJohz) + - [#140] add xmlrpclib support (thanks @Diaoul). +- 1.5.2 + - Fix crash when cassette path contains cassette library directory (thanks @gazpachoking). +- 1.5.0 + - Automatic cassette naming and 'application/json' post data filtering (thanks @marco-santamaria). +- 1.4.2 + - Fix a bug caused by requests 2.7 and chunked transfer encoding +- 1.4.1 + - Include README, tests, LICENSE in package. Thanks @ralphbean. +- 1.4.0 + - Filter post data parameters (thanks @eadmundo) + - Support for posting files through requests, inject\_cassette kwarg to access cassette from ``use_cassette`` decorated function, ``with_current_defaults`` actually works (thanks @samstav). +- 1.3.0 + - Fix/add support for urllib3 (thanks @aisch) + - Fix default port for https (thanks @abhinav). +- 1.2.0 + - Add custom\_patches argument to VCR/Cassette objects to allow users to stub custom classes when cassettes become active. +- 1.1.4 + - Add force reset around calls to actual connection from stubs, to ensure compatibility with the version of httplib/urlib2 in python 2.7.9. +- 1.1.3 + - Fix python3 headers field (thanks @rtaboada) + - fix boto test (thanks @telaviv) + - fix new\_episodes record mode (thanks @jashugan), + - fix Windows connectionpool stub bug (thanks @gazpachoking) + - add support for requests 2.5 +- 1.1.2 + - Add urllib==1.7.1 support. + - Make json serialize error handling correct + - Improve logging of match failures. +- 1.1.1 + - Use function signature preserving ``wrapt.decorator`` to write the decorator version of use\_cassette in order to ensure compatibility with py.test fixtures and python 2. + - Move all request filtering into the ``before_record_callable``. +- 1.1.0 + - Add ``before_record_response``. Fix several bugs related to the context management of cassettes. +- 1.0.3 + - Fix an issue with requests 2.4 and make sure case sensitivity is consistent across python versions +- 1.0.2 + - Fix an issue with requests 2.3 +- 1.0.1 + - Fix a bug with the new ignore requests feature and the once record mode +- 1.0.0 + - *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in the README. Take a look at the matcher section as well, you might want to update your ``match_on`` settings. + - Add support for filtering sensitive data from requests, matching query strings after the order changes and improving the built-in matchers, (thanks to @mshytikov) + - Support for ignoring requests to certain hosts, bump supported Python3 version to 3.4, fix some bugs with Boto support (thanks @marusich) + - Fix error with URL field capitalization in README (thanks @simon-weber) + - Added some log messages to help with debugging + - Added ``all_played`` property on cassette (thanks @mshytikov) + +- 0.7.0 + - VCR.py now supports Python 3! (thanks @asundg) + - Also I refactored the stub connections quite a bit to add support for the putrequest and putheader calls. + - This version also adds support for httplib2 (thanks @nilp0inter). + - I have added a couple tests for boto since it is an http client in its own right. + - Finally, this version includes a fix for a bug where requests wasn't being patched properly (thanks @msabramo). +- 0.6.0 + - Store response headers as a list since a HTTP response can have the same header twice (happens with set-cookie sometimes). + - This has the added benefit of preserving the order of headers. + - Thanks @smallcode for the bug report leading to this change. + - I have made an effort to ensure backwards compatibility with the old cassettes' header storage mechanism, but if you want to upgrade to the new header storage, you should delete your cassettes and re-record them. + - Also this release adds better error messages (thanks @msabramo) + - and adds support for using VCR as a decorator (thanks @smallcode for the motivation) +- 0.5.0 + - Change the ``response_of`` method to ``responses_of`` since cassettes can now contain more than one response for a request. + - Since this changes the API, I'm bumping the version. + - Also includes 2 bugfixes: + - a better error message when attempting to overwrite a cassette file, + - and a fix for a bug with requests sessions (thanks @msabramo) +- 0.4.0 + - Change default request recording behavior for multiple requests. + - If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all. + - Then, when you are replaying the cassette, the responses will be played back in the same order in which they were received. + - If you were making multiple requests to the same URL in a cassette before version 0.4.0, you might need to regenerate your cassette files. + - Also, removes support for the cassette.play\_count counter API, since individual requests aren't unique anymore. + - A cassette might contain the same request several times. + - Also removes secure overwrite feature since that was breaking overwriting files in Windows + - And fixes a bug preventing request's automatic body decompression from working. +- 0.3.5 + - Fix compatibility with requests 2.x +- 0.3.4 + - Bugfix: close file before renaming it. This fixes an issue on Windows. Thanks @smallcode for the fix. +- 0.3.3 + - Bugfix for error message when an unreigstered custom matcher was used +- 0.3.2 + - Fix issue with new config syntax and the ``match_on`` parameter. Thanks, @chromy! +- 0.3.1 + - Fix issue causing full paths to be sent on the HTTP request line. +- 0.3.0 + - *Backwards incompatible release* + - Added support for record modes, and changed the default recording behavior to the "once" record mode. Please see the documentation on record modes for more. + - Added support for custom request matching, and changed the default request matching behavior to match only on the URL and method. + - Also, improved the httplib mocking to add support for the ``HTTPConnection.send()`` method. + - This means that requests won't actually be sent until the response is read, since I need to record the entire request in order to match up the appropriate response. + - I don't think this should cause any issues unless you are sending requests without ever loading the response (which none of the standard httplib wrappers do, as far as I know). + - Thanks to @fatuhoku for some of the ideas and the motivation behind this release. +- 0.2.1 + - Fixed missing modules in setup.py +- 0.2.0 + - Added configuration API, which lets you configure some settings on VCR (see the README). + - Also, VCR no longer saves cassettes if they haven't changed at all and supports JSON as well as YAML (thanks @sirpengi). + - Added amazing new skeumorphic logo, thanks @hairarrow. +- 0.1.0 + - *backwards incompatible release - delete your old cassette files* + - This release adds the ability to access the cassette to make assertions on it + - as well as a major code refactor thanks to @dlecocq. + - It also fixes a couple longstanding bugs with redirects and HTTPS. [#3 and #4] +- 0.0.4 + - If you have libyaml installed, vcrpy will use the c bindings instead. Speed up your tests! Thanks @dlecocq +- 0.0.3 + - Add support for requests 1.2.3. Support for older versions of requests dropped (thanks @vitormazzi and @bryanhelmig) +- 0.0.2 + - Add support for requests / urllib3 +- 0.0.1 + - Initial Release + diff --git a/docs/conf.py b/docs/conf.py index dbc671b..94e6d8a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,8 +12,33 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import codecs import os +import re +here = os.path.abspath(os.path.dirname(__file__)) + + +def read(*parts): + # intentionally *not* adding an encoding option to open, See: + # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 + with codecs.open(os.path.join(here, *parts), "r") as fp: + return fp.read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) + if version_match: + return version_match.group(1) + + raise RuntimeError("Unable to find version string.") + + +autodoc_default_options = { + "members": None, + "undoc-members": None, +} # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -27,7 +52,14 @@ import os # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.viewcode"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.todo", + "sphinx.ext.githubpages", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -44,18 +76,18 @@ source_suffix = ".rst" master_doc = "index" # General information about the project. -project = u"vcrpy" -copyright = u"2015, Kevin McCarthy" -author = u"Kevin McCarthy" +project = "vcrpy" +copyright = "2015, Kevin McCarthy" +author = "Kevin McCarthy" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = "1.7.4" +# version = "1.7.4" # The full version, including alpha/beta/rc tags. -release = "1.7.4" +version = release = find_version("..", "vcr", "__init__.py") # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -106,11 +138,11 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. # https://read-the-docs.readthedocs.io/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -if "READTHEDOCS" not in os.environ: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# if "READTHEDOCS" not in os.environ: +# import sphinx_rtd_theme +# +# html_theme = "sphinx_rtd_theme" +# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -153,6 +185,8 @@ html_static_path = ["_static"] # Custom sidebar templates, maps document names to template names. # html_sidebars = {} +html_sidebars = {"**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"]} + # Additional templates that should be rendered to pages, maps page names to # template names. @@ -205,19 +239,21 @@ htmlhelp_basename = "vcrpydoc" latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', # Latex figure (float) alignment - #'figure_align': 'htbp', + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). -latex_documents = [(master_doc, "vcrpy.tex", u"vcrpy Documentation", u"Kevin McCarthy", "manual")] +latex_documents = [ + (master_doc, "vcrpy.tex", "vcrpy Documentation", "Kevin McCarthy", "manual"), +] # The name of an image file (relative to this directory) to place at the top of # the title page. @@ -244,7 +280,7 @@ latex_documents = [(master_doc, "vcrpy.tex", u"vcrpy Documentation", u"Kevin McC # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "vcrpy", u"vcrpy Documentation", [author], 1)] +man_pages = [(master_doc, "vcrpy", "vcrpy Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -259,12 +295,12 @@ texinfo_documents = [ ( master_doc, "vcrpy", - u"vcrpy Documentation", + "vcrpy Documentation", author, "vcrpy", "One line description of project.", "Miscellaneous", - ) + ), ] # Documents to append as an appendix to all manuals. @@ -282,3 +318,4 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"https://docs.python.org/": None} +html_theme = "alabaster" diff --git a/docs/contributing.rst b/docs/contributing.rst index 557f46d..80addf1 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -1,6 +1,10 @@ Contributing ============ +.. image:: _static/vcr.svg + :alt: vcr.py logo + :align: right + 🚀 Milestones -------------- For anyone interested in the roadmap and projected release milestones please see the following link: @@ -81,18 +85,22 @@ Running VCR's test suite The tests are all run automatically on `Travis CI `__, but you can also run them -yourself using `py.test `__ and -`Tox `__. Tox will automatically run them in -all environments VCR.py supports. The test suite is pretty big and slow, -but you can tell tox to only run specific tests like this:: +yourself using `pytest `__ and +`Tox `__. + +Tox will automatically run them in all environments VCR.py supports if they are available on your `PATH`. Alternatively you can use `tox-pyenv `_ with +`pyenv `_. +We recommend you read the documentation for each and see the section further below. + +The test suite is pretty big and slow, but you can tell tox to only run specific tests like this:: tox -e {pyNN}-{HTTP_LIBRARY} -- - tox -e py27-requests -- -v -k "'test_status_code or test_gzip'" + tox -e py36-requests -- -v -k "'test_status_code or test_gzip'" tox -e py37-requests -- -v --last-failed This will run only tests that look like ``test_status_code`` or -``test_gzip`` in the test suite, and only in the python 2.7 environment +``test_gzip`` in the test suite, and only in the python 3.6 environment that has ``requests`` installed. Also, in order for the boto tests to run, you will need an AWS key. @@ -106,8 +114,8 @@ Using PyEnv with VCR's test suite --------------------------------- PyEnv is a tool for managing multiple installation of python on your system. -See the full documentation at their `github ` -but we are also going to use `tox-pyenv ` +See the full documentation at their `github `_ +but we are also going to use `tox-pyenv `_ in this example:: git clone https://github.com/pyenv/pyenv ~/.pyenv @@ -122,10 +130,10 @@ in this example:: pip install tox tox-pyenv # Install supported versions (at time of writing), this does not activate them - pyenv install 2.7.10 3.5.7 3.6.9 3.7.4 3.8-dev pypy2.6-7.1.1 pypy3.6-7.1.1 + pyenv install 3.5.9 3.6.9 3.7.5 3.8.0 pypy3.6-7.2.0 # This activates them - pyenv local 2.7.10 3.5.7 3.6.9 3.7.4 3.8-dev pypy2.6-7.1.1 pypy3.6-7.1.1 + pyenv local 3.5.9 3.6.9 3.7.5 3.8.0 pypy3.6-7.2.0 # Run the whole test suite tox diff --git a/docs/debugging.rst b/docs/debugging.rst index 35177ed..ae06957 100644 --- a/docs/debugging.rst +++ b/docs/debugging.rst @@ -44,7 +44,7 @@ It can return multiple similar requests with : CannotOverwriteExistingCassetteException message example : -.. code:: python +.. code:: CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('cassette.yaml') in your current record mode ('once'). No match for the request () was found. diff --git a/docs/index.rst b/docs/index.rst index 0504cfb..abfc583 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ Contents ======== .. toctree:: - :maxdepth: 2 + :maxdepth: 3 installation usage @@ -15,6 +15,7 @@ Contents contributing changelog + ================== Indices and tables ================== diff --git a/docs/installation.rst b/docs/installation.rst index 8e475e0..abc417a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,8 +9,7 @@ with pip:: Compatibility ------------- -VCR.py supports Python 2.7 and 3.5+, and -`pypy `__. +VCR.py supports Python 3.5+, and `pypy `__. The following HTTP libraries are supported: diff --git a/setup.py b/setup.py index 9f21a8b..011bd6e 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,31 @@ #!/usr/bin/env python +import codecs +import os +import re import sys from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand long_description = open("README.rst", "r").read() +here = os.path.abspath(os.path.dirname(__file__)) + + +def read(*parts): + # intentionally *not* adding an encoding option to open, See: + # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 + with codecs.open(os.path.join(here, *parts), "r") as fp: + return fp.read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) + if version_match: + return version_match.group(1) + + raise RuntimeError("Unable to find version string.") class PyTest(TestCommand): @@ -26,26 +46,20 @@ install_requires = [ "PyYAML", "wrapt", "six>=1.5", - 'contextlib2; python_version=="2.7"', - 'mock; python_version=="2.7"', 'yarl; python_version>="3.6"', 'yarl<1.4; python_version=="3.5"', ] -excluded_packages = ["tests*"] -if sys.version_info[0] == 2: - excluded_packages.append("vcr.stubs.aiohttp_stubs") - setup( name="vcrpy", - version="3.0.0", + version=find_version("vcr", "__init__.py"), description=("Automatically mock your HTTP interactions to simplify and " "speed up testing"), long_description=long_description, author="Kevin McCarthy", author_email="me@kevinmccarthy.org", url="https://github.com/kevin1024/vcrpy", - packages=find_packages(exclude=excluded_packages), - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + packages=find_packages(exclude=["tests*"]), + python_requires=">=3.5", install_requires=install_requires, license="MIT", tests_require=["pytest", "mock", "pytest-httpbin"], @@ -54,12 +68,12 @@ setup( "Environment :: Console", "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", diff --git a/tests/integration/test_basic.py b/tests/integration/test_basic.py index bdcc4ed..56b969f 100644 --- a/tests/integration/test_basic.py +++ b/tests/integration/test_basic.py @@ -3,7 +3,7 @@ # External imports import os -from six.moves.urllib.request import urlopen +from urllib.request import urlopen # Internal imports import vcr diff --git a/tests/integration/test_boto.py b/tests/integration/test_boto.py index 4087fc9..2f6c3d1 100644 --- a/tests/integration/test_boto.py +++ b/tests/integration/test_boto.py @@ -6,14 +6,9 @@ import boto # NOQA import boto.iam # NOQA from boto.s3.connection import S3Connection # NOQA from boto.s3.key import Key # NOQA +from configparser import DuplicateSectionError # NOQA import vcr # NOQA -try: # NOQA - from ConfigParser import DuplicateSectionError # NOQA -except ImportError: # NOQA - # python3 - from configparser import DuplicateSectionError # NOQA - def test_boto_stubs(tmpdir): with vcr.use_cassette(str(tmpdir.join("boto-stubs.yml"))): diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index e0776bc..30c9089 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -2,7 +2,7 @@ import os import json import pytest import vcr -from six.moves.urllib.request import urlopen +from urllib.request import urlopen def test_set_serializer_default_config(tmpdir, httpbin): diff --git a/tests/integration/test_disksaver.py b/tests/integration/test_disksaver.py index c3e2e8c..c26e13a 100644 --- a/tests/integration/test_disksaver.py +++ b/tests/integration/test_disksaver.py @@ -4,7 +4,7 @@ # External imports import os import time -from six.moves.urllib.request import urlopen +from urllib.request import urlopen # Internal imports import vcr diff --git a/tests/integration/test_filter.py b/tests/integration/test_filter.py index 6ec8594..b42bb34 100644 --- a/tests/integration/test_filter.py +++ b/tests/integration/test_filter.py @@ -1,8 +1,8 @@ import base64 import pytest -from six.moves.urllib.request import urlopen, Request -from six.moves.urllib.parse import urlencode -from six.moves.urllib.error import HTTPError +from urllib.request import urlopen, Request +from urllib.parse import urlencode +from urllib.error import HTTPError import vcr import json from assertions import assert_cassette_has_one_response, assert_is_json diff --git a/tests/integration/test_httplib2.py b/tests/integration/test_httplib2.py index 7d05c90..f5ce7c0 100644 --- a/tests/integration/test_httplib2.py +++ b/tests/integration/test_httplib2.py @@ -3,7 +3,7 @@ import sys -from six.moves.urllib_parse import urlencode +from urllib.parse import urlencode import pytest import pytest_httpbin.certs @@ -111,7 +111,7 @@ def test_post_data(tmpdir, httpbin_both): def test_post_unicode_data(tmpdir, httpbin_both): """Ensure that it works when posting unicode data""" - data = urlencode({"snowman": u"☃".encode("utf-8")}) + data = urlencode({"snowman": "☃".encode()}) url = httpbin_both.url + "/post" with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))): _, res1 = http().request(url, "POST", data) diff --git a/tests/integration/test_ignore.py b/tests/integration/test_ignore.py index 574a67e..53aeee7 100644 --- a/tests/integration/test_ignore.py +++ b/tests/integration/test_ignore.py @@ -1,4 +1,4 @@ -from six.moves.urllib.request import urlopen +from urllib.request import urlopen import socket from contextlib import contextmanager import vcr diff --git a/tests/integration/test_matchers.py b/tests/integration/test_matchers.py index 9ec2804..c555e5c 100644 --- a/tests/integration/test_matchers.py +++ b/tests/integration/test_matchers.py @@ -1,6 +1,6 @@ import vcr import pytest -from six.moves.urllib.request import urlopen +from urllib.request import urlopen DEFAULT_URI = "http://httpbin.org/get?p1=q1&p2=q2" # base uri for testing diff --git a/tests/integration/test_multiple.py b/tests/integration/test_multiple.py index 0046e08..4368827 100644 --- a/tests/integration/test_multiple.py +++ b/tests/integration/test_multiple.py @@ -1,6 +1,6 @@ import pytest import vcr -from six.moves.urllib.request import urlopen +from urllib.request import urlopen def test_making_extra_request_raises_exception(tmpdir, httpbin): diff --git a/tests/integration/test_proxy.py b/tests/integration/test_proxy.py index 31e15a5..b465b59 100644 --- a/tests/integration/test_proxy.py +++ b/tests/integration/test_proxy.py @@ -5,8 +5,9 @@ import multiprocessing import pytest -from six.moves import socketserver, SimpleHTTPServer -from six.moves.urllib.request import urlopen +import http.server +import socketserver +from urllib.request import urlopen # Internal imports import vcr @@ -15,7 +16,7 @@ import vcr requests = pytest.importorskip("requests") -class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): +class Proxy(http.server.SimpleHTTPRequestHandler): """ Simple proxy server. diff --git a/tests/integration/test_record_mode.py b/tests/integration/test_record_mode.py index 7d3ca8d..38abce5 100644 --- a/tests/integration/test_record_mode.py +++ b/tests/integration/test_record_mode.py @@ -1,6 +1,6 @@ import pytest import vcr -from six.moves.urllib.request import urlopen +from urllib.request import urlopen def test_once_record_mode(tmpdir, httpbin): diff --git a/tests/integration/test_register_matcher.py b/tests/integration/test_register_matcher.py index 44e44ca..7b8ee1c 100644 --- a/tests/integration/test_register_matcher.py +++ b/tests/integration/test_register_matcher.py @@ -1,5 +1,5 @@ import vcr -from six.moves.urllib.request import urlopen +from urllib.request import urlopen def true_matcher(r1, r2): diff --git a/tests/integration/test_register_persister.py b/tests/integration/test_register_persister.py index 493b452..a7b134d 100644 --- a/tests/integration/test_register_persister.py +++ b/tests/integration/test_register_persister.py @@ -3,7 +3,7 @@ # External imports import os -from six.moves.urllib.request import urlopen +from urllib.request import urlopen # Internal imports import vcr diff --git a/tests/integration/test_register_serializer.py b/tests/integration/test_register_serializer.py index 9e698de..8983bd7 100644 --- a/tests/integration/test_register_serializer.py +++ b/tests/integration/test_register_serializer.py @@ -1,7 +1,7 @@ import vcr -class MockSerializer(object): +class MockSerializer: def __init__(self): self.serialize_count = 0 self.deserialize_count = 0 diff --git a/tests/integration/test_request.py b/tests/integration/test_request.py index 04f7385..ec499fd 100644 --- a/tests/integration/test_request.py +++ b/tests/integration/test_request.py @@ -1,5 +1,5 @@ import vcr -from six.moves.urllib.request import urlopen +from urllib.request import urlopen def test_recorded_request_uri_with_redirected_request(tmpdir, httpbin): diff --git a/tests/integration/test_stubs.py b/tests/integration/test_stubs.py index 4c24d06..bb6b562 100644 --- a/tests/integration/test_stubs.py +++ b/tests/integration/test_stubs.py @@ -1,7 +1,7 @@ import vcr import zlib import json -import six.moves.http_client as httplib +import http.client as httplib from assertions import assert_is_json diff --git a/tests/integration/test_urllib2.py b/tests/integration/test_urllib2.py index ec030ec..2582e0c 100644 --- a/tests/integration/test_urllib2.py +++ b/tests/integration/test_urllib2.py @@ -2,8 +2,8 @@ """Integration tests with urllib2""" import ssl -from six.moves.urllib.request import urlopen -from six.moves.urllib_parse import urlencode +from urllib.request import urlopen +from urllib.parse import urlencode import pytest_httpbin.certs # Internal imports @@ -104,7 +104,7 @@ def test_post_data(httpbin_both, tmpdir): def test_post_unicode_data(httpbin_both, tmpdir): """Ensure that it works when posting unicode data""" - data = urlencode({"snowman": u"☃".encode("utf-8")}).encode("utf-8") + data = urlencode({"snowman": "☃".encode()}).encode("utf-8") url = httpbin_both.url + "/post" with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))): res1 = urlopen_with_cafile(url, data).read() diff --git a/tests/integration/test_wild.py b/tests/integration/test_wild.py index 7fe57d7..997b092 100644 --- a/tests/integration/test_wild.py +++ b/tests/integration/test_wild.py @@ -1,16 +1,13 @@ +import http.client as httplib import multiprocessing import pytest -from six.moves import xmlrpc_client, xmlrpc_server +from xmlrpc.client import ServerProxy +from xmlrpc.server import SimpleXMLRPCServer requests = pytest.importorskip("requests") import vcr # NOQA -try: - import httplib -except ImportError: - import http.client as httplib - def test_domain_redirect(): """Ensure that redirects across domains are considered unique""" @@ -80,7 +77,7 @@ def test_amazon_doctype(tmpdir): def start_rpc_server(q): - httpd = xmlrpc_server.SimpleXMLRPCServer(("127.0.0.1", 0)) + httpd = SimpleXMLRPCServer(("127.0.0.1", 0)) httpd.register_function(pow) q.put("http://{}:{}".format(*httpd.server_address)) httpd.serve_forever() @@ -99,11 +96,11 @@ def rpc_server(): def test_xmlrpclib(tmpdir, rpc_server): with vcr.use_cassette(str(tmpdir.join("xmlrpcvideo.yaml"))): - roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True) + roundup_server = ServerProxy(rpc_server, allow_none=True) original_schema = roundup_server.pow(2, 4) with vcr.use_cassette(str(tmpdir.join("xmlrpcvideo.yaml"))): - roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True) + roundup_server = ServerProxy(rpc_server, allow_none=True) second_schema = roundup_server.pow(2, 4) assert original_schema == second_schema diff --git a/tests/unit/test_cassettes.py b/tests/unit/test_cassettes.py index 3e35d5a..915f235 100644 --- a/tests/unit/test_cassettes.py +++ b/tests/unit/test_cassettes.py @@ -1,12 +1,13 @@ +import contextlib import copy import inspect +import mock import os -from six.moves import http_client as httplib +import http.client as httplib import pytest import yaml -from vcr.compat import mock, contextlib from vcr.cassette import Cassette from vcr.errors import UnhandledHTTPRequestError from vcr.patch import force_reset @@ -208,7 +209,7 @@ def test_nesting_context_managers_by_checking_references_of_http_connection(): def test_custom_patchers(): - class Test(object): + class Test: attribute = None with Cassette.use(path="custom_patches", custom_patches=((Test, "attribute", VCRHTTPSConnection),)): diff --git a/tests/unit/test_errors.py b/tests/unit/test_errors.py index fb2ab80..fe79253 100644 --- a/tests/unit/test_errors.py +++ b/tests/unit/test_errors.py @@ -1,6 +1,7 @@ +import mock + import pytest -from vcr.compat import mock from vcr import errors from vcr.cassette import Cassette diff --git a/tests/unit/test_filters.py b/tests/unit/test_filters.py index bfdde20..d40bb0c 100644 --- a/tests/unit/test_filters.py +++ b/tests/unit/test_filters.py @@ -1,4 +1,4 @@ -from six import BytesIO +from io import BytesIO from vcr.filters import ( remove_headers, replace_headers, @@ -8,10 +8,10 @@ from vcr.filters import ( replace_post_data_parameters, decode_response, ) -from vcr.compat import mock from vcr.request import Request import gzip import json +import mock import zlib diff --git a/tests/unit/test_matchers.py b/tests/unit/test_matchers.py index 34eadd3..6c2bd4a 100644 --- a/tests/unit/test_matchers.py +++ b/tests/unit/test_matchers.py @@ -1,5 +1,5 @@ import itertools -from vcr.compat import mock +import mock import pytest diff --git a/tests/unit/test_response.py b/tests/unit/test_response.py index 8a7fdc4..30d82e7 100644 --- a/tests/unit/test_response.py +++ b/tests/unit/test_response.py @@ -1,8 +1,5 @@ # coding: UTF-8 import io -import unittest - -import six from vcr.stubs import VCRHTTPResponse @@ -58,7 +55,6 @@ def test_response_headers_should_have_correct_values(): assert response.headers.get("date") == "Fri, 24 Oct 2014 18:35:37 GMT" -@unittest.skipIf(six.PY2, "Regression test for Python3 only") def test_response_parses_correctly_and_fp_attribute_error_is_not_thrown(): """ Regression test for https://github.com/kevin1024/vcrpy/issues/440 diff --git a/tests/unit/test_serialize.py b/tests/unit/test_serialize.py index a0d9437..a7efb77 100644 --- a/tests/unit/test_serialize.py +++ b/tests/unit/test_serialize.py @@ -1,7 +1,8 @@ # -*- encoding: utf-8 -*- +import mock + import pytest -from vcr.compat import mock from vcr.request import Request from vcr.serialize import deserialize, serialize from vcr.serializers import yamlserializer, jsonserializer, compat @@ -29,7 +30,7 @@ def test_deserialize_new_json_cassette(): deserialize(f.read(), jsonserializer) -REQBODY_TEMPLATE = u"""\ +REQBODY_TEMPLATE = """\ interactions: - request: body: {req_body} diff --git a/tests/unit/test_stubs.py b/tests/unit/test_stubs.py index e06e4cd..07d15c1 100644 --- a/tests/unit/test_stubs.py +++ b/tests/unit/test_stubs.py @@ -1,9 +1,10 @@ +import mock + from vcr.stubs import VCRHTTPSConnection -from vcr.compat import mock from vcr.cassette import Cassette -class TestVCRConnection(object): +class TestVCRConnection: def test_setting_of_attributes_get_propogated_to_real_connection(self): vcr_connection = VCRHTTPSConnection("www.examplehost.com") vcr_connection.ssl_version = "example_ssl_version" diff --git a/tests/unit/test_vcr.py b/tests/unit/test_vcr.py index 4bdad8d..49e9000 100644 --- a/tests/unit/test_vcr.py +++ b/tests/unit/test_vcr.py @@ -1,10 +1,10 @@ +import mock import os import pytest -from six.moves import http_client as httplib +import http.client as httplib from vcr import VCR, use_cassette -from vcr.compat import mock from vcr.request import Request from vcr.stubs import VCRHTTPSConnection from vcr.patch import _HTTPConnection, force_reset @@ -170,7 +170,7 @@ def test_fixtures_with_use_cassette(random_fixture): def test_custom_patchers(): - class Test(object): + class Test: attribute = None attribute2 = None diff --git a/tox.ini b/tox.ini index 3f311d1..e73f477 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ skip_missing_interpreters=true envlist = cov-clean, lint, - {py27,py35,py36,py37,py38,pypy,pypy3}-{requests,httplib2,urllib3,tornado4,boto3}, - {py35,py36,py37,py38}-{aiohttp}, + {py35,py36,py37,py38}-{requests,httplib2,urllib3,tornado4,boto3,aiohttp}, + {pypy3}-{requests,httplib2,urllib3,tornado4,boto3}, cov-report @@ -34,6 +34,27 @@ deps = flake8 black +[testenv:docs] +# Running sphinx from inside the "docs" directory +# ensures it will not pick up any stray files that might +# get into a virtual environment under the top-level directory +# or other artifacts under build/ +changedir = docs +# The only dependency is sphinx +# If we were using extensions packaged separately, +# we would specify them here. +# A better practice is to specify a specific version of sphinx. +deps = + sphinx + sphinx_rtd_theme +# This is the sphinx command to generate HTML. +# In other circumstances, we might want to generate a PDF or an ebook +commands = + sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html +# We use Python 3.7. Tox sometimes tries to autodetect it based on the name of +# the testenv, but "docs" does not give useful clues so we have to be explicit. +basepython = python3.7 + [testenv] # Need to use develop install so that paths # for aggregate code coverage combine @@ -51,17 +72,17 @@ deps = requests: requests>=2.22.0 httplib2: httplib2 urllib3: urllib3 - {py27,py35,py36,pypy}-tornado4: tornado>=4,<5 - {py27,py35,py36,pypy}-tornado4: pytest-tornado - {py27,py35,py36}-tornado4: pycurl + {py35,py36}-tornado4: tornado>=4,<5 + {py35,py36}-tornado4: pytest-tornado + {py35,py36}-tornado4: pycurl boto3: boto3 boto3: urllib3 aiohttp: aiohttp aiohttp: pytest-asyncio aiohttp: pytest-aiohttp depends = - {py27,py35,py36,py37,pypy}-{lint,requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37}-{aiohttp}: cov-clean - cov-report: {py27,py35,py36,py37,pypy}-{lint,requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37}-{aiohttp} + lint,{py35,py36,py37,py38,pypy3}-{requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37,py38}-{aiohttp}: cov-clean + cov-report: lint,{py35,py36,py37,py38,pypy3}-{requests,httplib2,urllib3,tornado4,boto3},{py35,py36,py37,py38}-{aiohttp} passenv = AWS_ACCESS_KEY_ID AWS_DEFAULT_REGION diff --git a/vcr.png b/vcr.png index a10236c..3c3c9a3 100644 Binary files a/vcr.png and b/vcr.png differ diff --git a/vcr/__init__.py b/vcr/__init__.py index ed390c5..dc9ad51 100644 --- a/vcr/__init__.py +++ b/vcr/__init__.py @@ -1,23 +1,8 @@ import logging -import warnings -import sys from .config import VCR +from logging import NullHandler -# Set default logging handler to avoid "No handler found" warnings. -try: # Python 2.7+ - from logging import NullHandler -except ImportError: - - class NullHandler(logging.Handler): - def emit(self, record): - pass - - -if sys.version_info[0] == 2: - warnings.warn( - "Python 2.x support of vcrpy is deprecated and will be removed in an upcoming major release.", - DeprecationWarning, - ) +__version__ = "4.0.0" logging.getLogger(__name__).addHandler(NullHandler()) diff --git a/vcr/cassette.py b/vcr/cassette.py index bb29109..662c2d2 100644 --- a/vcr/cassette.py +++ b/vcr/cassette.py @@ -1,4 +1,5 @@ import collections +import contextlib import copy import sys import inspect @@ -6,13 +7,13 @@ import logging import wrapt -from .compat import contextlib from .errors import UnhandledHTTPRequestError from .matchers import requests_match, uri, method, get_matchers_results from .patch import CassettePatcherBuilder from .serializers import yamlserializer from .persisters.filesystem import FilesystemPersister from .util import partition_dict +from ._handle_coroutine import handle_coroutine try: from asyncio import iscoroutinefunction @@ -22,18 +23,10 @@ except ImportError: return False -if sys.version_info[:2] >= (3, 5): - from ._handle_coroutine import handle_coroutine -else: - - def handle_coroutine(*args, **kwags): - raise NotImplementedError("Not implemented on Python 2") - - log = logging.getLogger(__name__) -class CassetteContextDecorator(object): +class CassetteContextDecorator: """Context manager/decorator that handles installing the cassette and removing cassettes. @@ -159,7 +152,7 @@ class CassetteContextDecorator(object): return new_args_getter -class Cassette(object): +class Cassette: """A container for recorded requests and responses""" @classmethod diff --git a/vcr/compat.py b/vcr/compat.py deleted file mode 100644 index 1480928..0000000 --- a/vcr/compat.py +++ /dev/null @@ -1,14 +0,0 @@ -try: - from unittest import mock -except ImportError: - import mock - -try: - import contextlib -except ImportError: - import contextlib2 as contextlib -else: - if not hasattr(contextlib, "ExitStack"): - import contextlib2 as contextlib - -__all__ = ["mock", "contextlib"] diff --git a/vcr/config.py b/vcr/config.py index 5c63837..ce41978 100644 --- a/vcr/config.py +++ b/vcr/config.py @@ -1,9 +1,6 @@ import copy -try: - from collections import abc as collections_abc # only works on python 3.3+ -except ImportError: - import collections as collections_abc +from collections import abc as collections_abc import functools import inspect import os @@ -19,7 +16,7 @@ from . import matchers from . import filters -class VCR(object): +class VCR: @staticmethod def is_test_method(method_name, function): return method_name.startswith("test") and isinstance(function, types.FunctionType) @@ -102,7 +99,7 @@ class VCR(object): return matchers def use_cassette(self, path=None, **kwargs): - if path is not None and not isinstance(path, six.string_types): + if path is not None and not isinstance(path, str): function = path # Assume this is an attempt to decorate a function return self._use_cassette(**kwargs)(function) @@ -251,4 +248,5 @@ class VCR(object): def test_case(self, predicate=None): predicate = predicate or self.is_test_method + # TODO: Remove this reference to `six` in favor of the Python3 equivalent return six.with_metaclass(auto_decorate(self.use_cassette, predicate)) diff --git a/vcr/errors.py b/vcr/errors.py index 92926cc..4a2877e 100644 --- a/vcr/errors.py +++ b/vcr/errors.py @@ -3,7 +3,7 @@ class CannotOverwriteExistingCassetteException(Exception): self.cassette = kwargs["cassette"] self.failed_request = kwargs["failed_request"] message = self._get_message(kwargs["cassette"], kwargs["failed_request"]) - super(CannotOverwriteExistingCassetteException, self).__init__(message) + super().__init__(message) @staticmethod def _get_message(cassette, failed_request): diff --git a/vcr/filters.py b/vcr/filters.py index 83af946..62254ed 100644 --- a/vcr/filters.py +++ b/vcr/filters.py @@ -1,5 +1,5 @@ -from six import BytesIO, text_type -from six.moves.urllib.parse import urlparse, urlencode, urlunparse +from io import BytesIO +from urllib.parse import urlparse, urlencode, urlunparse import copy import json import zlib @@ -8,13 +8,11 @@ from .util import CaseInsensitiveDict def replace_headers(request, replacements): - """ - Replace headers in request according to replacements. The replacements - should be a list of (key, value) pairs where the value can be any of: - 1. A simple replacement string value. - 2. None to remove the given header. - 3. A callable which accepts (key, value, request) and returns a string - value or None. + """Replace headers in request according to replacements. + The replacements should be a list of (key, value) pairs where the value can be any of: + 1. A simple replacement string value. + 2. None to remove the given header. + 3. A callable which accepts (key, value, request) and returns a string value or None. """ new_headers = request.headers.copy() for k, rv in replacements: @@ -37,10 +35,9 @@ def remove_headers(request, headers_to_remove): def replace_query_parameters(request, replacements): - """ - Replace query parameters in request according to replacements. The - replacements should be a list of (key, value) pairs where the value can be - any of: + """Replace query parameters in request according to replacements. + + The replacements should be a list of (key, value) pairs where the value can be any of: 1. A simple replacement string value. 2. None to remove the given header. 3. A callable which accepts (key, value, request) and returns a string @@ -73,10 +70,9 @@ def remove_query_parameters(request, query_parameters_to_remove): def replace_post_data_parameters(request, replacements): - """ - Replace post data in request--either form data or json--according to - replacements. The replacements should be a list of (key, value) pairs where - the value can be any of: + """Replace post data in request--either form data or json--according to replacements. + + The replacements should be a list of (key, value) pairs where the value can be any of: 1. A simple replacement string value. 2. None to remove the given header. 3. A callable which accepts (key, value, request) and returns a string @@ -99,7 +95,7 @@ def replace_post_data_parameters(request, replacements): json_data[k] = rv request.body = json.dumps(json_data).encode("utf-8") else: - if isinstance(request.body, text_type): + if isinstance(request.body, str): request.body = request.body.encode("utf-8") splits = [p.partition(b"=") for p in request.body.split(b"&")] new_splits = [] diff --git a/vcr/matchers.py b/vcr/matchers.py index eabd61f..3dd4872 100644 --- a/vcr/matchers.py +++ b/vcr/matchers.py @@ -1,5 +1,6 @@ import json -from six.moves import urllib, xmlrpc_client +import urllib +import xmlrpc.client from .util import read_body import logging @@ -77,7 +78,7 @@ _checker_transformer_pairs = ( lambda body: urllib.parse.parse_qs(body.decode("ascii")), ), (_header_checker("application/json"), _transform_json), - (lambda request: _xml_header_checker(request) and _xmlrpc_header_checker(request), xmlrpc_client.loads), + (lambda request: _xml_header_checker(request) and _xmlrpc_header_checker(request), xmlrpc.client.loads), ) diff --git a/vcr/patch.py b/vcr/patch.py index ae49f7b..40b462a 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -1,10 +1,11 @@ """Utilities for patching in cassettes""" +import contextlib import functools import itertools +import mock -from .compat import contextlib, mock from .stubs import VCRHTTPConnection, VCRHTTPSConnection -from six.moves import http_client as httplib +import http.client as httplib import logging @@ -93,7 +94,7 @@ else: _AiohttpClientSessionRequest = aiohttp.client.ClientSession._request -class CassettePatcherBuilder(object): +class CassettePatcherBuilder: def _build_patchers_from_mock_triples_decorator(function): @functools.wraps(function) def wrapped(self, *args, **kwargs): @@ -358,7 +359,7 @@ class CassettePatcherBuilder(object): ) -class ConnectionRemover(object): +class ConnectionRemover: def __init__(self, connection_class): self._connection_class = connection_class self._connection_pool_to_connections = {} diff --git a/vcr/persisters/filesystem.py b/vcr/persisters/filesystem.py index e8d8270..45e9614 100644 --- a/vcr/persisters/filesystem.py +++ b/vcr/persisters/filesystem.py @@ -4,13 +4,13 @@ import os from ..serialize import serialize, deserialize -class FilesystemPersister(object): +class FilesystemPersister: @classmethod def load_cassette(cls, cassette_path, serializer): try: with open(cassette_path) as f: cassette_content = f.read() - except IOError: + except OSError: raise ValueError("Cassette not found.") cassette = deserialize(cassette_content, serializer) return cassette diff --git a/vcr/request.py b/vcr/request.py index d96517f..68073f0 100644 --- a/vcr/request.py +++ b/vcr/request.py @@ -1,13 +1,13 @@ import warnings -from six import BytesIO, text_type -from six.moves.urllib.parse import urlparse, parse_qsl +from io import BytesIO +from urllib.parse import urlparse, parse_qsl from .util import CaseInsensitiveDict import logging log = logging.getLogger(__name__) -class Request(object): +class Request: """ VCR's representation of a request. """ @@ -39,7 +39,7 @@ class Request(object): @body.setter def body(self, value): - if isinstance(value, text_type): + if isinstance(value, str): value = value.encode("utf-8") self._body = value @@ -136,4 +136,4 @@ class HeadersDict(CaseInsensitiveDict): if old: key = old[0] - super(HeadersDict, self).__setitem__(key, value) + super().__setitem__(key, value) diff --git a/vcr/serializers/compat.py b/vcr/serializers/compat.py index bbc201c..0ab358d 100644 --- a/vcr/serializers/compat.py +++ b/vcr/serializers/compat.py @@ -1,6 +1,3 @@ -import six - - def convert_to_bytes(resp): resp = convert_body_to_bytes(resp) return resp @@ -24,7 +21,7 @@ def convert_body_to_bytes(resp): http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support """ try: - if resp["body"]["string"] is not None and not isinstance(resp["body"]["string"], six.binary_type): + if resp["body"]["string"] is not None and not isinstance(resp["body"]["string"], bytes): resp["body"]["string"] = resp["body"]["string"].encode("utf-8") except (KeyError, TypeError, UnicodeEncodeError): # The thing we were converting either wasn't a dictionary or didn't @@ -44,7 +41,7 @@ def _convert_string_to_unicode(string): result = string try: - if string is not None and not isinstance(string, six.text_type): + if string is not None and not isinstance(string, str): result = string.decode("utf-8") except (TypeError, UnicodeDecodeError, AttributeError): # Sometimes the string actually is binary or StringIO object, diff --git a/vcr/stubs/__init__.py b/vcr/stubs/__init__.py index 37ab137..0b4811c 100644 --- a/vcr/stubs/__init__.py +++ b/vcr/stubs/__init__.py @@ -1,9 +1,12 @@ """Stubs for patching HTTP and HTTPS requests""" import logging -import six -from six.moves.http_client import HTTPConnection, HTTPSConnection, HTTPResponse -from six import BytesIO +from http.client import ( + HTTPConnection, + HTTPSConnection, + HTTPResponse, +) +from io import BytesIO from vcr.request import Request from vcr.errors import CannotOverwriteExistingCassetteException from . import compat @@ -11,7 +14,7 @@ from . import compat log = logging.getLogger(__name__) -class VCRFakeSocket(object): +class VCRFakeSocket: """ A socket that doesn't do anything! Used when playing back cassettes, when there @@ -143,7 +146,7 @@ class VCRHTTPResponse(HTTPResponse): return self._content.readable() -class VCRConnection(object): +class VCRConnection: # A reference to the cassette that's currently being patched in cassette = None @@ -296,8 +299,7 @@ class VCRConnection(object): self.real_connection.sock = value def __init__(self, *args, **kwargs): - if six.PY3: - kwargs.pop("strict", None) # apparently this is gone in py3 + kwargs.pop("strict", None) # apparently this is gone in py3 # need to temporarily reset here because the real connection # inherits from the thing that we are mocking out. Take out @@ -328,7 +330,7 @@ class VCRConnection(object): # we're setting the real_connection itself for the first time pass - super(VCRConnection, self).__setattr__(name, value) + super().__setattr__(name, value) def __getattr__(self, name): """ @@ -340,7 +342,7 @@ class VCRConnection(object): # we're setting the real_connection itself for the first time return getattr(self.real_connection, name) - return super(VCRConnection, self).__getattr__(name) + return super().__getattr__(name) for k, v in HTTPConnection.__dict__.items(): diff --git a/vcr/stubs/aiohttp_stubs/__init__.py b/vcr/stubs/aiohttp_stubs/__init__.py index 2301334..9bf1313 100644 --- a/vcr/stubs/aiohttp_stubs/__init__.py +++ b/vcr/stubs/aiohttp_stubs/__init__.py @@ -1,6 +1,4 @@ """Stubs for aiohttp HTTP clients""" -from __future__ import absolute_import - import asyncio import functools import logging diff --git a/vcr/stubs/boto3_stubs.py b/vcr/stubs/boto3_stubs.py index 1a0e4a2..a4d349a 100644 --- a/vcr/stubs/boto3_stubs.py +++ b/vcr/stubs/boto3_stubs.py @@ -1,6 +1,4 @@ """Stubs for boto3""" -import six - try: # boto using awsrequest from botocore.awsrequest import AWSHTTPConnection as HTTPConnection @@ -26,8 +24,7 @@ class VCRRequestsHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection): _baseclass = VerifiedHTTPSConnection def __init__(self, *args, **kwargs): - if six.PY3: - kwargs.pop("strict", None) # apparently this is gone in py3 + kwargs.pop("strict", None) # need to temporarily reset here because the real connection # inherits from the thing that we are mocking out. Take out diff --git a/vcr/stubs/compat.py b/vcr/stubs/compat.py index 938d651..61bdb50 100644 --- a/vcr/stubs/compat.py +++ b/vcr/stubs/compat.py @@ -1,11 +1,5 @@ -import six -from six import BytesIO -from six.moves.http_client import HTTPMessage - -try: - import http.client -except ImportError: - pass +from io import BytesIO +import http.client """ @@ -15,10 +9,7 @@ layer that tries to cope with this move. def get_header(message, name): - if six.PY3: - return message.getallmatchingheaders(name) - else: - return message.getheader(name) + return message.getallmatchingheaders(name) def get_header_items(message): @@ -29,16 +20,8 @@ def get_header_items(message): def get_headers(message): for key in set(message.keys()): - if six.PY3: - yield key, message.get_all(key) - else: - yield key, message.getheaders(key) + yield key, message.get_all(key) def get_httpmessage(headers): - if six.PY3: - return http.client.parse_headers(BytesIO(headers)) - msg = HTTPMessage(BytesIO(headers)) - msg.fp.seek(0) - msg.readheaders() - return msg + return http.client.parse_headers(BytesIO(headers)) diff --git a/vcr/stubs/tornado_stubs.py b/vcr/stubs/tornado_stubs.py index c6ecc2e..b482fd1 100644 --- a/vcr/stubs/tornado_stubs.py +++ b/vcr/stubs/tornado_stubs.py @@ -1,8 +1,6 @@ """Stubs for tornado HTTP clients""" -from __future__ import absolute_import - import functools -from six import BytesIO +from io import BytesIO from tornado import httputil from tornado.httpclient import HTTPResponse diff --git a/vcr/util.py b/vcr/util.py index 6fc9b2a..5c53e24 100644 --- a/vcr/util.py +++ b/vcr/util.py @@ -107,12 +107,12 @@ def auto_decorate(decorator, predicate=lambda name, value: isinstance(value, typ class DecorateAll(type): def __setattr__(cls, attribute, value): - return super(DecorateAll, cls).__setattr__(attribute, maybe_decorate(attribute, value)) + return super().__setattr__(attribute, maybe_decorate(attribute, value)) def __new__(cls, name, bases, attributes_dict): new_attributes_dict = { attribute: maybe_decorate(attribute, value) for attribute, value in attributes_dict.items() } - return super(DecorateAll, cls).__new__(cls, name, bases, new_attributes_dict) + return super().__new__(cls, name, bases, new_attributes_dict) return DecorateAll