Skip to content

Commit

Permalink
Add migration script
Browse files Browse the repository at this point in the history
  • Loading branch information
zstyblik committed Dec 27, 2020
1 parent ca0710e commit f509c52
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 0 deletions.
File renamed without changes.
72 changes: 72 additions & 0 deletions migrations/convert_cache_to_dataclass_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Convert original cache file(simple dict) to data class v1."""
import argparse
import logging
import pickle
import shutil
import sys
from dataclasses import dataclass, field


@dataclass
class CachedData:
"""CachedData represents locally cached data and state."""

items: dict = field(default_factory=dict)


def main():
"""Open cache file, convert it and overwrite it.
Backup file is created in the process. Manual cleanup is required after
migration.
"""
logging.basicConfig(stream=sys.stdout)
logger = logging.getLogger('migrate-to-dataclass')
args = parse_args()
if args.verbosity:
logger.setLevel(logging.DEBUG)

logger.info("Read cache from file '%s'.", args.cache)
with open(args.cache, 'rb') as fhandle:
cache = pickle.load(fhandle)

if not isinstance(cache, dict):
logger.error(
"Cache file '%s' has invalid format, dict is expected.", args.cache
)
sys.exit(1)

bak_file = '{}.bak'.format(args.cache)
logger.info("Create backup file '%s' from '%s'.", bak_file, args.cache)
shutil.copy2(args.cache, bak_file)

new_cache = CachedData()
for key, value in cache.items():
new_cache.items[key] = value

logger.info("Write converted cache into file '%s'.", args.cache)
with open(args.cache, 'wb') as fhandle:
pickle.dump(new_cache, fhandle, pickle.HIGHEST_PROTOCOL)

logger.info("Migration complete and '%s' can be removed.", bak_file)


def parse_args() -> argparse.Namespace:
"""Return parsed CLI args."""
parser = argparse.ArgumentParser()
parser.add_argument(
'-v', '--verbose',
dest='verbosity', action='store_true', default=False,
help='Increase logging verbosity.'
)
parser.add_argument(
'--cache',
dest='cache', type=str, default=None, required=True,
help='File which contains cache.'
)
return parser.parse_args()


if __name__ == '__main__':
main()
72 changes: 72 additions & 0 deletions migrations/tests/test_convert_cache_to_dataclass_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Unit tests for convert_cache_to_dataclass_v1.py."""
import io
import os
import pickle
import sys
from unittest.mock import patch

import pytest

import migrations.convert_cache_to_dataclass_v1 as migration # noqa:I202


@pytest.fixture
def fixture_bak_cleanup():
"""Cleanup bak file which is created during migration."""
bak_fnames = []

def _fixture_back_cleanup(bak_fname):
bak_fnames.append(bak_fname)
return bak_fname

yield _fixture_back_cleanup

for bak_fname in bak_fnames:
print("teardown of '{}'".format(bak_fname))
os.remove(bak_fname)


def test_migration(fixture_cache_file, fixture_bak_cleanup):
"""Test migration under ideal conditions."""
bak_file = '{}.bak'.format(fixture_cache_file)
_ = fixture_bak_cleanup(bak_file)
expected_cache = migration.CachedData(
items={
'test1': 1234,
'test2': 0,
}
)

test_data = {
'test1': 1234,
'test2': 0,
}
with open(fixture_cache_file, 'wb') as fhandle:
pickle.dump(test_data, fhandle, pickle.HIGHEST_PROTOCOL)

exception = None
args = [
'./convert_cache_to_dataclass_v1.py',
'--cache',
fixture_cache_file,
]

saved_stdout = sys.stdout
out = io.StringIO()
sys.stdout = out

with patch.object(sys, 'argv', args):
try:
migration.main()
except Exception as exc:
exception = exc
finally:
sys.stdout = saved_stdout

assert exception is None
assert os.path.exists(bak_file) is True

with open(fixture_cache_file, 'rb') as fhandle:
migrated_cache = pickle.load(fhandle)
assert migrated_cache == expected_cache

0 comments on commit f509c52

Please sign in to comment.