mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8620bc3af1 | ||
|
|
84bf7b6132 | ||
|
|
2cf779d776 | ||
|
|
d4494bae50 | ||
|
|
41f5fce895 | ||
|
|
a6806f2f99 | ||
|
|
624212ef15 | ||
|
|
144d25bc66 | ||
|
|
0d08157e5d | ||
|
|
ea24854093 | ||
|
|
49929e3064 | ||
|
|
188b57a2fa | ||
|
|
b84f8e963b | ||
|
|
ea13d51677 | ||
|
|
5ba4000f77 | ||
|
|
a4844d972b | ||
|
|
c2d857c585 | ||
|
|
89403c255c | ||
|
|
d50ded68ca | ||
|
|
16fa4f851d | ||
|
|
6200493896 | ||
|
|
b0a13ba690 | ||
|
|
d33b19b5bb |
@@ -1,13 +1,14 @@
|
||||
language: python
|
||||
before_install: openssl version
|
||||
env:
|
||||
- WITH_REQUESTS="True"
|
||||
- WITH_REQUESTS="2.x"
|
||||
- WITH_REQUESTS="1.x"
|
||||
- WITH_REQUESTS="False"
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- pypy
|
||||
install:
|
||||
- pip install PyYAML pytest --use-mirrors
|
||||
- if [ $WITH_REQUESTS = "True" ] ; then pip install requests; fi
|
||||
- if [ $WITH_REQUESTS = "1.x" ] ; then pip install requests==1.2.3; fi
|
||||
- if [ $WITH_REQUESTS = "2.x" ] ; then pip install requests; fi
|
||||
script: python setup.py test
|
||||
|
||||
23
README.md
23
README.md
@@ -22,7 +22,7 @@ This should work with Python 2.6 and 2.7, and [pypy](http://pypy.org).
|
||||
|
||||
Currently I've only tested this with urllib2, urllib3, and requests. It's known to *NOT WORK* with urllib.
|
||||
|
||||
##How to use it
|
||||
##Usage
|
||||
```python
|
||||
import vcr
|
||||
import urllib2
|
||||
@@ -40,6 +40,17 @@ pass, even if you are offline, or iana.org goes down for maintenance) and
|
||||
accurate (the response will contain the same headers and body you get from a
|
||||
real request).
|
||||
|
||||
You can also use VCR.py as a decorator. The same request above would look like this:
|
||||
|
||||
```
|
||||
@vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'):
|
||||
def test_iana():
|
||||
response = urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
assert 'Example domains' in response
|
||||
```
|
||||
|
||||
All of the parameters and configuration works the same for the decorator version.
|
||||
|
||||
## Configuration
|
||||
|
||||
If you don't like VCR's defaults, you can set options by instantiating a
|
||||
@@ -100,7 +111,7 @@ VCR supports 4 record modes (with the same behavior as Ruby's VCR):
|
||||
* Record new interactions if there is no cassette file.
|
||||
* Cause an error to be raised for new requests if there is a cassette file.
|
||||
|
||||
It is similar to the :new_episodes record mode, but will prevent new,
|
||||
It is similar to the new_episodes record mode, but will prevent new,
|
||||
unexpected requests from being made (i.e. because the request URI
|
||||
changed).
|
||||
|
||||
@@ -162,9 +173,7 @@ part of the API. The fields are as follows:
|
||||
* `responses`: A list of the responses made.
|
||||
* `play_count`: The number of times this cassette has had a response
|
||||
played back
|
||||
* `play_counts`: A collections.Counter showing the number of times each
|
||||
response has been played back, indexed by the request
|
||||
* `response_of(request)`: Access the response for a given request.
|
||||
* `responses_of(request)`: Access the responses that match a given request
|
||||
|
||||
The Request object has the following properties
|
||||
|
||||
@@ -259,6 +268,10 @@ This library is a work in progress, so the API might change on you.
|
||||
There are probably some [bugs](https://github.com/kevin1024/vcrpy/issues?labels=bug&page=1&state=open) floating around too.
|
||||
|
||||
##Changelog
|
||||
* 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
|
||||
|
||||
6
setup.py
6
setup.py
@@ -19,8 +19,8 @@ class PyTest(TestCommand):
|
||||
sys.exit(errno)
|
||||
|
||||
setup(name='vcrpy',
|
||||
version='0.3.4',
|
||||
description="A Python port of Ruby's VCR to make mocking HTTP easier",
|
||||
version='0.6.0',
|
||||
description="Automatically mock your HTTP interactions to simplify and speed up testing",
|
||||
author='Kevin McCarthy',
|
||||
author_email='me@kevinmccarthy.org',
|
||||
url='https://github.com/kevin1024/vcrpy',
|
||||
@@ -37,7 +37,7 @@ setup(name='vcrpy',
|
||||
'vcr.compat': 'vcr/compat',
|
||||
'vcr.persisters': 'vcr/persisters',
|
||||
},
|
||||
install_requires=['PyYAML'],
|
||||
install_requires=['PyYAML','contextdecorator'],
|
||||
license='MIT',
|
||||
tests_require=['pytest','mock'],
|
||||
cmdclass={'test': PyTest},
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
|
||||
|
||||
def assert_cassette_empty(cass):
|
||||
assert len(cass) == 0
|
||||
assert cass.play_count == 0
|
||||
@@ -6,3 +9,11 @@ def assert_cassette_empty(cass):
|
||||
def assert_cassette_has_one_response(cass):
|
||||
assert len(cass) == 1
|
||||
assert cass.play_count == 1
|
||||
|
||||
|
||||
def assert_is_json(a_string):
|
||||
try:
|
||||
json.loads(a_string)
|
||||
except Exception:
|
||||
assert False
|
||||
assert True
|
||||
|
||||
238
tests/fixtures/wild/domain_redirect.yaml
vendored
238
tests/fixtures/wild/domain_redirect.yaml
vendored
@@ -1,3 +1,19 @@
|
||||
- request: !!python/object:vcr.request.Request
|
||||
body: null
|
||||
headers: !!python/object/apply:__builtin__.frozenset
|
||||
- - !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
|
||||
- !!python/tuple [User-Agent, vcrpy-test]
|
||||
- !!python/tuple [Accept, '*/*']
|
||||
host: seomoz.org
|
||||
method: GET
|
||||
path: /
|
||||
port: 80
|
||||
protocol: http
|
||||
response:
|
||||
body: {string: ''}
|
||||
headers: ["Location: http://moz.com/\r\n", "Server: BigIP\r\n", "Connection: Keep-Alive\r\n",
|
||||
"Content-Length: 0\r\n"]
|
||||
status: {code: 301, message: Moved Permanently}
|
||||
- request: !!python/object:vcr.request.Request
|
||||
body: null
|
||||
headers: !!python/object/apply:__builtin__.frozenset
|
||||
@@ -12,120 +28,110 @@
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA+1c6XLbxpb+ffUUHWZsyhWBxEqQsiiXLMmxM06sWLKdjMulagANAhaIhrGQknNT
|
||||
Na8xrzdPMud0YyWpxbKT+XGvy5bARi/nnP7O2k3vfXf06vDs95NjEuTzaH9r7ztFeR/6JMrJi2Ni
|
||||
f9gne/iCuBHNsmkv5srHDF4qIZvIX2P5y+6RiMazaY/FPRjz3XsWe6H/QVGaKcv54M9tU95prvEt
|
||||
c90+ySwv58GGTYyuTKEo3WkCRr39LSAhD/OI7Z8ev5rzzyTMSMyX5Gf+eUBOuZ8vacoIjT1yyOfz
|
||||
Ig7zK+LzlDxlec5S8jNNL1gexrMBUXDM3lBOtrW1tTdnOSVuQNOM5dNekfvKuLdfNgd5nijsUxEu
|
||||
pr3flDcHCsye0Dx0ItYjLo9zFsOYF8dT5s3YjhukfM6m2sbhh7K3cnaVtMfm7DIfokge1zRIEsgQ
|
||||
ZonC+IKkLJr2aAR8xDSHsTnMAA1JEoUu0MLjYZplP1zOI3iFXE17r09PiT5QeyRImT/tIRm7w6HP
|
||||
mJcN8KdTpDFLBy6fD0GWTsRncjlBdEyBh94iZMuEp3mL1GXo5cFU01UVGMQNaXX3WOamYYLUtEa8
|
||||
YxGswUjO5Ua9Y8RNGTBBYBdJVm7bDplX2wM7SKOrPHQzGMOjbEdsKY0ichHGXka4X02OcwYsSsgV
|
||||
L0jEaBqTMHZ4Ad3nzWa/Y32ERZRB55KQPGBkyZx+RuY8y8kidFIK07k1bGANHoPgWTkRS7OBEA+C
|
||||
RXJJstSd9oZDzx0HXpzNP9NkMR+4ES88PwX6BjHLhwBwlmdDOSIDQXuwgeHndPAxe2Lrjmt7zLap
|
||||
5riMmTqzXG+sGyNv5Pu2DVp17noxoCMtWG9/r5xEwLWBRJZfRSwLGMurjb4jRWLcsAWggZtlTwxD
|
||||
tRxfdcwRo75N2djwTUObODZjVB+rbo/MmRdSWBf2EHS1xKEAMIzvkCzE1UYvrMWUnBduoISwg0qS
|
||||
MpB4wjPm9UgWfmZgDCz70rK/jJVwTmfAik8XOCuCWSmfFcseJPHsiW2YFrUskzqmMbKBPc03TXXs
|
||||
6MbYMzRbXdOzu1Bq65e2/s0otXVBKbXGk4lD9Ymqe7qjjSdsNKGe6lmqRtWJ7t6LUk0zL+HfN6MV
|
||||
5hLEjh2PTZAsylx/Yvi+Ri3DdI2JP9JVg92TWBOINb8hsaYkdqJabGR7NjVUUDTfZIAG6o9GFKge
|
||||
u56Ea9eepdzhedYyZTHnXiJxDT0bxlwa8xjUKFoxtkAH2td6RNu0sjT0r5SF1p5e1yIeLn58bg1/
|
||||
//1g5gQXv4W/zgM2fLe8LOzhZ//zyfPZs7NPHyN1uoHcGeczkG8W5kwR05eK3VrBPw/emma8PHHz
|
||||
o0+/G0FyFVv6M684A8b+078owp+eW+Pxr78enr7ZsMA8W9Ao9MB0D9Q23aOD0Vg9MEaGNTGfHR2a
|
||||
T/XJkaqZh7qlWiPj8Ki0BGKmJOUJS/MroMXZTWDXzkOvNdXYHE/AAuqmVQ4aSs+/tedw76qKGIQZ
|
||||
xx8KOAR2SRzqXsxStPvAfQS8t1tiHmZgP5EXL1yQ0MON5ejT6vCDwxA/4kvRC+McWBMmKTu05nKi
|
||||
gimw2wsKnoNmOXZUkHgKriJtFilH1m/qztUKrU5pvW6LQvDGvKYwS2hsEBcEVK5RdqaVjrQaoTmc
|
||||
z77EN5W6gzqDq5bKolrGxGMeKLfPxp5tUt+YOJbh+eCzfDCYEIpMe+DPe0RGBD1NR51l4SyAF6a2
|
||||
6glq6oa0ZnYI3NYfYrpo86vpHabKTkUTNEJ3+KcgqLrsR2HVBxRhTlamqYXWVdEh4NIrXNT2nDoC
|
||||
VcBSb/+kbG6RLUmPwm+wqAhZhhnj7VWB8ZfY/tcsWYc47SWN3n4dMf81y8rwslnR7O0/haa/SK4U
|
||||
YsC8vZzV2z/AthuX2xsW0QbMtVS1ApwC4U4KtqyIIiVFxEvN76BThsdbG7hotL0i//ta1XM+Qxue
|
||||
8KRI2gyMIABsJgG7QoRz+1SwDE08BqkgnOcQC+8Nw/22kiH5KceEYM7iol5IrEDET0WE0DQFQwTO
|
||||
OWlbmBbvIu7ralrrrRBFxPx8dYc6RCspn7FUrIcBbXer29Zg4+IKuoDV+ZP9vSwHkzbbvw4McrUz
|
||||
iPhRPOR54aB4IJyWwyAXPAbzf5UHmHpgIhFDcoRJwgXmlgJKInHZGyY30Lv6MUibLU1UAoFEzuda
|
||||
W7bl+r+WW5gRgVDyokxg6mz1SU1qM7YLVMDX/tsQ/L7Ia25UwX3ghKDmoQxEcjwrQsjbIMdKIeOK
|
||||
WJltcZgp7aZAKJ6UZbxIXZYNNmjqHWiorc/wU2//14d0njw+EJQ842kxR6FDZuiRT7VMlmEeAEHZ
|
||||
EhIwAq5rDgmeV4A8rgi7xEBilZK2Erf2pOm0alIysLVucB+dtDfrpJwQtPFUPNxDH8sZNmokbNlc
|
||||
RAgyRin7Kti8EgXECaBJpmclj2UYJz+dQzABkWkSUZcFPIK5pj1J8WAwqMkSsyjZHLLv7vQZi5ib
|
||||
r8woqxo3jxSjuSgSEAgnC6xiYJcDyO8h6xZ1Gfn6llFSrxudvtOgT7SFu/URoGqCr06bU4DuxnU0
|
||||
KD/JXyIgrJ5LXn/ke0PZ0kYlbtCNuKzVqOsqrg0d+CyMe5uJKt81SB1DSMFnYFqk5WuWlNrSDcSG
|
||||
4Lwq4sr28kEG4ywt32YgqLAlmM1hcolTBkalFcFujpKXATBd2kqrE9UXsVOkWb45fCb5kkHU342i
|
||||
W7HkCIDlQ6xrtBUpMOsumCvBajGSIGkk6A1IxiHbAaOUWCVN6hdEO0NIxzznCvIxxjG2BrhGXJlj
|
||||
xFytG4lKHo/dKHQvpr3zGf00SIos2H7fP4f4wr0AxxTn/R3Sx1wHkyV8xloLwdyzRBm2oVU/iGOQ
|
||||
lcvmOObDo8crtK6XTDu4p22wBmZbUFptCFEoco9cmmSVUIjwcEJYKzYCZF+nbLqU6QVLY8j20R+n
|
||||
DKmhxJHl2SWF2BOH7IPKpe2JwC14vPFCbUK1NqFGh9CczoT3Auq0ClMd+n4HV0ZccCYgXPAu4AVZ
|
||||
ugCa8o5kSt/7jjWVRiAGOm1wy8PA2JzcrACyndvcM11DRAxlOKWrqkjaHM/XLM90bI1NTGPkau7E
|
||||
sU1n4mu+6088s0zaXuMgBIDTSoLFTCgqs0noDKNJ6LSJvTmj69iyVXNRWgh4bAtAVA6zgOcdg/AV
|
||||
YmhmlNU+a2KbJnOYpdm6M3YmGiSv/kijk4nm+Z7RJK+gM1Wl+bSeoxGAqtmNBAxTX5VAxedGW9bm
|
||||
DcDfRmZtTF7EucgvMbTqkgM6QQFhgS7Hm53x0mCh1kE8t6FqXtXUUXHm0Dxjonp+hXiv6uPMB48k
|
||||
oiep7JsrEqvANdcsaVOECOMFWIZhFs5ijJMqgeS0ckszlDAQAOMVYcTATLoXX24AG7P3molIERwb
|
||||
Ln3+/ETavapZKLh4J+pgtZFrY3aFwSZC3wssIorkEKjRFBwqRmK7mpVcwgqvUpCbdets5gYxNYWG
|
||||
v0NCZ/QCNp+cgSRq8Yi2A9FGXvkEM6Pq8GxdRM1D0kpozJWE5iXnF4hBTChOXr96gqIHHwFQzUNA
|
||||
Xof7u7EDswj6UQovwdnhh+dY9js8O5Bs5JI1H2RGDFXx6BXJ05BGBD0LMjIQ5Cd1fHNDwFJWDYX9
|
||||
uz486VTg2l0WkELxDnI6oUXH8a2X777H8EBOQTyaU2XuQw6AprD9ppyRJyyWqYgi6s/3DCIkQoZi
|
||||
bvz8Fh9qhLScUyd9FyXXklmR17T99JLmkLQgqMCjple1H8SIc01X1l3E1tr+rAq+HYlZa9sk4kCe
|
||||
VJZpr8xn2+rYuP9NfImzRoKiUzy+jDsFivZCDt9QcL6maIIUd2OirisAZYRMFmVW23GU6RmSUpv/
|
||||
emyyLxzEZ1ZbcYQAnpJK246V/x3ihZmLBW0SgEREe3VaCtFWFoCWQ0wbAPGzADyFixojyisy8Z9z
|
||||
8Bsi68aRQrmEYAZktf7R+XR9LCxGX2vsvsq24cw1Zo8vkwhpR7LFm7VaX4vglYynFcKUsLkviKqN
|
||||
+bthJCrGVe0IIdRcdtgAo+YIvqr+CKXOWhDAQ3JGQaEx4F07VBf9LhiT8XCR4JmQR7ACUlVnPLZg
|
||||
EU8wGcnWSmd3hE6rPP4XwOd1VcvaCKH67f8LjOAB0JCJsOVvx9JPHLOngCGMDluEbEJS1Rc8sLqj
|
||||
quoPYEvmDqZTy4ALw5OGIHKBJxTr2k0LCTl5d2NjfQ8axMiyaoMhRjG/L6Japx9/AaLqM5SNiLr+
|
||||
hOVLEbXBedYHh+sRA0YTAbR0j0CXIYRm9JyZ3kIzLiFvrPtXb2Ab8VRehsAiJdodjycQ/sqMaNdS
|
||||
VYyFZcgillTKxAm6dZqrFMrCS0OSgpzN8SS4Cp1EA7ithIknWbUs9y5zAzanA57OhiJKeeV8BLZh
|
||||
InGe3MzkFenqcffJmfazZZ2KXG2ldx4UcyemYfQmjVojqjVlzQayvYEUh0CPx6IQD9Yh72QjT3MM
|
||||
3aaW4410b6yPVENlhu5alm34Y9c14KU68QYOVuLWly8XfPP65T0WH+kmMzTVcXxfHauab3q25xuO
|
||||
SUeWQy3mujbzJ77uXre42NovWNoHYARDMeo8iegVS88X+kAdZEv/ia5qhqJaimY+lKWUQw6In+r6
|
||||
ZOI5DwMPxPvAegpR/APraOpHi7pJggJbbV2tW3HnsS3w2n0L/AmNQOQD4+CB/gz+biQV2hs54QfP
|
||||
HDsgJG1ELdP2PcszmGG61J64rqMbhqPCvrnaGCVVryZQjOtp+lh9KGKjoxJbU02zBurDDEKrtzwq
|
||||
5myKtYCHIsuBwfeg8M5A+pjMHhjPRNnj3IVtPE8Z3pjxHhhHoG+XoFsPsUjq+5D9eKeMXUx9GmXs
|
||||
oVCw+9H2tTgrwPhR70gkKDXSKsDo2pmu7arGrjn+LzGYC73ebJzOmyAA37sR/No90u3Do6ejY+Xg
|
||||
eHSkaJrrK5PR07FimqZlGZYJ7kitDRhExQjdXZFRd01YwiFwhs3dTRkkU8D+47a1A0OT0JTOqzsw
|
||||
fBECO9UZxNerDTp1sUB3HRpFfOkXUVRdtCtXrC4DXjdEXhKkLkQuWa85YFnSq+yaYc7MRZWtO3+v
|
||||
ij/X9F7iFca6L0/op2vpEfwvaNrQ8W8L8a9sIRqUCJ2Q1d5voUFkRVmqCu2aQtSaQErQT0uwkxqr
|
||||
/wbpvzZIKzO35oHwev7N3kRT1QfXOhPxctO1/UsFtsG9WNIFUwQKe0TY2LZxFRoAv6WL3N+LeXkF
|
||||
vRX9tu7ei0seZS2gn5FTLMftDatB5bFF+2edI1aXI899+IEp5GrBrp1CVH1kgiI/VUlE+QlSqqvr
|
||||
C6p3uQy5sZpOOncfVxJovMSoyIXFZYLu9YubjuXsDXcsmztdEUACUut5Aok8XgRrPXYkUY2860m+
|
||||
uGE0FELBtOZQPpA32eph/ZdNK+tu+69ZeZukrondf0qw3jlsG8x6Uj595XzlUciQJmFv/+DkxVdy
|
||||
zNJ5piRpuKAubP4ZfiSiakBOZOP6/N07f50zDn3joX91Htw+8WjKYrCLWRF1b22tHGajbnqcZfH/
|
||||
/vf/5HgdGi1ba+AOliPEGUYfD8cRdXgv5g5XqsRl+jmLPTxmaH3AYgugO5S1LFGY8fh3LREkmyRQ
|
||||
3mY55MmVuOJIHkJ6fvWYoN8tLxPsgJFxBwRv7rzGLhlWzfAM3WvXZ25UOaM8UOzcjEuaulZ5yFRL
|
||||
neV9PBwlPtj22Mu+a6Tc4WL9nrBUUUUWvG+6MHzLFY8bvz+1aorkakqaoSKenq7YIimcW+7/3EhM
|
||||
vgzx6kRFw3Xrl91AJeTDN6djuVwOfOoyB5TiNmKqfr39Z+XTX0IOnoxAyBMLcrAummRPhKWdCosw
|
||||
AwemT+yRqU7E5zy9mNKY5efF7HyWJnhMeQ391cS9/ZfiCS9VfUv6M2AgiYpsIL/OIejXNN0yTdW2
|
||||
VFM1rZFqjkfGqCe/e5IUDtiIQDjrjQTLeXBKvJuGzz98c4knIfpNluWCXBmVrXvmkqC6M/iR6vFW
|
||||
iq6917l6oCgVfSVk+Zpv7H3G7+r5I9+zVXfsqaZDPW9sUtvV7Inpj0Y2ZZiDbP6uXrWy/HKr+Jrc
|
||||
R7qgshWviP8szHjsh7MBFpSzit8peY+13HNZH/3wuN3RYwka9tgFky460lyW2SH4O3fRMbHeTnlB
|
||||
tB5apCkWHME+k2aNP/58vLX1H9vYIQQf8qj+BBFoAeCbhZ/nHFvb3z5svgHZ+vJflysCGX9KsIAO
|
||||
i4hf//wneQ+kENKpqoOoD1wX4lRRVX9zoGhjWx3ZE0XD+vmG3kd8DqHjLxCj44DSB17T9wDTP6Gk
|
||||
KXTGnbmh30HsBrzVT1zd39D5UGSHbyl2tXb6KE/xbd7+Tn+B15/FHLpcaKu8/78yC+DnxSzmkN+8
|
||||
Zj6yIfUFK9zIydqqa/1bbN/WuY/3BfBcmMmTiLRaZXWcON84AQTid35lh5KDLdzObb+IxVnD9iPy
|
||||
h2AKN3hGYXs97hZ4zDeQR4rHkbiBuN2XaOgDjTM6QKRA3/4KVvriJc2uYhfeougfi8mhEXQVmrb7
|
||||
0ib2ybS1EmRd8kQK7HTOIZMnT0i/Mp5ZFvXJrvwsnUH/EfmB9EuDqtT3pKRroKDe/cc1S1mboxnL
|
||||
S3ayp1dndIa4axh7r354TDJQW1SsXyBlG+DNnTR/yiAoZNszukMyIeg/H22v65CwXrfoESHUS3kU
|
||||
wa4uzkMPSOv98sYePTdenh0//fHVyeHLZy/Md89Hx73HTd8kvCz7Hh29+OnFy59fHr89enr26vlv
|
||||
1osf3xi66FvvJyk3FHnnkcdjLNnC4GUYe3w5kJ9xRKcBOjSAkHgg5+clAdiBedNmN6Vg3fR6sPRK
|
||||
lh+1BgT4zWxAwHbpFns3Q+ARYKB2oNlA0iK/fblLKj9F2+3VYrD2AM1AaURhhwUgxV1b4KHf7lfi
|
||||
eG2v2lMJ4ArqfyC94cehOJSFuRNAWtVxe/sWkKH1Btyi2YyLKPqAaINPpT25O0Jb8Hw0oAk6jsMg
|
||||
jLxt6FMxFvrb9dY/+qN+3H70558CvtuPRM8Gv//4Bw4TxmFviN/KEXfDxX9qsfV/BBAejOZCAAA=
|
||||
headers: {accept-ranges: bytes, age: '3593', cache-control: 'no-cache, must-revalidate,
|
||||
s-maxage=3600', connection: keep-alive, content-encoding: gzip, content-length: '5300',
|
||||
content-type: text/html, date: 'Sat, 10 Aug 2013 23:33:35 GMT', expires: 'Fri,
|
||||
15 Oct 2004 12:00:00 GMT', server: nginx, server-name: dalmozwww02.dal.moz.com,
|
||||
vary: Accept-Encoding, via: 1.1 varnish, x-varnish: 2027569899 2027508286}
|
||||
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
|
||||
headers: ["Server: nginx\r\n", "Content-Type: text/html\r\n", "Vary: Accept-Encoding\r\n",
|
||||
"Cache-Control: no-cache, must-revalidate, s-maxage=3600\r\n", "Expires: Fri,
|
||||
15 Oct 2004 12:00:00 GMT\r\n", "Server-Name: dalmozwww01.dal.moz.com\r\n",
|
||||
"Content-Encoding: gzip\r\n", "Content-Length: 5683\r\n", "Accept-Ranges: bytes\r\n",
|
||||
"Date: Sat, 11 Jan 2014 18:45:11 GMT\r\n", "X-Varnish: 918768771 918700396\r\n",
|
||||
"Age: 3479\r\n", "Via: 1.1 varnish\r\n", "Connection: keep-alive\r\n"]
|
||||
status: {code: 200, message: OK}
|
||||
- request: !!python/object:vcr.request.Request
|
||||
body: null
|
||||
headers: !!python/object/apply:__builtin__.frozenset
|
||||
- - !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
|
||||
- !!python/tuple [User-Agent, vcrpy-test]
|
||||
- !!python/tuple [Accept, '*/*']
|
||||
host: seomoz.org
|
||||
method: GET
|
||||
path: /
|
||||
port: 80
|
||||
protocol: http
|
||||
response:
|
||||
body: {string: "<html>\r\n<head><title>301 Moved Permanently</title></head>\r\n<body
|
||||
bgcolor=\"white\">\r\n<center><h1>301 Moved Permanently</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"}
|
||||
headers: {accept-ranges: bytes, age: '0', connection: keep-alive, content-length: '178',
|
||||
content-type: text/html, date: 'Sat, 10 Aug 2013 23:33:35 GMT', location: 'http://moz.com/',
|
||||
server: nginx, server-name: dalmozwww01.dal.moz.com, via: 1.1 varnish, x-varnish: '2027569892'}
|
||||
status: {code: 301, message: Moved Permanently}
|
||||
|
||||
@@ -40,8 +40,10 @@ def test_override_set_cassette_library_dir(tmpdir):
|
||||
def test_override_match_on(tmpdir):
|
||||
my_vcr = vcr.VCR(match_on=['method'])
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))) as cass:
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))):
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))) as cass:
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
|
||||
assert len(cass) == 1
|
||||
|
||||
20
tests/integration/test_multiple.py
Normal file
20
tests/integration/test_multiple.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
from urllib2 import urlopen
|
||||
import vcr
|
||||
|
||||
|
||||
def test_making_extra_request_raises_exception(tmpdir):
|
||||
# make two requests in the first request that are considered
|
||||
# identical (since the match is based on method)
|
||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
||||
urlopen('http://httpbin.org/status/200')
|
||||
urlopen('http://httpbin.org/status/201')
|
||||
|
||||
# Now, try to make three requests. The first two should return the
|
||||
# correct status codes in order, and the third should raise an
|
||||
# exception.
|
||||
with vcr.use_cassette(str(tmpdir.join('test.json')), match_on=['method']):
|
||||
assert urlopen('http://httpbin.org/status/200').getcode() == 200
|
||||
assert urlopen('http://httpbin.org/status/201').getcode() == 201
|
||||
with pytest.raises(Exception):
|
||||
urlopen('http://httpbin.org/status/200')
|
||||
@@ -21,6 +21,28 @@ def test_once_record_mode(tmpdir):
|
||||
response = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
|
||||
|
||||
def test_once_record_mode_two_times(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get two of the same file
|
||||
response1 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="once") as cass:
|
||||
# do it again
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
|
||||
|
||||
def test_once_mode_three_times(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get three of the same file
|
||||
response1 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
|
||||
|
||||
def test_new_episodes_record_mode(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
|
||||
|
||||
@@ -18,7 +18,11 @@ def test_registered_serializer_true_matcher(tmpdir):
|
||||
# These 2 different urls are stored as the same request
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
urllib2.urlopen('https://httpbin.org/get')
|
||||
assert len(cass) == 1
|
||||
|
||||
with my_vcr.use_cassette(testfile, match_on=['true']) as cass:
|
||||
# I can get the response twice even though I only asked for it once
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urllib2.urlopen('https://httpbin.org/get')
|
||||
|
||||
|
||||
def test_registered_serializer_false_matcher(tmpdir):
|
||||
|
||||
@@ -24,7 +24,6 @@ def test_registered_serializer(tmpdir):
|
||||
my_vcr.register_serializer('mock', ms)
|
||||
tmpdir.join('test.mock').write('test_data')
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.mock')), serializer='mock'):
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
# Serializer deserialized once
|
||||
assert ms.serialize_count == 1
|
||||
# and serialized the test data string
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
import os
|
||||
import pytest
|
||||
import vcr
|
||||
from assertions import assert_cassette_empty, assert_cassette_has_one_response
|
||||
from assertions import (
|
||||
assert_cassette_empty,
|
||||
assert_cassette_has_one_response,
|
||||
assert_is_json
|
||||
)
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
|
||||
@@ -21,33 +25,30 @@ def test_status_code(scheme, tmpdir):
|
||||
'''Ensure that we can read the status code'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert requests.get(url).status_code == requests.get(url).status_code
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
status_code = requests.get(url).status_code
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
assert status_code == requests.get(url).status_code
|
||||
|
||||
|
||||
def test_headers(scheme, tmpdir):
|
||||
'''Ensure that we can read the headers back'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert requests.get(url).headers == requests.get(url).headers
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
headers = requests.get(url).headers
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
assert headers == requests.get(url).headers
|
||||
|
||||
|
||||
def test_body(tmpdir, scheme):
|
||||
'''Ensure the responses are all identical enough'''
|
||||
url = scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert requests.get(url).content == requests.get(url).content
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
content = requests.get(url).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
assert content == requests.get(url).content
|
||||
|
||||
|
||||
def test_auth(tmpdir, scheme):
|
||||
@@ -55,14 +56,12 @@ def test_auth(tmpdir, scheme):
|
||||
auth = ('user', 'passwd')
|
||||
url = scheme + '://httpbin.org/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
one = requests.get(url, auth=auth)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
|
||||
two = requests.get(url, auth=auth)
|
||||
assert one.content == two.content
|
||||
assert one.status_code == two.status_code
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_auth_failed(tmpdir, scheme):
|
||||
@@ -76,8 +75,6 @@ def test_auth_failed(tmpdir, scheme):
|
||||
two = requests.get(url, auth=auth)
|
||||
assert one.content == two.content
|
||||
assert one.status_code == two.status_code == 401
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_post(tmpdir, scheme):
|
||||
@@ -85,22 +82,22 @@ def test_post(tmpdir, scheme):
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
req1 = requests.post(url, data).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
req2 = requests.post(url, data).content
|
||||
assert req1 == req2
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
assert req1 == req2
|
||||
|
||||
|
||||
def test_redirects(tmpdir, scheme):
|
||||
'''Ensure that we can handle redirects'''
|
||||
url = scheme + '://httpbin.org/redirect-to?url=bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert requests.get(url).content == requests.get(url).content
|
||||
content = requests.get(url).content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
|
||||
assert content == requests.get(url).content
|
||||
# Ensure that we've now cached *two* responses. One for the redirect
|
||||
# and one for the final fetch
|
||||
assert len(cass) == 2
|
||||
@@ -117,3 +114,34 @@ def test_cross_scheme(tmpdir, scheme):
|
||||
requests.get('http://httpbin.org/')
|
||||
assert cass.play_count == 0
|
||||
assert len(cass) == 2
|
||||
|
||||
|
||||
def test_gzip(tmpdir, scheme):
|
||||
'''
|
||||
Ensure that requests (actually urllib3) is able to automatically decompress
|
||||
the response body
|
||||
'''
|
||||
url = scheme + '://httpbin.org/gzip'
|
||||
response = requests.get(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
|
||||
response = requests.get(url)
|
||||
assert_is_json(response.content)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
|
||||
assert_is_json(response.content)
|
||||
|
||||
|
||||
def test_session_and_connection_close(tmpdir, scheme):
|
||||
'''
|
||||
This tests the issue in https://github.com/kevin1024/vcrpy/issues/48
|
||||
|
||||
If you use a requests.session and the connection is closed, then an
|
||||
exception is raised in the urllib3 module vendored into requests:
|
||||
`AttributeError: 'NoneType' object has no attribute 'settimeout'`
|
||||
'''
|
||||
with vcr.use_cassette(str(tmpdir.join('session_connection_closed.yaml'))):
|
||||
session = requests.session()
|
||||
|
||||
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
|
||||
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
|
||||
|
||||
@@ -25,52 +25,44 @@ def test_response_code(scheme, tmpdir):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert urllib2.urlopen(url).getcode() == urllib2.urlopen(url).getcode()
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
code = urllib2.urlopen(url).getcode()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
assert code == urllib2.urlopen(url).getcode()
|
||||
|
||||
|
||||
def test_random_body(scheme, tmpdir):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
assert urllib2.urlopen(url).read() == urllib2.urlopen(url).read()
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
body = urllib2.urlopen(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
assert body == urllib2.urlopen(url).read()
|
||||
|
||||
|
||||
def test_response_headers(scheme, tmpdir):
|
||||
'''Ensure we can get information from the response'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
open1 = urllib2.urlopen(url).info().items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
open2 = urllib2.urlopen(url).info().items()
|
||||
assert open1 == open2
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_multiple_requests(scheme, tmpdir):
|
||||
'''Ensure that we can cache multiple requests'''
|
||||
urls = [
|
||||
scheme + '://httpbin.org/',
|
||||
scheme + '://httpbin.org/',
|
||||
scheme + '://httpbin.org/get',
|
||||
scheme + '://httpbin.org/bytes/1024'
|
||||
]
|
||||
with vcr.use_cassette(str(tmpdir.join('multiple.yaml'))) as cass:
|
||||
for index in range(len(urls)):
|
||||
url = urls[index]
|
||||
assert len(cass) == index
|
||||
assert cass.play_count == index
|
||||
assert urllib2.urlopen(url).read() == urllib2.urlopen(url).read()
|
||||
assert len(cass) == index + 1
|
||||
assert cass.play_count == index + 1
|
||||
map(urllib2.urlopen, urls)
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
def test_get_data(scheme, tmpdir):
|
||||
@@ -78,14 +70,12 @@ def test_get_data(scheme, tmpdir):
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = scheme + '://httpbin.org/get?' + data
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
res1 = urllib2.urlopen(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url).read()
|
||||
assert res1 == res2
|
||||
# Ensure that we've now cached a single response
|
||||
assert len(cass) == 1
|
||||
assert cass.play_count == 1
|
||||
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(scheme, tmpdir):
|
||||
@@ -93,13 +83,13 @@ def test_post_data(scheme, tmpdir):
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
res1 = urllib2.urlopen(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url, data).read()
|
||||
assert res1 == res2
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_post_unicode_data(scheme, tmpdir):
|
||||
@@ -107,13 +97,11 @@ def test_post_unicode_data(scheme, tmpdir):
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
assert_cassette_empty(cass)
|
||||
res1 = urllib2.urlopen(url, data).read()
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url, data).read()
|
||||
assert res1 == res2
|
||||
# Ensure that we've now cached a single response
|
||||
assert_cassette_has_one_response(cass)
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir):
|
||||
@@ -126,3 +114,17 @@ def test_cross_scheme(tmpdir):
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
assert len(cass) == 2
|
||||
assert cass.play_count == 0
|
||||
|
||||
def test_decorator(scheme, tmpdir):
|
||||
'''Test the decorator version of VCR.py'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner1():
|
||||
return urllib2.urlopen(url).getcode()
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner2():
|
||||
return urllib2.urlopen(url).getcode()
|
||||
|
||||
assert inner1() == inner2()
|
||||
|
||||
@@ -52,3 +52,20 @@ def test_flickr_should_respond_with_200(tmpdir):
|
||||
with vcr.use_cassette(testfile):
|
||||
r = requests.post("http://api.flickr.com/services/upload")
|
||||
assert r.status_code == 200
|
||||
|
||||
def x_test_zip_file(tmpdir):
|
||||
# TODO: How do I make this pass?
|
||||
zipfile = "http://www.colorado.edu/conflict/peace/download/peace_example.ZIP"
|
||||
testfile = str(tmpdir.join('test.json'))
|
||||
with vcr.use_cassette(testfile, serializer='json'):
|
||||
r = requests.post(zipfile)
|
||||
|
||||
def test_cookies(tmpdir):
|
||||
testfile = str(tmpdir.join('cookies.yml'))
|
||||
with vcr.use_cassette(testfile):
|
||||
s = requests.Session()
|
||||
r1 = s.get("http://httpbin.org/cookies/set?k1=v1&k2=v2")
|
||||
r2 = s.get("http://httpbin.org/cookies")
|
||||
assert len(r2.json()['cookies']) == 2
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import pytest
|
||||
import yaml
|
||||
import mock
|
||||
from vcr.cassette import Cassette
|
||||
from vcr.errors import UnhandledHTTPRequestError
|
||||
|
||||
|
||||
def test_cassette_load(tmpdir):
|
||||
@@ -18,21 +19,6 @@ def test_cassette_not_played():
|
||||
assert not a.play_count
|
||||
|
||||
|
||||
def test_cassette_played():
|
||||
a = Cassette('test')
|
||||
a.mark_played('foo')
|
||||
a.mark_played('foo')
|
||||
assert a.play_count == 2
|
||||
|
||||
|
||||
def test_cassette_play_counter():
|
||||
a = Cassette('test')
|
||||
a.mark_played('foo')
|
||||
a.mark_played('bar')
|
||||
assert a.play_counts['foo'] == 1
|
||||
assert a.play_counts['bar'] == 1
|
||||
|
||||
|
||||
def test_cassette_append():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
@@ -59,14 +45,22 @@ def test_cassette_contains():
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
def test_cassette_response_of():
|
||||
def test_cassette_responses_of():
|
||||
a = Cassette('test')
|
||||
a.append('foo', 'bar')
|
||||
assert a.response_of('foo') == 'bar'
|
||||
assert a.responses_of('foo') == ['bar']
|
||||
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
def test_cassette_get_missing_response():
|
||||
a = Cassette('test')
|
||||
with pytest.raises(KeyError):
|
||||
a.response_of('foo')
|
||||
with pytest.raises(UnhandledHTTPRequestError):
|
||||
a.responses_of('foo')
|
||||
|
||||
@mock.patch('vcr.cassette.requests_match', _mock_requests_match)
|
||||
def test_cassette_cant_read_same_request_twice():
|
||||
a = Cassette('test')
|
||||
a.append('foo','bar')
|
||||
a.play_response('foo')
|
||||
with pytest.raises(UnhandledHTTPRequestError):
|
||||
a.play_response('foo')
|
||||
|
||||
23
tox.ini
23
tox.ini
@@ -4,7 +4,7 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py26, py27, pypy, py26requests, py27requests, pypyrequests
|
||||
envlist = py26, py27, pypy, py26requests, py27requests, pypyrequests, py26oldrequests, py27oldrequests, pypyoldrequests
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
@@ -13,6 +13,27 @@ deps =
|
||||
pytest
|
||||
PyYAML
|
||||
|
||||
[testenv:py26oldrequests]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
|
||||
[testenv:py27oldrequests]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
|
||||
[testenv:pypyoldrequests]
|
||||
basepython = pypy
|
||||
deps =
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
|
||||
[testenv:py26requests]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
|
||||
@@ -3,6 +3,5 @@ from config import VCR
|
||||
default_vcr = VCR()
|
||||
|
||||
|
||||
# Also, make a 'load' function available
|
||||
def use_cassette(path, **kwargs):
|
||||
return default_vcr.use_cassette(path, **kwargs)
|
||||
|
||||
@@ -6,14 +6,17 @@ except ImportError:
|
||||
from .compat.counter import Counter
|
||||
from .compat.ordereddict import OrderedDict
|
||||
|
||||
from contextdecorator import ContextDecorator
|
||||
|
||||
# Internal imports
|
||||
from .patch import install, reset
|
||||
from .persist import load_cassette, save_cassette
|
||||
from .serializers import yamlserializer
|
||||
from .matchers import requests_match, url, method
|
||||
from .errors import UnhandledHTTPRequestError
|
||||
|
||||
|
||||
class Cassette(object):
|
||||
class Cassette(ContextDecorator):
|
||||
'''A container for recorded requests and responses'''
|
||||
|
||||
@classmethod
|
||||
@@ -36,6 +39,7 @@ class Cassette(object):
|
||||
self.data = []
|
||||
self.play_counts = Counter()
|
||||
self.dirty = False
|
||||
self.rewound = False
|
||||
self.record_mode = record_mode
|
||||
|
||||
@property
|
||||
@@ -50,47 +54,49 @@ class Cassette(object):
|
||||
def responses(self):
|
||||
return [response for (request, response) in self.data]
|
||||
|
||||
@property
|
||||
def rewound(self):
|
||||
"""
|
||||
If the cassette has already been recorded in another session, and has
|
||||
been loaded again fresh from disk, it has been "rewound". This means
|
||||
that it should be write-only, depending on the record mode specified
|
||||
"""
|
||||
return not self.dirty and self.play_count
|
||||
|
||||
@property
|
||||
def write_protected(self):
|
||||
return self.rewound and self.record_mode == 'once' or \
|
||||
self.record_mode == 'none'
|
||||
|
||||
def mark_played(self, request):
|
||||
'''
|
||||
Alert the cassette of a request that's been played
|
||||
'''
|
||||
self.play_counts[request] += 1
|
||||
|
||||
def append(self, request, response):
|
||||
'''Add a request, response pair to this cassette'''
|
||||
self.data.append((request, response))
|
||||
self.dirty = True
|
||||
|
||||
def response_of(self, request):
|
||||
def play_response(self, request):
|
||||
'''
|
||||
Find the response corresponding to a request
|
||||
|
||||
Get the response corresponding to a request, but only if it
|
||||
hasn't been played back before, and mark it as playe.d
|
||||
'''
|
||||
responses = []
|
||||
for stored_request, response in self.data:
|
||||
for index, (stored_request, response) in enumerate(self.data):
|
||||
if requests_match(request, stored_request, self._match_on):
|
||||
responses.append(response)
|
||||
index = self.play_counts[request]
|
||||
try:
|
||||
return responses[index]
|
||||
except IndexError:
|
||||
# I decided that a KeyError is the best exception to raise
|
||||
# if the cassette doesn't contain the request asked for.
|
||||
raise KeyError
|
||||
if self.play_counts[index] == 0:
|
||||
self.play_counts[index] += 1
|
||||
return response
|
||||
# The cassette doesn't contain the request asked for.
|
||||
raise UnhandledHTTPRequestError(
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
||||
% (self._path, request)
|
||||
)
|
||||
|
||||
def responses_of(self, request):
|
||||
'''
|
||||
Find the responses corresponding to a request.
|
||||
This function isn't actually used by VCR internally, but is
|
||||
provided as an external API.
|
||||
'''
|
||||
responses = \
|
||||
[resp for req, resp in self.data if
|
||||
requests_match(req, request, self._match_on)]
|
||||
|
||||
if responses:
|
||||
return responses
|
||||
# The cassette doesn't contain the request asked for.
|
||||
raise UnhandledHTTPRequestError(
|
||||
"The cassette (%r) doesn't contain the request (%r) asked for"
|
||||
% (self._path, request)
|
||||
)
|
||||
|
||||
def _as_dict(self):
|
||||
return {"requests": self.requests, "responses": self.responses}
|
||||
@@ -113,6 +119,7 @@ class Cassette(object):
|
||||
for request, response in zip(requests, responses):
|
||||
self.append(request, response)
|
||||
self.dirty = False
|
||||
self.rewound = True
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
10
vcr/errors.py
Normal file
10
vcr/errors.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class CannotOverwriteExistingCassetteException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnhandledHTTPRequestError(KeyError):
|
||||
'''
|
||||
Raised when a cassette does not c
|
||||
ontain the request we want
|
||||
'''
|
||||
pass
|
||||
11
vcr/patch.py
11
vcr/patch.py
@@ -36,7 +36,7 @@ def install(cassette):
|
||||
httplib.HTTPConnection.cassette = cassette
|
||||
httplib.HTTPSConnection.cassette = cassette
|
||||
|
||||
# patch requests
|
||||
# patch requests v1.x
|
||||
try:
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
from .stubs.requests_stubs import VCRVerifiedHTTPSConnection
|
||||
@@ -44,6 +44,11 @@ def install(cassette):
|
||||
cpool.VerifiedHTTPSConnection.cassette = cassette
|
||||
cpool.HTTPConnection = VCRHTTPConnection
|
||||
cpool.HTTPConnection.cassette = cassette
|
||||
# patch requests v2.x
|
||||
cpool.HTTPConnectionPool.ConnectionCls = VCRHTTPConnection
|
||||
cpool.HTTPConnectionPool.cassette = cassette
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = VCRHTTPSConnection
|
||||
cpool.HTTPSConnectionPool.cassette = cassette
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -68,6 +73,8 @@ def reset():
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
cpool.HTTPConnectionPool.ConnectionCls = _HTTPConnection
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = _HTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -75,5 +82,7 @@ def reset():
|
||||
import urllib3.connectionpool as cpool
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
cpool.HTTPConnectionPool.ConnectionCls = _HTTPConnection
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = _HTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -3,21 +3,10 @@ import os
|
||||
|
||||
|
||||
class FilesystemPersister(object):
|
||||
@classmethod
|
||||
def _secure_write(cls, path, contents):
|
||||
"""
|
||||
We'll overwrite the old version securely by writing out a temporary
|
||||
version and then moving it to replace the old version
|
||||
"""
|
||||
dirname, filename = os.path.split(path)
|
||||
fd, name = tempfile.mkstemp(dir=dirname, prefix=filename)
|
||||
with os.fdopen(fd, 'w') as fout:
|
||||
fout.write(contents)
|
||||
os.rename(name, path)
|
||||
|
||||
@classmethod
|
||||
def write(cls, cassette_path, data):
|
||||
dirname, filename = os.path.split(cassette_path)
|
||||
if dirname and not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
cls._secure_write(cassette_path, data)
|
||||
with open(cassette_path, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
@@ -4,6 +4,31 @@ from httplib import HTTPConnection, HTTPSConnection, HTTPMessage
|
||||
from cStringIO import StringIO
|
||||
|
||||
from vcr.request import Request
|
||||
from vcr.errors import CannotOverwriteExistingCassetteException
|
||||
|
||||
|
||||
def parse_headers_backwards_compat(header_dict):
|
||||
"""
|
||||
In vcr 0.6.0, I changed the cassettes to store
|
||||
headers as a list instead of a dict. This method
|
||||
parses the old dictionary-style headers for
|
||||
backwards-compatability reasons.
|
||||
"""
|
||||
msg = HTTPMessage(StringIO(""))
|
||||
for key, val in header_dict.iteritems():
|
||||
msg.addheader(key, val)
|
||||
msg.headers.append("{0}:{1}".format(key, val))
|
||||
return msg
|
||||
|
||||
|
||||
def parse_headers(header_list):
|
||||
if isinstance(header_list, dict):
|
||||
return parse_headers_backwards_compat(header_list)
|
||||
headers = "".join(header_list) + "\r\n"
|
||||
msg = HTTPMessage(StringIO(headers))
|
||||
msg.fp.seek(0)
|
||||
msg.readheaders()
|
||||
return msg
|
||||
|
||||
|
||||
class VCRHTTPResponse(object):
|
||||
@@ -16,18 +41,10 @@ class VCRHTTPResponse(object):
|
||||
self.status = recorded_response['status']['code']
|
||||
self.version = None
|
||||
self._content = StringIO(self.recorded_response['body']['string'])
|
||||
self.closed = False
|
||||
|
||||
# We are skipping the header parsing (they have already been parsed
|
||||
# at this point) and directly adding the headers to the header
|
||||
# container, so just pass an empty StringIO.
|
||||
self.msg = HTTPMessage(StringIO(''))
|
||||
|
||||
for key, val in self.recorded_response['headers'].iteritems():
|
||||
self.msg.addheader(key, val)
|
||||
# msg.addheaders adds the headers to msg.dict, but not to
|
||||
# the msg.headers list representation of headers, so
|
||||
# I have to add it to both.
|
||||
self.msg.headers.append("{0}:{1}".format(key, val))
|
||||
headers = self.recorded_response['headers']
|
||||
self.msg = parse_headers(headers)
|
||||
|
||||
self.length = self.msg.getheader('content-length') or None
|
||||
|
||||
@@ -37,15 +54,17 @@ class VCRHTTPResponse(object):
|
||||
return self._content.read(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
return True
|
||||
|
||||
def isclosed(self):
|
||||
# Urllib3 seems to call this because it actually uses
|
||||
# the weird chunking support in httplib
|
||||
return True
|
||||
return self.closed
|
||||
|
||||
def getheaders(self):
|
||||
return self.recorded_response['headers'].iteritems()
|
||||
headers = parse_headers(self.recorded_response['headers'])
|
||||
return headers.dict.iteritems()
|
||||
|
||||
|
||||
class VCRConnectionMixin:
|
||||
@@ -54,6 +73,10 @@ class VCRConnectionMixin:
|
||||
|
||||
def request(self, method, url, body=None, headers=None):
|
||||
'''Persist the request metadata in self._vcr_request'''
|
||||
# see VCRConnectionMixin._restore_socket for the motivation here
|
||||
if hasattr(self, 'sock'):
|
||||
del self.sock
|
||||
|
||||
self._vcr_request = Request(
|
||||
protocol=self._protocol,
|
||||
host=self.host,
|
||||
@@ -77,6 +100,22 @@ class VCRConnectionMixin:
|
||||
'''
|
||||
self._vcr_request.body = (self._vcr_request.body or '') + data
|
||||
|
||||
def close(self):
|
||||
self._restore_socket()
|
||||
self._baseclass.close(self)
|
||||
|
||||
def _restore_socket(self):
|
||||
"""
|
||||
Since some libraries (REQUESTS!!) decide to set options on
|
||||
connection.socket, I need to delete the socket attribute
|
||||
(which makes requests think this is a AppEngine connection)
|
||||
and then restore it when I want to make the actual request.
|
||||
This function restores it to its standard initial value
|
||||
(which is None)
|
||||
"""
|
||||
if not hasattr(self, 'sock'):
|
||||
self.sock = None
|
||||
|
||||
def _send_request(self, method, url, body, headers):
|
||||
"""
|
||||
Copy+pasted from python stdlib 2.6 source because it
|
||||
@@ -132,6 +171,7 @@ class VCRConnectionMixin:
|
||||
if isinstance(message_body, str):
|
||||
msg += message_body
|
||||
message_body = None
|
||||
self._restore_socket()
|
||||
self._baseclass.send(self, msg)
|
||||
if message_body is not None:
|
||||
#message_body was not a string (i.e. it is a file) and
|
||||
@@ -143,20 +183,25 @@ class VCRConnectionMixin:
|
||||
# Check to see if the cassette has a response for this request. If so,
|
||||
# then return it
|
||||
if self._vcr_request in self.cassette and \
|
||||
self.cassette.record_mode != "all":
|
||||
response = self.cassette.response_of(self._vcr_request)
|
||||
# Alert the cassette to the fact that we've served another
|
||||
# response for the provided requests
|
||||
self.cassette.mark_played(self._vcr_request)
|
||||
self.cassette.record_mode != "all" and \
|
||||
self.cassette.rewound:
|
||||
response = self.cassette.play_response(self._vcr_request)
|
||||
return VCRHTTPResponse(response)
|
||||
else:
|
||||
if self.cassette.write_protected:
|
||||
raise Exception("cassette is write protected")
|
||||
raise CannotOverwriteExistingCassetteException(
|
||||
"Can't overwrite existing cassette (%r) in "
|
||||
"your current record mode (%r)."
|
||||
% (self.cassette._path, self.cassette.record_mode)
|
||||
)
|
||||
|
||||
# Otherwise, we should send the request, then get the response
|
||||
# and return it.
|
||||
|
||||
# make the request
|
||||
# restore sock's value to None, since we need a real socket
|
||||
self._restore_socket()
|
||||
|
||||
#make the actual request
|
||||
self._baseclass.request(
|
||||
self,
|
||||
method=self._vcr_request.method,
|
||||
@@ -174,7 +219,7 @@ class VCRConnectionMixin:
|
||||
'code': response.status,
|
||||
'message': response.reason
|
||||
},
|
||||
'headers': dict(response.getheaders()),
|
||||
'headers': response.msg.headers,
|
||||
'body': {'string': response.read()},
|
||||
}
|
||||
self.cassette.append(self._vcr_request, response)
|
||||
@@ -187,9 +232,6 @@ class VCRHTTPConnection(VCRConnectionMixin, HTTPConnection):
|
||||
_baseclass = HTTPConnection
|
||||
_protocol = 'http'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
HTTPConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnectionMixin, HTTPSConnection):
|
||||
'''A Mocked class for HTTPS requests'''
|
||||
|
||||
Reference in New Issue
Block a user