def test_auto_crop_filename_generator_raises_on_bad_quality(self): """ Auto crop filename generator raises exception on bad quality """ params = dict(id=utils.generate_id('jpg'), size='100x200', factor='fit', output_format='jpg', upscale=True, quality="CRAP") pb = PathBuilder('12345') with assert_raises(x.InvalidArgumentException): pb.get_auto_crop_filename(**params)
def test_validate_signature(self): """ Validating signature contained within filename """ auto_id = utils.generate_id('test.jpg') auto_params = dict(id=auto_id, size='100x200', factor='fill', output_format='jpg', upscale=True, quality=80) manual_id = utils.generate_id('test.jpg') manual_params = dict(id=manual_id, sample_size='200x400', target_size='100x200', output_format='jpg', upscale=True, quality=80) pb = PathBuilder('12345') auto = pb.get_auto_crop_filename(**auto_params) manual = pb.get_manual_crop_filename(**manual_params) bad = manual.split('-') bad[4] = 'ZZZ' + bad[4] bad = '-'.join(bad) self.assertTrue(pb.validate_signature(auto_id, auto)) self.assertTrue(pb.validate_signature(manual_id, manual)) self.assertFalse(pb.validate_signature(manual_id, bad))
def test_missing_autocrop_format_defaults_to_original_format(self): """ Autocrop defaults to original format when format not specified""" params = dict(id=utils.generate_id('zz.gif'), size='100x200', factor='fill', upscale=True, quality=80) pb = PathBuilder('12345') filename = pb.get_auto_crop_filename(**params) self.assertTrue(filename.endswith('.gif'))
def test_create_auto_crop_filename(self): """ Creating filename for auto crop""" params = dict(id=utils.generate_id('jpg'), size='100x200', factor='fill', output_format='jpg', upscale=True, quality=80) pb = PathBuilder('12345') filename = pb.get_auto_crop_filename(**params) start = '100x200-fill-80-upscale' self.assertTrue(filename.startswith(start))
def test_resize_filename_parser_raises_on_bad_signature(self): """ Resize filename parser raises on bad signature""" id = utils.generate_id('test.jpg') auto_params = dict(id=id + 'SOME-CRAP', size='100x200', factor='fill', output_format='jpg', upscale=True, quality=80) pb = PathBuilder('12345') filename = pb.get_auto_crop_filename(**auto_params) with assert_raises(x.InvalidArgumentException): pb.filename_to_resize_params(id, filename)
def test_parse_auto_resize_filename(self): """ Parse auto resize filename into a set of parameters """ id = utils.generate_id('test.jpg') params = dict(id=id, size='100x200', factor='fill', output_format='jpg', upscale=True, quality=80) pb = PathBuilder('12345') filename = pb.get_auto_crop_filename(**params) result = pb.filename_to_resize_params(id, filename) self.assertEquals(id, result['id']) self.assertEquals(filename, result['filename']) self.assertEquals(params['size'], result['target_size']) self.assertEquals(params['output_format'], result['output_format']) self.assertEquals(params['quality'], result['quality']) self.assertEquals(params['factor'], result['factor']) self.assertEquals(params['upscale'], result['upscale'])
class Storage: def __init__(self, backend, secret_key, local_temp): """ Init :param backend:, shiftmedia.backend.Backend instance :param secret_key: string, random salt :param local_temp: string, path to local temp directory """ self.backend = backend self.paths = PathBuilder(secret_key) self._tmp_path = local_temp @property def tmp(self): """ Get temp path Returns path to local temp and creates one if necessary """ if not os.path.exists(self._tmp_path): os.makedirs(self._tmp_path) return self._tmp_path def put(self, src, delete_local=True, fix_orientation=False): """ Put local file to storage Generates a uuid for the file, tells backend to accept it by that id and removes original on success. """ if not os.path.exists(src): msg = 'Unable to find local file [{}]' raise x.LocalFileNotFound(msg.format(src)) path = Path(src) extension = ''.join(path.suffixes)[1:] name = path.name.replace('.' + extension, '') extension = utils.normalize_extension(extension) filename = name + '.' + extension id = utils.generate_id(filename) # fix image orientation before accepting if fix_orientation: Resizer.fix_orientation_and_save(src) self.backend.put_variant(src, id, filename.lower()) if delete_local: os.remove(src) return id def delete(self, id): """ Delete Removes file and all its artifacts from storage by id """ return self.backend.delete(id) def get_original_url(self, id): """ Get original URL Combines backend base url, path to object id and original filename. :return: string - full object url """ base = self.backend.get_url().rstrip('/') parts = self.backend.id_to_path(id) filename = parts[5] path = '/'.join(parts) return base + '/' + path + '/' + filename def get_auto_crop_url(self, *args, **kwargs): """ Get auto crop URL Combines backend base url, path to object id and generated filename. :param args: positional args to be passed to filename generator :param kwargs: keyword args to be passed to filename generator :return: string - full object url """ id = kwargs['id'] if 'id' in kwargs else args[0] base = self.backend.get_url().rstrip('/') parts = self.backend.id_to_path(id) path = '/'.join(parts) filename = self.paths.get_auto_crop_filename(*args, **kwargs) return base + '/' + path + '/' + filename def get_manual_crop_url(self, *args, **kwargs): """ Get manual crop URL Combines backend base url, path to object id and generated filename. :param args: positional args to be passed to filename generator :param kwargs: keyword args to be passed to filename generator :return: string - full object url """ id = kwargs['id'] if 'id' in kwargs else args[0] base = self.backend.get_url().rstrip('/') parts = self.backend.id_to_path(id) path = '/'.join(parts) filename = self.paths.get_manual_crop_filename(*args, **kwargs) return base + '/' + path + '/' + filename def create_resize(self, url): """ Create resize Accepts storage URL of a resize, parses and validates it and then creates the resize to be put back to storage. :param url: string - url of resize to be created :return: string - same url on success """ id, filename = self.backend.parse_url(url) params = self.paths.filename_to_resize_params(id, filename) mode = params['resize_mode'] modes = ['auto', 'manual'] if mode not in modes: err = 'Resize mode [' + mode + '] is not yet implemented.' raise x.NotImplementedError(err) local_original = self.backend.retrieve_original(id, self._tmp_path) local_resize = os.path.join(self._tmp_path, id, params['filename']) factor = Resizer.RESIZE_TO_FIT if params['factor'] == 'fill': factor = Resizer.RESIZE_TO_FILL resize = Resizer.auto_crop( src=local_original, dst=local_resize, size=params['target_size'], mode= factor, upscale=params['upscale'], format=params['output_format'], quality=params['quality'] ) try: self.backend.put_variant(resize, id, filename, force=True) except x.FileExists: pass os.remove(local_original) os.remove(resize) tmp_dir = os.path.join(self._tmp_path, id) if not os.listdir(tmp_dir): os.rmdir(tmp_dir) return url