From 1e0e541b575a377efa31b01bb13a60bb8c890d61 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Tue, 5 Nov 2019 23:17:19 -0800 Subject: [PATCH] Initial pass of removing Python 2 support This commit removes Six as a dependency for Pelican, replacing the relevant aliases with the proper Python 3 imports. It also removes references to Python 2 logic that did not require Six. --- .gitignore | 1 - THANKS | 1 + docs/conf.py | 1 - pelican/__init__.py | 20 +-- pelican/__main__.py | 1 - pelican/cache.py | 4 +- pelican/contents.py | 32 ++--- pelican/generators.py | 11 +- pelican/log.py | 53 +------- pelican/paginator.py | 5 +- pelican/readers.py | 24 ++-- pelican/rstdirectives.py | 5 +- pelican/server.py | 17 +-- pelican/settings.py | 13 +- pelican/signals.py | 1 - pelican/tests/default_conf.py | 1 - pelican/tests/support.py | 4 +- pelican/tests/test_cache.py | 1 - pelican/tests/test_contents.py | 25 ++-- pelican/tests/test_generators.py | 1 - pelican/tests/test_importer.py | 1 - pelican/tests/test_paginator.py | 1 - pelican/tests/test_pelican.py | 1 - pelican/tests/test_readers.py | 11 +- pelican/tests/test_rstdirectives.py | 1 - pelican/tests/test_server.py | 3 +- pelican/tests/test_settings.py | 1 - pelican/tests/test_testsuite.py | 1 - pelican/tests/test_urlwrappers.py | 1 - pelican/tests/test_utils.py | 9 +- pelican/tools/pelican_import.py | 40 ++---- pelican/tools/pelican_quickstart.py | 81 +++++------- pelican/tools/pelican_themes.py | 1 - pelican/tools/templates/pelicanconf.py.jinja2 | 1 - pelican/tools/templates/publishconf.py.jinja2 | 1 - pelican/urlwrappers.py | 16 +-- pelican/utils.py | 51 ++------ pelican/writers.py | 11 +- poetry.lock | 120 +----------------- pyproject.toml | 1 - samples/pelican.conf.py | 1 - samples/pelican.conf_FR.py | 1 - setup.py | 9 +- 43 files changed, 126 insertions(+), 459 deletions(-) diff --git a/.gitignore b/.gitignore index 45946946..c1835e10 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ tags .tox .coverage htmlcov -six-*.egg/ *.orig venv samples/output diff --git a/THANKS b/THANKS index 625c56d3..08ac7bb2 100644 --- a/THANKS +++ b/THANKS @@ -92,6 +92,7 @@ Joshua Adelman Julian Berman Justin Mayer Kevin Deldycke +Kevin Yap Kyle Fuller Laureline Guerin Leonard Huang diff --git a/docs/conf.py b/docs/conf.py index 43009cdd..f0589b84 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import os import sys diff --git a/pelican/__init__.py b/pelican/__init__.py index 499ded04..456d0691 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import argparse try: import collections.abc as collections except ImportError: import collections -import locale import logging import multiprocessing import os @@ -15,8 +13,6 @@ import sys import time import traceback -import six - # pelican.log has to be the first pelican module to be loaded # because logging.setLoggerClass has to be called before logging.getLogger from pelican.log import init as init_logging @@ -76,11 +72,10 @@ class Pelican(object): sys.path.insert(0, pluginpath) for plugin in self.settings['PLUGINS']: # if it's a string, then import it - if isinstance(plugin, six.string_types): + if isinstance(plugin, str): logger.debug("Loading plugin `%s`", plugin) try: - plugin = __import__(plugin, globals(), locals(), - str('module')) + plugin = __import__(plugin, globals(), locals(), 'module') except ImportError as e: logger.error( "Cannot load plugin `%s`\n%s", plugin, e) @@ -375,15 +370,6 @@ def get_config(args): config['BIND'] = args.bind config['DEBUG'] = args.verbosity == logging.DEBUG - # argparse returns bytes in Py2. There is no definite answer as to which - # encoding argparse (or sys.argv) uses. - # "Best" option seems to be locale.getpreferredencoding() - # http://mail.python.org/pipermail/python-list/2006-October/405766.html - if not six.PY3: - enc = locale.getpreferredencoding() - for key in config: - if key in ('PATH', 'OUTPUT_PATH', 'THEME'): - config[key] = config[key].decode(enc) return config @@ -397,7 +383,7 @@ def get_instance(args): settings = read_settings(config_file, override=get_config(args)) cls = settings['PELICAN_CLASS'] - if isinstance(cls, six.string_types): + if isinstance(cls, str): module, cls_name = cls.rsplit('.', 1) module = __import__(module) cls = getattr(module, cls_name) diff --git a/pelican/__main__.py b/pelican/__main__.py index 141823fc..69a5b95d 100644 --- a/pelican/__main__.py +++ b/pelican/__main__.py @@ -1,7 +1,6 @@ """ python -m pelican module entry point to run via python -m """ -from __future__ import absolute_import from . import main diff --git a/pelican/cache.py b/pelican/cache.py index e6c10cb9..0d36234a 100644 --- a/pelican/cache.py +++ b/pelican/cache.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import hashlib import logging import os - -from six.moves import cPickle as pickle +import pickle from pelican.utils import mkdir_p diff --git a/pelican/contents.py b/pelican/contents.py index a862db2d..5193061f 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,25 +1,20 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import copy +import datetime import locale import logging import os import re -import sys +from urllib.parse import urljoin, urlparse, urlunparse import pytz -import six -from six.moves.urllib.parse import urljoin, urlparse, urlunparse - from pelican import signals from pelican.settings import DEFAULT_CONFIG -from pelican.utils import (SafeDatetime, deprecated_attribute, memoized, - path_to_url, posixize_path, - python_2_unicode_compatible, sanitised_join, - set_date_tzinfo, slugify, strftime, - truncate_html_words) +from pelican.utils import (deprecated_attribute, memoized, path_to_url, + posixize_path, sanitised_join, set_date_tzinfo, + slugify, truncate_html_words) # Import these so that they're avalaible when you import from pelican.contents. from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA @@ -27,7 +22,6 @@ from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA logger = logging.getLogger(__name__) -@python_2_unicode_compatible class Content(object): """Represents a content. @@ -121,9 +115,6 @@ class Content(object): if isinstance(self.date_format, tuple): locale_string = self.date_format[0] - if sys.version_info < (3, ) and isinstance(locale_string, - six.text_type): - locale_string = locale_string.encode('ascii') locale.setlocale(locale.LC_ALL, locale_string) self.date_format = self.date_format[1] @@ -133,11 +124,11 @@ class Content(object): if hasattr(self, 'date'): self.date = set_date_tzinfo(self.date, timezone) - self.locale_date = strftime(self.date, self.date_format) + self.locale_date = self.date.strftime(self.date_format) if hasattr(self, 'modified'): self.modified = set_date_tzinfo(self.modified, timezone) - self.locale_modified = strftime(self.modified, self.date_format) + self.locale_modified = self.modified.strftime(self.date_format) # manage status if not hasattr(self, 'status'): @@ -213,7 +204,7 @@ class Content(object): 'path': path_to_url(path), 'slug': getattr(self, 'slug', ''), 'lang': getattr(self, 'lang', 'en'), - 'date': getattr(self, 'date', SafeDatetime.now()), + 'date': getattr(self, 'date', datetime.datetime.now()), 'author': self.author.slug if hasattr(self, 'author') else '', 'category': self.category.slug if hasattr(self, 'category') else '' }) @@ -512,22 +503,21 @@ class Article(Content): # handle WITH_FUTURE_DATES (designate article to draft based on date) if not self.settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'): if self.date.tzinfo is None: - now = SafeDatetime.now() + now = datetime.datetime.now() else: - now = SafeDatetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) if self.date > now: self.status = 'draft' # if we are a draft and there is no date provided, set max datetime if not hasattr(self, 'date') and self.status == 'draft': - self.date = SafeDatetime.max + self.date = datetime.datetime.max def _expand_settings(self, key): klass = 'draft' if self.status == 'draft' else 'article' return super(Article, self)._expand_settings(key, klass) -@python_2_unicode_compatible class Static(Content): mandatory_properties = ('title',) default_status = 'published' diff --git a/pelican/generators.py b/pelican/generators.py index ef021070..27c895e4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import calendar import errno @@ -15,15 +14,12 @@ from operator import attrgetter from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader, PrefixLoader, TemplateNotFound) -import six - from pelican import signals from pelican.cache import FileStampDataCacher from pelican.contents import Article, Page, Static from pelican.readers import Readers from pelican.utils import (DateFormatter, copy, mkdir_p, order_content, - posixize_path, process_translations, - python_2_unicode_compatible) + posixize_path, process_translations) logger = logging.getLogger(__name__) @@ -33,7 +29,6 @@ class PelicanTemplateNotFound(Exception): pass -@python_2_unicode_compatible class Generator(object): """Baseclass generator""" @@ -138,7 +133,7 @@ class Generator(object): extensions are allowed) """ # backward compatibility for older generators - if isinstance(paths, six.string_types): + if isinstance(paths, str): paths = [paths] # group the exclude dir names by parent path, for use with os.walk() @@ -513,8 +508,6 @@ class ArticlesGenerator(CachingGenerator): context["period"] = (_period,) else: month_name = calendar.month_name[_period[1]] - if not six.PY3: - month_name = month_name.decode('utf-8') if key == period_date_key['month']: context["period"] = (_period[0], month_name) diff --git a/pelican/log.py b/pelican/log.py index 6f353264..c971636e 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -1,17 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals -import locale import logging import os import sys from collections import defaultdict -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping - -import six __all__ = [ 'init' @@ -29,20 +21,17 @@ class BaseFormatter(logging.Formatter): # format multiline messages 'nicely' to make it clear they are together record.msg = record.msg.replace('\n', '\n | ') record.args = tuple(arg.replace('\n', '\n | ') if - isinstance(arg, six.string_types) else + isinstance(arg, str) else arg for arg in record.args) return super(BaseFormatter, self).format(record) def formatException(self, ei): ''' prefix traceback info for better representation ''' - # .formatException returns a bytestring in py2 and unicode in py3 - # since .format will handle unicode conversion, - # str() calls are used to normalize formatting string s = super(BaseFormatter, self).formatException(ei) # fancy format traceback - s = str('\n').join(str(' | ') + line for line in s.splitlines()) + s = '\n'.join(' | ' + line for line in s.splitlines()) # separate the traceback from the preceding lines - s = str(' |___\n{}').format(s) + s = ' |___\n{}'.format(s) return s def _get_levelname(self, name): @@ -140,41 +129,7 @@ class LimitFilter(logging.Filter): return True -class SafeLogger(logging.Logger): - """ - Base Logger which properly encodes Exceptions in Py2 - """ - _exc_encoding = locale.getpreferredencoding() - - def _log(self, level, msg, args, exc_info=None, extra=None): - # if the only argument is a Mapping, Logger uses that for formatting - # format values for that case - if args and len(args) == 1 and isinstance(args[0], Mapping): - args = ({k: self._decode_arg(v) for k, v in args[0].items()},) - # otherwise, format each arg - else: - args = tuple(self._decode_arg(arg) for arg in args) - super(SafeLogger, self)._log( - level, msg, args, exc_info=exc_info, extra=extra) - - def _decode_arg(self, arg): - ''' - properly decode an arg for Py2 if it's Exception - - - localized systems have errors in native language if locale is set - so convert the message to unicode with the correct encoding - ''' - if isinstance(arg, Exception): - text = str('%s: %s') % (arg.__class__.__name__, arg) - if six.PY2: - text = text.decode(self._exc_encoding) - return text - else: - return arg - - -class LimitLogger(SafeLogger): +class LimitLogger(logging.Logger): """ A logger which adds LimitFilter automatically """ diff --git a/pelican/paginator.py b/pelican/paginator.py index fe63863e..61899056 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import functools import logging @@ -7,8 +6,6 @@ import os from collections import namedtuple from math import ceil -import six - logger = logging.getLogger(__name__) PaginationRule = namedtuple( 'PaginationRule', @@ -131,7 +128,7 @@ class Page(object): prop_value = getattr(rule, key) - if not isinstance(prop_value, six.string_types): + if not isinstance(prop_value, str): logger.warning('%s is set to %s', key, prop_value) return prop_value diff --git a/pelican/readers.py b/pelican/readers.py index 52378c4f..bb4d6d81 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals +import datetime import logging import os import re from collections import OrderedDict +from html.parser import HTMLParser +from io import StringIO import docutils import docutils.core @@ -12,16 +14,11 @@ import docutils.io from docutils.parsers.rst.languages import get_language as get_docutils_lang from docutils.writers.html4css1 import HTMLTranslator, Writer -import six -from six import StringIO -from six.moves.html_parser import HTMLParser - from pelican import rstdirectives # NOQA from pelican import signals from pelican.cache import FileStampDataCacher from pelican.contents import Author, Category, Page, Tag -from pelican.utils import SafeDatetime, escape_html, get_date, pelican_open, \ - posixize_path +from pelican.utils import escape_html, get_date, pelican_open, posixize_path try: from markdown import Markdown @@ -79,7 +76,7 @@ def ensure_metadata_list(text): Regardless, all list items undergo .strip() before returning, and empty items are discarded. """ - if isinstance(text, six.text_type): + if isinstance(text, str): if ';' in text: text = text.split(';') else: @@ -212,8 +209,7 @@ class RstReader(BaseReader): """ def __init__(self, *args, **kwargs): - if six.PY3: - kwargs['mode'] = kwargs.get('mode', 'r').replace('U', '') + kwargs['mode'] = kwargs.get('mode', 'r').replace('U', '') docutils.io.FileInput.__init__(self, *args, **kwargs) def __init__(self, *args, **kwargs): @@ -685,10 +681,10 @@ def default_metadata(settings=None, process=None): metadata['category'] = value if settings.get('DEFAULT_DATE', None) and \ settings['DEFAULT_DATE'] != 'fs': - if isinstance(settings['DEFAULT_DATE'], six.string_types): + if isinstance(settings['DEFAULT_DATE'], str): metadata['date'] = get_date(settings['DEFAULT_DATE']) else: - metadata['date'] = SafeDatetime(*settings['DEFAULT_DATE']) + metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE']) return metadata @@ -696,7 +692,7 @@ def path_metadata(full_path, source_path, settings=None): metadata = {} if settings: if settings.get('DEFAULT_DATE', None) == 'fs': - metadata['date'] = SafeDatetime.fromtimestamp( + metadata['date'] = datetime.datetime.fromtimestamp( os.stat(full_path).st_mtime) # Apply EXTRA_PATH_METADATA for the source path and the paths of any @@ -731,7 +727,7 @@ def parse_path_metadata(source_path, settings=None, process=None): ... process=reader.process_metadata) >>> pprint.pprint(metadata) # doctest: +ELLIPSIS {'category': , - 'date': SafeDatetime(2013, 1, 1, 0, 0), + 'date': datetime.datetime(2013, 1, 1, 0, 0), 'slug': 'my-slug'} """ metadata = {} diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index b4f44aa1..dda0e6a7 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import re @@ -10,8 +9,6 @@ from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import TextLexer, get_lexer_by_name -import six - import pelican.settings as pys @@ -49,7 +46,7 @@ class Pygments(Directive): # Fetch the defaults if pys.PYGMENTS_RST_OPTIONS is not None: - for k, v in six.iteritems(pys.PYGMENTS_RST_OPTIONS): + for k, v in pys.PYGMENTS_RST_OPTIONS.items(): # Locally set options overrides the defaults if k not in self.options: self.options[k] = v diff --git a/pelican/server.py b/pelican/server.py index 841b8e88..8434ded5 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import argparse import logging @@ -7,16 +6,14 @@ import os import posixpath import ssl import sys +import urllib +from http import server try: from magic import from_file as magic_from_file except ImportError: magic_from_file = None -from six.moves import BaseHTTPServer -from six.moves import SimpleHTTPServer as srvmod -from six.moves import urllib - from pelican.log import init as init_logging logger = logging.getLogger(__name__) @@ -44,7 +41,7 @@ def parse_arguments(): return parser.parse_args() -class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): +class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): SUFFIXES = ['.html', '/index.html', '/', ''] def translate_path(self, path): @@ -76,7 +73,7 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): if not self.path: return - srvmod.SimpleHTTPRequestHandler.do_GET(self) + server.SimpleHTTPRequestHandler.do_GET(self) def get_path_that_exists(self, original_path): # Try to strip trailing slash @@ -96,7 +93,7 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): def guess_type(self, path): """Guess at the mime type for the specified file. """ - mimetype = srvmod.SimpleHTTPRequestHandler.guess_type(self, path) + mimetype = server.SimpleHTTPRequestHandler.guess_type(self, path) # If the default guess is too generic, try the python-magic library if mimetype == 'application/octet-stream' and magic_from_file: @@ -105,9 +102,9 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): return mimetype -class RootedHTTPServer(BaseHTTPServer.HTTPServer): +class RootedHTTPServer(server.HTTPServer): def __init__(self, base_path, *args, **kwargs): - BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs) + server.HTTPServer.__init__(self, *args, **kwargs) self.RequestHandlerClass.base_path = base_path diff --git a/pelican/settings.py b/pelican/settings.py index 58e6c63c..11bad8c4 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import copy import inspect @@ -10,8 +9,6 @@ import re from os.path import isabs from posixpath import join as posix_join -import six - from pelican.log import LimitFilter @@ -278,7 +275,7 @@ def handle_deprecated_settings(settings): del settings['PLUGIN_PATH'] # PLUGIN_PATHS: str -> [str] - if isinstance(settings.get('PLUGIN_PATHS'), six.string_types): + if isinstance(settings.get('PLUGIN_PATHS'), str): logger.warning("Defining PLUGIN_PATHS setting as string " "has been deprecated (should be a list)") settings['PLUGIN_PATHS'] = [settings['PLUGIN_PATHS']] @@ -547,13 +544,13 @@ def configure_settings(settings): # standardize strings to lists for key in ['LOCALE']: - if key in settings and isinstance(settings[key], six.string_types): + if key in settings and isinstance(settings[key], str): settings[key] = [settings[key]] # check settings that must be a particular type for key, types in [ - ('OUTPUT_SOURCES_EXTENSION', six.string_types), - ('FILENAME_METADATA', six.string_types), + ('OUTPUT_SOURCES_EXTENSION', str), + ('FILENAME_METADATA', str), ]: if key in settings and not isinstance(settings[key], types): value = settings.pop(key) @@ -647,7 +644,7 @@ def configure_settings(settings): 'PAGE_PATHS', ) for PATH_KEY in filter(lambda k: k in settings, path_keys): - if isinstance(settings[PATH_KEY], six.string_types): + if isinstance(settings[PATH_KEY], str): logger.warning("Detected misconfiguration with %s setting " "(must be a list), falling back to the default", PATH_KEY) diff --git a/pelican/signals.py b/pelican/signals.py index 18a745b4..253b5fc3 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals from blinker import signal diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index a567dc10..13014572 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals AUTHOR = 'Alexis Métaireau' SITENAME = "Alexis' log" SITEURL = 'http://blog.notmyidea.org' diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 751fb5ec..86bf984f 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import locale import logging @@ -10,12 +9,11 @@ import sys import unittest from contextlib import contextmanager from functools import wraps +from io import StringIO from logging.handlers import BufferingHandler from shutil import rmtree from tempfile import mkdtemp -from six import StringIO - from pelican.contents import Article from pelican.readers import default_metadata from pelican.settings import DEFAULT_CONFIG diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index ceba649e..7357cd9f 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import os from shutil import rmtree diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index efc438c8..e9592bd4 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals +import datetime import locale import logging import os.path @@ -9,15 +9,12 @@ from sys import platform from jinja2.utils import generate_lorem_ipsum -import six - from pelican.contents import Article, Author, Category, Page, Static from pelican.settings import DEFAULT_CONFIG from pelican.signals import content_object_init from pelican.tests.support import (LoggedTestCase, get_context, get_settings, unittest) -from pelican.utils import (SafeDatetime, path_to_url, posixize_path, - truncate_html_words) +from pelican.utils import (path_to_url, posixize_path, truncate_html_words) # generate one paragraph, enclosed with

@@ -185,7 +182,7 @@ class TestPage(LoggedTestCase): def test_datetime(self): # If DATETIME is set to a tuple, it should be used to override LOCALE - dt = SafeDatetime(2015, 9, 13) + dt = datetime.datetime(2015, 9, 13) page_kwargs = self._copy_page_kwargs() @@ -286,9 +283,7 @@ class TestPage(LoggedTestCase): 'link')) def test_intrasite_link(self): - # type does not take unicode in PY2 and bytes in PY3, which in - # combination with unicode literals leads to following insane line: - cls_name = '_DummyArticle' if six.PY3 else b'_DummyArticle' + cls_name = '_DummyArticle' article = type(cls_name, (object,), {'url': 'article.html'}) args = self.page_kwargs.copy() @@ -370,9 +365,7 @@ class TestPage(LoggedTestCase): self.assertEqual(p.custom, linked) def test_intrasite_link_more(self): - # type does not take unicode in PY2 and bytes in PY3, which in - # combination with unicode literals leads to following insane line: - cls_name = '_DummyAsset' if six.PY3 else b'_DummyAsset' + cls_name = '_DummyAsset' args = self.page_kwargs.copy() args['settings'] = get_settings() @@ -487,9 +480,7 @@ class TestPage(LoggedTestCase): ) def test_intrasite_link_markdown_spaces(self): - # Markdown introduces %20 instead of spaces, this tests that - # we support markdown doing this. - cls_name = '_DummyArticle' if six.PY3 else b'_DummyArticle' + cls_name = '_DummyArticle' article = type(cls_name, (object,), {'url': 'article-spaces.html'}) args = self.page_kwargs.copy() @@ -512,7 +503,7 @@ class TestPage(LoggedTestCase): def test_intrasite_link_source_and_generated(self): """Test linking both to the source and the generated article """ - cls_name = '_DummyAsset' if six.PY3 else b'_DummyAsset' + cls_name = '_DummyAsset' args = self.page_kwargs.copy() args['settings'] = get_settings() @@ -538,7 +529,7 @@ class TestPage(LoggedTestCase): def test_intrasite_link_to_static_content_with_filename(self): """Test linking to a static resource with deprecated {filename} """ - cls_name = '_DummyAsset' if six.PY3 else b'_DummyAsset' + cls_name = '_DummyAsset' args = self.page_kwargs.copy() args['settings'] = get_settings() diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 9ade301e..5b03b1d1 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import locale import os diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 6eb62852..942b95fe 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import locale import os diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 4e3ef035..8080c146 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals import locale diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 502a45ac..0d495ac7 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals try: import collections.abc as collections diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 3f05bb4a..5b87aeac 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -1,10 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import os -import six - from pelican import readers from pelican.tests.support import get_settings, unittest from pelican.utils import SafeDatetime @@ -64,8 +61,7 @@ class TestAssertDictHasSubset(ReaderTest): self.assertDictHasSubset(self.dictionary, self.dictionary) def test_fail_not_set(self): - six.assertRaisesRegex( - self, + self.assertRaisesRegex( AssertionError, r'Expected.*key-c.*to have value.*val-c.*but was not in Dict', self.assertDictHasSubset, @@ -73,8 +69,7 @@ class TestAssertDictHasSubset(ReaderTest): {'key-c': 'val-c'}) def test_fail_wrong_val(self): - six.assertRaisesRegex( - self, + self.assertRaisesRegex( AssertionError, r'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*', self.assertDictHasSubset, @@ -445,7 +440,7 @@ class RstReaderTest(ReaderTest): def test_parse_error(self): # Verify that it raises an Exception, not nothing and not SystemExit or # some such - with six.assertRaisesRegex(self, Exception, "underline too short"): + with self.assertRaisesRegex(Exception, "underline too short"): self.read_file(path='../parse_error/parse_error.rst') diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py index f6a7221f..0ff28a4a 100644 --- a/pelican/tests/test_rstdirectives.py +++ b/pelican/tests/test_rstdirectives.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals from pelican.tests.support import unittest diff --git a/pelican/tests/test_server.py b/pelican/tests/test_server.py index 8704c6b5..824e01ef 100644 --- a/pelican/tests/test_server.py +++ b/pelican/tests/test_server.py @@ -1,9 +1,8 @@ import os +from io import BytesIO from shutil import rmtree from tempfile import mkdtemp -from six import BytesIO - from pelican.server import ComplexHTTPRequestHandler from pelican.tests.support import unittest diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 21759d40..d995527c 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import copy import locale diff --git a/pelican/tests/test_testsuite.py b/pelican/tests/test_testsuite.py index 66c992bd..4f567516 100644 --- a/pelican/tests/test_testsuite.py +++ b/pelican/tests/test_testsuite.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import warnings diff --git a/pelican/tests/test_urlwrappers.py b/pelican/tests/test_urlwrappers.py index 8ff3d9d6..37af232e 100644 --- a/pelican/tests/test_urlwrappers.py +++ b/pelican/tests/test_urlwrappers.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from pelican.tests.support import unittest from pelican.urlwrappers import Author, Category, Tag, URLWrapper diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 444190d3..ad90a78e 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals import locale import logging @@ -11,8 +10,6 @@ from tempfile import mkdtemp import pytz -import six - from pelican import utils from pelican.generators import TemplatePagesGenerator from pelican.settings import read_settings @@ -720,8 +717,7 @@ class TestSanitisedJoin(unittest.TestCase): @unittest.skipIf(platform == 'win32', "Different filesystem root on Windows") def test_detect_parent_breakout(self): - with six.assertRaisesRegex( - self, + with self.assertRaisesRegex( RuntimeError, "Attempted to break out of output directory to /foo/test"): utils.sanitised_join( @@ -732,8 +728,7 @@ class TestSanitisedJoin(unittest.TestCase): @unittest.skipIf(platform == 'win32', "Different filesystem root on Windows") def test_detect_root_breakout(self): - with six.assertRaisesRegex( - self, + with self.assertRaisesRegex( RuntimeError, "Attempted to break out of output directory to /test"): utils.sanitised_join( diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index d6566e32..9b2edf4c 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import argparse import logging @@ -11,10 +10,9 @@ import sys import time from codecs import open from collections import defaultdict - -from six.moves.urllib.error import URLError -from six.moves.urllib.parse import quote, urlparse, urlsplit, urlunsplit -from six.moves.urllib.request import urlretrieve +from urllib.error import URLError +from urllib.parse import quote, urlparse, urlsplit, urlunsplit +from urllib.request import urlretrieve # because logging.setLoggerClass has to be called before logging.getLogger from pelican.log import init @@ -24,7 +22,7 @@ from pelican.utils import SafeDatetime, slugify try: from html import unescape # py3.5+ except ImportError: - from six.moves.html_parser import HTMLParser + from html.parser import HTMLParser unescape = HTMLParser().unescape logger = logging.getLogger(__name__) @@ -396,19 +394,8 @@ def posterous2fields(api_token, email, password): """Imports posterous posts""" import base64 from datetime import timedelta - try: - # py3k import - import json - except ImportError: - # py2 import - import simplejson as json - - try: - # py3k import - import urllib.request as urllib_request - except ImportError: - # py2 import - import urllib2 as urllib_request + import json + import urllib.request as urllib_request def get_posterous_posts(api_token, email, password, page=1): base64string = base64.encodestring( @@ -451,19 +438,8 @@ def posterous2fields(api_token, email, password): def tumblr2fields(api_key, blogname): """ Imports Tumblr posts (API v2)""" - try: - # py3k import - import json - except ImportError: - # py2 import - import simplejson as json - - try: - # py3k import - import urllib.request as urllib_request - except ImportError: - # py2 import - import urllib2 as urllib_request + import json + import urllib.request as urllib_request def get_tumblr_posts(api_key, blogname, offset=0): url = ("http://api.tumblr.com/v2/blog/%s.tumblr.com/" diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 463db900..5ff3dc33 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -1,12 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import argparse import codecs import locale import os -import sys from jinja2 import Environment, FileSystemLoader @@ -23,8 +21,6 @@ try: except ImportError: _DEFAULT_TIMEZONE = 'Europe/Paris' -import six - from pelican import __version__ locale.setlocale(locale.LC_ALL, '') @@ -77,41 +73,24 @@ CONF = { # url for list of valid timezones _TZ_URL = 'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones' -_input_compat = six.moves.input -str_compat = six.text_type - # Create a 'marked' default path, to determine if someone has supplied # a path on the command-line. -class _DEFAULT_PATH_TYPE(str_compat): +class _DEFAULT_PATH_TYPE(str): is_default_path = True _DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir) -def decoding_strings(f): - def wrapper(*args, **kwargs): - out = f(*args, **kwargs) - if isinstance(out, six.string_types) and not six.PY3: - # todo: make encoding configurable? - if six.PY3: - return out - else: - return out.decode(sys.stdin.encoding) - return out - return wrapper - - -@decoding_strings -def ask(question, answer=str_compat, default=None, length=None): - if answer == str_compat: +def ask(question, answer=str, default=None, length=None): + if answer == str: r = '' while True: if default: - r = _input_compat('> {0} [{1}] '.format(question, default)) + r = input('> {0} [{1}] '.format(question, default)) else: - r = _input_compat('> {0} '.format(question, default)) + r = input('> {0} '.format(question, default)) r = r.strip() @@ -133,11 +112,11 @@ def ask(question, answer=str_compat, default=None, length=None): r = None while True: if default is True: - r = _input_compat('> {0} (Y/n) '.format(question)) + r = input('> {0} (Y/n) '.format(question)) elif default is False: - r = _input_compat('> {0} (y/N) '.format(question)) + r = input('> {0} (y/N) '.format(question)) else: - r = _input_compat('> {0} (y/n) '.format(question)) + r = input('> {0} (y/n) '.format(question)) r = r.strip().lower() @@ -157,9 +136,9 @@ def ask(question, answer=str_compat, default=None, length=None): r = None while True: if default: - r = _input_compat('> {0} [{1}] '.format(question, default)) + r = input('> {0} [{1}] '.format(question, default)) else: - r = _input_compat('> {0} '.format(question)) + r = input('> {0} '.format(question)) r = r.strip() @@ -175,14 +154,14 @@ def ask(question, answer=str_compat, default=None, length=None): return r else: raise NotImplementedError( - 'Argument `answer` must be str_compat, bool, or integer') + 'Argument `answer` must be str, bool, or integer') def ask_timezone(question, default, tzurl): """Prompt for time zone and validate input""" lower_tz = [tz.lower() for tz in pytz.all_timezones] while True: - r = ask(question, str_compat, default) + r = ask(question, str, default) r = r.strip().replace(' ', '_').lower() if r in lower_tz: r = pytz.all_timezones[lower_tz.index(r)] @@ -227,20 +206,20 @@ needed by Pelican. else: CONF['basedir'] = os.path.abspath(os.path.expanduser( ask('Where do you want to create your new web site?', - answer=str_compat, default=args.path))) + answer=str, default=args.path))) CONF['sitename'] = ask('What will be the title of this web site?', - answer=str_compat, default=args.title) + answer=str, default=args.title) CONF['author'] = ask('Who will be the author of this web site?', - answer=str_compat, default=args.author) + answer=str, default=args.author) CONF['lang'] = ask('What will be the default language of this web site?', - str_compat, args.lang or CONF['lang'], 2) + str, args.lang or CONF['lang'], 2) if ask('Do you want to specify a URL prefix? e.g., https://example.com ', answer=bool, default=True): CONF['siteurl'] = ask('What is your URL prefix? (see ' 'above example; no trailing slash)', - str_compat, CONF['siteurl']) + str, CONF['siteurl']) CONF['with_pagination'] = ask('Do you want to enable article pagination?', bool, bool(CONF['default_pagination'])) @@ -263,49 +242,49 @@ needed by Pelican. answer=bool, default=False): CONF['ftp'] = True, CONF['ftp_host'] = ask('What is the hostname of your FTP server?', - str_compat, CONF['ftp_host']) + str, CONF['ftp_host']) CONF['ftp_user'] = ask('What is your username on that server?', - str_compat, CONF['ftp_user']) + str, CONF['ftp_user']) CONF['ftp_target_dir'] = ask('Where do you want to put your ' 'web site on that server?', - str_compat, CONF['ftp_target_dir']) + str, CONF['ftp_target_dir']) if ask('Do you want to upload your website using SSH?', answer=bool, default=False): CONF['ssh'] = True, CONF['ssh_host'] = ask('What is the hostname of your SSH server?', - str_compat, CONF['ssh_host']) + str, CONF['ssh_host']) CONF['ssh_port'] = ask('What is the port of your SSH server?', int, CONF['ssh_port']) CONF['ssh_user'] = ask('What is your username on that server?', - str_compat, CONF['ssh_user']) + str, CONF['ssh_user']) CONF['ssh_target_dir'] = ask('Where do you want to put your ' 'web site on that server?', - str_compat, CONF['ssh_target_dir']) + str, CONF['ssh_target_dir']) if ask('Do you want to upload your website using Dropbox?', answer=bool, default=False): CONF['dropbox'] = True, CONF['dropbox_dir'] = ask('Where is your Dropbox directory?', - str_compat, CONF['dropbox_dir']) + str, CONF['dropbox_dir']) if ask('Do you want to upload your website using S3?', answer=bool, default=False): CONF['s3'] = True, CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', - str_compat, CONF['s3_bucket']) + str, CONF['s3_bucket']) if ask('Do you want to upload your website using ' 'Rackspace Cloud Files?', answer=bool, default=False): CONF['cloudfiles'] = True, CONF['cloudfiles_username'] = ask('What is your Rackspace ' - 'Cloud username?', str_compat, + 'Cloud username?', str, CONF['cloudfiles_username']) CONF['cloudfiles_api_key'] = ask('What is your Rackspace ' - 'Cloud API key?', str_compat, + 'Cloud API key?', str, CONF['cloudfiles_api_key']) CONF['cloudfiles_container'] = ask('What is the name of your ' 'Cloud Files container?', - str_compat, + str, CONF['cloudfiles_container']) if ask('Do you want to upload your website using GitHub Pages?', @@ -363,9 +342,7 @@ needed by Pelican. try: with codecs.open(os.path.join(CONF['basedir'], 'Makefile'), 'w', 'utf-8') as fd: - py_v = 'python' - if six.PY3: - py_v = 'python3' + py_v = 'python3' _template = _jinja_env.get_template('Makefile.jinja2') fd.write(_template.render(py_v=py_v, **CONF)) fd.close() diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 9f9b2328..03acd9ce 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import argparse import os diff --git a/pelican/tools/templates/pelicanconf.py.jinja2 b/pelican/tools/templates/pelicanconf.py.jinja2 index 79f26a01..62939b6a 100644 --- a/pelican/tools/templates/pelicanconf.py.jinja2 +++ b/pelican/tools/templates/pelicanconf.py.jinja2 @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -from __future__ import unicode_literals AUTHOR = {{author}} SITENAME = {{sitename}} diff --git a/pelican/tools/templates/publishconf.py.jinja2 b/pelican/tools/templates/publishconf.py.jinja2 index 913de7f1..bb18966b 100755 --- a/pelican/tools/templates/publishconf.py.jinja2 +++ b/pelican/tools/templates/publishconf.py.jinja2 @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -from __future__ import unicode_literals # This file is only used if you use `make publish` or # explicitly specify it as your config file. diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index baa8eb23..edfb11b4 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -1,18 +1,14 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import functools import logging import os -import six - -from pelican.utils import python_2_unicode_compatible, slugify +from pelican.utils import slugify logger = logging.getLogger(__name__) -@python_2_unicode_compatible @functools.total_ordering class URLWrapper(object): def __init__(self, name, settings): @@ -66,26 +62,26 @@ class URLWrapper(object): def _normalize_key(self, key): subs = self.settings.get('SLUG_REGEX_SUBSTITUTIONS', []) - return six.text_type(slugify(key, regex_subs=subs)) + return str(slugify(key, regex_subs=subs)) def __eq__(self, other): if isinstance(other, self.__class__): return self.slug == other.slug - if isinstance(other, six.text_type): + if isinstance(other, str): return self.slug == self._normalize_key(other) return False def __ne__(self, other): if isinstance(other, self.__class__): return self.slug != other.slug - if isinstance(other, six.text_type): + if isinstance(other, str): return self.slug != self._normalize_key(other) return True def __lt__(self, other): if isinstance(other, self.__class__): return self.slug < other.slug - if isinstance(other, six.text_type): + if isinstance(other, str): return self.slug < self._normalize_key(other) return False @@ -105,7 +101,7 @@ class URLWrapper(object): """ setting = "%s_%s" % (self.__class__.__name__.upper(), key) value = self.settings[setting] - if not isinstance(value, six.string_types): + if not isinstance(value, str): logger.warning('%s is set to %s', setting, value) return value else: diff --git a/pelican/utils.py b/pelican/utils.py index 9629c7e0..d031503d 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals import codecs import datetime @@ -12,12 +11,15 @@ import re import shutil import sys import traceback +import urllib try: from collections.abc import Hashable except ImportError: from collections import Hashable from contextlib import contextmanager from functools import partial +from html import entities +from html.parser import HTMLParser from itertools import groupby from operator import attrgetter @@ -27,10 +29,6 @@ from jinja2 import Markup import pytz -import six -from six.moves import html_entities -from six.moves.html_parser import HTMLParser - try: from html import escape except ImportError: @@ -98,10 +96,6 @@ def strftime(date, date_format): else: formatted = date.strftime(candidate) - # convert Py2 result to unicode - if not six.PY3 and enc is not None: - formatted = formatted.decode(enc) - # strip zeros if '-' prefix is used if conversion: formatted = conversion(formatted) @@ -150,22 +144,6 @@ class DateFormatter(object): return formatted -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - - From django.utils.encoding. - """ - if not six.PY3: - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - class memoized(object): """Function decorator to cache return values. @@ -214,15 +192,15 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None): content of the dummy method is ignored. """ def _warn(): - version = '.'.join(six.text_type(x) for x in since) + version = '.'.join(str(x) for x in since) message = ['{} has been deprecated since {}'.format(old, version)] if remove: - version = '.'.join(six.text_type(x) for x in remove) + version = '.'.join(str(x) for x in remove) message.append( ' and will be removed by version {}'.format(version)) message.append('. Use {} instead.'.format(new)) logger.warning(''.join(message)) - logger.debug(''.join(six.text_type(x) for x + logger.debug(''.join(str(x) for x in traceback.format_stack())) def fget(self): @@ -279,10 +257,8 @@ def slugify(value, regex_subs=()): # value must be unicode per se import unicodedata from unidecode import unidecode - # unidecode returns str in Py2 and 3, so in Py2 we have to make - # it unicode again value = unidecode(value) - if isinstance(value, six.binary_type): + if isinstance(value, bytes): value = value.decode('ascii') # still unicode value = unicodedata.normalize('NFKD', value) @@ -584,8 +560,8 @@ class _HTMLWordTruncator(HTMLParser): `name` is the entity ref without ampersand and semicolon (e.g. `mdash`) """ try: - codepoint = html_entities.name2codepoint[name] - char = six.unichr(codepoint) + codepoint = entities.name2codepoint[name] + char = chr(codepoint) except KeyError: char = '' self._handle_ref(name, char) @@ -602,7 +578,7 @@ class _HTMLWordTruncator(HTMLParser): codepoint = int(name[1:], 16) else: codepoint = int(name) - char = six.unichr(codepoint) + char = chr(codepoint) except (ValueError, OverflowError): char = '' self._handle_ref('#' + name, char) @@ -663,7 +639,7 @@ def process_translations(content_list, translation_id=None): if not translation_id: return content_list, [] - if isinstance(translation_id, six.string_types): + if isinstance(translation_id, str): translation_id = {translation_id} index = [] @@ -753,7 +729,7 @@ def order_content(content_list, order_by='slug'): content_list.sort(key=order_by) except Exception: logger.error('Error sorting with function %s', order_by) - elif isinstance(order_by, six.string_types): + elif isinstance(order_by, str): if order_by.startswith('reversed-'): order_reversed = True order_by = order_by.replace('reversed-', '', 1) @@ -901,8 +877,7 @@ def is_selected_for_writing(settings, path): def path_to_file_url(path): '''Convert file-system path to file:// URL''' - return six.moves.urllib_parse.urljoin( - "file://", six.moves.urllib.request.pathname2url(path)) + return urllib.parse.urljoin("file://", urllib.request.pathname2url(path)) def maybe_pluralize(count, singular, plural): diff --git a/pelican/writers.py b/pelican/writers.py index b610e2e2..daeb9dec 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -1,24 +1,18 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals, with_statement import logging import os +from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri from jinja2 import Markup -import six -from six.moves.urllib.parse import urljoin - from pelican import signals from pelican.paginator import Paginator from pelican.utils import (get_relative_path, is_selected_for_writing, path_to_url, sanitised_join, set_date_tzinfo) -if not six.PY3: - from codecs import open - logger = logging.getLogger(__name__) @@ -165,8 +159,7 @@ class Writer(object): except Exception: pass - encoding = 'utf-8' if six.PY3 else None - with self._open_w(complete_path, encoding, override_output) as fp: + with self._open_w(complete_path, 'utf-8', override_output) as fp: feed.write(fp, 'utf-8') logger.info('Writing %s', complete_path) diff --git a/poetry.lock b/poetry.lock index 971f6520..50ba33bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,15 +17,6 @@ version = "2.7.0" [package.dependencies] pytz = ">=2015.7" -[[package]] -category = "dev" -description = "backports.functools_lru_cache" -marker = "python_version < \"3\"" -name = "backports.functools-lru-cache" -optional = false -python-versions = ">=2.6" -version = "1.5" - [[package]] category = "dev" description = "Screen-scraping library" @@ -54,24 +45,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.4.1" -[[package]] -category = "dev" -description = "Updated configparser from Python 3.7 for Python 2.6+." -marker = "python_version < \"3.2\"" -name = "configparser" -optional = false -python-versions = ">=2.6" -version = "3.7.4" - -[[package]] -category = "dev" -description = "Backports and enhancements for the contextlib module" -marker = "python_version < \"3\"" -name = "contextlib2" -optional = false -python-versions = "*" -version = "0.5.5" - [[package]] category = "main" description = "Docutils -- Python Documentation Utilities" @@ -88,20 +61,6 @@ optional = false python-versions = ">=2.7" version = "0.3" -[package.dependencies] -[package.dependencies.configparser] -python = ">=2.7,<2.8" -version = ">=3.5" - -[[package]] -category = "dev" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -marker = "python_version < \"3.4\"" -name = "enum34" -optional = false -python-versions = "*" -version = "1.1.6" - [[package]] category = "main" description = "Standalone version of django.utils.feedgenerator" @@ -136,22 +95,6 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.5.0,<2.6.0" pyflakes = ">=2.1.0,<2.2.0" -[package.dependencies.configparser] -python = "<3.2" -version = "*" - -[package.dependencies.enum34] -python = "<3.4" -version = "*" - -[package.dependencies.functools32] -python = "<3.2" -version = "*" - -[package.dependencies.typing] -python = "<3.5" -version = "*" - [[package]] category = "dev" description = "Flake8 and pylama plugin that checks the ordering of import statements." @@ -164,28 +107,6 @@ version = "0.18.1" pycodestyle = "*" setuptools = "*" -[package.dependencies.enum34] -python = "<2.8" -version = "*" - -[[package]] -category = "dev" -description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" -marker = "python_version < \"3.3\"" -name = "funcsigs" -optional = false -python-versions = "*" -version = "1.0.2" - -[[package]] -category = "dev" -description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." -marker = "python_version < \"3.2\"" -name = "functools32" -optional = false -python-versions = "*" -version = "3.2.3-2" - [[package]] category = "dev" description = "Getting image size from png/jpeg/jpeg2000/gif file" @@ -205,14 +126,6 @@ version = "0.18" [package.dependencies] zipp = ">=0.5" -[package.dependencies.configparser] -python = "<3" -version = ">=3.5" - -[package.dependencies.contextlib2] -python = "<3" -version = "*" - [[package]] category = "main" description = "A small but fast and easy to use stand-alone template engine written in pure python." @@ -270,10 +183,6 @@ version = "3.0.5" [package.dependencies] six = "*" -[package.dependencies.funcsigs] -python = "<3.3" -version = ">=1" - [[package]] category = "dev" description = "Core utilities for Python packages" @@ -362,7 +271,7 @@ description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.12.0" +version = "1.13.0" [[package]] category = "dev" @@ -388,11 +297,6 @@ optional = false python-versions = "*" version = "1.9.2" -[package.dependencies] -[package.dependencies."backports.functools-lru-cache"] -python = "<3" -version = "*" - [[package]] category = "dev" description = "Python documentation generator" @@ -449,15 +353,6 @@ six = ">=1.0.0,<2" toml = ">=0.9.4" virtualenv = ">=14.0.0" -[[package]] -category = "dev" -description = "Type Hints for Python" -marker = "python_version < \"3.5\"" -name = "typing" -optional = false -python-versions = "*" -version = "3.7.4" - [[package]] category = "dev" description = "Filters to enhance web typography, including support for Django & Jinja templates" @@ -497,27 +392,21 @@ version = "0.5.1" markdown = ["markdown"] [metadata] -content-hash = "d22ff0db3331186ab2f809313de1f9efef1f9c708cc354eaa1638a5111404612" -python-versions = "~2.7 || ^3.5" +content-hash = "0163177d87dca0955d111319de9481cf2ed0ef014e63a3740ffaf32a1cf07212" +python-versions = "^3.5" [metadata.hashes] alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] -"backports.functools-lru-cache" = ["9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", "f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd"] beautifulsoup4 = ["034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", "945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348", "ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718"] blinker = ["471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] -configparser = ["8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32", "da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75"] -contextlib2 = ["509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", "f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"] docutils = ["02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", "51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", "7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"] entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] -enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"] feedgenerator = ["5ae05daa9cfa47fa406ee4744d0b7fa1c8a05a7a47ee0ad328ddf55327cfb106"] filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"] flake8 = ["859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", "a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"] flake8-import-order = ["90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543", "a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"] -funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"] -functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"] imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"] importlib-metadata = ["6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", "cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] @@ -535,7 +424,7 @@ pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", pyparsing = ["1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", "9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"] -six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] +six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"] smartypants = ["8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"] snowballstemmer = ["9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9"] soupsieve = ["72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946", "e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de"] @@ -543,7 +432,6 @@ sphinx = ["82cd2728c906be96e307b81352d3fd9fb731869234c6b835cc25e9a3dfb4b7e4", "b sphinx-rtd-theme = ["00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4", "728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] tox = ["dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", "ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd"] -typing = ["38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", "53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", "84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55"] typogrify = ["8be4668cda434163ce229d87ca273a11922cb1614cb359970b7dc96eed13cb38"] unidecode = ["1d7a042116536098d05d599ef2b8616759f02985c85b4fef50c78a5aaf10822a", "2b6aab710c2a1647e928e36d69c21e76b453cd455f4e2621000e54b2a9b8cce8"] virtualenv = ["b7335cddd9260a3dd214b73a2521ffc09647bde3e9457fcca31dc3be3999d04a", "d28ca64c0f3f125f59cabf13e0a150e1c68e5eea60983cc4395d88c584495783"] diff --git a/pyproject.toml b/pyproject.toml index f2f2d0f9..50408d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ unidecode = "^1.1" python-dateutil = "^2.8" docutils = "^0.14" markdown = {version = "~3.1.1", optional = true} -six = "^1.4" [tool.poetry.dev-dependencies] BeautifulSoup4 = "^4.7" diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index 0f67ee83..861c1f53 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals AUTHOR = 'Alexis Métaireau' SITENAME = "Alexis' log" diff --git a/samples/pelican.conf_FR.py b/samples/pelican.conf_FR.py index b1571e8a..f87653a4 100644 --- a/samples/pelican.conf_FR.py +++ b/samples/pelican.conf_FR.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals AUTHOR = 'Alexis Métaireau' SITENAME = "Alexis' log" diff --git a/setup.py b/setup.py index faf28f10..d79f055a 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -import sys + from io import open from os import walk from os.path import join, relpath @@ -10,8 +10,7 @@ from setuptools import setup version = "4.2.0" requires = ['feedgenerator >= 1.9', 'jinja2 >= 2.7', 'pygments', 'docutils', - 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', - 'python-dateutil'] + 'pytz >= 0a', 'blinker', 'unidecode', 'python-dateutil'] entry_points = { 'console_scripts': [ @@ -25,9 +24,7 @@ entry_points = { README = open('README.rst', encoding='utf-8').read() CHANGELOG = open('docs/changelog.rst', encoding='utf-8').read() -description = u'\n'.join([README, CHANGELOG]) -if sys.version_info.major < 3: - description = description.encode('utf-8') +description = '\n'.join([README, CHANGELOG]) setup( name='pelican',