diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..adb1727d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,24 @@ +[run] +source = imgee + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code + def __repr__ + if self\.debug + + # Don't complain about importerror handlers + except ImportError + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if False: + if __name__ == .__main__.: diff --git a/.gitignore b/.gitignore index 32c84c02..4904881e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ nosetests.xml .sass-cache instance/production.py instance/development.py +instance/testing.py baseframe-packed.css baseframe-packed.js error.log diff --git a/.travis.yml b/.travis.yml index 1e23783c..d0a28772 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ env: - FLASK_ENV=TESTING language: python python: - - 2.7 + - 3.6 services: - redis-server before_install: diff --git a/imgee/models/__init__.py b/imgee/models/__init__.py index aeff7f71..9a42364e 100644 --- a/imgee/models/__init__.py +++ b/imgee/models/__init__.py @@ -6,7 +6,7 @@ db = SQLAlchemy(app) -from imgee.models.user import * # NOQA # isort:skip -from imgee.models.stored_file import * # NOQA # isort:skip -from imgee.models.thumbnail import * # NOQA # isort:skip -from imgee.models.profile import * # NOQA # isort:skip +from .user import * # NOQA # isort:skip +from .stored_file import * # NOQA # isort:skip +from .thumbnail import * # NOQA # isort:skip +from .profile import * # NOQA # isort:skip diff --git a/imgee/models/stored_file.py b/imgee/models/stored_file.py index 65a1ce35..9d339fc9 100644 --- a/imgee/models/stored_file.py +++ b/imgee/models/stored_file.py @@ -79,7 +79,7 @@ def dict_data(self): 'title': self.title, 'uploaded': self.created_at.isoformat() + 'Z', 'filesize': app.jinja_env.filters['filesizeformat'](self.size), - 'imgsize': u'%s×%s px' % (self.width, self.height), + 'imgsize': '%s×%s px' % (self.width, self.height), 'url': url_for('view_image', profile=self.profile.name, image=self.name), 'thumb_url': url_for( 'get_image', image=self.name, size=app.config.get('THUMBNAIL_SIZE') diff --git a/imgee/storage.py b/imgee/storage.py index 4fd40b7c..c1d6def3 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -90,7 +90,7 @@ def save_file(fp, profile, title=None): img_name = "%s%s" % (id_, extn) local_path = path_for(img_name) - with open(local_path, 'w') as img: + with open(local_path, 'wb') as img: img.write(fp.read()) stored_file = save_img_in_db( @@ -116,13 +116,13 @@ def save_img_in_db(name, title, local_path, profile, mimetype, orig_extn): width, height = get_width_height(local_path) stored_file = StoredFile( name=name, - title=unicode(title), + title=title, profile=profile, - orig_extn=unicode(orig_extn), + orig_extn=orig_extn, size=size_in_bytes, width=width, height=height, - mimetype=unicode(mimetype), + mimetype=mimetype, ) if ( 'thumb_extn' in ALLOWED_MIMETYPES[mimetype] @@ -153,23 +153,24 @@ def save_tn_in_db(img, tn_name, size): return name -def save_on_s3(filename, remotename='', content_type='', bucket='', folder=''): +def save_on_s3(filename, remotename='', content_type=''): """ Save contents from file named `filename` to `remotename` on S3. """ - b = bucket or get_s3_bucket() - folder = get_s3_folder(folder) + bucket = get_s3_bucket() + folder = get_s3_folder() + key = os.path.join(folder, filename) - with open(path_for(filename)) as fp: + with open(path_for(filename), 'rb') as fp: filename = remotename or filename - k = b.new_key(folder + filename) - headers = { - 'Cache-Control': 'max-age=31536000', # 60*60*24*365 - 'Content-Type': get_file_type(fp, filename), - # once cached, it is set to expire after a year - 'Expires': datetime.utcnow() + timedelta(days=365), - } - k.set_contents_from_file(fp, policy='public-read', headers=headers) + bucket.put_object( + ACL='public-read', + Key=key, + Body=fp.read(), + CacheControl='max-age=31536000', + ContentType=content_type or get_file_type(fp, filename), + Expires=datetime.utcnow() + timedelta(days=365), + ) return filename @@ -181,7 +182,7 @@ def parse_size(size): Calculate and return (w, h) from the query parameter `size`. Returns None if not formattable. """ - if isinstance(size, (str, unicode)): + if isinstance(size, str): # return (w, h) if size is 'wxh' r = r'^(\d+)(x(\d+))?$' matched = re.match(r, size) @@ -236,7 +237,7 @@ def get_fitting_size(original_size, size): w, h = w * size[1] / float(h), size[1] size = int(w), int(h) - size = map(lambda x: max(x, 1), size) # let the width or height be atleast 1px. + size = [max(x, 1) for x in size] # let the width or height be atleast 1px. return size @@ -353,7 +354,9 @@ def delete(stored_file, commit=True): keys = [(get_s3_folder() + thumbnail.name + extn) for thumbnail in thumbnails] keys.append(get_s3_folder() + stored_file.name + extn) bucket = get_s3_bucket() - bucket.delete_keys(keys) + bucket.delete_objects( + Delete={'Objects': [{'Key': key} for key in keys], 'Quiet': True} + ) # remove from the db # remove thumbnails explicitly. diff --git a/imgee/tasks.py b/imgee/tasks.py index 06169ee8..e8860e28 100644 --- a/imgee/tasks.py +++ b/imgee/tasks.py @@ -32,7 +32,7 @@ def is_valid_query(self, query): return bool(self.filename_pattern.match(query)) def key_for(self, taskid): - return u'{key_prefix}:{taskid}'.format( + return '{key_prefix}:{taskid}'.format( key_prefix=self.key_prefix, taskid=taskid ) @@ -50,7 +50,7 @@ def search(self, query): # >> KEYS imgee:registry:default:*query* if not self.is_valid_query(query): raise InvalidRedisQueryException( - u'Invalid query for searching redis keys: {}'.format(query) + 'Invalid query for searching redis keys: {}'.format(query) ) return self.connection.keys(self.key_for('*{}*'.format(query))) @@ -64,7 +64,7 @@ def keys_starting_with(self, query): # thumbnail keys, which look like "name_wNN_hNN", hence the _ if not self.is_valid_query(query): raise InvalidRedisQueryException( - u'Invalid query for searching redis keys, starting with: {}'.format( + 'Invalid query for searching redis keys, starting with: {}'.format( query ) ) diff --git a/imgee/utils.py b/imgee/utils.py index 3c109840..f6e68ad7 100644 --- a/imgee/utils.py +++ b/imgee/utils.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- from subprocess import CalledProcessError, check_output -from urlparse import urljoin +from urllib.parse import urljoin from uuid import uuid4 import os.path import re from flask import request -from boto import connect_s3 -from boto.s3.bucket import Bucket -from boto.s3.key import Key +import boto3 +import botocore from PIL import Image import defusedxml.cElementTree as ElementTree import magic @@ -19,6 +18,7 @@ from . import app + THUMBNAIL_COMMANDS = { 'inkscape': "inkscape -z -f {src} -e {src}.original.png && convert -quiet -thumbnail {width}x{height} {src}.original.png -colorspace sRGB -quality 75% {dest}", 'rsvg-convert': "rsvg-convert --width={width} --height={height} --keep-aspect-ratio=TRUE --format={format} {src} > {dest}", @@ -28,212 +28,212 @@ } ALLOWED_MIMETYPES = { - 'image/jpg': {'allowed_extns': [u'.jpe', u'.jpg', u'.jpeg'], 'extn': u'.jpeg'}, - 'image/jpe': {'allowed_extns': [u'.jpe', u'.jpg', u'.jpeg'], 'extn': u'.jpeg'}, - 'image/jpeg': {'allowed_extns': [u'.jpe', u'.jpg', u'.jpeg'], 'extn': u'.jpeg'}, - 'image/pjpeg': {'allowed_extns': [u'.jpe', u'.jpg', u'.jpeg'], 'extn': u'.jpeg'}, - 'image/png': {'allowed_extns': [u'.png'], 'extn': u'.png'}, + 'image/jpg': {'allowed_extns': ['.jpe', '.jpg', '.jpeg'], 'extn': '.jpeg'}, + 'image/jpe': {'allowed_extns': ['.jpe', '.jpg', '.jpeg'], 'extn': '.jpeg'}, + 'image/jpeg': {'allowed_extns': ['.jpe', '.jpg', '.jpeg'], 'extn': '.jpeg'}, + 'image/pjpeg': {'allowed_extns': ['.jpe', '.jpg', '.jpeg'], 'extn': '.jpeg'}, + 'image/png': {'allowed_extns': ['.png'], 'extn': '.png'}, 'image/gif': { - 'allowed_extns': [u'.gif'], - 'extn': u'.gif', + 'allowed_extns': ['.gif'], + 'extn': '.gif', 'processor': 'convert-layered', }, 'image/vnd.adobe.photoshop': { - 'allowed_extns': [u'.psd'], - 'extn': u'.psd', + 'allowed_extns': ['.psd'], + 'extn': '.psd', 'thumb_extn': '.jpeg', 'processor': 'convert-layered', }, 'application/pdf': { - 'allowed_extns': [u'.pdf', u'.ai'], - 'extn': [u'.pdf', u'.ai'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.pdf', '.ai'], + 'extn': ['.pdf', '.ai'], + 'thumb_extn': '.png', 'processor': 'convert-pdf', }, 'application/illustrator': { - 'allowed_extns': [u'.ai'], - 'extn': u'.ai', - 'thumb_extn': u'.png', + 'allowed_extns': ['.ai'], + 'extn': '.ai', + 'thumb_extn': '.png', }, 'application/postscript': { - 'allowed_extns': [u'.eps'], - 'extn': u'.eps', - 'thumb_extn': u'.png', + 'allowed_extns': ['.eps'], + 'extn': '.eps', + 'thumb_extn': '.png', }, 'image/svg+xml': { - 'allowed_extns': [u'.svg'], - 'extn': u'.svg', - 'thumb_extn': u'.png', + 'allowed_extns': ['.svg'], + 'extn': '.svg', + 'thumb_extn': '.png', 'processor': 'rsvg-convert', }, 'application/x-gzip': { - 'allowed_extns': [u'.svgz'], - 'extn': u'.svgz', - 'thumb_extn': u'.png', + 'allowed_extns': ['.svgz'], + 'extn': '.svgz', + 'thumb_extn': '.png', 'processor': 'rsvg-convert', }, - 'image/bmp': {'allowed_extns': [u'.bmp'], 'extn': u'.bmp', 'thumb_extn': u'.jpeg'}, + 'image/bmp': {'allowed_extns': ['.bmp'], 'extn': '.bmp', 'thumb_extn': '.jpeg'}, 'image/x-bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/x-bitmap': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/x-xbitmap': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/x-win-bitmap': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/x-windows-bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/ms-bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'image/x-ms-bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'application/bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'application/x-bmp': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'application/x-win-bitmap': { - 'allowed_extns': [u'.bmp'], - 'extn': u'.bmp', - 'thumb_extn': u'.jpeg', + 'allowed_extns': ['.bmp'], + 'extn': '.bmp', + 'thumb_extn': '.jpeg', }, 'application/cdr': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'application/coreldraw': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'application/x-cdr': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'application/x-coreldraw': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'application/vnd.corel-draw': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'image/cdr': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'image/x-cdr': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'image/x-coreldraw': { - 'allowed_extns': [u'.cdr'], - 'extn': u'.cdr', + 'allowed_extns': ['.cdr'], + 'extn': '.cdr', 'thumb_extn': '.png', 'processor': 'inkscape', }, 'application/eps': { - 'allowed_extns': [u'.eps'], - 'extn': u'.eps', - 'thumb_extn': u'.png', + 'allowed_extns': ['.eps'], + 'extn': '.eps', + 'thumb_extn': '.png', }, 'application/x-eps': { - 'allowed_extns': [u'.eps'], - 'extn': u'.eps', - 'thumb_extn': u'.png', + 'allowed_extns': ['.eps'], + 'extn': '.eps', + 'thumb_extn': '.png', }, - 'image/eps': {'allowed_extns': [u'.eps'], 'extn': u'.eps', 'thumb_extn': u'.png'}, - 'image/x-eps': {'allowed_extns': [u'.eps'], 'extn': u'.eps', 'thumb_extn': u'.png'}, + 'image/eps': {'allowed_extns': ['.eps'], 'extn': '.eps', 'thumb_extn': '.png'}, + 'image/x-eps': {'allowed_extns': ['.eps'], 'extn': '.eps', 'thumb_extn': '.png'}, 'image/tif': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'image/x-tif': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'image/tiff': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'image/x-tiff': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'application/tif': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'application/x-tif': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'application/tiff': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'application/x-tiff': { - 'allowed_extns': [u'.tif', u'.tiff'], - 'extn': [u'.tif', u'.tiff'], - 'thumb_extn': u'.png', + 'allowed_extns': ['.tif', '.tiff'], + 'extn': ['.tif', '.tiff'], + 'thumb_extn': '.png', }, 'image/webp': { - 'allowed_extns': [u'.webp'], + 'allowed_extns': ['.webp'], 'extn': '.webp', - 'thumb_extn': u'.jpeg', + 'thumb_extn': '.jpeg', }, - 'image/x-xcf': {'allowed_extns': [u'.xcf'], 'extn': '.xcf', 'thumb_extn': u'.jpeg'}, + 'image/x-xcf': {'allowed_extns': ['.xcf'], 'extn': '.xcf', 'thumb_extn': '.jpeg'}, } EXTNS = [] -for mimetype, data in ALLOWED_MIMETYPES.iteritems(): +for mimetype, data in ALLOWED_MIMETYPES.items(): if type(data['extn']) == list: for extn in data['extn']: EXTNS.append(extn) @@ -243,7 +243,7 @@ def newid(): - return unicode(uuid4().hex) + return str(uuid4().hex) def get_media_domain(scheme=None): @@ -303,9 +303,8 @@ def is_animated_gif(local_path): def get_file_type(fp, filename=None): fp.seek(0) - data = fp.read(1024) # https://github.com/ahupp/python-magic#usage + result = magic.from_buffer(fp.read(), mime=True) fp.seek(0) - result = magic.from_buffer(data, mime=True) if result in ('text/plain', 'text/xml', 'application/xml'): if is_svg(fp): return 'image/svg+xml' @@ -322,14 +321,18 @@ def is_file_allowed(fp, provided_mimetype=None, filename=None): # -- s3 related -- -def get_s3_connection(): - return connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) + + +def get_s3_client(): + return boto3.resource( + 's3', + aws_access_key_id=app.config['AWS_ACCESS_KEY'], + aws_secret_access_key=app.config['AWS_SECRET_KEY'], + ) def get_s3_bucket(): - conn = get_s3_connection() - bucket = Bucket(conn, app.config['AWS_BUCKET']) - return bucket + return get_s3_client().Bucket(app.config['AWS_BUCKET']) def get_s3_folder(f=''): @@ -347,12 +350,10 @@ def exists_in_s3(thumb): if 'thumb_extn' in ALLOWED_MIMETYPES[thumb.stored_file.mimetype]: extn = ALLOWED_MIMETYPES[thumb.stored_file.mimetype]['thumb_extn'] key = os.path.join(folder, thumb.name + extn) - # print("checking whether exists in s3: {}".format(key)) - resp = bucket.get_key(key) - if not resp: - # print("does not exist") + try: + bucket.Object(key).load() + except botocore.exceptions.ClientError: return False - # print("exists") return True @@ -361,9 +362,8 @@ def download_from_s3(img_name): if not os.path.exists(local_path): bucket = get_s3_bucket() folder = get_s3_folder() - k = Key(bucket) - k.key = os.path.join(folder, img_name) - k.get_contents_to_filename(local_path) + key = os.path.join(folder, img_name) + bucket.Object(key).download_file(local_path) return local_path @@ -378,6 +378,7 @@ def get_width_height(img_path): o = check_output( 'identify -quiet -ping -format "%wx%h" {}[0]'.format(img_path), shell=True, + universal_newlines=True ) w, h = o.split('x') elif extn in ['.cdr']: @@ -394,7 +395,8 @@ def get_width_height(img_path): w, h = int(round(float(wo))), int(round(float(ho))) else: o = check_output( - 'identify -quiet -ping -format "%wx%h" {}'.format(img_path), shell=True + 'identify -quiet -ping -format "%wx%h" {}'.format(img_path), shell=True, + universal_newlines=True ) w, h = o.split('x') return (w, h) diff --git a/imgee/views/files.py b/imgee/views/files.py index f45e7d70..db56a04e 100644 --- a/imgee/views/files.py +++ b/imgee/views/files.py @@ -39,10 +39,11 @@ def _redirect_url_frm_upload(profile_name): def upload_file(profile): upload_form = UploadImageForm() if upload_form.validate_on_submit(): - file_ = request.files['upload_file'] - title, stored_file = save_file(file_, profile=profile) - flash('"%s" uploaded successfully.' % title) - return redirect(_redirect_url_frm_upload(profile.name)) + imgfile = request.files['upload_file'] + if imgfile.filename != '': + title, stored_file = save_file(imgfile, profile=profile) + flash('"%s" uploaded successfully.' % title) + return redirect(_redirect_url_frm_upload(profile.name)) return render_template('form.html.jinja2', form=upload_form, profile=profile) diff --git a/imgee/views/labels.py b/imgee/views/labels.py index 00734f1c..55e734a6 100644 --- a/imgee/views/labels.py +++ b/imgee/views/labels.py @@ -162,7 +162,7 @@ def utils_save_label(label_name, profile, commit=True): def utils_delete_label(label): - if isinstance(label, basestring): + if isinstance(label, str): label = Label.query.filter_by(title=label).first() db.session.delete(label) db.session.commit() diff --git a/imgee/views/login.py b/imgee/views/login.py index 9df7731a..975d444c 100644 --- a/imgee/views/login.py +++ b/imgee/views/login.py @@ -16,7 +16,7 @@ def login(): @app.route('/logout') @lastuser.logout_handler def logout(): - flash(u"You are now logged out", category='info') + flash("You are now logged out", category='info') return get_next_url() @@ -41,8 +41,8 @@ def lastuser_error(error, error_description=None, error_uri=None): flash("You denied the request to login", category='error') return redirect(get_next_url()) return Response( - u"Error: %s\n" - u"Description: %s\n" - u"URI: %s" % (error, error_description, error_uri), + "Error: %s\n" + "Description: %s\n" + "URI: %s" % (error, error_description, error_uri), mimetype="text/plain", ) diff --git a/instance/testing.py b/instance/testing.py deleted file mode 100644 index 3a2430be..00000000 --- a/instance/testing.py +++ /dev/null @@ -1,21 +0,0 @@ -from os import environ - -# this will sit inside `app.static_folder` -UPLOADED_FILES_DIR = 'test_uploads' - -AWS_FOLDER = 'test/' - -UNKNOWN_FILE_THUMBNAIL = 'unknown.jpeg' - -#: S3 Configuration; AWS credentials with restricted access -AWS_ACCESS_KEY = environ.get('AWS_ACCESS_KEY') -AWS_SECRET_KEY = environ.get('AWS_SECRET_KEY') -AWS_BUCKET = environ.get('AWS_BUCKET', 'imgee-testing') -AWS_FOLDER = 'test' - -MEDIA_DOMAIN = 'https://%s.s3.amazonaws.com' % AWS_BUCKET - -SQLALCHEMY_DATABASE_URI = 'sqlite://' -WTF_CSRF_ENABLED = False - -THUMBNAIL_SIZE = '75x75' diff --git a/migrations/env.py b/migrations/env.py index a3aa1031..9e70bcb7 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement + from logging.config import fileConfig import logging diff --git a/migrations/versions/200442fae8bd_remove_size_in_tn.py b/migrations/versions/200442fae8bd_remove_size_in_tn.py index 0a6acc00..3d10c5d6 100644 --- a/migrations/versions/200442fae8bd_remove_size_in_tn.py +++ b/migrations/versions/200442fae8bd_remove_size_in_tn.py @@ -16,13 +16,13 @@ def upgrade(): ### commands auto generated by Alembic - please adjust! ### - op.drop_column('thumbnail', u'size') + op.drop_column('thumbnail', 'size') ### end Alembic commands ### def downgrade(): ### commands auto generated by Alembic - please adjust! ### op.add_column( - 'thumbnail', sa.Column(u'size', sa.VARCHAR(length=100), nullable=False) + 'thumbnail', sa.Column('size', sa.VARCHAR(length=100), nullable=False) ) ### end Alembic commands ### diff --git a/migrations/versions/347ba3ac054f_update_image_attrs.py b/migrations/versions/347ba3ac054f_update_image_attrs.py index d7289941..5715120f 100644 --- a/migrations/versions/347ba3ac054f_update_image_attrs.py +++ b/migrations/versions/347ba3ac054f_update_image_attrs.py @@ -43,9 +43,9 @@ def upgrade(): img.width, img.height = get_width_height(gpath[0]) img.size = os.path.getsize(gpath[0]) - print 'updated attributes of %s\n' % img.title, # NOQA + print('updated attributes of %s\n' % img.title, end=' ') # NOQA else: - print 'local file not found for %s\n' % img.title, # NOQA + print('local file not found for %s\n' % img.title, end=' ') # NOQA session.commit() diff --git a/migrations/versions/4b2844325813_related_names_for_th.py b/migrations/versions/4b2844325813_related_names_for_th.py index 16c40145..1c801f49 100644 --- a/migrations/versions/4b2844325813_related_names_for_th.py +++ b/migrations/versions/4b2844325813_related_names_for_th.py @@ -18,7 +18,7 @@ def upgrade(): ### commands auto generated by Alembic - please adjust! ### op.alter_column( 'thumbnail', - u'name', + 'name', existing_type=sa.VARCHAR(length=32), type_=sa.Unicode(length=64), existing_nullable=False, @@ -30,7 +30,7 @@ def downgrade(): ### commands auto generated by Alembic - please adjust! ### op.alter_column( 'thumbnail', - u'name', + 'name', existing_type=sa.Unicode(length=64), type_=sa.VARCHAR(length=32), existing_nullable=False, diff --git a/requirements.txt b/requirements.txt index d933cf7e..034ed5ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,8 @@ Flask-SQLAlchemy==2.4.1 psycopg2==2.8.4 https://github.com/hasgeek/flask-lastuser/zipball/master https://github.com/jace/flask-alembic/archive/master.zip -boto==2.49.0 +boto3==1.10.46 redis==3.3.11 -xml4h==0.2.0 python-magic==0.4.15 defusedxml==0.6.0 Flask-Migrate==2.5.2 diff --git a/runtests.sh b/runtests.sh index 2e9cb98b..594fb3ab 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,5 +1,6 @@ #!/bin/sh set -e +export PYTHONIOENCODING="UTF-8" export FLASK_ENV="TESTING" -coverage run `which nosetests` "$@" +coverage run -m pytest "$@" coverage report -m diff --git a/setup.cfg b/setup.cfg index 20b2b742..3bad47c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,12 +30,3 @@ sections = FUTURE, STDLIB, SQLALCHEMY, FLASK, THIRDPARTY, FIRSTPARTY, LOCALFOLDE # Bandit config for flake8-bandit. There's another copy in .pre-commit-config.yaml [bandit] exclude = tests, migrations, instance - -[nosetests] -match=^test -nocapture=1 -cover-package=imgee -with-coverage=1 -cover-erase=1 -with-doctest=1 -where=tests diff --git a/tests/fixtures.py b/tests/fixtures.py index 8a759dc1..fed7823b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -9,16 +9,16 @@ class ImgeeTestCase(unittest.TestCase): def get_test_user(self, name='testuser', uid=1): - u = User.query.filter_by(username=unicode(name)).first() + u = User.query.filter_by(username=name).first() if not u: - userid = ''.join(random.sample(string.letters, 22)) + userid = ''.join(random.sample(string.ascii_letters, 22)) u = User( - username=unicode(name), - userid=unicode(userid), - lastuser_token_scope=u'id email organizations', - lastuser_token_type=u'bearer', - lastuser_token=u'last-user-token', - fullname=unicode(name.capitalize()), + username=name, + userid=userid, + lastuser_token_scope='id email organizations', + lastuser_token_type='bearer', + lastuser_token='last-user-token', + fullname=name.capitalize(), id=uid, ) db.session.add(u) @@ -33,7 +33,7 @@ def setUp(self): app.testing = True db.create_all() - self.test_user_name = u'testuser' + self.test_user_name = 'testuser' test_user = self.get_test_user(name=self.test_user_name) self.client = app.test_client() with self.client.session_transaction() as session: diff --git a/tests/test_labels.py b/tests/test_labels.py index 567ef0a1..5b1cd7c5 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -14,24 +14,24 @@ class LabelTestCase(ImgeeTestCase): def setUp(self): super(LabelTestCase, self).setUp() self.img_id = None - self.test_files = ['../imgee/static/img/imgee.png'] + self.test_files = ['imgee/static/img/imgee.png'] self.test_labels = ['logos', 'banners', 'profile-photos'] def upload(self, path=None): profile = self.get_test_profile() sf = None - with open(path or self.test_files[0]) as fp: + with open(path or self.test_files[0], 'rb') as fp: fs = FileStorage(fp) title, sf = save_file(fs, profile) return sf def test_empty(self): - self.assertEquals(len(Label.query.all()), 0) + self.assertEqual(len(Label.query.all()), 0) def test_create_label(self): label_title = self.test_labels[0] utils_save_label(label_title, self.get_test_profile()) - self.assertEquals(len(Label.query.filter_by(title=label_title).all()), 1) + self.assertEqual(len(Label.query.filter_by(title=label_title).all()), 1) def test_add_remove_label(self): # upload image @@ -46,14 +46,14 @@ def test_add_remove_label(self): self.assertEqual(total_saved, 1) img = StoredFile.query.get(img_id) - self.assertEquals(len(img.labels), 1) + self.assertEqual(len(img.labels), 1) self.assertEqual(len(self.get_test_profile().labels), 1) # remove the label from image total_saved, msg = utils_save_labels([], img, self.get_test_profile()) img = StoredFile.query.get(img_id) - self.assertEquals(len(img.labels), 0) + self.assertEqual(len(img.labels), 0) def test_delete_label(self): label_title = self.test_labels[0] @@ -61,7 +61,7 @@ def test_delete_label(self): utils_save_label(label_title, self.get_test_profile()) # delete label utils_delete_label(label_title) - self.assertEquals(len(Label.query.filter_by(title=label_title).all()), 0) + self.assertEqual(len(Label.query.filter_by(title=label_title).all()), 0) if __name__ == '__main__': diff --git a/tests/test_upload.py b/tests/test_upload.py index b87fe53c..1730dfee 100644 --- a/tests/test_upload.py +++ b/tests/test_upload.py @@ -17,10 +17,10 @@ def setUp(self): super(UploadTestCase, self).setUp() self.img_id = None self.test_files = [ - '../imgee/static/img/imgee.png', - '../imgee/static/img/imgee.jpeg', - '../imgee/static/img/imgee.svg', - '../imgee/static/img/loading.gif', + 'imgee/static/img/imgee.png', + 'imgee/static/img/imgee.jpeg', + 'imgee/static/img/imgee.svg', + 'imgee/static/img/loading.gif', ] self.test_labels = ['logos', 'banners', 'profile-photos'] self.files = self.upload_all() @@ -38,7 +38,7 @@ def upload(self, path=None): profile = self.get_test_profile() sf = None if path: - with open(path) as fp: + with open(path, 'rb') as fp: fs = FileStorage(fp) title, sf = save_file(fs, profile) return sf @@ -47,8 +47,8 @@ def test_save_file(self): with app.test_request_context('/'): for test_file in self.files: resp = requests.get(get_image_url(test_file)) - self.assertEquals(resp.status_code, 200) - self.assertEquals( + self.assertEqual(resp.status_code, 200) + self.assertEqual( resp.headers.get('Content-Type', ''), test_file.mimetype ) @@ -62,8 +62,8 @@ def test_resize(self): size=app.config.get('THUMBNAIL_SIZE'), ) ) - self.assertEquals(resp.status_code, 301) - self.assertEquals( + self.assertEqual(resp.status_code, 301) + self.assertEqual( resp.headers.get('Location'), get_thumbnail_url(test_file) ) @@ -76,7 +76,7 @@ def test_delete_file(self): for test_file in self.files: with app.test_request_context('/'): resp = requests.get(get_image_url(test_file)) - self.assertEquals( + self.assertEqual( resp.status_code, 403 ) # S3 throws 403 for non existing files