[Python-modules-commits] [betamax] 02/08: Import betamax_0.7.0.orig.tar.gz

Daniele Tricoli eriol-guest at moszumanska.debian.org
Sat May 28 21:06:22 UTC 2016


This is an automated email from the git hooks/post-receive script.

eriol-guest pushed a commit to branch master
in repository betamax.

commit c06efe6e5043cfd4b46c4dcb69572f0dfd0ac3b7
Author: Daniele Tricoli <eriol at mornie.org>
Date:   Sat May 28 12:47:44 2016 +0200

    Import betamax_0.7.0.orig.tar.gz
---
 AUTHORS.rst                                        |   1 +
 HISTORY.rst                                        |  26 ++
 MANIFEST.in                                        |   4 +-
 PKG-INFO                                           |  47 ++-
 betamax.egg-info/PKG-INFO                          |  47 ++-
 betamax.egg-info/SOURCES.txt                       |  55 ++-
 betamax.egg-info/top_level.txt                     |   1 -
 betamax/__init__.py                                |   9 +-
 betamax/adapter.py                                 |  92 +++-
 betamax/cassette/__init__.py                       |   5 +-
 betamax/cassette/cassette.py                       | 125 ++++--
 betamax/cassette/interaction.py                    |  56 ++-
 betamax/configure.py                               |  48 ++-
 betamax/exceptions.py                              |   4 +
 betamax/fixtures/pytest.py                         |  36 +-
 betamax/{cassette => }/headers.py                  |   0
 betamax/matchers/__init__.py                       |   5 +-
 betamax/matchers/body.py                           |   2 +-
 betamax/matchers/digest_auth.py                    |   2 +-
 betamax/matchers/headers.py                        |   2 +-
 betamax/matchers/query.py                          |  26 +-
 betamax/{cassette => }/mock_response.py            |   2 +-
 betamax/options.py                                 |   9 +-
 betamax/serializers/__init__.py                    |   5 +-
 betamax/serializers/proxy.py                       |   7 +
 betamax/{cassette => }/util.py                     |   8 +-
 docs/Makefile                                      | 130 ++++++
 docs/api.rst                                       | 197 +++++++++
 docs/cassettes.rst                                 | 151 +++++++
 docs/conf.py                                       | 249 +++++++++++
 docs/configuring.rst                               | 348 ++++++++++++++++
 docs/implementation_details.rst                    |  36 ++
 docs/index.rst                                     |  33 ++
 docs/integrations.rst                              | 137 ++++++
 docs/introduction.rst                              | 130 ++++++
 docs/long_term_usage.rst                           | 148 +++++++
 docs/matchers.rst                                  | 117 ++++++
 docs/record_modes.rst                              | 129 ++++++
 docs/serializers.rst                               |  49 +++
 docs/third_party_packages.rst                      | 167 ++++++++
 docs/usage_patterns.rst                            | 120 ++++++
 setup.cfg                                          |   4 +-
 setup.py                                           |   8 +-
 tests/__init__.py                                  |   0
 tests/cassettes/GitHub_create_issue.json           |   1 +
 tests/cassettes/GitHub_emojis.json                 |   1 +
 .../global_preserve_exact_body_bytes.json          |   1 +
 tests/cassettes/handles_digest_auth.json           |   1 +
 tests/cassettes/once_record_mode.json              |   1 +
 tests/cassettes/preserve_exact_bytes.json          |   1 +
 tests/cassettes/replay_interactions.json           |   1 +
 tests/cassettes/replay_multiple_times.json         |   1 +
 .../test-multiple-cookies-regression.json          |   1 +
 tests/cassettes/test.json                          |   1 +
 .../test_replays_response_on_right_order.json      |  63 +++
 ...res.TestPyTestFixtures.test_pytest_fixture.json |   1 +
 tests/conftest.py                                  |   8 +
 tests/integration/test_allow_playback_repeats.py   |  27 ++
 tests/integration/test_backwards_compat.py         |   2 +-
 tests/integration/test_hooks.py                    |  62 +++
 tests/integration/test_placeholders.py             |  12 +-
 .../integration/test_preserve_exact_body_bytes.py  |   4 +-
 tests/integration/test_record_modes.py             |  30 +-
 .../test_can_replay_interactions_multiple_times.py |  19 +
 .../test_cassettes_retain_global_configuration.py  |  26 ++
 tests/regression/test_gzip_compression.py          |  35 ++
 .../test_once_prevents_new_interactions.py         |  18 +
 tests/regression/test_works_with_digest_auth.py    |  23 +
 tests/unit/test_adapter.py                         |  37 ++
 tests/unit/test_betamax.py                         |  57 +++
 tests/unit/test_cassette.py                        | 462 +++++++++++++++++++++
 tests/unit/test_configure.py                       |  60 +++
 tests/unit/test_decorator.py                       |  39 ++
 tests/unit/test_fixtures.py                        |  94 +++++
 tests/unit/test_matchers.py                        | 171 ++++++++
 tests/unit/test_options.py                         |  51 +++
 tests/unit/test_recorder.py                        |  76 ++++
 tests/unit/test_replays.py                         |  21 +
 tests/unit/test_serializers.py                     |  42 ++
 79 files changed, 4052 insertions(+), 175 deletions(-)

diff --git a/AUTHORS.rst b/AUTHORS.rst
index 5a640cb..8bbdd19 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -18,3 +18,4 @@ Contributors
 
 - Marc Abramowitz (@msabramo)
 - Bryce Boe <bbzbryce at gmail.com> (@bboe)
+- Alex Richard-Hoyling <@arhoyling)
diff --git a/HISTORY.rst b/HISTORY.rst
index 2dfb05b..f613177 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,6 +1,32 @@
 History
 =======
 
+0.7.0 - 2016-04-29
+------------------
+
+- Add ``before_record`` and ``before_playback`` hooks
+
+- Allow per-cassette placeholders to be merged and override global
+  placeholders
+
+- Fix bug where the ``QueryMatcher`` failed matching on high Unicode points
+
+0.6.0 - 2016-04-12
+------------------
+
+- Add ``betamax_recorder`` pytest fixture
+
+- Change default behaviour to allow duplicate interactions to be recorded in
+  single cassette
+
+- Add ``allow_playback_repeats`` to allow an interaction to be used more than
+  once from a single cassette
+
+- Always return a new ``Response`` object from an Interaction to allow for a
+  streaming response to be usable multiple times
+
+- Remove CI support for Pythons 2.6 and 3.2
+
 0.5.1 - 2015-10-24
 ------------------
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 9da1ed8..114c680 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,7 +2,7 @@ include README.rst
 include LICENSE
 include HISTORY.rst
 include AUTHORS.rst
-recursive-include docs/
-recursive-include tests/
+recursive-include docs Makefile *.py *.rst
+recursive-include tests *.json *.py
 prune *.pyc
 prune docs/_build
diff --git a/PKG-INFO b/PKG-INFO
index f7942fb..8a77cbd 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,24 +1,11 @@
 Metadata-Version: 1.1
 Name: betamax
-Version: 0.5.1
+Version: 0.7.0
 Summary: A VCR imitation for python-requests
 Home-page: https://github.com/sigmavirus24/betamax
 Author: Ian Cordasco
 Author-email: graffatcolmingov at gmail.com
-License: Copyright 2013 Ian Cordasco
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
+License: Apache 2.0
 Description: betamax
         =======
         
@@ -111,6 +98,32 @@ Description: betamax
         History
         =======
         
+        0.7.0 - 2016-04-29
+        ------------------
+        
+        - Add ``before_record`` and ``before_playback`` hooks
+        
+        - Allow per-cassette placeholders to be merged and override global
+          placeholders
+        
+        - Fix bug where the ``QueryMatcher`` failed matching on high Unicode points
+        
+        0.6.0 - 2016-04-12
+        ------------------
+        
+        - Add ``betamax_recorder`` pytest fixture
+        
+        - Change default behaviour to allow duplicate interactions to be recorded in
+          single cassette
+        
+        - Add ``allow_playback_repeats`` to allow an interaction to be used more than
+          once from a single cassette
+        
+        - Always return a new ``Response`` object from an Interaction to allow for a
+          streaming response to be usable multiple times
+        
+        - Remove CI support for Pythons 2.6 and 3.2
+        
         0.5.1 - 2015-10-24
         ------------------
         
@@ -283,9 +296,9 @@ Classifier: License :: OSI Approved
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
diff --git a/betamax.egg-info/PKG-INFO b/betamax.egg-info/PKG-INFO
index f7942fb..8a77cbd 100644
--- a/betamax.egg-info/PKG-INFO
+++ b/betamax.egg-info/PKG-INFO
@@ -1,24 +1,11 @@
 Metadata-Version: 1.1
 Name: betamax
-Version: 0.5.1
+Version: 0.7.0
 Summary: A VCR imitation for python-requests
 Home-page: https://github.com/sigmavirus24/betamax
 Author: Ian Cordasco
 Author-email: graffatcolmingov at gmail.com
-License: Copyright 2013 Ian Cordasco
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
+License: Apache 2.0
 Description: betamax
         =======
         
@@ -111,6 +98,32 @@ Description: betamax
         History
         =======
         
+        0.7.0 - 2016-04-29
+        ------------------
+        
+        - Add ``before_record`` and ``before_playback`` hooks
+        
+        - Allow per-cassette placeholders to be merged and override global
+          placeholders
+        
+        - Fix bug where the ``QueryMatcher`` failed matching on high Unicode points
+        
+        0.6.0 - 2016-04-12
+        ------------------
+        
+        - Add ``betamax_recorder`` pytest fixture
+        
+        - Change default behaviour to allow duplicate interactions to be recorded in
+          single cassette
+        
+        - Add ``allow_playback_repeats`` to allow an interaction to be used more than
+          once from a single cassette
+        
+        - Always return a new ``Response`` object from an Interaction to allow for a
+          streaming response to be usable multiple times
+        
+        - Remove CI support for Pythons 2.6 and 3.2
+        
         0.5.1 - 2015-10-24
         ------------------
         
@@ -283,9 +296,9 @@ Classifier: License :: OSI Approved
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
diff --git a/betamax.egg-info/SOURCES.txt b/betamax.egg-info/SOURCES.txt
index 492e7c2..9122c30 100644
--- a/betamax.egg-info/SOURCES.txt
+++ b/betamax.egg-info/SOURCES.txt
@@ -10,8 +10,11 @@ betamax/adapter.py
 betamax/configure.py
 betamax/decorator.py
 betamax/exceptions.py
+betamax/headers.py
+betamax/mock_response.py
 betamax/options.py
 betamax/recorder.py
+betamax/util.py
 betamax.egg-info/PKG-INFO
 betamax.egg-info/SOURCES.txt
 betamax.egg-info/dependency_links.txt
@@ -20,10 +23,7 @@ betamax.egg-info/requires.txt
 betamax.egg-info/top_level.txt
 betamax/cassette/__init__.py
 betamax/cassette/cassette.py
-betamax/cassette/headers.py
 betamax/cassette/interaction.py
-betamax/cassette/mock_response.py
-betamax/cassette/util.py
 betamax/fixtures/__init__.py
 betamax/fixtures/pytest.py
 betamax/fixtures/unittest.py
@@ -41,12 +41,59 @@ betamax/serializers/__init__.py
 betamax/serializers/base.py
 betamax/serializers/json_serializer.py
 betamax/serializers/proxy.py
+docs/Makefile
+docs/api.rst
+docs/cassettes.rst
+docs/conf.py
+docs/configuring.rst
+docs/implementation_details.rst
+docs/index.rst
+docs/integrations.rst
+docs/introduction.rst
+docs/long_term_usage.rst
+docs/matchers.rst
+docs/record_modes.rst
+docs/serializers.rst
+docs/third_party_packages.rst
+docs/usage_patterns.rst
+tests/__init__.py
+tests/conftest.py
+tests/cassettes/GitHub_create_issue.json
+tests/cassettes/GitHub_emojis.json
+tests/cassettes/global_preserve_exact_body_bytes.json
+tests/cassettes/handles_digest_auth.json
+tests/cassettes/once_record_mode.json
+tests/cassettes/preserve_exact_bytes.json
+tests/cassettes/replay_interactions.json
+tests/cassettes/replay_multiple_times.json
+tests/cassettes/test-multiple-cookies-regression.json
+tests/cassettes/test.json
+tests/cassettes/test_replays_response_on_right_order.json
+tests/cassettes/tests.integration.test_fixtures.TestPyTestFixtures.test_pytest_fixture.json
 tests/integration/__init__.py
 tests/integration/helper.py
+tests/integration/test_allow_playback_repeats.py
 tests/integration/test_backwards_compat.py
 tests/integration/test_fixtures.py
+tests/integration/test_hooks.py
 tests/integration/test_multiple_cookies.py
 tests/integration/test_placeholders.py
 tests/integration/test_preserve_exact_body_bytes.py
 tests/integration/test_record_modes.py
-tests/integration/test_unicode.py
\ No newline at end of file
+tests/integration/test_unicode.py
+tests/regression/test_can_replay_interactions_multiple_times.py
+tests/regression/test_cassettes_retain_global_configuration.py
+tests/regression/test_gzip_compression.py
+tests/regression/test_once_prevents_new_interactions.py
+tests/regression/test_works_with_digest_auth.py
+tests/unit/test_adapter.py
+tests/unit/test_betamax.py
+tests/unit/test_cassette.py
+tests/unit/test_configure.py
+tests/unit/test_decorator.py
+tests/unit/test_fixtures.py
+tests/unit/test_matchers.py
+tests/unit/test_options.py
+tests/unit/test_recorder.py
+tests/unit/test_replays.py
+tests/unit/test_serializers.py
\ No newline at end of file
diff --git a/betamax.egg-info/top_level.txt b/betamax.egg-info/top_level.txt
index 80691e6..764a889 100644
--- a/betamax.egg-info/top_level.txt
+++ b/betamax.egg-info/top_level.txt
@@ -1,2 +1 @@
 betamax
-tests
diff --git a/betamax/__init__.py b/betamax/__init__.py
index 19835a3..700165b 100644
--- a/betamax/__init__.py
+++ b/betamax/__init__.py
@@ -1,10 +1,11 @@
 """
-betamax
+betamax.
+
 =======
 
-See http://betamax.rtfd.org/ for documentation.
+See https://betamax.readthedocs.io/ for documentation.
 
-:copyright: (c) 2013 by Ian Cordasco
+:copyright: (c) 2013-2016 by Ian Cordasco
 :license: Apache 2.0, see LICENSE for more details
 
 """
@@ -21,5 +22,5 @@ __author__ = 'Ian Cordasco'
 __copyright__ = 'Copyright 2013-2014 Ian Cordasco'
 __license__ = 'Apache 2.0'
 __title__ = 'betamax'
-__version__ = '0.5.1'
+__version__ = '0.7.0'
 __version_info__ = tuple(int(i) for i in __version__.split('.'))
diff --git a/betamax/adapter.py b/betamax/adapter.py
index 7160ba1..47425ba 100644
--- a/betamax/adapter.py
+++ b/betamax/adapter.py
@@ -1,13 +1,21 @@
+"""
+betamax.adapter.
+
+==============
+
+adapter for betamax
+"""
 import os
 
-from .cassette import Cassette
+from . import cassette
 from .exceptions import BetamaxError
 from datetime import datetime, timedelta
 from requests.adapters import BaseAdapter, HTTPAdapter
 
+_SENTINEL = object()
 
-class BetamaxAdapter(BaseAdapter):
 
+class BetamaxAdapter(BaseAdapter):
     """This object is an implementation detail of the library.
 
     It is not meant to be a public API and is not exported as such.
@@ -24,43 +32,68 @@ class BetamaxAdapter(BaseAdapter):
         self.options = {}
 
     def cassette_exists(self):
+        """Check if cassette exists on file system.
+
+        :returns: bool -- True if exists, False otherwise
+        """
         if self.cassette_name and os.path.exists(self.cassette_name):
             return True
         return False
 
     def close(self):
+        """Propagate close to underlying adapter."""
         self.http_adapter.close()
 
     def eject_cassette(self):
+        """Eject currently loaded cassette."""
         if self.cassette:
             self.cassette.eject()
         self.cassette = None  # Allow self.cassette to be garbage-collected
 
     def load_cassette(self, cassette_name, serialize, options):
+        """Load cassette.
+
+        Loads a previously serialized http response as a cassette
+
+        :param str cassette_name: (required), name of cassette
+        :param str serialize: (required), type of serialization i.e 'json'
+        :options dict options: (required), options for cassette
+        """
         self.cassette_name = cassette_name
         self.serialize = serialize
         self.options.update(options.items())
-        placeholders = self.options.get('placeholders', [])
+        placeholders = self.options.get('placeholders', {})
+        cassette_options = {}
 
-        default_options = Cassette.default_cassette_options
+        default_options = cassette.Cassette.default_cassette_options
 
         match_requests_on = self.options.get(
             'match_requests_on', default_options['match_requests_on']
             )
 
-        preserve_exact_body_bytes = self.options.get(
+        cassette_options['preserve_exact_body_bytes'] = self.options.get(
             'preserve_exact_body_bytes',
             )
 
-        self.cassette = Cassette(
+        cassette_options['allow_playback_repeats'] = self.options.get(
+            'allow_playback_repeats'
+            )
+
+        cassette_options['record_mode'] = self.options.get('record')
+
+        for option, value in list(cassette_options.items()):
+            if value is None:
+                cassette_options.pop(option)
+
+        self.cassette = cassette.Cassette(
             cassette_name, serialize, placeholders=placeholders,
-            record_mode=self.options.get('record'),
-            preserve_exact_body_bytes=preserve_exact_body_bytes,
-            cassette_library_dir=self.options.get('cassette_library_dir')
+            cassette_library_dir=self.options.get('cassette_library_dir'),
+            **cassette_options
             )
 
         if 'record' in self.options:
             self.cassette.record_mode = self.options['record']
+
         self.cassette.match_options = match_requests_on
 
         re_record_interval = timedelta.max
@@ -73,22 +106,28 @@ class BetamaxAdapter(BaseAdapter):
 
     def send(self, request, stream=False, timeout=None, verify=True,
              cert=None, proxies=None):
+        """Send request.
+
+        :param request request: request
+        :returns: A Response object
+        """
         interaction = None
+        current_cassette = self.cassette
 
-        if not self.cassette:
+        if not current_cassette:
             raise BetamaxError('No cassette was specified or found.')
 
-        if self.cassette.interactions:
-            interaction = self.cassette.find_match(request)
+        if current_cassette.interactions:
+            interaction = current_cassette.find_match(request)
 
-        if not interaction and self.cassette.is_recording():
+        if not interaction and current_cassette.is_recording():
             interaction = self.send_and_record(
                 request, stream, timeout, verify, cert, proxies
                 )
 
         if not interaction:
             raise BetamaxError(unhandled_request_message(request,
-                                                         self.cassette))
+                                                         current_cassette))
 
         resp = interaction.as_response()
         resp.connection = self
@@ -96,15 +135,35 @@ class BetamaxAdapter(BaseAdapter):
 
     def send_and_record(self, request, stream=False, timeout=None,
                         verify=True, cert=None, proxies=None):
+        """Send request and record response.
+
+        The response will be serialized and saved to a
+        cassette which can be replayed in the future.
+
+        :param request request: request
+        :param bool stream: (optional) defer download until content is accessed
+        :param float timeout: (optional) time to wait for a response
+        :param bool verify: (optional) verify SSL certificate
+        :param str cert: (optional) path to SSL client
+        :param proxies dict: (optional) mapping protocol to URL of the proxy
+        :return: Interaction
+        :rtype: class:`betamax.cassette.Interaction`
+        """
         adapter = self.find_adapter(request.url)
         response = adapter.send(
             request, stream=True, timeout=timeout, verify=verify,
             cert=cert, proxies=proxies
             )
-        self.cassette.save_interaction(response, request)
-        return self.cassette.interactions[-1]
+        return self.cassette.save_interaction(response, request)
 
     def find_adapter(self, url):
+        """Find adapter.
+
+        Searches for an existing adapter where the url and prefix match.
+
+        :param url str: (required) url of the adapter
+        :returns: betamax adapter
+        """
         for (prefix, adapter) in self.old_adapters.items():
 
             if url.lower().startswith(prefix):
@@ -125,6 +184,7 @@ The settings on the cassette are:
 
 
 def unhandled_request_message(request, cassette):
+    """Generate exception for unhandled requests."""
     return UNHANDLED_REQUEST_EXCEPTION.format(
         url=request.url, cassette_file_path=cassette.cassette_name,
         cassette_record_mode=cassette.record_mode,
diff --git a/betamax/cassette/__init__.py b/betamax/cassette/__init__.py
index a3fddcb..7a7e366 100644
--- a/betamax/cassette/__init__.py
+++ b/betamax/cassette/__init__.py
@@ -1,5 +1,4 @@
-from .cassette import Cassette
+from .cassette import Cassette, dispatch_hooks
 from .interaction import Interaction
-from .mock_response import MockHTTPResponse
 
-__all__ = ('Cassette', 'Interaction', 'MockHTTPResponse')
+__all__ = ('Cassette', 'Interaction', 'dispatch_hooks')
diff --git a/betamax/cassette/cassette.py b/betamax/cassette/cassette.py
index c3da3d7..7e5da48 100644
--- a/betamax/cassette/cassette.py
+++ b/betamax/cassette/cassette.py
@@ -1,14 +1,16 @@
 # -*- coding: utf-8 -*-
-from .interaction import Interaction
-from .util import (_option_from, serialize_prepared_request,
-                   serialize_response, timestamp)
-from betamax.matchers import matcher_registry
-from betamax.serializers import serializer_registry, SerializerProxy
+import collections
 from datetime import datetime
 from functools import partial
-
 import os.path
 
+from .interaction import Interaction
+
+from .. import matchers
+from .. import serializers
+from betamax.util import (_option_from, serialize_prepared_request,
+                          serialize_response, timestamp)
+
 
 class Cassette(object):
 
@@ -17,9 +19,12 @@ class Cassette(object):
         'match_requests_on': ['method', 'uri'],
         're_record_interval': None,
         'placeholders': [],
-        'preserve_exact_body_bytes': False
+        'preserve_exact_body_bytes': False,
+        'allow_playback_repeats': False,
     }
 
+    hooks = collections.defaultdict(list)
+
     def __init__(self, cassette_name, serialization_format, **kwargs):
         #: Short name of the cassette
         self.cassette_name = cassette_name
@@ -32,22 +37,27 @@ class Cassette(object):
         self.record_mode = _option_from('record_mode', kwargs, defaults)
 
         # Retrieve the serializer for this cassette
-        self.serializer = SerializerProxy.find(
+        self.serializer = serializers.SerializerProxy.find(
             serialization_format, kwargs.get('cassette_library_dir'),
             cassette_name
             )
         self.cassette_path = self.serializer.cassette_path
 
         # Determine which placeholders to use
-        self.placeholders = kwargs.get('placeholders')
-        if not self.placeholders:
-            self.placeholders = defaults['placeholders']
+        default_placeholders = defaults['placeholders'][:]
+        cassette_placeholders = kwargs.get('placeholders', [])
+        self.placeholders = merge_placeholder_lists(default_placeholders,
+                                                    cassette_placeholders)
 
         # Determine whether to preserve exact body bytes
         self.preserve_exact_body_bytes = _option_from(
             'preserve_exact_body_bytes', kwargs, defaults
             )
 
+        self.allow_playback_repeats = _option_from(
+            'allow_playback_repeats', kwargs, defaults
+            )
+
         # Initialize the interactions
         self.interactions = []
 
@@ -66,7 +76,9 @@ class Cassette(object):
         if record_mode in ['once', 'all', 'new_episodes']:
             recording = True
 
-        serializer = serializer_registry.get(serialize_with)
+        serializer = serializers.serializer_registry.get(
+            serialize_with
+        )
         if not serializer:
             raise ValueError(
                 'Serializer {0} is not registered with Betamax'.format(
@@ -104,20 +116,37 @@ class Cassette(object):
         ``use_cassette`` and passes in the request currently in progress.
 
         :param request: ``requests.PreparedRequest``
-        :returns: :class:`Interaction <Interaction>`
+        :returns: :class:`~betamax.cassette.Interaction`
         """
+        # if we are recording, do not filter by match
+        if self.is_recording() and self.record_mode != 'all':
+            return None
+
         opts = self.match_options
         # Curry those matchers
-        matchers = [partial(matcher_registry[o].match, request) for o in opts]
-
-        for i in self.interactions:
-            if i.match(matchers):  # If the interaction matches everything
-                if self.record_mode == 'all':
-                    # If we're recording everything and there's a matching
-                    # interaction we want to overwrite it, so we remove it.
-                    self.interactions.remove(i)
-                    break
-                return i
+        curried_matchers = [
+            partial(matchers.matcher_registry[o].match, request)
+            for o in opts
+        ]
+
+        for interaction in self.interactions:
+            if not interaction.match(curried_matchers):
+                continue
+
+            if interaction.used or interaction.ignored:
+                continue
+
+            # If the interaction matches everything
+            if self.record_mode == 'all':
+                # If we're recording everything and there's a matching
+                # interaction we want to overwrite it, so we remove it.
+                self.interactions.remove(interaction)
+                break
+
+            # set interaction as used before returning
+            if not self.allow_playback_repeats:
+                interaction.used = True
+            return interaction
 
         # No matches. So sad.
         return None
@@ -142,16 +171,20 @@ class Cassette(object):
         self.interactions = [Interaction(i) for i in interactions]
 
         for i in self.interactions:
-            i.replace_all(self.placeholders, ('placeholder', 'replace'))
-            i.deserialize()  # this needs to happen *after* replace_all
+            dispatch_hooks('before_playback', i, self)
+            i.replace_all(self.placeholders, False)
 
     def sanitize_interactions(self):
         for i in self.interactions:
-            i.replace_all(self.placeholders)
+            i.replace_all(self.placeholders, True)
 
     def save_interaction(self, response, request):
-        interaction = self.serialize_interaction(response, request)
-        self.interactions.append(Interaction(interaction, response))
+        serialized_data = self.serialize_interaction(response, request)
+        interaction = Interaction(serialized_data, response)
+        dispatch_hooks('before_record', interaction, self)
+        if not interaction.ignored:  # If a hook caused this to be ignored
+            self.interactions.append(interaction)
+        return interaction
 
     def serialize_interaction(self, response, request):
         return {
@@ -172,7 +205,41 @@ class Cassette(object):
         self.sanitize_interactions()
 
         cassette_data = {
-            'http_interactions': [i.json for i in self.interactions],
+            'http_interactions': [i.data for i in self.interactions],
             'recorded_with': 'betamax/{0}'.format(__version__)
         }
         self.serializer.serialize(cassette_data)
+
+
+class Placeholder(collections.namedtuple('Placeholder',
+                                         'placeholder replace')):
+    """Encapsulate some logic about Placeholders."""
+
+    @classmethod
+    def from_dict(cls, dictionary):
+        return cls(**dictionary)
+
+    def unpack(self, serializing):
+        if serializing:
+            return self.replace, self.placeholder
+        else:
+            return self.placeholder, self.replace
+
+
+def merge_placeholder_lists(defaults, overrides):
+    overrides = [Placeholder.from_dict(override) for override in overrides]
+    overrides_dict = dict((p.placeholder, p) for p in overrides)
+    placeholders = [overrides_dict.pop(p.placeholder, p)
+                    for p in map(Placeholder.from_dict, defaults)]
+    return placeholders + [p for p in overrides
+                           if p.placeholder in overrides_dict]
+
+
+def dispatch_hooks(hook_name, *args):
+    """Dispatch registered hooks."""
+    # Cassette.hooks is a dictionary that defaults to an empty list,
+    # we neither need to check for the presence of hook_name in it, nor
+    # need to worry about whether the return value will be iterable
+    hooks = Cassette.hooks[hook_name]
+    for hook in hooks:
+        hook(*args)
diff --git a/betamax/cassette/interaction.py b/betamax/cassette/interaction.py
index ab2aeba..3a81be9 100644
--- a/betamax/cassette/interaction.py
+++ b/betamax/cassette/interaction.py
@@ -1,8 +1,8 @@
-from .util import (deserialize_response, deserialize_prepared_request,
-                   from_list)
 from requests.cookies import extract_cookies_to_jar
 from datetime import datetime
 
+from betamax import util
+
 
 class Interaction(object):
 
@@ -22,28 +22,39 @@ class Interaction(object):
     """
 
     def __init__(self, interaction, response=None):
-        self.recorded_at = None
-        self.json = interaction
+        self.data = interaction
         self.orig_response = response
-        self.deserialize()
+        self.recorded_response = self.deserialize()
+        self.used = False
+        self.ignored = False
+
+    def ignore(self):
+        """Ignore this interaction.
+
+        This is only to be used from a before_record or a before_playback
+        callback.
+        """
+        self.ignored = True
 
     def as_response(self):
         """Return the Interaction as a Response object."""
+        self.recorded_response = self.deserialize()
         return self.recorded_response
 
+    @property
+    def recorded_at(self):
+        return datetime.strptime(self.data['recorded_at'], '%Y-%m-%dT%H:%M:%S')
+
     def deserialize(self):
         """Turn a serialized interaction into a Response."""
-        r = deserialize_response(self.json['response'])
-        r.request = deserialize_prepared_request(self.json['request'])
+        r = util.deserialize_response(self.data['response'])
+        r.request = util.deserialize_prepared_request(self.data['request'])
         extract_cookies_to_jar(r.cookies, r.request, r.raw)
-        self.recorded_at = datetime.strptime(
-            self.json['recorded_at'], '%Y-%m-%dT%H:%M:%S'
-        )
-        self.recorded_response = r
+        return r
 
     def match(self, matchers):
         """Return whether this interaction is a match."""
-        request = self.json['request']
+        request = self.data['request']
         return all(m(request) for m in matchers)
 
     def replace(self, text_to_replace, placeholder):
@@ -52,22 +63,21 @@ class Interaction(object):
         self.replace_in_body(text_to_replace, placeholder)
         self.replace_in_uri(text_to_replace, placeholder)
 
-    def replace_all(self, replacements, key_order=('replace', 'placeholder')):
+    def replace_all(self, replacements, serializing):
         """Easy way to accept all placeholders registered."""
-        (replace_key, placeholder_key) = key_order
-        for r in replacements:
-            self.replace(r[replace_key], r[placeholder_key])
+        for placeholder in replacements:
+            self.replace(*placeholder.unpack(serializing))
 
     def replace_in_headers(self, text_to_replace, placeholder):
         for obj in ('request', 'response'):
-            headers = self.json[obj]['headers']
+            headers = self.data[obj]['headers']
             for k, v in list(headers.items()):
-                v = from_list(v)
+                v = util.from_list(v)
                 headers[k] = v.replace(text_to_replace, placeholder)
 
     def replace_in_body(self, text_to_replace, placeholder):
         for obj in ('request', 'response'):
-            body = self.json[obj]['body']
+            body = self.data[obj]['body']
             old_style = hasattr(body, 'replace')
             if not old_style:
                 body = body.get('string', '')
@@ -75,14 +85,14 @@ class Interaction(object):
             if text_to_replace in body:
                 body = body.replace(text_to_replace, placeholder)
             if old_style:
-                self.json[obj]['body'] = body
+                self.data[obj]['body'] = body
             else:
-                self.json[obj]['body']['string'] = body
+                self.data[obj]['body']['string'] = body
 
     def replace_in_uri(self, text_to_replace, placeholder):
         for (obj, key) in (('request', 'uri'), ('response', 'url')):
-            uri = self.json[obj][key]
+            uri = self.data[obj][key]
             if text_to_replace in uri:
-                self.json[obj][key] = uri.replace(
+                self.data[obj][key] = uri.replace(
                     text_to_replace, placeholder
                 )
diff --git a/betamax/configure.py b/betamax/configure.py
index a0aef63..1eca77c 100644
--- a/betamax/configure.py
+++ b/betamax/configure.py
@@ -33,6 +33,52 @@ class Configuration(object):
         else:
             super(Configuration, self).__setattr__(prop, value)
 
+    def before_playback(self, tag=None, callback=None):
+        """Register a function to call before playing back an interaction.
+
+        Example usage:
+
+        .. code-block:: python
+
+            def before_playback(interaction, cassette):
+                pass
+
+            with Betamax.configure() as config:
+                config.before_playback(callback=before_playback)
+
+        :param str tag:
+            Limits the interactions passed to the function based on the
+            interaction's tag (currently unsupported).
+        :param callable callback:
+            The function which either accepts just an interaction or an
+            interaction and a cassette and mutates the interaction before
+            returning.
+        """
+        Cassette.hooks['before_playback'].append(callback)
+
+    def before_record(self, tag=None, callback=None):
+        """Register a function to call before recording an interaction.
+
+        Example usage:
+
+        .. code-block:: python
+
+            def before_record(interaction, cassette):
+                pass
+
+            with Betamax.configure() as config:
+                config.before_record(callback=before_record)
+
+        :param str tag:
+            Limits the interactions passed to the function based on the
+            interaction's tag (currently unsupported).
+        :param callable callback:
+            The function which either accepts just an interaction or an
+            interaction and a cassette and mutates the interaction before
+            returning.
+        """
+        Cassette.hooks['before_record'].append(callback)
+
     @property
     def cassette_library_dir(self):
         """Retrieve and set the directory to store the cassettes in."""
@@ -74,5 +120,5 @@ class Configuration(object):
         """
         self.default_cassette_options['placeholders'].append({
             'placeholder': placeholder,
-            'replace': replace
+            'replace': replace,
         })
diff --git a/betamax/exceptions.py b/betamax/exceptions.py
index d77cb31..a327266 100644
--- a/betamax/exceptions.py
... 4328 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/betamax.git



More information about the Python-modules-commits mailing list