import re from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime from random import randrange from typing import List import boto3 from sme_logger.logger import LoggerFactory from apollo_delphi_migration.cli import read_cli_args from apollo_delphi_migration.migration import ( MigrationTaskFactory, ApolloReportLicensorKeyMatcher, MigrationTaskPostProcessor, MigrationContext, ValidationTaskFactory, TheOrchardKeyMatcher) from apollo_delphi_migration.postprocessing import ( FileSizeValidationPostProcessor, FingerprintTaggingPostProcessor, FingerprintValidationPostProcessor ) from apollo_delphi_migration.renaming import ( SpotifyRenamingStrategy, AppleMusicRenamingStrategy, RenamingStrategy, ApolloAppleMusicRenamingStrategy, TheOrchardAppleMusicRenamingStrategy) from apollo_delphi_migration.s3 import Bucket, RegexKeyMatcher from apollo_delphi_migration.utils import current_time_mills, dates_between def main(): args = read_cli_args() log = LoggerFactory('apollo-delphi-migration', args.log_level).get_logger() s3_client = boto3.client('s3') start_date = datetime.strptime(args.start_date, '%Y-%m-%d').date() end_date = datetime.strptime(args.end_date, '%Y-%m-%d').date() src_bucket = Bucket(log, s3_client, args.src_bucket) dst_bucket = Bucket(log, s3_client, args.dst_bucket) post_processors = [] renaming_strategies = [ SpotifyRenamingStrategy(log), ApolloAppleMusicRenamingStrategy(log), TheOrchardAppleMusicRenamingStrategy(log) ] if args.with_size_validation: log.info('Size validation post processor enabled') post_processors.append(FileSizeValidationPostProcessor(log)) with ThreadPoolExecutor() as executor: if args.with_fingerprint_tagging: log.info('Fingerprint tagging post processor enabled') post_processors.append(FingerprintTaggingPostProcessor(log, executor)) if args.with_fingerprint_validation: log.info('Fingerprint validation post processor enabled') post_processors.append(FingerprintValidationPostProcessor(log, executor)) context = MigrationContext( src_bucket=src_bucket, dst_bucket=dst_bucket, renaming_strategies=renaming_strategies, post_processors=post_processors ) if args.no_copy: task_factory = ValidationTaskFactory(log, context) else: task_factory = MigrationTaskFactory(log, context) if args.src_org == 'apollo': key_matcher = ApolloReportLicensorKeyMatcher(args.report_type, args.licensor) elif args.src_org == 'orchard': key_matcher = TheOrchardKeyMatcher(args.report_type) else: raise ValueError(f'{args.src_org} is not supported') millis = current_time_mills() log.info('Identifying keys in bucket `%s`', src_bucket.name) # Built S3 prefixes by list of dates prefixes = map(lambda d: args.dsp + '/' + str(d), dates_between(start_date, end_date)) # Find all keys with required prefixes keys = executor.map(lambda p: src_bucket.find_all(p, key_matcher), list(prefixes)) keys_list = [key for sublist in keys for key in sublist] if len(keys_list) > 0: log.info('%s keys found. Data are being processed (`%s` -> `%s`)', len(keys_list), src_bucket.name, dst_bucket.name) if args.random_files_check: keys_before_filtering = len(keys_list) keys_list = filter_random(keys_list, renaming_strategies) log.info('Random files validation enabled. %s/%s keys are being validated.', len(keys_list), keys_before_filtering) # Copy files results = list(executor.map(lambda key: task_factory.create(key).execute(), keys_list)) log.info('Processing complete. Files: %s. Time: %sms', len(results), current_time_mills() - millis) else: log.info('Nothing to copy.') def filter_random(keys: List[str], renaming_strategies: List[RenamingStrategy]): excluded_patterns = [ re.compile('^spotify/streams/v1/report_date=2019-09-[0-9]{2}/report_licensor=[a-zA-Z]+$'), re.compile('^spotify/users/v1/report_date=2019-09-[0-9]{2}/report_licensor=[a-zA-Z]+$') ] grouped_keys = {} for key in keys: renaming_strategy = RenamingStrategy.select_renaming_strategy(renaming_strategies, key) if not renaming_strategy: raise NotImplementedError('No suitable renaming strategy found.') # transform key to SLZ format and remove filename grouping_key = renaming_strategy.rename(key).rsplit('/', 1)[0] # Decide if we have to skip current key skip = False for exclusion in excluded_patterns: if exclusion.fullmatch(grouping_key): skip = True if skip: continue if grouping_key not in grouped_keys: grouped_keys[grouping_key] = [key] else: grouped_keys[grouping_key].append(key) filtered = [] for keys_list in grouped_keys.values(): filtered.append(keys_list[0 if len(keys_list) == 1 else randrange(len(keys_list))]) return filtered