Skip to content

Commit

Permalink
Initial pass of removing Python 2 support
Browse files Browse the repository at this point in the history
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 25, 2019
1 parent ae73d06 commit 1e0e541
Show file tree
Hide file tree
Showing 43 changed files with 126 additions and 459 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ tags
.tox .tox
.coverage .coverage
htmlcov htmlcov
six-*.egg/
*.orig *.orig
venv venv
samples/output samples/output
Expand Down
1 change: 1 addition & 0 deletions THANKS
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Joshua Adelman
Julian Berman Julian Berman
Justin Mayer Justin Mayer
Kevin Deldycke Kevin Deldycke
Kevin Yap
Kyle Fuller Kyle Fuller
Laureline Guerin Laureline Guerin
Leonard Huang Leonard Huang
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals


import os import os
import sys import sys
Expand Down
20 changes: 3 additions & 17 deletions pelican/__init__.py
Original file line number Original file line Diff line number Diff line change
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals


import argparse import argparse
try: try:
import collections.abc as collections import collections.abc as collections
except ImportError: except ImportError:
import collections import collections
import locale
import logging import logging
import multiprocessing import multiprocessing
import os import os
Expand All @@ -15,8 +13,6 @@
import time import time
import traceback import traceback


import six

# pelican.log has to be the first pelican module to be loaded # pelican.log has to be the first pelican module to be loaded
# because logging.setLoggerClass has to be called before logging.getLogger # because logging.setLoggerClass has to be called before logging.getLogger
from pelican.log import init as init_logging from pelican.log import init as init_logging
Expand Down Expand Up @@ -76,11 +72,10 @@ def init_plugins(self):
sys.path.insert(0, pluginpath) sys.path.insert(0, pluginpath)
for plugin in self.settings['PLUGINS']: for plugin in self.settings['PLUGINS']:
# if it's a string, then import it # if it's a string, then import it
if isinstance(plugin, six.string_types): if isinstance(plugin, str):
logger.debug("Loading plugin `%s`", plugin) logger.debug("Loading plugin `%s`", plugin)
try: try:
plugin = __import__(plugin, globals(), locals(), plugin = __import__(plugin, globals(), locals(), 'module')
str('module'))
except ImportError as e: except ImportError as e:
logger.error( logger.error(
"Cannot load plugin `%s`\n%s", plugin, e) "Cannot load plugin `%s`\n%s", plugin, e)
Expand Down Expand Up @@ -375,15 +370,6 @@ def get_config(args):
config['BIND'] = args.bind config['BIND'] = args.bind
config['DEBUG'] = args.verbosity == logging.DEBUG 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 return config




Expand All @@ -397,7 +383,7 @@ def get_instance(args):
settings = read_settings(config_file, override=get_config(args)) settings = read_settings(config_file, override=get_config(args))


cls = settings['PELICAN_CLASS'] cls = settings['PELICAN_CLASS']
if isinstance(cls, six.string_types): if isinstance(cls, str):
module, cls_name = cls.rsplit('.', 1) module, cls_name = cls.rsplit('.', 1)
module = __import__(module) module = __import__(module)
cls = getattr(module, cls_name) cls = getattr(module, cls_name)
Expand Down
1 change: 0 additions & 1 deletion pelican/__main__.py
Original file line number Original file line Diff line number Diff line change
@@ -1,7 +1,6 @@
""" """
python -m pelican module entry point to run via python -m python -m pelican module entry point to run via python -m
""" """
from __future__ import absolute_import


from . import main from . import main


Expand Down
4 changes: 1 addition & 3 deletions pelican/cache.py
Original file line number Original file line Diff line number Diff line change
@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals


import hashlib import hashlib
import logging import logging
import os import os

import pickle
from six.moves import cPickle as pickle


from pelican.utils import mkdir_p from pelican.utils import mkdir_p


Expand Down
32 changes: 11 additions & 21 deletions pelican/contents.py
Original file line number Original file line Diff line number Diff line change
@@ -1,33 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals


import copy import copy
import datetime
import locale import locale
import logging import logging
import os import os
import re import re
import sys from urllib.parse import urljoin, urlparse, urlunparse


import pytz import pytz


import six
from six.moves.urllib.parse import urljoin, urlparse, urlunparse

from pelican import signals from pelican import signals
from pelican.settings import DEFAULT_CONFIG from pelican.settings import DEFAULT_CONFIG
from pelican.utils import (SafeDatetime, deprecated_attribute, memoized, from pelican.utils import (deprecated_attribute, memoized, path_to_url,
path_to_url, posixize_path, posixize_path, sanitised_join, set_date_tzinfo,
python_2_unicode_compatible, sanitised_join, slugify, truncate_html_words)
set_date_tzinfo, slugify, strftime,
truncate_html_words)


# Import these so that they're avalaible when you import from pelican.contents. # Import these so that they're avalaible when you import from pelican.contents.
from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)




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


if isinstance(self.date_format, tuple): if isinstance(self.date_format, tuple):
locale_string = self.date_format[0] 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) locale.setlocale(locale.LC_ALL, locale_string)
self.date_format = self.date_format[1] self.date_format = self.date_format[1]


Expand All @@ -133,11 +124,11 @@ def __init__(self, content, metadata=None, settings=None,


if hasattr(self, 'date'): if hasattr(self, 'date'):
self.date = set_date_tzinfo(self.date, timezone) 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'): if hasattr(self, 'modified'):
self.modified = set_date_tzinfo(self.modified, timezone) 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 # manage status
if not hasattr(self, 'status'): if not hasattr(self, 'status'):
Expand Down Expand Up @@ -213,7 +204,7 @@ def url_format(self):
'path': path_to_url(path), 'path': path_to_url(path),
'slug': getattr(self, 'slug', ''), 'slug': getattr(self, 'slug', ''),
'lang': getattr(self, 'lang', 'en'), '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 '', 'author': self.author.slug if hasattr(self, 'author') else '',
'category': self.category.slug if hasattr(self, 'category') else '' 'category': self.category.slug if hasattr(self, 'category') else ''
}) })
Expand Down Expand Up @@ -512,22 +503,21 @@ def __init__(self, *args, **kwargs):
# handle WITH_FUTURE_DATES (designate article to draft based on date) # handle WITH_FUTURE_DATES (designate article to draft based on date)
if not self.settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'): if not self.settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'):
if self.date.tzinfo is None: if self.date.tzinfo is None:
now = SafeDatetime.now() now = datetime.datetime.now()
else: else:
now = SafeDatetime.utcnow().replace(tzinfo=pytz.utc) now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
if self.date > now: if self.date > now:
self.status = 'draft' self.status = 'draft'


# if we are a draft and there is no date provided, set max datetime # if we are a draft and there is no date provided, set max datetime
if not hasattr(self, 'date') and self.status == 'draft': if not hasattr(self, 'date') and self.status == 'draft':
self.date = SafeDatetime.max self.date = datetime.datetime.max


def _expand_settings(self, key): def _expand_settings(self, key):
klass = 'draft' if self.status == 'draft' else 'article' klass = 'draft' if self.status == 'draft' else 'article'
return super(Article, self)._expand_settings(key, klass) return super(Article, self)._expand_settings(key, klass)




@python_2_unicode_compatible
class Static(Content): class Static(Content):
mandatory_properties = ('title',) mandatory_properties = ('title',)
default_status = 'published' default_status = 'published'
Expand Down
11 changes: 2 additions & 9 deletions pelican/generators.py
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals


import calendar import calendar
import errno import errno
Expand All @@ -15,15 +14,12 @@
from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader, from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader,
PrefixLoader, TemplateNotFound) PrefixLoader, TemplateNotFound)


import six

from pelican import signals from pelican import signals
from pelican.cache import FileStampDataCacher from pelican.cache import FileStampDataCacher
from pelican.contents import Article, Page, Static from pelican.contents import Article, Page, Static
from pelican.readers import Readers from pelican.readers import Readers
from pelican.utils import (DateFormatter, copy, mkdir_p, order_content, from pelican.utils import (DateFormatter, copy, mkdir_p, order_content,
posixize_path, process_translations, posixize_path, process_translations)
python_2_unicode_compatible)




logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
Expand All @@ -33,7 +29,6 @@ class PelicanTemplateNotFound(Exception):
pass pass




@python_2_unicode_compatible
class Generator(object): class Generator(object):
"""Baseclass generator""" """Baseclass generator"""


Expand Down Expand Up @@ -138,7 +133,7 @@ def get_files(self, paths, exclude=[], extensions=None):
extensions are allowed) extensions are allowed)
""" """
# backward compatibility for older generators # backward compatibility for older generators
if isinstance(paths, six.string_types): if isinstance(paths, str):
paths = [paths] paths = [paths]


# group the exclude dir names by parent path, for use with os.walk() # group the exclude dir names by parent path, for use with os.walk()
Expand Down Expand Up @@ -513,8 +508,6 @@ def _generate_period_archives(dates, key, save_as_fmt, url_fmt):
context["period"] = (_period,) context["period"] = (_period,)
else: else:
month_name = calendar.month_name[_period[1]] month_name = calendar.month_name[_period[1]]
if not six.PY3:
month_name = month_name.decode('utf-8')
if key == period_date_key['month']: if key == period_date_key['month']:
context["period"] = (_period[0], context["period"] = (_period[0],
month_name) month_name)
Expand Down
53 changes: 4 additions & 49 deletions pelican/log.py
Original file line number Original file line Diff line number Diff line change
@@ -1,17 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals


import locale
import logging import logging
import os import os
import sys import sys
from collections import defaultdict from collections import defaultdict
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping

import six


__all__ = [ __all__ = [
'init' 'init'
Expand All @@ -29,20 +21,17 @@ def format(self, record):
# format multiline messages 'nicely' to make it clear they are together # format multiline messages 'nicely' to make it clear they are together
record.msg = record.msg.replace('\n', '\n | ') record.msg = record.msg.replace('\n', '\n | ')
record.args = tuple(arg.replace('\n', '\n | ') if record.args = tuple(arg.replace('\n', '\n | ') if
isinstance(arg, six.string_types) else isinstance(arg, str) else
arg for arg in record.args) arg for arg in record.args)
return super(BaseFormatter, self).format(record) return super(BaseFormatter, self).format(record)


def formatException(self, ei): def formatException(self, ei):
''' prefix traceback info for better representation ''' ''' 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) s = super(BaseFormatter, self).formatException(ei)
# fancy format traceback # 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 # separate the traceback from the preceding lines
s = str(' |___\n{}').format(s) s = ' |___\n{}'.format(s)
return s return s


def _get_levelname(self, name): def _get_levelname(self, name):
Expand Down Expand Up @@ -140,41 +129,7 @@ def filter(self, record):
return True return True




class SafeLogger(logging.Logger): class LimitLogger(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):
""" """
A logger which adds LimitFilter automatically A logger which adds LimitFilter automatically
""" """
Expand Down
5 changes: 1 addition & 4 deletions pelican/paginator.py
Original file line number Original file line Diff line number Diff line change
@@ -1,14 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals


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


import six

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


prop_value = getattr(rule, 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) logger.warning('%s is set to %s', key, prop_value)
return prop_value return prop_value


Expand Down
Loading

0 comments on commit 1e0e541

Please sign in to comment.