mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-09 17:15:35 +00:00
Added migration script for old cassettes
This commit is contained in:
31
tests/fixtures/migration/new_cassette.json
vendored
Normal file
31
tests/fixtures/migration/new_cassette.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"body": null,
|
||||||
|
"method": "GET",
|
||||||
|
"headers": {
|
||||||
|
"Accept-Encoding": "gzip, deflate, compress",
|
||||||
|
"Accept": "*/*",
|
||||||
|
"User-Agent": "python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0"
|
||||||
|
},
|
||||||
|
"uri" : "http://httpbin.org:80/ip"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": {
|
||||||
|
"message": "OK",
|
||||||
|
"code": 200
|
||||||
|
},
|
||||||
|
"headers": [
|
||||||
|
"Access-Control-Allow-Origin: *\r\n",
|
||||||
|
"Content-Type: application/json\r\n",
|
||||||
|
"Date: Mon, 21 Apr 2014 23:13:40 GMT\r\n",
|
||||||
|
"Server: gunicorn/0.17.4\r\n",
|
||||||
|
"Content-Length: 32\r\n",
|
||||||
|
"Connection: keep-alive\r\n"
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"string": "{\n \"origin\": \"217.122.164.194\"\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
15
tests/fixtures/migration/new_cassette.yaml
vendored
Normal file
15
tests/fixtures/migration/new_cassette.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- 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, python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0]
|
||||||
|
- !!python/tuple [Accept, '*/*']
|
||||||
|
method: GET
|
||||||
|
uri: http://httpbin.org:80/ip
|
||||||
|
response:
|
||||||
|
body: {string: !!python/unicode "{\n \"origin\": \"217.122.164.194\"\n}"}
|
||||||
|
headers: [!!python/unicode "Access-Control-Allow-Origin: *\r\n", !!python/unicode "Content-Type:
|
||||||
|
application/json\r\n", !!python/unicode "Date: Mon, 21 Apr 2014 23:06:09 GMT\r\n",
|
||||||
|
!!python/unicode "Server: gunicorn/0.17.4\r\n", !!python/unicode "Content-Length:
|
||||||
|
32\r\n", !!python/unicode "Connection: keep-alive\r\n"]
|
||||||
|
status: {code: 200, message: OK}
|
||||||
1
tests/fixtures/migration/not_cassette.txt
vendored
Normal file
1
tests/fixtures/migration/not_cassette.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is not a cassette
|
||||||
34
tests/fixtures/migration/old_cassette.json
vendored
Normal file
34
tests/fixtures/migration/old_cassette.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"body": null,
|
||||||
|
"protocol": "http",
|
||||||
|
"method": "GET",
|
||||||
|
"headers": {
|
||||||
|
"Accept-Encoding": "gzip, deflate, compress",
|
||||||
|
"Accept": "*/*",
|
||||||
|
"User-Agent": "python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0"
|
||||||
|
},
|
||||||
|
"host": "httpbin.org",
|
||||||
|
"path": "/ip",
|
||||||
|
"port": 80
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": {
|
||||||
|
"message": "OK",
|
||||||
|
"code": 200
|
||||||
|
},
|
||||||
|
"headers": [
|
||||||
|
"Access-Control-Allow-Origin: *\r\n",
|
||||||
|
"Content-Type: application/json\r\n",
|
||||||
|
"Date: Mon, 21 Apr 2014 23:13:40 GMT\r\n",
|
||||||
|
"Server: gunicorn/0.17.4\r\n",
|
||||||
|
"Content-Length: 32\r\n",
|
||||||
|
"Connection: keep-alive\r\n"
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"string": "{\n \"origin\": \"217.122.164.194\"\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
18
tests/fixtures/migration/old_cassette.yaml
vendored
Normal file
18
tests/fixtures/migration/old_cassette.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
- 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, python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0]
|
||||||
|
- !!python/tuple [Accept, '*/*']
|
||||||
|
host: httpbin.org
|
||||||
|
method: GET
|
||||||
|
path: /ip
|
||||||
|
port: 80
|
||||||
|
protocol: http
|
||||||
|
response:
|
||||||
|
body: {string: !!python/unicode "{\n \"origin\": \"217.122.164.194\"\n}"}
|
||||||
|
headers: [!!python/unicode "Access-Control-Allow-Origin: *\r\n", !!python/unicode "Content-Type:
|
||||||
|
application/json\r\n", !!python/unicode "Date: Mon, 21 Apr 2014 23:06:09 GMT\r\n",
|
||||||
|
!!python/unicode "Server: gunicorn/0.17.4\r\n", !!python/unicode "Content-Length:
|
||||||
|
32\r\n", !!python/unicode "Connection: keep-alive\r\n"]
|
||||||
|
status: {code: 200, message: OK}
|
||||||
36
tests/unit/test_migration.py
Normal file
36
tests/unit/test_migration.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import filecmp
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import vcr.migration
|
||||||
|
|
||||||
|
|
||||||
|
def test_try_migrate_with_json(tmpdir):
|
||||||
|
cassette = tmpdir.join('cassette').strpath
|
||||||
|
shutil.copy('tests/fixtures/migration/old_cassette.json', cassette)
|
||||||
|
assert vcr.migration.try_migrate(cassette)
|
||||||
|
with open('tests/fixtures/migration/new_cassette.json', 'r') as f:
|
||||||
|
expected_json = json.load(f)
|
||||||
|
with open(cassette, 'r') as f:
|
||||||
|
actual_json = json.load(f)
|
||||||
|
assert actual_json == expected_json
|
||||||
|
|
||||||
|
|
||||||
|
def test_try_migrate_with_yaml(tmpdir):
|
||||||
|
cassette = tmpdir.join('cassette').strpath
|
||||||
|
shutil.copy('tests/fixtures/migration/old_cassette.yaml', cassette)
|
||||||
|
assert vcr.migration.try_migrate(cassette)
|
||||||
|
assert filecmp.cmp(cassette, 'tests/fixtures/migration/new_cassette.yaml')
|
||||||
|
|
||||||
|
|
||||||
|
def test_try_migrate_with_invalid_or_new_cassettes(tmpdir):
|
||||||
|
cassette = tmpdir.join('cassette').strpath
|
||||||
|
files = [
|
||||||
|
'tests/fixtures/migration/not_cassette.txt',
|
||||||
|
'tests/fixtures/migration/new_cassette.yaml',
|
||||||
|
'tests/fixtures/migration/new_cassette.json',
|
||||||
|
]
|
||||||
|
for file_path in files:
|
||||||
|
shutil.copy(file_path, cassette)
|
||||||
|
assert not vcr.migration.try_migrate(cassette)
|
||||||
|
assert filecmp.cmp(cassette, file_path) # shold not change file
|
||||||
104
vcr/migration.py
Normal file
104
vcr/migration.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
Migration script for old 'yaml' and 'json' cassettes
|
||||||
|
|
||||||
|
.. warning:: Backup your cassettes files before migration.
|
||||||
|
|
||||||
|
It merges and deletes the request obsolete keys (protocol, host, port, path)
|
||||||
|
into new 'uri' key.
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
python -m vcr.migration PATH
|
||||||
|
|
||||||
|
The PATH can be path to the directory with cassettes or cassette itself
|
||||||
|
"""
|
||||||
|
|
||||||
|
from contextlib import closing
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
PARTS = [
|
||||||
|
'protocol',
|
||||||
|
'host',
|
||||||
|
'port',
|
||||||
|
'path',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_uri(**parts):
|
||||||
|
return "{protocol}://{host}:{port}{path}".format(**parts)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_json(in_fp, out_fp):
|
||||||
|
data = json.load(in_fp)
|
||||||
|
for item in data:
|
||||||
|
req = item['request']
|
||||||
|
uri = {k: req.pop(k) for k in PARTS}
|
||||||
|
req['uri'] = build_uri(**uri)
|
||||||
|
json.dump(data, out_fp, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_yml(in_fp, out_fp):
|
||||||
|
migrated = False
|
||||||
|
uri = dict.fromkeys(PARTS, None)
|
||||||
|
for line in in_fp:
|
||||||
|
for part in uri:
|
||||||
|
match = re.match('\s+{}:\s(.*)'.format(part), line)
|
||||||
|
if match:
|
||||||
|
uri[part] = match.group(1)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
out_fp.write(line)
|
||||||
|
|
||||||
|
if None not in uri.values(): # if all uri parts are collected
|
||||||
|
out_fp.write(" uri: {}\n".format(build_uri(**uri)))
|
||||||
|
uri = dict.fromkeys(PARTS, None) # reset dict
|
||||||
|
migrated = True
|
||||||
|
if not migrated:
|
||||||
|
raise RuntimeError("migration failed")
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(file_path, migration_fn):
|
||||||
|
# because we assume that original files can be reverted
|
||||||
|
# we will try to copy the content. (os.rename not needed)
|
||||||
|
with closing(tempfile.TemporaryFile()) as out_fp:
|
||||||
|
with open(file_path, 'r') as in_fp:
|
||||||
|
migration_fn(in_fp, out_fp)
|
||||||
|
with open(file_path, 'w') as in_fp:
|
||||||
|
out_fp.seek(0)
|
||||||
|
shutil.copyfileobj(out_fp, in_fp)
|
||||||
|
|
||||||
|
|
||||||
|
def try_migrate(path):
|
||||||
|
try: # try to migrate as json
|
||||||
|
migrate(path, migrate_json)
|
||||||
|
except: # probably the file is not a json
|
||||||
|
try: # let's try to migrate as yaml
|
||||||
|
migrate(path, migrate_yml)
|
||||||
|
except: # oops probably the file is not a cassette
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
raise SystemExit("Please provide path to cassettes directory or file. "
|
||||||
|
"Usage: python -m vcr.migration PATH")
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for file_name in files:
|
||||||
|
file_path = os.path.join(root, file_name)
|
||||||
|
migrated = try_migrate(file_path)
|
||||||
|
status = 'OK' if migrated else 'FAIL'
|
||||||
|
sys.stderr.write("[{}] {}\n".format(status, file_path))
|
||||||
|
sys.stderr.write("Done.\n")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user