Skip to content

Commit

Permalink
Merge pull request #70 from sigmavirus24/docs-rewrite
Browse files Browse the repository at this point in the history
Start rewriting and reorganizing the documentation
  • Loading branch information
sigmavirus24 committed Jul 12, 2015
2 parents 8868948 + 3bf9876 commit 3018c8f
Show file tree
Hide file tree
Showing 27 changed files with 1,281 additions and 5 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

# General information about the project.
project = u'Betamax'
copyright = u'2013 - Ian Cordasco'
copyright = u'2013-2015 - Ian Cordasco'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
265 changes: 265 additions & 0 deletions docs/configuring.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
Configuring Betamax
===================

By now you've seen examples where we pass a great deal of keyword arguments to
:meth:`~betamax.Betamax.use_cassette`. You have also seen that we used
:meth:`betamax.Betamax.configure`. In this section, we'll go into a deep
description of the different approaches and why you might pick one over the
other.

Global Configuration
--------------------

Admittedly, I am not too proud of my decision to borrow this design from
`VCR`_, but I did and I use it and it isn't entirely terrible. (Note: I do
hope to come up with an elegant way to redesign it for v1.0.0 but that's a
long way off.)

The best way to configure Betamax globally is by using
:meth:`betamax.Betamax.configure`. This returns a
:class:`betamax.configure.Configuration` instance. This instance can be used
as a context manager in order to make the usage look more like `VCR`_'s way of
configuring the library. For example, in `VCR`_, you might do

.. code-block:: ruby
VCR.configure do |config|
config.cassette_library_dir = 'examples/cassettes'
config.default_cassette_options[:record] = :none
# ...
end
Where as with Betamax you might do

.. code-block:: python
from betamax import Betamax
with Betamax.configure() as config:
config.cassette_library_dir = 'examples/cassettes'
config.default_cassette_options['record_mode'] = 'none'
Alternatively, since the object returned is really just an object and does not
do anything special as a context manager, you could just as easily do

.. code-block:: python
from betamax import Betamax
config = Betamax.configure()
config.cassette_library_dir = 'examples/cassettes'
config.default_cassette_options['record_mode'] = 'none'
We'll now move on to specific use-cases when configuring Betamax. We'll
exclude the portion of each example where we create a
:class:`~betamax.configure.Configuration` instance.

Setting the Directory in which Betamax Should Store Cassette Files
``````````````````````````````````````````````````````````````````

Each and every time we use Betamax we need to tell it where to store (and
discover) cassette files. By default we do this by setting the
``cassette_library_dir`` attribute on our ``config`` object, e.g.,

.. code-block:: python
config.cassette_library_dir = 'tests/integration/cassettes'
Note that these paths are relative to what Python thinks is the current
working directory. Wherever you run your tests from, write the path to be
relative to that directory.

Setting Default Cassette Options
````````````````````````````````

Cassettes have default options used by Betmax if none are set. For example,

- The default record mode is ``once``.

- The default matchers used are ``method`` and ``uri``.

- Cassettes do **not** preserve the exact body bytes by default.

These can all be configured as you please. For example, if you want to change
the default matchers and preserve exact body bytes, you would do

.. code-block:: python
config.default_cassette_options['match_requests_on'] = [
'method',
'uri',
'headers',
]
config.preserve_exact_body_bytes = True
Filtering Sensitive Data
````````````````````````

It's unlikely that you'll want to record an interaction that will not require
authentication. For this we can define placeholders in our cassettes. Let's
use a very real example.

Let's say that you want to get your user data from GitHub using Requests. You
might have code that looks like this:

.. code-block:: python
def me(username, password, session):
r = session.get('https://api.github.com/user', auth=(username, password))
r.raise_for_status()
return r.json()
You would test this something like:

.. code-block:: python
import os
import betmax
import requests
from my_module import me
session = requests.Session()
recorder = betamax.Betamax(session)
username = os.environ.get('USERNAME', 'testuser')
password = os.environ.get('PASSWORD', 'testpassword')
with recorder.use_cassette('test-me'):
json = me(username, password, session)
# assertions about the JSON returned
The problem is that now your username and password will be recorded in the
cassette which you don't then want to push to your version control. How can we
prevent that from happening?

.. code-block:: python
import base64
username = os.environ.get('USERNAME', 'testuser')
password = os.environ.get('PASSWORD', 'testpassword')
config.define_cassette_placeholder(
'<GITHUB-AUTH>',
base64.b64encode(
'{0}:{1}'.format(username, password).encode('utf-8')
)
)
.. note::

Obviously you can refactor this a bit so you can pull those environment
variables out in only one place, but I'd rather be clear than not here.

The first time you run the test script you would invoke your tests like so:

.. code-block:: sh
$ USERNAME='my-real-username' PASSWORD='supersecretep@55w0rd' \
python test_script.py
Future runs of the script could simply be run without those environment
variables, e.g.,

.. code-block:: sh
$ python test_script.py
This means that you can run these tests on a service like Travis-CI without
providing credentials.

Per-Use Configuration
---------------------

Each time you create a :class:`~betamax.Betamax` instance or use
:meth:`~betamax.Betamax.use_cassette`, you can pass some of the options from
above.

Setting the Directory in which Betamax Should Store Cassette Files
``````````````````````````````````````````````````````````````````

When using per-use configuration of Betamax, you can specify the cassette
directory when you instantiate a :class:`~betamax.Betamax` object:

.. code-block:: python
session = requests.Session()
recorder = betamax.Betamax(session,
cassette_library_dir='tests/cassettes/')
Setting Default Cassette Options
````````````````````````````````

You can also set default cassette options when instantiating a
:class:`~betamax.Betamax` object:

.. code-block:: python
session = requests.Session()
recorder = betamax.Betamax(session, default_cassette_options={
'record_mode': 'once',
'match_requests_on': ['method', 'uri', 'headers'],
'preserve_exact_body_bytes': True
})
You can also set the above when calling :meth:`~betamax.Betamax.use_cassette`:

.. code-block:: python
session = requests.Session()
recorder = betamax.Betamax(session)
with recorder.use_cassette('cassette-name',
preserve_exact_body_bytes=True,
match_requests_on=['method', 'uri', 'headers'],
record='once'):
session.get('https://httpbin.org/get')
Filtering Sensitive Data
````````````````````````

Filtering sensitive data on a per-usage basis is the only difficult (or
perhaps, less convenient) case. Cassette placeholders are part of the default
cassette options, so we'll set this value similarly to how we set the other
default cassette options, the catch is that placeholders have a specific
structure. Placeholders are stored as a list of dictionaries. Let's use our
example above and convert it.

.. code-block:: python
import base64
username = os.environ.get('USERNAME', 'testuser')
password = os.environ.get('PASSWORD', 'testpassword')
session = requests.Session()
recorder = betamax.Betamax(session, default_cassette_options={
'placeholders': [
{
'placeholder': '<GITHUB-AUTH>',
'replace': base64.b64encode(
'{0}:{1}'.format(username, password).encode('utf-8')
)
}
]
})
Note that what we passed as our first argument is assigned to the
``'placeholder'`` key while the value we're replacing is assigned to the
``'replace'`` key.

This isn't the typical way that people filter sensitive data because they tend
to want to do it globally.

Mixing and Matching
-------------------

It's not uncommon to mix and match configuration methodologies. I do this in
`github3.py`_. I use global configuration to filter sensitive data and set
defaults based on the environment the tests are running in. On Travis-CI, the
record mode is set to ``'none'``. I also set how we match requests and when we
preserve exact body bytes on a per-use basis.

.. links
.. _VCR: https://relishapp.com/vcr/vcr
.. _github3.py: https://github.com/sigmavirus24/github3.py
15 changes: 12 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@ Contents of Betamax's Documentation
===================================

.. toctree::
:caption: Narrative Documentation
:maxdepth: 3

introduction
long_term_usage
configuring
record_modes
third_party_packages
usage_patterns

.. toctree::
:caption: API Documentation
:maxdepth: 2

api
cassettes
implementation_details
matchers
serializers
usage_patterns



Indices and tables
------------------
Expand Down

0 comments on commit 3018c8f

Please sign in to comment.