[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