diff --git a/.travis.yml b/.travis.yml index 56eb7d501..f942dd705 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: python jobs: include: - - os: linux - python: 2.7 - os: linux python: 3.6 - os: linux @@ -34,8 +32,8 @@ before_install: - source activate testenv # Pin R to 3.6.3 for now - conda install -c conda-forge r-base=3.6.3 r-knitr - # Install regex through conda to avoid issues with Linux 2.7 and Mac wheels - - if [[ "$TRAVIS_OS_NAME" == "osx" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then + # Install regex through conda to avoid issues with Mac wheels + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then conda install -c conda-forge regex; fi install: diff --git a/appveyor.yml b/appveyor.yml index 5103b1529..c7b7a1aa8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,13 +2,16 @@ build: false environment: matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.8" - PYTHON_ARCH: "32" - MINICONDA: C:\Miniconda - - PYTHON: "C:\\Python36-x64" - PYTHON_VERSION: "3.6.6" + PYTHON_VERSION: "3.6.10" + PYTHON_ARCH: "64" + MINICONDA: C:\Miniconda3 + - PYTHON: "C:\\Python37-x64" + PYTHON_VERSION: "3.7.7" + PYTHON_ARCH: "64" + MINICONDA: C:\Miniconda3 + - PYTHON: "C:\\Python38-x64" + PYTHON_VERSION: "3.8.3" PYTHON_ARCH: "64" MINICONDA: C:\Miniconda3 diff --git a/docs/requirements.txt b/docs/requirements.txt index d1f2427a9..531d93c6c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,3 @@ -future -enum34 pyyaml markdown pygments @@ -18,4 +16,4 @@ werkzeug>=1.0 gunicorn inflection pillow -weasyprint<=0.42.3 \ No newline at end of file +weasyprint \ No newline at end of file diff --git a/knowledge_repo/_version.py b/knowledge_repo/_version.py index 0344c355d..5ba21815d 100644 --- a/knowledge_repo/_version.py +++ b/knowledge_repo/_version.py @@ -18,8 +18,6 @@ # should be defined elsewhere. __dependencies__ = [ # Knowledge Repository Dependencies - 'future', # Python 2/3 support - 'enum34', # Python 3.4+ enum object used for Post status 'pyyaml', # Yaml parser and utilities 'markdown', # Markdown conversion utilities 'pygments', # Code highlighting support in markdown @@ -41,13 +39,13 @@ 'gunicorn', # Deployed webserver 'inflection', # String transformation library 'pillow', # Image thumbnailing - 'weasyprint<=0.42.3', # Post PDF download option. Pinned to maintain compatibility with Python 2 + 'weasyprint', # Post PDF download option ] __optional_dependencies__ = { # ipynb notebook conversion suport 'ipynb': [ - 'nbformat<5.0.0', + 'nbformat', 'nbconvert[execute]', 'traitlets' ], diff --git a/knowledge_repo/app/__init__.py b/knowledge_repo/app/__init__.py index 54ea1a785..b093934e4 100644 --- a/knowledge_repo/app/__init__.py +++ b/knowledge_repo/app/__init__.py @@ -1,4 +1,2 @@ -from __future__ import absolute_import - from . import auth_providers from .app import KnowledgeFlask diff --git a/knowledge_repo/app/app.py b/knowledge_repo/app/app.py index 5943bb1bb..2e2457de3 100644 --- a/knowledge_repo/app/app.py +++ b/knowledge_repo/app/app.py @@ -1,14 +1,12 @@ -from __future__ import absolute_import - import os -import imp +import importlib +import sys import logging import traceback import math import uuid import mimetypes -import six from flask import Flask, current_app, render_template, request, session from flask_login import LoginManager, user_loaded_from_request from flask_mail import Mail @@ -52,8 +50,12 @@ def __init__(self, repo, db_uri=None, debug=None, config=None, **kwargs): # Load configuration from file or provided object if config: - if isinstance(config, six.string_types): - config = imp.load_source('knowledge_server_config', os.path.abspath(config)) + if isinstance(config, str): + module_name = 'knowledge_server_config' + spec = importlib.util.spec_from_file_location(module_name, os.path.abspath(config)) + config = importlib.util.module_from_spec(spec) + sys.modules[module_name] = config + spec.loader.exec_module(config) self.config.from_object(config) # Add configuration passed in as keyword arguments @@ -75,7 +77,7 @@ def __init__(self, repo, db_uri=None, debug=None, config=None, **kwargs): # Configure database if db_uri: self.config['SQLALCHEMY_DATABASE_URI'] = db_uri - logger.debug(u"Using database: {}".format(self.config['SQLALCHEMY_DATABASE_URI'])) + logger.debug("Using database: {}".format(self.config['SQLALCHEMY_DATABASE_URI'])) # Register database schema with flask app sqlalchemy_db.init_app(self) @@ -235,7 +237,7 @@ def modify_query(**new_values): for key, value in new_values.items(): args[key] = value - return u'{}?{}'.format(request.path, url_encode(args)) + return '{}?{}'.format(request.path, url_encode(args)) @self.template_global() def pagination_pages(current_page, page_count, max_pages=5, extremes=True): diff --git a/knowledge_repo/app/auth_provider.py b/knowledge_repo/app/auth_provider.py index 154ba6ef3..91b91be29 100644 --- a/knowledge_repo/app/auth_provider.py +++ b/knowledge_repo/app/auth_provider.py @@ -1,16 +1,14 @@ from abc import abstractmethod -from builtins import object from flask_principal import identity_changed, Identity from flask_login import login_user -from future.utils import with_metaclass from flask import redirect, current_app, Blueprint, url_for, session from .utils.auth import prepare_user from ..utils.registry import SubclassRegisteringABCMeta -class KnowledgeAuthProvider(with_metaclass(SubclassRegisteringABCMeta, object)): +class KnowledgeAuthProvider(object, metaclass=SubclassRegisteringABCMeta): _registry_keys = None @classmethod diff --git a/knowledge_repo/app/auth_providers/oauth2.py b/knowledge_repo/app/auth_providers/oauth2.py index 29baf159f..5c501b2a2 100644 --- a/knowledge_repo/app/auth_providers/oauth2.py +++ b/knowledge_repo/app/auth_providers/oauth2.py @@ -1,9 +1,8 @@ import posixpath import json -import six from flask import request, redirect -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin from ..models import User from ..auth_provider import KnowledgeAuthProvider @@ -158,7 +157,7 @@ def extract_from_dict(d, key): key = key[0] else: return extract_from_dict(d[key[0]], key[1:]) - if isinstance(key, six.string_types): + if isinstance(key, str): return d[key] raise RuntimeError("Invalid key type: {}.".format(key)) diff --git a/knowledge_repo/app/deploy/common.py b/knowledge_repo/app/deploy/common.py index 36edbe43d..fcfb34cb1 100644 --- a/knowledge_repo/app/deploy/common.py +++ b/knowledge_repo/app/deploy/common.py @@ -4,9 +4,7 @@ import sys import textwrap from abc import abstractmethod -from future.utils import with_metaclass -import six import knowledge_repo from knowledge_repo.utils.registry import SubclassRegisteringABCMeta @@ -21,7 +19,7 @@ def get_app(): return get_app -class KnowledgeDeployer(with_metaclass(SubclassRegisteringABCMeta, object)): +class KnowledgeDeployer(object, metaclass=SubclassRegisteringABCMeta): def __init__(self, knowledge_builder, @@ -29,8 +27,8 @@ def __init__(self, port=7000, workers=4, timeout=60): - assert isinstance(knowledge_builder, six.string_types + (types.FunctionType, )), \ - u"Unknown builder type {}".format(type(knowledge_builder)) + assert isinstance(knowledge_builder, str + (types.FunctionType, )), \ + "Unknown builder type {}".format(type(knowledge_builder)) self.knowledge_builder = knowledge_builder self.host = host self.port = port @@ -57,17 +55,17 @@ def builder_str(self): if isinstance(self.knowledge_builder, types.FunctionType): out = [] for nl, cell in zip(self.knowledge_builder.__code__.co_freevars, self.knowledge_builder.__closure__): - if isinstance(cell.cell_contents, six.string_types): - out.append(u'{} = "{}"'.format(nl, cell.cell_contents.replace('"', '\\"'))) + if isinstance(cell.cell_contents, str): + out.append('{} = "{}"'.format(nl, cell.cell_contents.replace('"', '\\"'))) else: - out.append(u'{} = {}'.format(nl, cell.cell_contents)) + out.append('{} = {}'.format(nl, cell.cell_contents)) out.append(textwrap.dedent(inspect.getsource(self.knowledge_builder))) - return u'\n'.join(out) + return '\n'.join(out) return self.knowledge_builder @property def builder_func(self): - if isinstance(self.knowledge_builder, six.string_types): + if isinstance(self.knowledge_builder, str): knowledge_builder = 'def get_app():\n\t' + self.knowledge_builder.replace('\n', '\t') + '\n\treturn app' namespace = {} exec(knowledge_builder, namespace) @@ -91,16 +89,16 @@ def write_temp_files(self): out = [] out.append('import sys') - out.append(u'sys.path.insert(0, "{}")'.format(kr_path)) + out.append('sys.path.insert(0, "{}")'.format(kr_path)) out.append('import knowledge_repo') out.append(self.builder_str) - if not isinstance(self.knowledge_builder, six.string_types): + if not isinstance(self.knowledge_builder, str): out.append('app = %s()' % self.knowledge_builder.__name__) out.append('app.start_indexing()') with open(tmp_path, 'w') as f: - f.write(u'\n'.join(out)) + f.write('\n'.join(out)) return tmp_dir diff --git a/knowledge_repo/app/deploy/gunicorn.py b/knowledge_repo/app/deploy/gunicorn.py index 540b218ac..abb55d86c 100644 --- a/knowledge_repo/app/deploy/gunicorn.py +++ b/knowledge_repo/app/deploy/gunicorn.py @@ -5,8 +5,6 @@ Adapted from example in http://docs.gunicorn.org/en/stable/custom.html. """ -from __future__ import absolute_import - from gunicorn.app.base import BaseApplication from .common import KnowledgeDeployer @@ -30,7 +28,7 @@ def load_config(self): # Update the configuration with the options specified via KnowledgeDeployer options = { - 'bind': u'{}:{}'.format(self.host, self.port), + 'bind': '{}:{}'.format(self.host, self.port), 'workers': self.workers, 'timeout': self.timeout, } diff --git a/knowledge_repo/app/deploy/uwsgi.py b/knowledge_repo/app/deploy/uwsgi.py index ecb463836..838c31974 100644 --- a/knowledge_repo/app/deploy/uwsgi.py +++ b/knowledge_repo/app/deploy/uwsgi.py @@ -12,7 +12,7 @@ class uWSGIDeployer(KnowledgeDeployer): _registry_keys = ['uwsgi'] - COMMAND = u"uwsgi {protocol} --plugin python --wsgi-file {{path}} --callable app --master --processes {{processes}} --threads {{threads}}" + COMMAND = "uwsgi {protocol} --plugin python --wsgi-file {{path}} --callable app --master --processes {{processes}} --threads {{threads}}" def run(self): if not self.app.check_thread_support(): @@ -21,7 +21,7 @@ def run(self): tmp_dir = self.write_temp_files() options = { - 'socket': u'{}:{}'.format(self.host, self.port), + 'socket': '{}:{}'.format(self.host, self.port), 'processes': self.workers, 'threads': 2, 'timeout': self.timeout, @@ -36,8 +36,8 @@ def run(self): self.COMMAND = self.COMMAND.format(protocol="--http {socket}") try: - cmd = u"cd {};".format(tmp_dir) + self.COMMAND.format(**options) - logger.info("Starting server with command: %s", u" ".join(cmd)) + cmd = "cd {};".format(tmp_dir) + self.COMMAND.format(**options) + logger.info("Starting server with command: %s", " ".join(cmd)) subprocess.check_output(cmd, shell=True) finally: shutil.rmtree(tmp_dir) diff --git a/knowledge_repo/app/index.py b/knowledge_repo/app/index.py index 8c93fefad..43da1afc4 100644 --- a/knowledge_repo/app/index.py +++ b/knowledge_repo/app/index.py @@ -1,10 +1,7 @@ -from __future__ import absolute_import - import logging import multiprocessing import os import time -from builtins import str from .proxies import db_session, current_repo, current_app from .models import ErrorLog, Post, IndexMetadata @@ -160,12 +157,12 @@ def update_index(check_timeouts=True, force=False, reindex=False): # If UUID has changed, check if we can find it elsewhere in the repository, and if so update index path if post.uuid and ((post.path not in kr_dir) or (post.uuid != kr_dir[post.path].uuid)): if post.uuid in kr_uuids: - logger.info(u'Updating location of post: {} -> {}'.format(post.path, kr_uuids[post.uuid].path)) + logger.info('Updating location of post: {} -> {}'.format(post.path, kr_uuids[post.uuid].path)) post.path = kr_uuids[post.uuid].path # If path of post no longer in directory, mark as unpublished if post.path not in kr_dir: - logger.info(u'Recording unpublished status for post at {}'.format(post.path)) + logger.info('Recording unpublished status for post at {}'.format(post.path)) post.status = current_repo.PostStatus.UNPUBLISHED continue @@ -177,17 +174,17 @@ def update_index(check_timeouts=True, force=False, reindex=False): # Update metadata of post if required if reindex or (kp.revision > post.revision or not post.is_published or kp.uuid != post.uuid): if kp.is_valid(): - logger.info(u'Recording update to post at: {}'.format(kp.path)) + logger.info('Recording update to post at: {}'.format(kp.path)) post.update_metadata_from_kp(kp) else: - logger.warning(u'Update to post at "{}" is corrupt.'.format(kp.path)) + logger.warning('Update to post at "{}" is corrupt.'.format(kp.path)) # Add the new posts that remain in kr_dir for kp_path, kp in kr_dir.items(): if not kp.is_valid(): - logger.warning(u'New post at "{}" is corrupt.'.format(kp.path)) + logger.warning('New post at "{}" is corrupt.'.format(kp.path)) continue - logger.info(u'creating new post from path {}'.format(kp_path)) + logger.info('creating new post from path {}'.format(kp_path)) post = Post() db_session.add(post) db_session.flush() # (matthew) Fix groups logic so this is not necessary diff --git a/knowledge_repo/app/migrations/env.py b/knowledge_repo/app/migrations/env.py index 01d6fdbb2..2b0e008ce 100755 --- a/knowledge_repo/app/migrations/env.py +++ b/knowledge_repo/app/migrations/env.py @@ -1,4 +1,3 @@ -from __future__ import with_statement from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig diff --git a/knowledge_repo/app/models.py b/knowledge_repo/app/models.py index c70409438..59553c293 100644 --- a/knowledge_repo/app/models.py +++ b/knowledge_repo/app/models.py @@ -3,8 +3,6 @@ import datetime import logging import traceback -from builtins import str -from future.utils import raise_with_traceback from flask import current_app, request from flask_login import UserMixin from flask_sqlalchemy import SQLAlchemy @@ -124,9 +122,9 @@ def from_exception(cls, e): filename = os.path.relpath(filename, os.path.join(os.path.dirname(__file__), '..')) return ErrorLog( function=function, - location=u'{}:{}'.format(filename, linenumber), - message=u'{}: {}'.format(e.__class__.__name__, u"; ".join(str(a) for a in e.args)), - traceback=u"\n".join(traceback.format_tb(tb)) + location='{}:{}'.format(filename, linenumber), + message='{}: {}'.format(e.__class__.__name__, "; ".join(str(a) for a in e.args)), + traceback="\n".join(traceback.format_tb(tb)) ) @classmethod @@ -138,7 +136,7 @@ def wrapped(*args, **kwargs): db_session.rollback() db_session.add(ErrorLog.from_exception(e)) db_session.commit() - raise_with_traceback(e) + raise e.with_traceback() return wrapped @@ -340,7 +338,7 @@ class Tag(db.Model): def description(self): if self._description: return self._description - return u"All posts with tag '{}'.".format(self.name) + return "All posts with tag '{}'.".format(self.name) @description.expression def description(self): @@ -407,7 +405,7 @@ def authors(self, authors): @hybrid_property def authors_string(self): - return u', '.join([author.format_name for author in self.authors]) + return ', '.join([author.format_name for author in self.authors]) @authors_string.expression def authors_string(self): diff --git a/knowledge_repo/app/routes/debug.py b/knowledge_repo/app/routes/debug.py index 067d3fa17..a5f6845d0 100644 --- a/knowledge_repo/app/routes/debug.py +++ b/knowledge_repo/app/routes/debug.py @@ -2,6 +2,8 @@ import platform import types import logging +from urllib.parse import unquote + import tabulate import knowledge_repo @@ -68,7 +70,6 @@ def force_reindex(): @blueprint.route('/debug/views') def show_views(): - import urllib output = [] for rule in current_app.url_map.iter_rules(): options = {} @@ -77,7 +78,7 @@ def show_views(): methods = ','.join(rule.methods) url = url_for(rule.endpoint, **options) - line = urllib.unquote("{:50s} {:20s} {}".format(rule.endpoint, methods, url)) + line = unquote("{:50s} {:20s} {}".format(rule.endpoint, methods, url)) output.append(line) return "
".join(sorted(output)) diff --git a/knowledge_repo/app/routes/editor.py b/knowledge_repo/app/routes/editor.py index 1266dc62c..3d77bb4c5 100644 --- a/knowledge_repo/app/routes/editor.py +++ b/knowledge_repo/app/routes/editor.py @@ -2,8 +2,9 @@ import logging import sys import os -from builtins import str from datetime import datetime +from urllib.parse import unquote + from flask import request, render_template, Blueprint, current_app, url_for, send_from_directory, g from sqlalchemy import or_ from werkzeug.utils import secure_filename @@ -17,10 +18,6 @@ from ..index import update_index -if sys.version_info.major > 2: - from urllib.parse import unquote as urlunquote -else: - from urllib import unquote as urlunquote logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -130,7 +127,7 @@ def save_post(): if prefixes is not None: if not any([path.startswith(prefix) for prefix in prefixes]): - return json.dumps({'msg': (u"Your post path must begin with one of {}").format(prefixes), + return json.dumps({'msg': ("Your post path must begin with one of {}").format(prefixes), 'success': False}) # TODO better handling of overwriting @@ -138,7 +135,7 @@ def save_post(): if path in current_repo: kp = current_repo.post(path) if current_user.identifier not in kp.headers['authors'] and current_user.identifier not in current_repo.config.editors: - return json.dumps({'msg': (u"Post with path {} already exists and you are not an author!" + return json.dumps({'msg': ("Post with path {} already exists and you are not an author!" "\nPlease try a different path").format(path), 'success': False}) @@ -159,7 +156,7 @@ def save_post(): if 'proxy' in data: headers['proxy'] = data['proxy'] - kp.write(urlunquote(data['markdown']), headers=headers) + kp.write(unquote(data['markdown']), headers=headers) # add to repo current_repo.add(kp, update=True, message=headers['title']) # THIS IS DANGEROUS @@ -193,7 +190,7 @@ def publish_post(): """ Publish the post by changing the status """ path = request.args.get('path', None) if path not in current_repo: - return json.dumps({'msg': u"Unable to retrieve post with path = {}!".format(path), 'success': False}) + return json.dumps({'msg': "Unable to retrieve post with path = {}!".format(path), 'success': False}) current_repo.publish(path) update_index(check_timeouts=False) @@ -207,7 +204,7 @@ def unpublish_post(): """ Unpublish the post """ path = request.args.get('path', None) if path not in current_repo: - return json.dumps({'msg': u"Unable to retrieve post with path = {}!".format(path), 'success': False}) + return json.dumps({'msg': "Unable to retrieve post with path = {}!".format(path), 'success': False}) current_repo.unpublish(path) update_index(check_timeouts=False) @@ -221,7 +218,7 @@ def accept(): """ Accept the post """ path = request.args.get('path', None) if path not in current_repo: - return json.dumps({'msg': u"Unable to retrieve post with path = {}!".format(path), 'success': False}) + return json.dumps({'msg': "Unable to retrieve post with path = {}!".format(path), 'success': False}) current_repo.accept(path) update_index() return 'OK' @@ -234,7 +231,7 @@ def delete_post(): """ Delete a post """ path = request.args.get('path', None) if path not in current_repo: - return json.dumps({'msg': u"Unable to retrieve post with path = {}!".format(path), 'success': False}) + return json.dumps({'msg': "Unable to retrieve post with path = {}!".format(path), 'success': False}) kp = current_repo.post(path) if current_user.identifier not in kp.headers['authors']: return json.dumps({'msg': "You can only delete a post where you are an author!", 'success': False}) @@ -301,7 +298,7 @@ def file_upload(): send_from_directory(dst_folder, filename) uploadedFiles += [url_for("static", filename=os.path.join(upload_folder, filename))] except Exception as e: - error_msg = u"ERROR during image upload: {}".format(str(e)) + error_msg = "ERROR during image upload: {}".format(str(e)) logger.error(error_msg) return json.dumps({'error_msg': error_msg, 'success': False}) @@ -313,11 +310,11 @@ def file_upload(): num_pages = src_pdf.getNumPages() for page_num in range(num_pages): page_png = pdf_page_to_png(src_pdf, page_num) - page_name = u"{filename}_{page_num}.jpg".format(**locals()) + page_name = "{filename}_{page_num}.jpg".format(**locals()) page_png.save(filename=os.path.join(dst_folder, page_name)) uploadedFiles += [url_for("static", filename=os.path.join(upload_folder, page_name))] except Exception as e: - error_msg = u"ERROR during pdf upload: {}".format(str(e)) + error_msg = "ERROR during pdf upload: {}".format(str(e)) logger.error(error_msg) return json.dumps({'error_msg': error_msg, 'success': False}) diff --git a/knowledge_repo/app/routes/index.py b/knowledge_repo/app/routes/index.py index 980d24e59..8bb53f51d 100644 --- a/knowledge_repo/app/routes/index.py +++ b/knowledge_repo/app/routes/index.py @@ -8,7 +8,6 @@ """ import os import json -from builtins import str from collections import namedtuple from flask import request, render_template, redirect, Blueprint, current_app, make_response from flask_login import login_required @@ -42,7 +41,7 @@ def site_map(): # url = url_for(rule.endpoint, **(rule.defaults or {})) links.append((str(rule), rule.endpoint)) # links is now a list of url, endpoint tuples - return u'
'.join(str(link) for link in links) + return '
'.join(str(link) for link in links) @blueprint.route('/') @@ -219,7 +218,7 @@ def unpack(d): _, grouped_data = unpack(folder_to_posts) else: - raise ValueError(u"Group by `{}` not understood.".format(group_by)) + raise ValueError("Group by `{}` not understood.".format(group_by)) def rec_sort(content, sort_by): sorted_content = [] @@ -329,5 +328,5 @@ def generate_projects_typeahead(): if not permissions.index_view.can(): return '[]' # return path stubs for all repositories - stubs = [u'/'.join(p.split('/')[:-1]) for p in current_repo.dir()] + stubs = ['/'.join(p.split('/')[:-1]) for p in current_repo.dir()] return json.dumps(list(set(stubs))) diff --git a/knowledge_repo/app/routes/posts.py b/knowledge_repo/app/routes/posts.py index 87c624399..0b6f43b55 100755 --- a/knowledge_repo/app/routes/posts.py +++ b/knowledge_repo/app/routes/posts.py @@ -1,6 +1,5 @@ import logging import os -from builtins import str from flask import request, url_for, redirect, render_template, current_app, Blueprint, g, Response, abort from .. import permissions @@ -54,7 +53,7 @@ def render(path): .filter(Post.path == knowledge_aliases[path]) .first()) if not post: - logger.warning(u"unable to find post at {}".format(path)) + logger.warning("unable to find post at {}".format(path)) return abort(404) if post.contains_excluded_tag: # It's possible that someone gets a direct link to a post that has an excluded tag @@ -146,7 +145,7 @@ def _render_preview(path, tmpl): post = current_repo.post(knowledge_aliases[path]) if not post: - raise Exception(u"unable to find post at {}".format(path)) + raise Exception("unable to find post at {}".format(path)) rendered = render_post(post, with_toc=True) raw_post = render_post_raw(post) if (mode == 'raw') else None @@ -159,7 +158,7 @@ def _render_preview(path, tmpl): raw_post=raw_post, comments=[], username=None, - post_author=u', '.join(post.headers['authors']), + post_author=', '.join(post.headers['authors']), title=post.headers['title'], page_views=0, unique_views=0, @@ -196,13 +195,13 @@ def download(): return Response( post.to_string(format=resource_type), mimetype="application/zip", - headers={u"Content-disposition": "attachment; filename={}".format(filename)}) + headers={"Content-disposition": "attachment; filename={}".format(filename)}) elif resource_type == 'pdf': filename = os.path.basename(post.path)[:-3] + '.pdf' return Response( post.to_string(format=resource_type), mimetype="application/pdf", - headers={u"Content-disposition": "attachment; filename={}".format(filename)}) + headers={"Content-disposition": "attachment; filename={}".format(filename)}) elif resource_type == 'source': path = request.args.get('path', None) assert path is not None, "Source path not provided." @@ -210,6 +209,6 @@ def download(): return Response( post._read_ref(path), mimetype="application/octet-stream", - headers={u"Content-disposition": "attachment; filename={}".format(os.path.basename(path))}) + headers={"Content-disposition": "attachment; filename={}".format(os.path.basename(path))}) else: - raise RuntimeError(u"Invalid resource_type: {}".format(resource_type)) + raise RuntimeError("Invalid resource_type: {}".format(resource_type)) diff --git a/knowledge_repo/app/routes/tags.py b/knowledge_repo/app/routes/tags.py index 1e42db265..0ba2821cd 100644 --- a/knowledge_repo/app/routes/tags.py +++ b/knowledge_repo/app/routes/tags.py @@ -15,7 +15,6 @@ from sqlalchemy import and_ import logging import math -from builtins import str from .. import permissions from ..proxies import db_session diff --git a/knowledge_repo/app/utils/auth.py b/knowledge_repo/app/utils/auth.py index f48821dee..221278759 100644 --- a/knowledge_repo/app/utils/auth.py +++ b/knowledge_repo/app/utils/auth.py @@ -1,6 +1,6 @@ import datetime -from future.moves.urllib.parse import urlparse, urlencode, urljoin +from urllib.parse import urlparse, urlencode, urljoin from flask import request, url_for from flask_login import AnonymousUserMixin, login_user diff --git a/knowledge_repo/app/utils/emails.py b/knowledge_repo/app/utils/emails.py index 25741b8f7..1130765f5 100644 --- a/knowledge_repo/app/utils/emails.py +++ b/knowledge_repo/app/utils/emails.py @@ -89,7 +89,7 @@ def send_subscription_email(post, tag): return default_recipients = ['knowledge_consumer@notreal.com'] - subject = u"New knowledge post: {}".format(post.title) + subject = "New knowledge post: {}".format(post.title) post_authors = [p.format_name for p in post.authors] post_tags = [t.name for t in post.tags] msg = Message(subject=subject, recipients=default_recipients, bcc=recipients_bcc) diff --git a/knowledge_repo/app/utils/knowledge_metadata.py b/knowledge_repo/app/utils/knowledge_metadata.py index 99c3d6d16..c5c3802a6 100644 --- a/knowledge_repo/app/utils/knowledge_metadata.py +++ b/knowledge_repo/app/utils/knowledge_metadata.py @@ -1,7 +1,3 @@ - - -from __future__ import absolute_import -from __future__ import unicode_literals from markdown import Extension from markdown.preprocessors import Preprocessor diff --git a/knowledge_repo/app/utils/posts.py b/knowledge_repo/app/utils/posts.py index 928c1d517..2855aa806 100644 --- a/knowledge_repo/app/utils/posts.py +++ b/knowledge_repo/app/utils/posts.py @@ -5,7 +5,6 @@ - get_all_post_stats """ import math -from builtins import str from flask import current_app from sqlalchemy import func, distinct, or_ diff --git a/knowledge_repo/app/utils/render.py b/knowledge_repo/app/utils/render.py index c7042fa40..e0b6f2cfd 100644 --- a/knowledge_repo/app/utils/render.py +++ b/knowledge_repo/app/utils/render.py @@ -34,7 +34,7 @@ def render_post_tldr(post): def render_post_header(post): - header_template = Template(u""" + header_template = Template("""
{{title}} {% if subtitle %}{{subtitle}}{% endif %} @@ -47,8 +47,8 @@ def render_post_header(post): """) def get_authors(usernames, authors): - authors = [u"{}".format(url_for('index.render_feed', authors=username), author) for username, author in zip(usernames, authors)] - return u' and '.join(u', '.join(authors).rsplit(', ', 1)) + authors = ["{}".format(url_for('index.render_feed', authors=username), author) for username, author in zip(usernames, authors)] + return ' and '.join(', '.join(authors).rsplit(', ', 1)) if isinstance(post, KnowledgePost): return header_template.render(title=post.headers['title'], diff --git a/knowledge_repo/app/utils/search.py b/knowledge_repo/app/utils/search.py index aea4c2712..8bf3b86cd 100644 --- a/knowledge_repo/app/utils/search.py +++ b/knowledge_repo/app/utils/search.py @@ -5,22 +5,22 @@ 1. To hardcode the list of nltk stopwords in English 2. Create a list of keywords from a list of words """ -ENGLISH_STOPWORDS = [u'i', u'me', u'my', u'myself', u'we', u'our', u'ours', u'ourselves', - u'you', u'your', u'yours', u'yourself', u'yourselves', u'he', u'him', - u'his', u'himself', u'she', u'her', u'hers', u'herself', u'it', u'its', - u'itself', u'they', u'them', u'their', u'theirs', u'themselves', u'what', - u'which', u'who', u'whom', u'this', u'that', u'these', u'those', u'am', - u'is', u'are', u'was', u'were', u'be', u'been', u'being', u'have', u'has', - u'had', u'having', u'do', u'does', u'did', u'doing', u'a', u'an', u'the', - u'and', u'but', u'if', u'or', u'because', u'as', u'until', u'while', - u'of', u'at', u'by', u'for', u'with', u'about', u'against', u'between', - u'into', u'through', u'during', u'before', u'after', u'above', u'below', - u'to', u'from', u'up', u'down', u'in', u'out', u'on', u'off', u'over', - u'under', u'again', u'further', u'then', u'once', u'here', u'there', - u'when', u'where', u'why', u'how', u'all', u'any', u'both', u'each', - u'few', u'more', u'most', u'other', u'some', u'such', u'no', u'nor', - u'not', u'only', u'own', u'same', u'so', u'than', u'too', u'very', - u's', u't', u'can', u'will', u'just', u'don', u'should', u'now'] +ENGLISH_STOPWORDS = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', + 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', + 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', + 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', + 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', + 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', + 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', + 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', + 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', + 'into', 'through', 'during', 'before', 'after', 'above', 'below', + 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', + 'under', 'again', 'further', 'then', 'once', 'here', 'there', + 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', + 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', + 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', + 's', 't', 'can', 'will', 'just', 'don', 'should', 'now'] def get_keywords(post): @@ -30,5 +30,5 @@ def get_keywords(post): tldr = post.tldr.split(" ") tags = [tag.name for tag in post.tags] full_search_text = author + title + tldr + tags - keywords = u' '.join([word for word in full_search_text if word not in ENGLISH_STOPWORDS]) + keywords = ' '.join([word for word in full_search_text if word not in ENGLISH_STOPWORDS]) return keywords diff --git a/knowledge_repo/config.py b/knowledge_repo/config.py index 5c915184b..dbe35e11e 100644 --- a/knowledge_repo/config.py +++ b/knowledge_repo/config.py @@ -1,13 +1,12 @@ import functools -import imp +import importlib +import sys import logging import os import time import types import yaml -import six - logger = logging.getLogger(__name__) @@ -45,16 +44,16 @@ def update(self, *values, **kwargs): dict.update(self, value) elif isinstance(value, types.ModuleType): self.__update_from_module(value) - elif isinstance(value, six.string_types): + elif isinstance(value, str): if os.path.exists(value): self.__update_from_file(value) else: logger.warning( - u"Configuration file {} does not exist.".format(value)) + "Configuration file {} does not exist.".format(value)) elif isinstance(value, type(None)): pass else: - raise ValueError(u"Cannot interpret {}".format(value)) + raise ValueError("Cannot interpret {}".format(value)) dict.update(self, kwargs) def update_defaults(self, *values, **kwargs): @@ -63,15 +62,15 @@ def update_defaults(self, *values, **kwargs): self.DEFAULT_CONFIGURATION.update(value) elif isinstance(value, types.ModuleType): self.__defaults_from_module(value) - elif isinstance(value, six.string_types): + elif isinstance(value, str): if os.path.exists(value): self.__defaults_from_file(value) else: - logger.warning(u"Configuration file {} does not exist.".format(value)) + logger.warning("Configuration file {} does not exist.".format(value)) elif isinstance(value, type(None)): pass else: - raise ValueError(u"Cannot interpret {}".format(value)) + raise ValueError("Cannot interpret {}".format(value)) self.DEFAULT_CONFIGURATION.update(kwargs) def __defaults_from_file(self, filename): @@ -88,7 +87,11 @@ def __update_from_module(self, module): def __set_from_file(self, d, filename, force=False): if filename.endswith('.py'): - config = imp.load_source(u'knowledge_repo.config_{}'.format(str(time.time()).replace('.', '')), filename) + module_name = 'knowledge_repo.config_{}'.format(str(time.time()).replace('.', '')) + spec = importlib.util.spec_from_file_location(module_name, filename) + config = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) self.__set_from_module(d, config, force) elif filename.endswith('.yml'): with open(filename) as f: diff --git a/knowledge_repo/converter.py b/knowledge_repo/converter.py index 2e138f925..dee430581 100644 --- a/knowledge_repo/converter.py +++ b/knowledge_repo/converter.py @@ -1,4 +1,3 @@ -from builtins import object import os from functools import wraps @@ -6,7 +5,6 @@ from .postprocessor import KnowledgePostProcessor from .utils.registry import SubclassRegisteringABCMeta from .utils.dependencies import check_dependencies -from future.utils import with_metaclass def get_format(filename, format=None): @@ -24,7 +22,7 @@ def get_format(filename, format=None): return format -class KnowledgePostConverter(with_metaclass(SubclassRegisteringABCMeta, object)): +class KnowledgePostConverter(object, metaclass=SubclassRegisteringABCMeta): _registry_keys = None # File extensions def __init__(self, kp, format=None, postprocessors=None, interactive=False, **kwargs): diff --git a/knowledge_repo/converters/gdoc.py b/knowledge_repo/converters/gdoc.py index 9861b0e43..1084d6d31 100644 --- a/knowledge_repo/converters/gdoc.py +++ b/knowledge_repo/converters/gdoc.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import cooked_input as ci import logging import os diff --git a/knowledge_repo/converters/html.py b/knowledge_repo/converters/html.py index 984474f5c..727d03716 100644 --- a/knowledge_repo/converters/html.py +++ b/knowledge_repo/converters/html.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals import markdown from markdown import Extension from markdown.blockprocessors import BlockProcessor @@ -117,7 +115,7 @@ def run(self, parent, blocks): block_is_html = block_is_html and not isinstance(sibling.text, AtomicString) - block = u'\n'.join([sibling.text, block]) + block = '\n'.join([sibling.text, block]) output = sibling else: # This is a new codeblock. Create the elements and insert text. diff --git a/knowledge_repo/mapping.py b/knowledge_repo/mapping.py index 7aa21fdc7..cdd7033fa 100644 --- a/knowledge_repo/mapping.py +++ b/knowledge_repo/mapping.py @@ -1,4 +1,3 @@ -from builtins import object import re @@ -29,7 +28,7 @@ def apply(self, text): else: output.append(replacement) output.append(text[last_offset:]) - return u''.join(output) + return ''.join(output) def find_matches(self, text, reverse=False): matches = [] diff --git a/knowledge_repo/post.py b/knowledge_repo/post.py index 3f2a5b4df..df1c55a41 100755 --- a/knowledge_repo/post.py +++ b/knowledge_repo/post.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from builtins import next -from builtins import object from collections import namedtuple import io import itertools @@ -16,7 +13,6 @@ import cooked_input as ci import PIL.Image -import six from .utils.encoding import encode, decode @@ -26,20 +22,20 @@ Header = namedtuple('Header', ('name', 'type', 'input')) HEADER_REQUIRED_FIELD_TYPES = [ - Header('title', six.string_types, ci.GetInput(prompt='title')), + Header('title', str, ci.GetInput(prompt='title')), Header('authors', list, ci.GetInput(prompt='authors (comma separated)', convertor=ci.ListConvertor())), - Header('tldr', six.string_types, ci.GetInput(prompt='tldr')), + Header('tldr', str, ci.GetInput(prompt='tldr')), Header('created_at', datetime.datetime, ci.GetInput(prompt='created_at', convertor=ci.DateConvertor(), default=datetime.date.today())), ] HEADER_OPTIONAL_FIELD_TYPES = [ - Header('subtitle', six.string_types, ci.GetInput(prompt='subtitle', required=False)), + Header('subtitle', str, ci.GetInput(prompt='subtitle', required=False)), Header('tags', list, ci.GetInput(prompt='tags (comma separated)', convertor=ci.ListConvertor(), required=False)), - Header('path', six.string_types, ci.GetInput(prompt='path', required=False)), + Header('path', str, ci.GetInput(prompt='path', required=False)), Header('updated_at', datetime.datetime, ci.GetInput(prompt='updated_at', convertor=ci.DateConvertor(), default=datetime.datetime.now())), Header('private', bool, ci.GetInput(prompt='private', convertor=ci.BooleanConvertor(), required=False)), # If true, this post starts out private Header('allowed_groups', list, ci.GetInput(prompt='allowed_groups (comma separated)', convertor=ci.ListConvertor(), required=False)), - Header('thumbnail', (int, ) + six.string_types, ci.GetInput(prompt='thumbnail', required=False)), + Header('thumbnail', (int, str), ci.GetInput(prompt='thumbnail', required=False)), ] HEADERS_ALL = { @@ -331,7 +327,7 @@ def _get_headers_from_yaml(self, yaml_str): try: if not yaml_str.strip().startswith('---'): raise StopIteration() - return next(yaml.load_all(yaml_str)) + return next(yaml.safe_load_all(yaml_str)) except yaml.YAMLError as e: logger.info( "YAML header is incorrectly formatted or missing. The following " @@ -395,7 +391,7 @@ def headers(self): if isinstance(value, datetime.date): headers[key] = datetime.datetime.combine(value, datetime.time(0)) if key == 'tags' and isinstance(value, list): - headers[key] = [str(v) if six.PY3 else unicode(v) for v in value] + headers[key] = [str(v) for v in value] return headers @headers.setter @@ -416,7 +412,7 @@ def update_headers(self, **headers): def thumbnail_uri(self): thumbnail = self.headers.get('thumbnail') - if not thumbnail or not isinstance(thumbnail, six.string_types): + if not thumbnail or not isinstance(thumbnail, str): return None if ':' not in thumbnail: # if thumbnail points to a local reference diff --git a/knowledge_repo/postprocessor.py b/knowledge_repo/postprocessor.py index 29c935144..d677e092c 100644 --- a/knowledge_repo/postprocessor.py +++ b/knowledge_repo/postprocessor.py @@ -1,9 +1,7 @@ -from builtins import object from .utils.registry import SubclassRegisteringABCMeta -from future.utils import with_metaclass -class KnowledgePostProcessor(with_metaclass(SubclassRegisteringABCMeta, object)): +class KnowledgePostProcessor(object, metaclass=SubclassRegisteringABCMeta): _registry_keys = [] def process(self, kp): diff --git a/knowledge_repo/postprocessors/extract_images.py b/knowledge_repo/postprocessors/extract_images.py index e7842caa9..e748af3b2 100644 --- a/knowledge_repo/postprocessors/extract_images.py +++ b/knowledge_repo/postprocessors/extract_images.py @@ -1,7 +1,6 @@ import os import posixpath import re -import six import logging from ..postprocessor import KnowledgePostProcessor @@ -22,7 +21,7 @@ def update_thumbnail_uri(self, kp, images, image_mapping): thumbnail = kp.headers.get('thumbnail', 0) # if thumbnail is a number, select the nth image in this post as the thumbnail - if isinstance(thumbnail, six.string_types) and thumbnail.isdigit(): + if isinstance(thumbnail, str) and thumbnail.isdigit(): thumbnail = int(thumbnail) if isinstance(thumbnail, int): if len(images) > 0: diff --git a/knowledge_repo/postprocessors/extract_images_to_local.py b/knowledge_repo/postprocessors/extract_images_to_local.py index f35add036..2c4773d92 100644 --- a/knowledge_repo/postprocessors/extract_images_to_local.py +++ b/knowledge_repo/postprocessors/extract_images_to_local.py @@ -5,11 +5,7 @@ import logging import time import tempfile - -try: - from urllib.parse import urljoin -except ImportError: # Python 2 - from urlparse import urljoin +from urllib.parse import urljoin from .extract_images import ExtractImages diff --git a/knowledge_repo/postprocessors/format_checks.py b/knowledge_repo/postprocessors/format_checks.py index 07328378b..be9bc1918 100644 --- a/knowledge_repo/postprocessors/format_checks.py +++ b/knowledge_repo/postprocessors/format_checks.py @@ -1,8 +1,5 @@ import datetime -import six -from past.builtins import basestring - from ..post import HEADER_OPTIONAL_FIELD_TYPES, HEADER_REQUIRED_FIELD_TYPES from ..postprocessor import KnowledgePostProcessor diff --git a/knowledge_repo/repositories/dbrepository.py b/knowledge_repo/repositories/dbrepository.py index 1381f0a93..4f1ad29b9 100644 --- a/knowledge_repo/repositories/dbrepository.py +++ b/knowledge_repo/repositories/dbrepository.py @@ -1,4 +1,3 @@ -from builtins import object import logging import posixpath @@ -32,7 +31,7 @@ def init(self, auto_create=True): # TODO handle if user does not pass in table sqlite://path.db uri_splt = self.uri.split(":") - engine_uri = u":".join(uri_splt[:-1]) + engine_uri = ":".join(uri_splt[:-1]) table_name = uri_splt[-1] metadata = MetaData() diff --git a/knowledge_repo/repositories/folder.py b/knowledge_repo/repositories/folder.py index 564d9a9df..c522f342d 100644 --- a/knowledge_repo/repositories/folder.py +++ b/knowledge_repo/repositories/folder.py @@ -1,13 +1,9 @@ -from __future__ import print_function - import os import shutil import logging import time from io import open -import six - from ..post import KnowledgePost from ..repository import KnowledgeRepository from ..utils.encoding import encode @@ -67,7 +63,7 @@ def path(self): @path.setter def path(self, path): - assert isinstance(path, six.string_types), "The path specified must be a string." + assert isinstance(path, str), "The path specified must be a string." path = os.path.abspath(os.path.expanduser(path)) if not os.path.exists(path): path = os.path.abspath(path) diff --git a/knowledge_repo/repositories/gitrepository.py b/knowledge_repo/repositories/gitrepository.py index 646d7f92c..0ea8ed053 100644 --- a/knowledge_repo/repositories/gitrepository.py +++ b/knowledge_repo/repositories/gitrepository.py @@ -1,6 +1,3 @@ -from __future__ import print_function -from builtins import input - import os import shutil import logging @@ -9,11 +6,9 @@ from io import open import git -import six import yaml from ..repository import KnowledgeRepository -from ..utils.types import str_types from ..utils.encoding import encode logger = logging.getLogger(__name__) @@ -81,7 +76,7 @@ def init(self, config='git:///.knowledge_repo_config.yml', auto_create=False): if config.startswith('git:///'): assert config.endswith('.yml'), "In-repository configuration must be a YAML file." try: - self.config.update(yaml.load(self.git_read(config.replace('git:///', '')))) + self.config.update(yaml.safe_load(self.git_read(config.replace('git:///', '')))) except KeyError: logger.warning("Repository missing configuration file: {}".format(config)) else: @@ -93,7 +88,7 @@ def path(self): @path.setter def path(self, path): - assert isinstance(path, six.string_types), "The path specified must be a string." + assert isinstance(path, str), "The path specified must be a string." path = os.path.abspath(os.path.expanduser(path)) if not os.path.exists(path): path = os.path.abspath(path) @@ -200,7 +195,7 @@ def __get_path_from_ref(self, ref): break if not ref.endswith('.kp'): return None - return u'/'.join(refs[:i + 1]) + return '/'.join(refs[:i + 1]) def git_local_posts(self, branches=None, as_dict=False): if branches is None: @@ -259,7 +254,7 @@ def git_branch(self, branch=None): if branch is None: return self.git.active_branch - if not isinstance(branch, str_types): + if not isinstance(branch, str): raise ValueError("'{}' of type `{}` is not a valid branch descriptor.".format(branch, type(branch))) try: diff --git a/knowledge_repo/repository.py b/knowledge_repo/repository.py index e6da32246..111f1932d 100644 --- a/knowledge_repo/repository.py +++ b/knowledge_repo/repository.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function - -from builtins import object import sys import os import posixpath @@ -9,23 +5,16 @@ import datetime from collections import OrderedDict from enum import Enum - -import six +from urllib.parse import urlparse from . import config_defaults from .post import KnowledgePost from .config import KnowledgeRepositoryConfig from .postprocessor import KnowledgePostProcessor from .utils.registry import SubclassRegisteringABCMeta -from future.utils import with_metaclass - -if sys.version_info.major > 2: - from urllib.parse import urlparse -else: - from urlparse import urlparse -class KnowledgeRepository(with_metaclass(SubclassRegisteringABCMeta, object)): +class KnowledgeRepository(object, metaclass=SubclassRegisteringABCMeta): _registry_keys = None class PostStatus(Enum): @@ -50,7 +39,7 @@ def for_uri(cls, uri, *args, **kwargs): def for_uris(cls, uri): # Import this within this method so as not to cause import resolution problems from .repositories.meta import MetaKnowledgeRepository - if isinstance(uri, six.string_types): + if isinstance(uri, str): uris = {'': uri} else: uris = uri @@ -100,14 +89,14 @@ def uris(self): # It assumes that self.uri is either a string or a dictionary mapping # of form: # {: } - if isinstance(self.uri, six.string_types): + if isinstance(self.uri, str): return {'': self.uri} elif isinstance(self.uri, dict): uri_dict = {} def add_uris(uri_dict, uri, parent=''): - if isinstance(uri, six.string_types): + if isinstance(uri, str): uri_dict[parent] = uri elif isinstance(uri, dict): for mountpoint, u in uri.items(): @@ -128,14 +117,14 @@ def revisions(self): # This method provides a mapping from uri to revision for this repository # and/or any nested repositories. This is most useful when checking if an # update is required server side. - if isinstance(self.uri, six.string_types): + if isinstance(self.uri, str): return {self.uri: self.revision} elif isinstance(self.uri, dict): revision_dict = {} def add_revisions(revision_dict, uri): - if isinstance(uri, six.string_types): + if isinstance(uri, str): revision_dict[uri] = KnowledgeRepository.for_uri(uri).revision elif isinstance(uri, dict): for u in uri.values(): @@ -190,13 +179,13 @@ def post(self, path, revision=None): return KnowledgePost(path=path, repository=self, revision=revision or self._kp_get_revision(path)) def dir(self, prefix=None, status=None): - if prefix is None or isinstance(prefix, six.string_types): + if prefix is None or isinstance(prefix, str): prefixes = [prefix] else: prefixes = prefix - assert all([prefix is None or isinstance(prefix, six.string_types) for prefix in prefixes]), "All path prefixes must be strings." + assert all([prefix is None or isinstance(prefix, str) for prefix in prefixes]), "All path prefixes must be strings." prefixes = [prefix if prefix is None else posixpath.relpath(prefix) for prefix in prefixes] - if isinstance(status, six.string_types): + if isinstance(status, str): if status == 'all': status = [self.PostStatus.DRAFT, self.PostStatus.SUBMITTED, self.PostStatus.PUBLISHED, self.PostStatus.UNPUBLISHED] else: diff --git a/knowledge_repo/templates/knowledge_template.ipynb b/knowledge_repo/templates/knowledge_template.ipynb index e64bd72e7..789b10a22 100644 --- a/knowledge_repo/templates/knowledge_template.ipynb +++ b/knowledge_repo/templates/knowledge_template.ipynb @@ -56,9 +56,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -136,23 +134,23 @@ "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.10" + "pygments_lexer": "ipython3", + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/knowledge_repo/utils/dependencies.py b/knowledge_repo/utils/dependencies.py index 3834ff015..d1053327a 100644 --- a/knowledge_repo/utils/dependencies.py +++ b/knowledge_repo/utils/dependencies.py @@ -14,4 +14,4 @@ def check_dependencies(dependencies, message=None): "\t{install_command}\n\n" "Note: Depending on your system's installation of Python, you may " "need to use `pip2` or `pip3` instead of `pip`.").format(install_command='pip install --upgrade ' + ' '.join(missing_deps)) - raise RuntimeError(u'\n\n'.join([message, fix])) + raise RuntimeError('\n\n'.join([message, fix])) diff --git a/knowledge_repo/utils/encoding.py b/knowledge_repo/utils/encoding.py index e40c6d886..0dcdc94b0 100644 --- a/knowledge_repo/utils/encoding.py +++ b/knowledge_repo/utils/encoding.py @@ -2,7 +2,6 @@ import sys import logging -from future.utils import raise_with_traceback __all__ = ['encode', 'decode'] @@ -11,25 +10,19 @@ def encode(data, encoding='utf-8'): assert encoding == 'utf-8', "Only UTF-8 encoding is currently supported." - # Check if data is alreqdy encoded (for the purposes of unicode only + # Check if data is already encoded (for the purposes of unicode only # If not, convert to a string if necessary, and then encode as utf-8 bytes - if sys.version_info.major == 2: - if not isinstance(data, basestring): - data = unicode(data) - elif not isinstance(data, unicode): - return data # UTF-8 data is already encoded - else: - if isinstance(data, bytes): - return data # UTF-8 data is already encoded - elif not isinstance(data, str): - data = str(data) + if isinstance(data, bytes): + return data # UTF-8 data is already encoded + elif not isinstance(data, str): + data = str(data) # Encode UTF-8 data if encoding is not None: try: data = data.encode(encoding) except Exception as e: if os.environ.get('DEBUG'): - raise_with_traceback(e) + raise e.with_traceback() logger.warning("An encoding error has occurred... continuing anyway. To capture these errors, rerun the current command prefixed with `DEBUG=1 `.") data = data.encode(encoding, errors='ignore') return data @@ -42,7 +35,7 @@ def decode(data, encoding='utf-8'): data = data.decode(encoding) except Exception as e: if os.environ.get('DEBUG'): - raise_with_traceback(e) + raise e.with_traceback() logger.warning("An decoding error has occurred... continuing anyway. To capture these errors, rerun the current command prefixed with `DEBUG=1 `.") data = data.decode(encoding, errors='ignore') return data diff --git a/knowledge_repo/utils/exec_code.py b/knowledge_repo/utils/exec_code.py index 568e5e913..b0e12f0dd 100644 --- a/knowledge_repo/utils/exec_code.py +++ b/knowledge_repo/utils/exec_code.py @@ -1,12 +1,13 @@ import sys -import imp +import importlib import time def get_module_for_source(source, module_name, register_globally=True): module_name = module_name + '_' + str(time.time()).replace('.', '') - module = imp.new_module(module_name) - exec(source, module.__dict__) + spec = importlib.util.find_spec(module_name) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) if register_globally: sys.modules[module_name] = module return module diff --git a/knowledge_repo/utils/types.py b/knowledge_repo/utils/types.py index 1f03292f6..98098caa8 100644 --- a/knowledge_repo/utils/types.py +++ b/knowledge_repo/utils/types.py @@ -3,13 +3,6 @@ from sqlalchemy import Text from sqlalchemy.dialects.mysql import MEDIUMTEXT -__all__ = ['str_types'] - -if sys.version_info.major > 2: - str_types = (str,) -else: - str_types = (str, unicode) - def MediumText(): return Text().with_variant(MEDIUMTEXT(), 'mysql') diff --git a/scripts/knowledge_repo b/scripts/knowledge_repo index 7b0240e37..013a0db33 100755 --- a/scripts/knowledge_repo +++ b/scripts/knowledge_repo @@ -1,6 +1,4 @@ #!/usr/bin/env python -from __future__ import print_function -from __future__ import unicode_literals import sys import os diff --git a/scripts/kp b/scripts/kp index b054340fb..edf499a22 100755 --- a/scripts/kp +++ b/scripts/kp @@ -1,8 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function -from __future__ import unicode_literals - import argparse import cooked_input as ci import os diff --git a/setup.py b/setup.py index 958cfe3d4..911734b7b 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,6 @@ def run(self): 'Intended Audience :: Information Technology', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/tests/test_comments.py b/tests/test_comments.py index fe6155170..4642c2643 100644 --- a/tests/test_comments.py +++ b/tests/test_comments.py @@ -1,4 +1,3 @@ -from __future__ import print_function import unittest import json diff --git a/tests/test_posts/addition_priciples.ipynb b/tests/test_posts/addition_principles.ipynb similarity index 96% rename from tests/test_posts/addition_priciples.ipynb rename to tests/test_posts/addition_principles.ipynb index 2e2460039..1d32e3d1a 100644 --- a/tests/test_posts/addition_priciples.ipynb +++ b/tests/test_posts/addition_principles.ipynb @@ -43,9 +43,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/tests/test_posts/bokeh.ipynb b/tests/test_posts/bokeh.ipynb index 61d3bb149..ba06ceefc 100644 --- a/tests/test_posts/bokeh.ipynb +++ b/tests/test_posts/bokeh.ipynb @@ -20,9 +20,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -189,9 +187,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -391,9 +387,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/tests/test_posts/numerical_tags.ipynb b/tests/test_posts/numerical_tags.ipynb index e8033970c..30cb85ba2 100644 --- a/tests/test_posts/numerical_tags.ipynb +++ b/tests/test_posts/numerical_tags.ipynb @@ -42,9 +42,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.2" + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/tests/test_posts/one_plus_one.ipynb b/tests/test_posts/one_plus_one.ipynb index 85e45ed08..072bb26bc 100644 --- a/tests/test_posts/one_plus_one.ipynb +++ b/tests/test_posts/one_plus_one.ipynb @@ -91,9 +91,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/tests/test_posts/plotly.ipynb b/tests/test_posts/plotly.ipynb index d134f77ca..e6f1ae38d 100644 --- a/tests/test_posts/plotly.ipynb +++ b/tests/test_posts/plotly.ipynb @@ -879,23 +879,23 @@ "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.8.2" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/tests/test_posts/riemann_hypothesis.ipynb b/tests/test_posts/riemann_hypothesis.ipynb index 27f435bea..dfe6ad919 100644 --- a/tests/test_posts/riemann_hypothesis.ipynb +++ b/tests/test_posts/riemann_hypothesis.ipynb @@ -73,9 +73,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.6.10" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 }