New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replication #26
Replication #26
Changes from all commits
6cf144b
fa61fb1
386c32d
4fdec69
100a539
bab7046
68d8a9e
8080b15
cd04773
789883a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from . import utils | ||
from collections import defaultdict | ||
|
||
|
||
class Batch(object): | ||
|
@@ -15,7 +16,7 @@ def request(self, method, endpoint, data=None, permissions=None, | |
# is called. | ||
self.requests.append((method, endpoint, data, permissions, headers)) | ||
# This is the signature of the session request. | ||
return None, None | ||
return defaultdict(dict), defaultdict(dict) | ||
|
||
def reset(self): | ||
# Reinitialize the batch. | ||
|
@@ -24,9 +25,10 @@ def reset(self): | |
def _build_requests(self): | ||
requests = [] | ||
for (method, url, data, permissions, headers) in self.requests: | ||
# Strip the prefix in batch requests. | ||
request = { | ||
'method': method.upper(), | ||
'path': url} | ||
'path': url.replace('v1/', '')} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you have a test somewhere for this one? |
||
|
||
request['body'] = {} | ||
if data is not None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,8 +3,12 @@ class KintoException(Exception): | |
|
||
|
||
class BucketNotFound(KintoException): | ||
def __init__(self, message, exception): | ||
def __init__(self, message=None, exception=None): | ||
super(BucketNotFound, self).__init__(self, message) | ||
self.message = message | ||
self.request = exception.request | ||
self.response = exception.response | ||
if exception is not None: | ||
self.request = exception.request | ||
self.response = exception.response | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't provide default value for those attributes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea :) |
||
else: | ||
self.request = None | ||
self.response = None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import argparse | ||
import logging | ||
|
||
from kinto_client import Client | ||
from kinto_client import exceptions | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def replicate(origin, destination): | ||
"""Replicates records from one collection to another one. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could maybe add a note to specify that it replicates all records and not just the ones that changed. |
||
All records are replicated, not only the ones that changed. | ||
""" | ||
msg = 'Replication from {0} to {1}'.format(origin, destination) | ||
logger.info(msg) | ||
|
||
try: | ||
destination.get_bucket() | ||
except exceptions.BucketNotFound: | ||
destination.create_bucket() | ||
try: | ||
destination.get_collection() | ||
except exceptions.KintoException: | ||
collection_data = origin.get_collection() | ||
destination.create_collection( | ||
data=collection_data['data'], | ||
permissions=collection_data['permissions'], safe=False) | ||
|
||
records = origin.get_records() | ||
logger.info('replication of {0} records'.format(len(records))) | ||
with destination.batch() as batch: | ||
for record in records: | ||
if record.get('deleted', False) is True: | ||
batch.delete_record(record['id'], | ||
last_modified=record['last_modified']) | ||
else: | ||
batch.update_record(data=record, safe=False) | ||
|
||
|
||
def get_arguments(): # pragma: nocover | ||
description = 'Migrate data from one kinto instance to another one.' | ||
parser = argparse.ArgumentParser(description=description) | ||
parser.add_argument('origin_server', | ||
help='The location of the origin server (with prefix)') | ||
parser.add_argument('destination_server', | ||
help=('The location of the destination server ' | ||
'(with prefix)')) | ||
parser.add_argument('bucket', help='The name of the bucket') | ||
parser.add_argument('collection', help='The name of the collection') | ||
|
||
# Auth: XXX improve later. For now only support Basic Auth. | ||
parser.add_argument('-a', '--auth', dest='auth', | ||
help='Authentication, in the form "username:password"') | ||
|
||
# Optional arguments. They will be derivated from the "bucket" | ||
# and "collection" ones. | ||
parser.add_argument('--destination-bucket', dest='destination_bucket', | ||
help='The name of the destination bucket', | ||
default=None) | ||
parser.add_argument('--destination-collection', | ||
dest='destination_collection', | ||
help='The name of the destination bucket', | ||
default=None) | ||
|
||
# Defaults | ||
parser.add_argument('-v', '--verbose', action='store_const', | ||
const=logging.INFO, dest='verbosity', | ||
help='Show all messages.') | ||
|
||
parser.add_argument('-q', '--quiet', action='store_const', | ||
const=logging.CRITICAL, dest='verbosity', | ||
help='Show only critical errors.') | ||
|
||
parser.add_argument('-D', '--debug', action='store_const', | ||
const=logging.DEBUG, dest='verbosity', | ||
help='Show all messages, including debug messages.') | ||
return parser.parse_args() | ||
|
||
|
||
def setup_logger(args): # pragma: nocover | ||
logger.addHandler(logging.StreamHandler()) | ||
if args.verbosity: | ||
logger.setLevel(args.verbosity) | ||
|
||
|
||
def main(): # pragma: nocover | ||
args = get_arguments() | ||
setup_logger(args) | ||
|
||
auth = tuple(args.auth.split(':')) if args.auth else None | ||
|
||
origin = Client( | ||
server_url=args.origin_server, | ||
auth=auth, | ||
bucket=args.bucket, | ||
collection=args.collection | ||
) | ||
destination = Client( | ||
server_url=args.destination_server, | ||
auth=auth, | ||
bucket=args.destination_bucket or args.bucket, | ||
collection=args.destination_collection or args.collection | ||
) | ||
|
||
replicate(origin, destination) | ||
|
||
|
||
if __name__ == "__main__": # pragma: nocover | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this conflicts with #34. I prefer the other approach, returning constants for batched responses here.