changeset 5:474c9882decf

Upgrade to Python 3.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 11 Aug 2014 22:36:47 -0700
parents 7dc71c2dc9a8
children f5ca5c5bed85
files piecrust/app.py piecrust/baking/baker.py piecrust/baking/records.py piecrust/cache.py piecrust/commands/builtin/info.py piecrust/commands/builtin/util.py piecrust/configuration.py piecrust/data/assetor.py piecrust/data/base.py piecrust/data/builder.py piecrust/data/debug.py piecrust/data/filters.py piecrust/data/paginator.py piecrust/data/provider.py piecrust/page.py piecrust/processing/base.py piecrust/processing/less.py piecrust/processing/tree.py piecrust/records.py piecrust/rendering.py piecrust/serving.py piecrust/sources/base.py piecrust/sources/posts.py piecrust/templating/jinjaengine.py piecrust/uriutil.py tests/test_page.py tests/test_uriutil.py
diffstat 27 files changed, 103 insertions(+), 105 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/app.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/app.py	Mon Aug 11 22:36:47 2014 -0700
@@ -58,7 +58,7 @@
             self._values = self._validateAll({})
             return
 
-        path_times = map(lambda p: os.path.getmtime(p), self.paths)
+        path_times = [os.path.getmtime(p) for p in self.paths]
         cache_key = hashlib.md5("version=%s&cache=%d" % (
                 APP_VERSION, CACHE_VERSION)).hexdigest()
 
@@ -122,7 +122,7 @@
         sitec = values.get('site')
         if sitec is None:
             sitec = {}
-        for key, val in default_sitec.iteritems():
+        for key, val in default_sitec.items():
             sitec.setdefault(key, val)
         values['site'] = sitec
 
@@ -135,7 +135,7 @@
             raise ConfigurationError("The 'site/auto_formats' setting must be a dictionary.")
         cachec['auto_formats_re'] = r"\.(%s)$" % (
                 '|'.join(
-                        map(lambda i: re.escape(i), sitec['auto_formats'].keys())))
+                        [re.escape(i) for i in list(sitec['auto_formats'].keys())]))
         if sitec['default_auto_format'] not in sitec['auto_formats']:
             raise ConfigurationError("Default auto-format '%s' is not declared." % sitec['default_auto_format'])
 
@@ -254,7 +254,7 @@
         # Add the theme page source if no sources were defined in the theme
         # configuration itself.
         has_any_theme_source = False
-        for sn, sc in sourcesc.iteritems():
+        for sn, sc in sourcesc.items():
             if sc.get('realm') == REALM_THEME:
                 has_any_theme_source = True
                 break
@@ -272,7 +272,7 @@
 
         # Sources have the `default` scanner by default, duh. Also, a bunch
         # of other default values for other configuration stuff.
-        for sn, sc in sourcesc.iteritems():
+        for sn, sc in sourcesc.items():
             if not isinstance(sc, dict):
                 raise ConfigurationError("All sources in 'site/sources' must be dictionaries.")
             sc.setdefault('type', 'default')
@@ -296,7 +296,7 @@
                 raise ConfigurationError("Route URLs must start with '/'.")
             if rc.get('source') is None:
                 raise ConfigurationError("Routes must specify a source.")
-            if rc['source'] not in sourcesc.keys():
+            if rc['source'] not in list(sourcesc.keys()):
                 raise ConfigurationError("Route is referencing unknown source: %s" %
                         rc['source'])
             rc.setdefault('taxonomy', None)
@@ -305,7 +305,7 @@
         # Validate taxonomies.
         sitec.setdefault('taxonomies', {})
         taxonomiesc = sitec.get('taxonomies')
-        for tn, tc in taxonomiesc.iteritems():
+        for tn, tc in taxonomiesc.items():
             tc.setdefault('multiple', False)
             tc.setdefault('term', tn)
             tc.setdefault('page', '_%s.%%ext%%' % tc['term'])
@@ -314,7 +314,7 @@
         reserved_endpoints = set(['piecrust', 'site', 'page', 'route',
                                   'assets', 'pagination', 'siblings',
                                   'family'])
-        for name, src in sitec['sources'].iteritems():
+        for name, src in sitec['sources'].items():
             endpoint = src['data_endpoint']
             if endpoint in reserved_endpoints:
                 raise ConfigurationError(
@@ -366,10 +366,10 @@
                 tplc = sitec.get('templates_dirs')
                 if tplc is None:
                     return
-                if isinstance(tplc, types.StringTypes):
+                if isinstance(tplc, str):
                     tplc = [tplc]
-                sitec['templates_dirs'] = filter(tplc,
-                        lambda p: os.path.join(self.theme_dir, p))
+                sitec['templates_dirs'] = list(filter(tplc,
+                        lambda p: os.path.join(self.theme_dir, p)))
             config.fixups.append(_fixupThemeTemplatesDir)
 
             # We'll also need to flag all page sources as coming from
@@ -383,7 +383,7 @@
                     config['site'] = sitec
                 srcc = sitec.get('sources')
                 if srcc is not None:
-                    for sn, sc in srcc.iteritems():
+                    for sn, sc in srcc.items():
                         sc['realm'] = REALM_THEME
             config.fixups.append(_fixupThemeSources)
 
@@ -425,7 +425,7 @@
             defs[cls.SOURCE_NAME] = cls
 
         sources = []
-        for n, s in self.config.get('site/sources').iteritems():
+        for n, s in self.config.get('site/sources').items():
             cls = defs.get(s['type'])
             if cls is None:
                 raise ConfigurationError("No such page source type: %s" % s['type'])
@@ -444,7 +444,7 @@
     @cached_property
     def taxonomies(self):
         taxonomies = []
-        for tn, tc in self.config.get('site/taxonomies').iteritems():
+        for tn, tc in self.config.get('site/taxonomies').items():
             tax = Taxonomy(self, tn, tc)
             taxonomies.append(tax)
         return taxonomies
@@ -491,11 +491,10 @@
         # Add custom directories from the configuration.
         conf_dirs = self.config.get(conf_name)
         if conf_dirs is not None:
-            if isinstance(conf_dirs, types.StringTypes):
+            if isinstance(conf_dirs, str):
                 dirs.append(os.path.join(self.root_dir, conf_dirs))
             else:
-                dirs += filter(lambda p: os.path.join(self.root_dir, p),
-                        conf_dirs)
+                dirs += [p for p in conf_dirs if os.path.join(self.root_dir, p)]
 
         # Add the default directory if it exists.
         default_dir = os.path.join(self.root_dir, default_rel_dir)
--- a/piecrust/baking/baker.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/baking/baker.py	Mon Aug 11 22:36:47 2014 -0700
@@ -1,11 +1,11 @@
 import time
 import os.path
 import codecs
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 import hashlib
 import logging
 import threading
-from Queue import Queue, Empty
+from queue import Queue, Empty
 from piecrust.baking.records import TransitionalBakeRecord, BakeRecordPageEntry
 from piecrust.chefutil import format_timed
 from piecrust.data.filters import (PaginationFilter, HasFilterClause,
@@ -61,7 +61,7 @@
 
     def getOutputPath(self, uri):
         bake_path = [self.out_dir]
-        decoded_uri = urllib2.unquote(uri.lstrip('/')).decode('utf8')
+        decoded_uri = urllib.parse.unquote(uri.lstrip('/')).decode('utf8')
         if self.pretty_urls:
             bake_path.append(decoded_uri)
             bake_path.append('index.html')
@@ -188,7 +188,7 @@
 
         out_dir = os.path.dirname(out_path)
         if not os.path.isdir(out_dir):
-            os.makedirs(out_dir, 0755)
+            os.makedirs(out_dir, 0o755)
 
         with codecs.open(out_path, 'w', 'utf-8') as fp:
             fp.write(rp.content.decode('utf-8'))
@@ -228,7 +228,7 @@
 
         # Make sure the output directory exists.
         if not os.path.isdir(self.out_dir):
-            os.makedirs(self.out_dir, 0755)
+            os.makedirs(self.out_dir, 0o755)
 
         # Load/create the bake record.
         record = TransitionalBakeRecord()
@@ -313,7 +313,7 @@
 
         # Now see which ones are 'dirty' based on our bake record.
         logger.debug("Gathering dirty taxonomy terms")
-        for prev_entry, cur_entry in record.transitions.itervalues():
+        for prev_entry, cur_entry in record.transitions.values():
             for tax in self.app.taxonomies:
                 changed_terms = None
                 # Re-bake all taxonomy pages that include new or changed
@@ -343,7 +343,7 @@
         # Re-bake the combination pages for terms that are 'dirty'.
         known_combinations = set()
         logger.debug("Gathering dirty term combinations")
-        for prev_entry, cur_entry in record.transitions.itervalues():
+        for prev_entry, cur_entry in record.transitions.values():
             if cur_entry:
                 known_combinations |= cur_entry.used_taxonomy_terms
             elif prev_entry:
@@ -355,8 +355,8 @@
 
         # Start baking those terms.
         pool, queue, abort = self._createWorkerPool(record, self.num_workers)
-        for source_name, source_taxonomies in buckets.iteritems():
-            for tax_name, terms in source_taxonomies.iteritems():
+        for source_name, source_taxonomies in buckets.items():
+            for tax_name, terms in source_taxonomies.items():
                 if len(terms) == 0:
                     continue
 
--- a/piecrust/baking/records.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/baking/records.py	Mon Aug 11 22:36:47 2014 -0700
@@ -84,7 +84,7 @@
         return None
 
     def collapseRecords(self):
-        for pair in self.transitions.itervalues():
+        for pair in self.transitions.values():
             prev = pair[0]
             cur = pair[1]
 
--- a/piecrust/cache.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/cache.py	Mon Aug 11 22:36:47 2014 -0700
@@ -26,7 +26,7 @@
                 if c is None:
                     c_dir = os.path.join(self.base_dir, name)
                     if not os.path.isdir(c_dir):
-                        os.makedirs(c_dir, 0755)
+                        os.makedirs(c_dir, 0o755)
 
                     c = SimpleCache(c_dir)
                     self.caches[name] = c
@@ -71,7 +71,7 @@
         cache_path = self.getCachePath(path)
         cache_dir = os.path.dirname(cache_path)
         if not os.path.isdir(cache_dir):
-            os.makedirs(cache_dir, 0755)
+            os.makedirs(cache_dir, 0o755)
         with codecs.open(cache_path, 'w', 'utf-8') as fp:
             fp.write(content)
 
--- a/piecrust/commands/builtin/info.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/commands/builtin/info.py	Mon Aug 11 22:36:47 2014 -0700
@@ -106,7 +106,7 @@
         sources = list(ctx.app.sources)
         if ctx.args.endpoint:
             endpoints = ctx.args.endpoint
-            sources = filter(lambda s: s.endpoint in endpoints, sources)
+            sources = [s for s in sources if s.endpoint in endpoints]
         for src in sources:
             page_facs = src.getPageFactories()
             for pf in page_facs:
@@ -116,7 +116,7 @@
                         name = pf.path
                     if ctx.args.metadata:
                         logger.info("path:%s" % pf.path)
-                        for key, val in pf.metadata.iteritems():
+                        for key, val in pf.metadata.items():
                             logger.info("%s:%s" % (key, val))
                         logger.info("---")
                     else:
--- a/piecrust/commands/builtin/util.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/commands/builtin/util.py	Mon Aug 11 22:36:47 2014 -0700
@@ -29,11 +29,11 @@
             destination = os.getcwd()
 
         if not os.path.isdir(destination):
-            os.makedirs(destination, 0755)
+            os.makedirs(destination, 0o755)
 
         config_path = os.path.join(destination, CONFIG_PATH)
         if not os.path.isdir(os.path.dirname(config_path)):
-            os.makedirs(os.path.dirname(config_path), 0755)
+            os.makedirs(os.path.dirname(config_path), 0o755)
 
         config_text = yaml.dump({
                 'site': {
@@ -96,7 +96,7 @@
 
         logger.info("Creating page: %s" % os.path.relpath(page_path, app.root_dir))
         if not os.path.exists(os.path.dirname(page_path)):
-            os.makedirs(os.path.dirname(page_path), 0755)
+            os.makedirs(os.path.dirname(page_path), 0o755)
         with open(page_path, 'w') as f:
             f.write('---\n')
             f.write('title: %s\n' % 'Unknown title')
--- a/piecrust/configuration.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/configuration.py	Mon Aug 11 22:36:47 2014 -0700
@@ -89,7 +89,7 @@
 
 
 def _recurse_merge_dicts(local_cur, incoming_cur, parent_path, validator):
-    for k, v in incoming_cur.iteritems():
+    for k, v in incoming_cur.items():
         key_path = k
         if parent_path is not None:
             key_path = parent_path + '/' + k
@@ -113,7 +113,7 @@
 def parse_config_header(text):
     m = header_regex.match(text)
     if m is not None:
-        header = unicode(m.group('header'))
+        header = str(m.group('header'))
         config = yaml.load(header, Loader=yaml.BaseLoader)
         offset = m.end()
     else:
--- a/piecrust/data/assetor.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/assetor.py	Mon Aug 11 22:36:47 2014 -0700
@@ -55,7 +55,7 @@
 
     def _debugRenderAssetNames(self):
         self._cacheAssets()
-        return self._cache.keys()
+        return list(self._cache.keys())
 
     def _cacheAssets(self):
         if self._cache is not None:
--- a/piecrust/data/base.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/base.py	Mon Aug 11 22:36:47 2014 -0700
@@ -96,7 +96,7 @@
         logger.exception("Error rendering segments for '%s': %s" % (uri, e))
         raise
 
-    for k, v in segs.iteritems():
+    for k, v in segs.items():
         data.mapLoader(k, None)
         data.setValue(k, v)
 
--- a/piecrust/data/builder.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/builder.py	Mon Aug 11 22:36:47 2014 -0700
@@ -59,7 +59,7 @@
 
 def build_layout_data(page, page_data, contents):
     data = dict(page_data)
-    for name, txt in contents.iteritems():
+    for name, txt in contents.items():
         if name in data:
             logger.warning("Content segment '%s' will hide existing data." %
                     name)
--- a/piecrust/data/debug.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/debug.py	Mon Aug 11 22:36:47 2014 -0700
@@ -1,8 +1,9 @@
 import re
 import cgi
 import logging
-import StringIO
+import io
 from piecrust import APP_VERSION, PIECRUST_URL
+import collections
 
 
 logger = logging.getLogger(__name__)
@@ -49,7 +50,7 @@
 def build_debug_info(page, data):
     """ Generates HTML debug info for the given page's data.
     """
-    output = StringIO.StringIO()
+    output = io.StringIO()
     try:
         _do_build_debug_info(page, data, output)
         return output.getvalue()
@@ -61,10 +62,10 @@
     app = page.app
     exec_info = app.env.exec_info_stack.current_page_info
 
-    print >>output, '<div id="piecrust-debug-info" style="%s">' % CSS_DEBUGINFO
+    print('<div id="piecrust-debug-info" style="%s">' % CSS_DEBUGINFO, file=output)
 
-    print >>output, '<div>'
-    print >>output, '<p style="%s"><strong>PieCrust %s</strong> &mdash; ' % (CSS_P, APP_VERSION)
+    print('<div>', file=output)
+    print('<p style="%s"><strong>PieCrust %s</strong> &mdash; ' % (CSS_P, APP_VERSION), file=output)
 
     # If we have some execution info in the environment,
     # add more information.
@@ -91,25 +92,25 @@
     else:
         output.write('no timing information available')
 
-    print >>output, '</p>'
-    print >>output, '</div>'
+    print('</p>', file=output)
+    print('</div>', file=output)
 
     if data:
-        print >>output, '<div>'
-        print >>output, ('<p style="%s cursor: pointer;" onclick="var l = '
+        print('<div>', file=output)
+        print(('<p style="%s cursor: pointer;" onclick="var l = '
                          'document.getElementById(\'piecrust-debug-details\'); '
                          'if (l.style.display == \'none\') l.style.display = '
-                         '\'block\'; else l.style.display = \'none\';">' % CSS_P)
-        print >>output, ('<span style="%s">Template engine data</span> '
-                         '&mdash; click to toggle</a>.</p>' % CSS_BIGHEADER)
+                         '\'block\'; else l.style.display = \'none\';">' % CSS_P), file=output)
+        print(('<span style="%s">Template engine data</span> '
+                         '&mdash; click to toggle</a>.</p>' % CSS_BIGHEADER), file=output)
 
-        print >>output, '<div id="piecrust-debug-details" style="display: none;">'
-        print >>output, ('<p style="%s">The following key/value pairs are '
+        print('<div id="piecrust-debug-details" style="display: none;">', file=output)
+        print(('<p style="%s">The following key/value pairs are '
                          'available in the layout\'s markup, and most are '
-                         'available in the page\'s markup.</p>' % CSS_DOC)
+                         'available in the page\'s markup.</p>' % CSS_DOC), file=output)
 
         filtered_data = dict(data)
-        for k in filtered_data.keys():
+        for k in list(filtered_data.keys()):
             if k.startswith('__'):
                 del filtered_data[k]
 
@@ -120,10 +121,10 @@
                 "This section comes from the page's configuration header.")
         renderer.renderData(filtered_data)
 
-        print >>output, '</div>'
-        print >>output, '</div>'
+        print('</div>', file=output)
+        print('</div>', file=output)
 
-    print >>output, '</div>'
+    print('</div>', file=output)
 
 
 class DebugDataRenderer(object):
@@ -174,7 +175,7 @@
             self._write('<span style="%s">%4.2f</span>' % (CSS_VALUE, data))
             return
 
-        if data_type in (str, unicode):
+        if data_type in (str, str):
             if data_type == str:
                 data = data.decode('utf8')
             if len(data) > DebugDataRenderer.MAX_VALUE_LENGTH:
@@ -203,7 +204,7 @@
         self._renderDoc(data, path)
         self._renderAttributes(data, path)
         rendered_count = self._renderIterable(data, path,
-                lambda d: sorted(d.iteritems(), key=lambda i: i[0]))
+                lambda d: sorted(iter(d.items()), key=lambda i: i[0]))
         if rendered_count == 0:
             self._writeLine('<p style="%s %s">(empty dictionary)</p>' % (CSS_P, CSS_DOC))
         self._writeLine('</div>')
@@ -289,7 +290,7 @@
                 # dynamic attribute.
                 attr = getattr(data, name)
 
-            if callable(attr):
+            if isinstance(attr, collections.Callable):
                 attr_func = getattr(data, name)
                 argcount = attr_func.__code__.co_argcount
                 var_names = attr_func.__code__.co_varnames
--- a/piecrust/data/filters.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/filters.py	Mon Aug 11 22:36:47 2014 -0700
@@ -30,7 +30,7 @@
             self.root_clause = AndBooleanClause()
 
     def _addClausesFromConfigRecursive(self, config, parent_clause):
-        for key, val in config.iteritems():
+        for key, val in config.items():
             if key == 'and':
                 if not isinstance(val, list) or len(val) == 0:
                     raise Exception("The given boolean 'AND' filter clause "
@@ -145,7 +145,7 @@
             return False
 
         if self.coercer:
-            actual_value = map(self.coercer, actual_value)
+            actual_value = list(map(self.coercer, actual_value))
 
         return self.value in actual_value
 
--- a/piecrust/data/paginator.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/paginator.py	Mon Aug 11 22:36:47 2014 -0700
@@ -152,7 +152,7 @@
             return []
 
         if radius <= 0 or total_page_count < (2 * radius + 1):
-            return range(1, total_page_count)
+            return list(range(1, total_page_count))
 
         first_num = self._page_num - radius
         last_num = self._page_num + radius
@@ -164,7 +164,7 @@
             last_num = total_page_count
         first_num = max(1, first_num)
         last_num = min(total_page_count, last_num)
-        return range(first_num, last_num)
+        return list(range(first_num, last_num))
 
     def page(self, index):
         return self._getPageUri(index)
--- a/piecrust/data/provider.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/data/provider.py	Mon Aug 11 22:36:47 2014 -0700
@@ -28,7 +28,7 @@
 
     def _debugRenderUserData(self):
         if self._user_data:
-            return self._user_data.keys()
+            return list(self._user_data.keys())
         return []
 
 
@@ -121,7 +121,7 @@
             year = post.datetime.strftime('%Y')
 
             posts_this_year = next(
-                    itertools.ifilter(lambda y: y.name == year, self._yearly),
+                    filter(lambda y: y.name == year, self._yearly),
                     None)
             if posts_this_year is None:
                 timestamp = time.mktime(
@@ -146,7 +146,7 @@
             month = post.datetime.strftime('%B %Y')
 
             posts_this_month = next(
-                    itertools.ifilter(lambda m: m.name == month, self._monthly),
+                    filter(lambda m: m.name == month, self._monthly),
                     None)
             if posts_this_month is None:
                 timestamp = time.mktime(
@@ -177,7 +177,7 @@
                 posts_by_tax_value[val].append(post)
 
         entries = []
-        for value, ds in posts_by_tax_value.iteritems():
+        for value, ds in posts_by_tax_value.items():
             source = ArraySource(self._page.app, ds)
             entries.append(BlogTaxonomyEntry(self._page, source, value))
         self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name)
--- a/piecrust/page.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/page.py	Mon Aug 11 22:36:47 2014 -0700
@@ -135,7 +135,7 @@
 
 def json_load_segments(data):
     segments = {}
-    for key, seg_data in data.iteritems():
+    for key, seg_data in data.items():
         seg = ContentSegment()
         for p_data in seg_data:
             part = ContentSegmentPart(p_data['c'], p_data['f'], p_data['l'])
@@ -146,7 +146,7 @@
 
 def json_save_segments(segments):
     data = {}
-    for key, seg in segments.iteritems():
+    for key, seg in segments.items():
         seg_data = []
         for part in seg.parts:
             p_data = {'c': part.content, 'f': part.fmt, 'l': part.line}
@@ -162,7 +162,7 @@
         logger.exception("Error loading page: %s" %
                 os.path.relpath(path, app.root_dir))
         _, __, traceback = sys.exc_info()
-        raise PageLoadingError(path, e), None, traceback
+        raise PageLoadingError(path, e).with_traceback(traceback)
 
 
 def _do_load_page(app, path):
@@ -197,7 +197,7 @@
 
     config = PageConfiguration(header)
     content = parse_segments(raw, offset)
-    config.set('segments', list(content.iterkeys()))
+    config.set('segments', list(content.keys()))
 
     # Save to the cache.
     cache_data = {
--- a/piecrust/processing/base.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/processing/base.py	Mon Aug 11 22:36:47 2014 -0700
@@ -4,7 +4,7 @@
 import os.path
 import logging
 import threading
-from Queue import Queue, Empty
+from queue import Queue, Empty
 from piecrust.chefutil import format_timed
 from piecrust.processing.tree import (ProcessingTreeBuilder,
         ProcessingTreeRunner, STATE_DIRTY, print_node)
@@ -183,7 +183,7 @@
             # Process only the given path.
             # Find out if this source directory is in a mount point.
             base_dir = self.app.root_dir
-            for name, path in self.mounts.iteritems():
+            for name, path in self.mounts.items():
                 if src_dir_or_file[:len(path)] == path:
                     base_dir = path
 
@@ -200,7 +200,7 @@
             logger.debug("Initiating processing pipeline on: %s" % self.app.root_dir)
             self.processDirectory(ctx, self.app.root_dir)
             ctx.is_multi_mount = True
-            for name, path in self.mounts.iteritems():
+            for name, path in self.mounts.items():
                 mount_ctx = ProcessingContext(path, queue, record)
                 logger.debug("Initiating processing pipeline on: %s" % path)
                 self.processDirectory(mount_ctx, path)
@@ -325,7 +325,7 @@
                     .replace(r'\*', r'[^/\\]*')
                     .replace(r'\?', r'[^/\\]'))
             re_patterns.append(escaped_pat)
-    return map(lambda p: re.compile(p), re_patterns)
+    return [re.compile(p) for p in re_patterns]
 
 
 def re_matchany(filename, patterns):
--- a/piecrust/processing/less.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/processing/less.py	Mon Aug 11 22:36:47 2014 -0700
@@ -35,7 +35,7 @@
             path_dir = os.path.dirname(path)
             def _makeAbs(p):
                 return os.path.join(path_dir, p)
-            return map(_makeAbs, source[:-1])
+            return list(map(_makeAbs, source[:-1]))
         except IOError:
             # Map file not found... rebuild.
             logger.debug("No map file found for LESS file '%s' at '%s'. "
--- a/piecrust/processing/tree.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/processing/tree.py	Mon Aug 11 22:36:47 2014 -0700
@@ -150,7 +150,7 @@
             except Exception as e:
                 import sys
                 _, __, traceback = sys.exc_info()
-                raise Exception("Error processing: %s" % node.path, e), None, traceback
+                raise Exception("Error processing: %s" % node.path, e).with_traceback(traceback)
 
         # All outputs of a node must go to the same directory, so we can get
         # the output directory off of the first output.
@@ -161,9 +161,9 @@
             if self.lock:
                 with self.lock:
                     if not os.path.isdir(out_dir):
-                        os.makedirs(out_dir, 0755)
+                        os.makedirs(out_dir, 0o755)
             else:
-                os.makedirs(out_dir, 0755)
+                os.makedirs(out_dir, 0o755)
 
         try:
             start_time = time.clock()
@@ -180,7 +180,7 @@
         except Exception as e:
             import sys
             _, __, traceback = sys.exc_info()
-            raise Exception("Error processing: %s" % node.path, e), None, traceback
+            raise Exception("Error processing: %s" % node.path, e).with_traceback(traceback)
 
     def _computeNodeState(self, node):
         if node.state != STATE_UNKNOWN:
--- a/piecrust/records.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/records.py	Mon Aug 11 22:36:47 2014 -0700
@@ -5,7 +5,7 @@
 from piecrust.events import Event
 
 try:
-    import cPickle as pickle
+    import pickle as pickle
 except ImportError:
     import pickle
 
@@ -33,7 +33,7 @@
     def save(self, path):
         path_dir = os.path.dirname(path)
         if not os.path.isdir(path_dir):
-            os.makedirs(path_dir, 0755)
+            os.makedirs(path_dir, 0o755)
 
         with open(path, 'w') as fp:
             pickle.dump(self, fp, pickle.HIGHEST_PROTOCOL)
--- a/piecrust/rendering.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/rendering.py	Mon Aug 11 22:36:47 2014 -0700
@@ -136,8 +136,8 @@
         raise PageRenderingError("Can't find template engine '%s'." % engine_name)
 
     formatted_content = {}
-    for seg_name, seg in page.raw_content.iteritems():
-        seg_text = u''
+    for seg_name, seg in page.raw_content.items():
+        seg_text = ''
         for seg_part in seg.parts:
             part_format = seg_part.fmt or format_name
             part_text = engine.renderString(seg_part.content, page_data,
--- a/piecrust/serving.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/serving.py	Mon Aug 11 22:36:47 2014 -0700
@@ -4,7 +4,7 @@
 import os.path
 import hashlib
 import logging
-import StringIO
+import io
 from werkzeug.exceptions import (NotFound, MethodNotAllowed,
         InternalServerError)
 from werkzeug.serving import run_simple
@@ -237,7 +237,7 @@
         if ('gzip' in request.accept_encodings and
                 app.config.get('site/enable_gzip')):
             try:
-                gzip_buffer = StringIO.StringIO()
+                gzip_buffer = io.StringIO()
                 gzip_file = gzip.GzipFile(
                         mode='wb',
                         compresslevel=9,
--- a/piecrust/sources/base.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/sources/base.py	Mon Aug 11 22:36:47 2014 -0700
@@ -103,7 +103,7 @@
         self._page_ref = page_ref
         self._paths = None
         self._first_valid_path_index = -2
-        self._exts = app.config.get('site/auto_formats').keys()
+        self._exts = list(app.config.get('site/auto_formats').keys())
 
     @property
     def exists(self):
@@ -266,7 +266,7 @@
         super(SimplePageSource, self).__init__(app, name, config)
         self.fs_endpoint = config.get('fs_endpoint', name)
         self.fs_endpoint_path = os.path.join(self.root_dir, CONTENT_DIR, self.fs_endpoint)
-        self.supported_extensions = app.config.get('site/auto_formats').keys()
+        self.supported_extensions = list(app.config.get('site/auto_formats').keys())
 
     def buildPageFactories(self):
         logger.debug("Scanning for pages in: %s" % self.fs_endpoint_path)
@@ -275,7 +275,7 @@
 
         for dirpath, dirnames, filenames in os.walk(self.fs_endpoint_path):
             rel_dirpath = os.path.relpath(dirpath, self.fs_endpoint_path)
-            dirnames[:] = filter(self._filterPageDirname, dirnames)
+            dirnames[:] = list(filter(self._filterPageDirname, dirnames))
             for f in filter(self._filterPageFilename, filenames):
                 slug, ext = os.path.splitext(os.path.join(rel_dirpath, f))
                 if slug.startswith('./') or slug.startswith('.\\'):
--- a/piecrust/sources/posts.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/sources/posts.py	Mon Aug 11 22:36:47 2014 -0700
@@ -20,7 +20,7 @@
         super(PostsSource, self).__init__(app, name, config)
         self.fs_endpoint = config.get('fs_endpoint', name)
         self.fs_endpoint_path = os.path.join(self.root_dir, CONTENT_DIR, self.fs_endpoint)
-        self.supported_extensions = app.config.get('site/auto_formats').keys()
+        self.supported_extensions = list(app.config.get('site/auto_formats').keys())
 
     @property
     def path_format(self):
@@ -105,9 +105,7 @@
         today = datetime.date.today()
         year, month, day = today.year, today.month, today.day
         if args.date:
-            year, month, day = filter(
-                    lambda s: int(s),
-                    args.date.split('/'))
+            year, month, day = [s for s in args.date.split('/') if int(s)]
         return {'year': year, 'month': month, 'day': day, 'slug': args.slug}
 
     def _checkFsEndpointPath(self):
@@ -163,7 +161,7 @@
         year_pattern = re.compile(r'(\d{4})$')
         file_pattern = re.compile(r'(\d{2})-(\d{2})_(.*)\.(\w+)$')
         _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
-        year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
+        year_dirs = [d for d in year_dirs if year_pattern.match(d)]
         for yd in year_dirs:
             if year_pattern.match(yd) is None:
                 logger.warning("'%s' is not formatted as 'YYYY' and will be ignored. "
@@ -201,13 +199,13 @@
         month_pattern = re.compile(r'(\d{2})$')
         file_pattern = re.compile(r'(\d{2})_(.*)\.(\w+)$')
         _, year_dirs, __ = next(os.walk(self.fs_endpoint_path))
-        year_dirs = filter(lambda d: year_pattern.match(d), year_dirs)
+        year_dirs = [d for d in year_dirs if year_pattern.match(d)]
         for yd in year_dirs:
             year = int(yd)
             year_dir = os.path.join(self.fs_endpoint_path, yd)
 
             _, month_dirs, __ = next(os.walk(year_dir))
-            month_dirs = filter(lambda d: month_pattern.match(d), month_dirs)
+            month_dirs = [d for d in month_dirs if month_pattern.match(d)]
             for md in month_dirs:
                 month = int(md)
                 month_dir = os.path.join(year_dir, md)
--- a/piecrust/templating/jinjaengine.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/templating/jinjaengine.py	Mon Aug 11 22:36:47 2014 -0700
@@ -36,7 +36,7 @@
                 tse.filename = filename
             import sys
             _, __, traceback = sys.exc_info()
-            raise tse, None, traceback
+            raise tse.with_traceback(traceback)
 
     def renderFile(self, paths, data):
         self._ensureLoaded()
--- a/piecrust/uriutil.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/piecrust/uriutil.py	Mon Aug 11 22:36:47 2014 -0700
@@ -49,7 +49,7 @@
 
     uri = '/' + uri.strip('/')
 
-    for rn, rc in routes.iteritems():
+    for rn, rc in routes.items():
         pattern = route_to_pattern(rn)
         m = re.match(pattern, uri)
         if m is not None:
@@ -68,7 +68,7 @@
 
 
 def multi_replace(text, replacements):
-    reps = dict((re.escape(k), v) for k, v in replacements.iteritems())
-    pattern = re.compile("|".join(reps.keys()))
+    reps = dict((re.escape(k), v) for k, v in replacements.items())
+    pattern = re.compile("|".join(list(reps.keys())))
     return pattern.sub(lambda m: reps[re.escape(m.group(0))], text)
 
--- a/tests/test_page.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/tests/test_page.py	Mon Aug 11 22:36:47 2014 -0700
@@ -58,8 +58,8 @@
 def test_parse_segments(text, expected):
     actual = parse_segments(text)
     assert actual is not None
-    assert actual.keys() == expected.keys()
-    for key, val in expected.iteritems():
+    assert list(actual.keys()) == list(expected.keys())
+    for key, val in expected.items():
         if isinstance(val, str):
             assert len(actual[key].parts) == 1
             assert actual[key].parts[0].content == val
--- a/tests/test_uriutil.py	Mon Aug 11 22:36:36 2014 -0700
+++ b/tests/test_uriutil.py	Mon Aug 11 22:36:47 2014 -0700
@@ -17,7 +17,7 @@
 def test_parse_uri(routes, uri, expected):
     if expected is not None:
         expected.uri = uri
-    for pattern, args in routes.iteritems():
+    for pattern, args in routes.items():
         if 'taxonomy' not in args:
             args['taxonomy'] = None