Skip to content
Permalink
Browse files

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.
  • Loading branch information
iKevinY committed Nov 6, 2019
1 parent ae73d06 commit 1e0e541b575a377efa31b01bb13a60bb8c890d61
Showing with 126 additions and 459 deletions.
  1. +0 −1 .gitignore
  2. +1 −0 THANKS
  3. +0 −1 docs/conf.py
  4. +3 −17 pelican/__init__.py
  5. +0 −1 pelican/__main__.py
  6. +1 −3 pelican/cache.py
  7. +11 −21 pelican/contents.py
  8. +2 −9 pelican/generators.py
  9. +4 −49 pelican/log.py
  10. +1 −4 pelican/paginator.py
  11. +10 −14 pelican/readers.py
  12. +1 −4 pelican/rstdirectives.py
  13. +7 −10 pelican/server.py
  14. +5 −8 pelican/settings.py
  15. +0 −1 pelican/signals.py
  16. +0 −1 pelican/tests/default_conf.py
  17. +1 −3 pelican/tests/support.py
  18. +0 −1 pelican/tests/test_cache.py
  19. +8 −17 pelican/tests/test_contents.py
  20. +0 −1 pelican/tests/test_generators.py
  21. +0 −1 pelican/tests/test_importer.py
  22. +0 −1 pelican/tests/test_paginator.py
  23. +0 −1 pelican/tests/test_pelican.py
  24. +3 −8 pelican/tests/test_readers.py
  25. +0 −1 pelican/tests/test_rstdirectives.py
  26. +1 −2 pelican/tests/test_server.py
  27. +0 −1 pelican/tests/test_settings.py
  28. +0 −1 pelican/tests/test_testsuite.py
  29. +0 −1 pelican/tests/test_urlwrappers.py
  30. +2 −7 pelican/tests/test_utils.py
  31. +8 −32 pelican/tools/pelican_import.py
  32. +29 −52 pelican/tools/pelican_quickstart.py
  33. +0 −1 pelican/tools/pelican_themes.py
  34. +0 −1 pelican/tools/templates/pelicanconf.py.jinja2
  35. +0 −1 pelican/tools/templates/publishconf.py.jinja2
  36. +6 −10 pelican/urlwrappers.py
  37. +13 −38 pelican/utils.py
  38. +2 −9 pelican/writers.py
  39. +4 −116 poetry.lock
  40. +0 −1 pyproject.toml
  41. +0 −1 samples/pelican.conf.py
  42. +0 −1 samples/pelican.conf_FR.py
  43. +3 −6 setup.py
@@ -11,7 +11,6 @@ tags
.tox
.coverage
htmlcov
six-*.egg/
*.orig
venv
samples/output
1 THANKS
@@ -92,6 +92,7 @@ Joshua Adelman
Julian Berman
Justin Mayer
Kevin Deldycke
Kevin Yap
Kyle Fuller
Laureline Guerin
Leonard Huang
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import os
import sys
@@ -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 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 @@ def init_plugins(self):
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)
@@ -1,7 +1,6 @@
"""
python -m pelican module entry point to run via python -m
"""
from __future__ import absolute_import

from . import main

@@ -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

@@ -1,33 +1,27 @@
# -*- 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

logger = logging.getLogger(__name__)


@python_2_unicode_compatible
class Content(object):
"""Represents a content.
@@ -121,9 +115,6 @@ def __init__(self, content, metadata=None, settings=None,

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 @@ def __init__(self, content, metadata=None, settings=None,

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 @@ def url_format(self):
'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 @@ def __init__(self, *args, **kwargs):
# 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'
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals

import calendar
import errno
@@ -15,15 +14,12 @@
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 @@ def get_files(self, paths, exclude=[], extensions=None):
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 @@ def _generate_period_archives(dates, key, save_as_fmt, url_fmt):
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)
@@ -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 @@ def format(self, record):
# 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 @@ def filter(self, record):
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
"""
@@ -1,14 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals

import functools
import logging
import os
from collections import namedtuple
from math import ceil

import six

logger = logging.getLogger(__name__)
PaginationRule = namedtuple(
'PaginationRule',
@@ -131,7 +128,7 @@ def _from_settings(self, key):

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

0 comments on commit 1e0e541

Please sign in to comment.
You can’t perform that action at this time.