You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2020/03/10 15:58:48 UTC

[allura] branch db/8354 created (now e02b2e4)

This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a change to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git.


      at e02b2e4  [#8354] modernize run_tests script

This branch includes the following new commits:

     new aaafbb9  [#8354] latest virtualenv works with py2 again
     new 9ad951b  [#8354] replace webhelpers literal with Markup (already inherited from it, we already use it aka jinja2.Markup elsewhere too)
     new 0c530a4  [#8354] fix six cookie import
     new 7ed67d7  [#8354] change StringIO uses that really are BytesIO
     new e11520d  [#8354] six-ify email mime imports
     new de2d4a5  [#8354] webhelpers.paginate -> standalone paginate package
     new a5c0916  [#8354] webhelpers.feedgenerator -> standalone feedgenerator package
     new a9e5c2e  [#8354] webhelpers -> webhelpers2
     new a720ee8  [#8354] misc other import-time weird fixes
     new b7db90c  [#8354] remove iteritems() usage in templates; assuming none of these are such huge lists that py2-non-iter will matter
     new 670f81c  [#8354] make sure jinja2 config values are of the right type
     new b5052ae  [#8354] unicode/byte fixes encountered during nearly all tests setup
     new 2f3961b  [#8354] avoid error: dictionary changed size during iteration
     new 75539c1  [#8354] make .ini settings compatible with py3 configparser
     new 9aba823  [#8354] antispam fixes for py3
     new e02b2e4  [#8354] modernize run_tests script

The 16 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[allura] 14/16: [#8354] make .ini settings compatible with py3 configparser

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 75539c1cdb677c0279e9c01c3dbe9b446528afc1
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:59:14 2020 -0500

    [#8354] make .ini settings compatible with py3 configparser
---
 Allura/allura/model/project.py               |  8 +++++++-
 Allura/allura/tests/functional/test_admin.py | 13 +++++++++++--
 Allura/allura/tests/test_tasks.py            |  1 +
 Allura/development.ini                       | 24 +++++++++++++++---------
 Allura/docker-dev.ini                        |  3 ++-
 Allura/production-docker-example.ini         |  3 ++-
 Allura/test.ini                              |  9 ++++-----
 7 files changed, 42 insertions(+), 19 deletions(-)

diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 5c95d19..2a6c8ff 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -1103,7 +1103,13 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
         elif not self.is_root:
             shortname = self.shortname.split('/')[1]
 
-        return config['bulk_export_filename'].format(project=shortname, date=datetime.utcnow())
+        filename_format = config['bulk_export_filename']
+        if six.PY2:
+            # in py3 ConfigParser requires %% to escape literal "%"
+            # since % has interpolation meaning within ConfigParser
+            # but in py2 the "%%" stays as "%%" so we have to switch it back to a single one
+            filename_format = filename_format.replace('%%', '%')
+        return filename_format.format(project=shortname, date=datetime.utcnow())
 
     def bulk_export_status(self):
         '''
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index cb3a182..2450152 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -19,6 +19,8 @@
 from __future__ import unicode_literals
 from __future__ import absolute_import
 import os
+from datetime import datetime
+
 import allura
 import pkg_resources
 from io import BytesIO
@@ -1030,6 +1032,7 @@ class TestExport(TestController):
         assert_in('error', self.webflash(r))
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
+    @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     def test_selected_one_tool(self, export_tasks):
         r = self.app.post('/admin/export', {'tools': 'wiki'})
         assert_in('ok', self.webflash(r))
@@ -1037,6 +1040,7 @@ class TestExport(TestController):
             ['wiki'], 'test.zip', send_email=True, with_attachments=False)
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
+    @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     def test_selected_multiple_tools(self, export_tasks):
         r = self.app.post('/admin/export', {'tools': ['wiki', 'wiki2']})
         assert_in('ok', self.webflash(r))
@@ -1060,11 +1064,15 @@ class TestExport(TestController):
     @td.with_user_project('test-user')
     def test_bulk_export_filename_for_user_project(self):
         project = M.Project.query.get(shortname='u/test-user')
-        assert_equals(project.bulk_export_filename(), 'test-user.zip')
+        filename = project.bulk_export_filename()
+        assert filename.startswith('test-user-backup-{}-'.format(datetime.utcnow().year))
+        assert filename.endswith('.zip')
 
     def test_bulk_export_filename_for_nbhd(self):
         project = M.Project.query.get(name='Home Project for Projects')
-        assert_equals(project.bulk_export_filename(), 'p.zip')
+        filename = project.bulk_export_filename()
+        assert filename.startswith('p-backup-{}-'.format(datetime.utcnow().year))
+        assert filename.endswith('.zip')
 
     def test_bulk_export_path_for_nbhd(self):
         project = M.Project.query.get(name='Home Project for Projects')
@@ -1129,6 +1137,7 @@ class TestRestExport(TestRestApiBase):
     @mock.patch('allura.model.project.MonQTask')
     @mock.patch('allura.ext.admin.admin_main.AdminApp.exportable_tools_for')
     @mock.patch('allura.ext.admin.admin_main.export_tasks.bulk_export')
+    @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     def test_export_ok(self, bulk_export, exportable_tools, MonQTask):
         MonQTask.query.get.return_value = None
         exportable_tools.return_value = [
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 8f63478..c2d9117 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -641,6 +641,7 @@ class TestExportTasks(unittest.TestCase):
 
     @mock.patch('allura.tasks.export_tasks.shutil')
     @mock.patch('allura.tasks.export_tasks.zipdir')
+    @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     @td.with_wiki
     def test_bulk_export(self, zipdir, shutil):
         M.MonQTask.query.remove()
diff --git a/Allura/development.ini b/Allura/development.ini
index ad56a7a..010b550 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -89,8 +89,8 @@ base_url = http://localhost:8080
 ; `Allura/allura/public/nf/images/` and specify file name below
 ; logo.link = /
 ; logo.path = sf10a.png
-; logo.width = 125 ; in px
-; logo.height = 18 ; in px
+; logo.width = 125 (in px)
+; logo.height = 18 (in px)
 
 ; Used to uniquify references to static resources, can be a timestamp or any unique value
 ; This should be updated each time you deploy (or make significant changes, like new tools, new css)
@@ -149,7 +149,8 @@ project_icon_sizes = 16 24 32 48 64 72 90 96 128 135 180 270
 ; For LDAP see https://forge-allura.apache.org/docs/getting_started/installation.html#using-ldap
 ;auth.method = ldap
 auth.method = local
-auth.remember_for = 365  ; in days, for the "remember me" checkbox on login
+; in days, for the "remember me" checkbox on login
+auth.remember_for = 365
 
 ; Customize login/logout URLs only if you have some custom authentication set up.
 auth.login_url = /auth/
@@ -163,7 +164,8 @@ auth.max_password_len = 30
 
 ; password expiration options (disabled if neither is set)
 ;auth.pwdexpire.days = 1
-;auth.pwdexpire.before = 1401949912  ; unix timestamp
+; unix timestamp:
+;auth.pwdexpire.before = 1401949912
 
 ; if using LDAP, also run `pip install python-ldap` in your Allura environment
 
@@ -261,7 +263,8 @@ site_admin_project_nbhd = Projects
 ; for stopforumspam, should be a listed_ip_*_all.txt file
 ;spam.stopforumspam.ip_addr_file =
 ;spam.stopforumspam.threshold = 20
-spam.form_post_expiration = 345600 ; 4 days
+; 4 days:
+spam.form_post_expiration = 345600
 
 ; Phone verification service: Nexmo Verify
 ; phone.method = nexmo
@@ -312,7 +315,8 @@ webhook.repo_push.max_hooks = {"git": 3, "hg": 3, "svn": 3}
 ; To use ssl if and only if a user is logged in:
 ;force_ssl.logged_in = true
 ; If you set force_ssl.logged_in, you probably want some URLs to be ssl when logged out:
-;force_ssl.pattern = ^/auth|^/[a-z0-9-]+/import_project/  ; import_project uses a login overlay
+;   (import_project uses a login overlay)
+;force_ssl.pattern = ^/auth|^/[a-z0-9-]+/import_project/
 ; And to permit some URLs to be accessed over http anyway:
 ;    /_test_vars is used when running `paster shell`
 ;no_redirect.pattern = ^/nf/\d+/_(ew|static)_/|^/rest/|^/nf/tool_icon_css|^/auth/refresh_repo|^/_test_vars
@@ -328,7 +332,8 @@ static.script_name = /nf/%(build_key)s/_static_/
 static.url_base = /nf/%(build_key)s/_static_/
 
 ; Expires header for "static" resources served through allura (e.g. icons, attachments, /nf/tool_icon_css)
-files_expires_header_secs = 1209600 ; 2 weeks
+; 2 weeks:
+files_expires_header_secs = 1209600
 
 ; EasyWidgets settings
 ; This CORS header is necessary if serving webfonts via a different domain
@@ -432,7 +437,7 @@ scm.view.max_syntax_highlight_bytes = 500000
 ; If you keep bulk_export_enabled, you should set up your server to securely share bulk_export_path with users somehow
 bulk_export_path = /tmp/bulk_export/{nbhd}/{project}
 ; bulk_export_tmpdir can be set to hold files before building the zip file.  Defaults to use bulk_export_path
-bulk_export_filename = {project}-backup-{date:%Y-%m-%d-%H%M%S}.zip
+bulk_export_filename = {project}-backup-{date:%%Y-%%m-%%d-%%H%%M%%S}.zip
 ; You will need to specify site-specific instructions here for accessing the exported files.
 bulk_export_download_instructions = Sample instructions for {project}
 
@@ -656,7 +661,8 @@ next=main
 ;
 [app:task]
 use = main
-override_root = task ; TurboGears will use controllers/task.py as root controller
+; TurboGears will use controllers/task.py as root controller
+override_root = task
 
 
 
diff --git a/Allura/docker-dev.ini b/Allura/docker-dev.ini
index 980d8b8..7a7faaf 100644
--- a/Allura/docker-dev.ini
+++ b/Allura/docker-dev.ini
@@ -66,7 +66,8 @@ forgemail.port = 8825
 
 [app:task]
 use = main
-override_root = task ; TurboGears will use controllers/task.py as root controller
+; TurboGears will use controllers/task.py as root controller
+override_root = task
 
 [loggers]
 keys = root, allura, sqlalchemy, paste, ew, taskdstatus, timermiddleware, tmw_details
diff --git a/Allura/production-docker-example.ini b/Allura/production-docker-example.ini
index b5ba53c..1824aa1 100644
--- a/Allura/production-docker-example.ini
+++ b/Allura/production-docker-example.ini
@@ -89,7 +89,8 @@ stats.sample_rate = .01
 
 [app:task]
 use = main
-override_root = task ; TurboGears will use controllers/task.py as root controller
+; TurboGears will use controllers/task.py as root controller
+override_root = task
 
 
 
diff --git a/Allura/test.ini b/Allura/test.ini
index 82132b5..efe4228 100644
--- a/Allura/test.ini
+++ b/Allura/test.ini
@@ -26,7 +26,8 @@
 
 [app:main]
 use = config:development.ini#main
-override_root=basetest_project_root ; TurboGears will use controllers/basetest_project_root.py as root controller
+; TurboGears will use controllers/basetest_project_root.py as root controller
+override_root=basetest_project_root
 disable_template_overrides = True
 
 ; Use in-memory MongoDB
@@ -80,9 +81,6 @@ support_tool_choices = wiki tickets discussion
 ; tests expect max length of 40000
 markdown_render_max_length = 40000
 
-; TODO: make this and tests match development.ini
-bulk_export_filename = {project}.zip
-
 ; TODO: update tests and let this be true
 solr.use_new_types = false
 
@@ -91,7 +89,8 @@ auth.require_email_addr = false
 
 [app:task]
 use = main
-override_root = task ; TurboGears will use controllers/task.py as root controller
+; TurboGears will use controllers/task.py as root controller
+override_root = task
 
 ;
 ; Logging goes to a test.log file in current directory


[allura] 05/16: [#8354] six-ify email mime imports

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e11520ddcd01777d76c1ec3b0c2b426fa8fa0625
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 14:55:16 2020 -0500

    [#8354] six-ify email mime imports
---
 Allura/allura/lib/mail_util.py                                 | 4 ++--
 Allura/allura/tests/test_mail_util.py                          | 4 ++--
 ForgeDiscussion/forgediscussion/tests/functional/test_forum.py | 6 +++---
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index cce61a9..bdde46c 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.py
@@ -21,8 +21,8 @@ import re
 import logging
 import smtplib
 import email.feedparser
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
+from six.moves.email_mime_multipart import MIMEMultipart
+from six.moves.email_mime_text import MIMEText
 from email import header
 
 import six
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index 4b3d21e..80ab989 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -20,8 +20,8 @@
 from __future__ import unicode_literals
 from __future__ import absolute_import
 import unittest
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
+from six.moves.email_mime_multipart import MIMEMultipart
+from six.moves.email_mime_text import MIMEText
 
 import mock
 from nose.tools import raises, assert_equal, assert_false, assert_true, assert_in
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index f4b59d6..900f190 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -22,9 +22,9 @@ from __future__ import absolute_import
 import mock
 import random
 import logging
-from email.mime.text import MIMEText
-from email.mime.image import MIMEImage
-from email.mime.multipart import MIMEMultipart
+from six.moves.email_mime_text import MIMEText
+from six.moves.email_mime_image import MIMEImage
+from six.moves.email_mime_multipart import MIMEMultipart
 
 import pkg_resources
 import pymongo


[allura] 08/16: [#8354] webhelpers -> webhelpers2

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit a9e5c2e08b69cb73dafd16357b2456189edb6e06
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 16:23:16 2020 -0500

    [#8354] webhelpers -> webhelpers2
---
 Allura/allura/lib/helpers.py | 2 +-
 requirements.in              | 2 +-
 requirements.txt             | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 6bfe048..79e57b3 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -65,7 +65,7 @@ import formencode
 from jinja2 import Markup
 from jinja2.filters import contextfilter, escape, do_filesizeformat
 from paste.deploy.converters import asbool, aslist, asint
-from webhelpers import date, html, number, misc, text
+from webhelpers2 import date, text
 from webob.exc import HTTPUnauthorized
 
 from allura.lib import exceptions as exc
diff --git a/requirements.in b/requirements.in
index 83f8ccf..d25b8d1 100644
--- a/requirements.in
+++ b/requirements.in
@@ -45,7 +45,7 @@ setproctitle==1.1.9
 six==1.12.0
 TimerMiddleware==0.5.1
 TurboGears2==2.3.12
-WebHelpers==1.3
+WebHelpers2
 WebOb==1.7.4
 wrapt==1.11.2
 
diff --git a/requirements.txt b/requirements.txt
index 97d5881..3ad0464 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -93,7 +93,7 @@ urllib3==1.25.3           # via requests
 waitress==1.3.0           # via webtest
 wcwidth==0.1.7            # via prompt-toolkit
 webencodings==0.5.1       # via bleach, html5lib
-webhelpers==1.3
+webhelpers2==2.0
 webob==1.7.4
 webtest==2.0.33
 wrapt==1.11.2


[allura] 01/16: [#8354] latest virtualenv works with py2 again

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit aaafbb90c2d60e605b545fe83f2915306b39181c
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 12:22:42 2020 -0500

    [#8354] latest virtualenv works with py2 again
---
 scripts/init-docker-dev.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/init-docker-dev.sh b/scripts/init-docker-dev.sh
index fa4905f..3e75f21 100755
--- a/scripts/init-docker-dev.sh
+++ b/scripts/init-docker-dev.sh
@@ -38,7 +38,7 @@ echo "# No robots.txt rules here" > /allura-data/www-misc/robots.txt
 # share venv to allow update and sharing across containers
 if [ ! -e /allura-data/virtualenv ]; then
     echo -e "Creating virtualenv\n"
-    pip install 'virtualenv < 20'  # https://github.com/pypa/virtualenv/issues/1670
+    pip install 'virtualenv >= 20.0.8'  # https://github.com/pypa/virtualenv/issues/1684
     virtualenv /allura-data/virtualenv
     ln -s /usr/lib/python2.7/dist-packages/pysvn /allura-data/virtualenv/lib/python2.7/site-packages/
     echo # just a new line


[allura] 07/16: [#8354] webhelpers.feedgenerator -> standalone feedgenerator package

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit a5c09165bab9d3432897d3f2df26554f88769d20
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 16:22:51 2020 -0500

    [#8354] webhelpers.feedgenerator -> standalone feedgenerator package
---
 Allura/allura/lib/helpers.py                       | 3 +--
 Allura/allura/model/artifact.py                    | 4 ++--
 ForgeActivity/forgeactivity/main.py                | 2 +-
 ForgeBlog/forgeblog/tests/functional/test_feeds.py | 4 ++--
 ForgeTracker/forgetracker/tracker_main.py          | 2 +-
 requirements.in                                    | 1 +
 requirements.txt                                   | 1 +
 7 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index d2506e7..6bfe048 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -65,8 +65,7 @@ import formencode
 from jinja2 import Markup
 from jinja2.filters import contextfilter, escape, do_filesizeformat
 from paste.deploy.converters import asbool, aslist, asint
-
-from webhelpers import date, feedgenerator, html, number, misc, text
+from webhelpers import date, html, number, misc, text
 from webob.exc import HTTPUnauthorized
 
 from allura.lib import exceptions as exc
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index d3a6f1c..cc98b45 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -29,7 +29,7 @@ from ming.orm import state, session
 from ming.orm import FieldProperty, ForeignIdProperty, RelationProperty
 from ming.orm.declarative import MappedClass
 from ming.utils import LazyProperty
-from webhelpers import feedgenerator as FG
+import feedgenerator as FG
 
 from allura.lib import helpers as h
 from allura.lib import security
@@ -961,7 +961,7 @@ class Feed(MappedClass):
     @classmethod
     def feed(cls, q, feed_type, title, link, description,
              since=None, until=None, page=None, limit=None):
-        "Produces webhelper.feedgenerator Feed"
+        "Produces feedgenerator Feed"
         d = dict(title=title, link=h.absurl(h.urlquote(link)),
                  description=description, language='en',
                  feed_url=request.url)
diff --git a/ForgeActivity/forgeactivity/main.py b/ForgeActivity/forgeactivity/main.py
index 5f1c78b..0ee91d5 100644
--- a/ForgeActivity/forgeactivity/main.py
+++ b/ForgeActivity/forgeactivity/main.py
@@ -30,7 +30,7 @@ from tg import expose, validate, config
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from paste.deploy.converters import asbool, asint
 from webob import exc
-from webhelpers import feedgenerator as FG
+import feedgenerator as FG
 from activitystream.storage.mingstorage import Activity
 
 from allura.app import Application
diff --git a/ForgeBlog/forgeblog/tests/functional/test_feeds.py b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
index 2608ea8..f75ea4e 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_feeds.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
@@ -71,10 +71,10 @@ class TestFeeds(TestController):
     def test_rss_feed_contains_self_link(self):
         r = self.app.get('/blog/feed.rss')
         # atom namespace included
-        assert_in('<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">', r)
+        assert_in('<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">', r)
         # ...and atom:link points to feed url
         assert_in('<atom:link href="http://localhost/blog/feed.rss" '
-                  'type="application/rss+xml" rel="self"></atom:link>', r)
+                  'rel="self" type="application/rss+xml"></atom:link>', r)
 
     def test_post_feeds(self):
         self._post()
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 06cf18e..980ad5e 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -37,7 +37,7 @@ from formencode import validators
 from bson import ObjectId
 from bson.son import SON
 from bson.errors import InvalidId
-from webhelpers import feedgenerator as FG
+import feedgenerator as FG
 
 from ming import schema
 from ming.odm import session
diff --git a/requirements.in b/requirements.in
index 98e4dc7..83f8ccf 100644
--- a/requirements.in
+++ b/requirements.in
@@ -9,6 +9,7 @@ decorator
 EasyWidgets>=0.3.3
 emoji
 faulthandler ; python_version < "3.3"
+feedgenerator
 feedparser
 # FormEncode may need v2.0 to work past py3.3 or so?  https://github.com/formencode/formencode/issues/140
 FormEncode
diff --git a/requirements.txt b/requirements.txt
index 04f722d..97d5881 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -26,6 +26,7 @@ easywidgets==0.3.5
 emoji==0.5.3
 enum34==1.1.6             # via colander, cryptography, traitlets
 faulthandler==3.1 ; python_version < "3.3"
+feedgenerator==1.9.1
 feedparser==5.2.1
 formencode==1.3.1
 funcsigs==1.0.2           # via beaker, mock


[allura] 09/16: [#8354] misc other import-time weird fixes

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit a720ee8d9f2596732ee828cd76503ea203626473
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:11:43 2020 -0500

    [#8354] misc other import-time weird fixes
---
 Allura/allura/app.py               | 4 ++--
 Allura/allura/model/multifactor.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index 056c776..24ea6ff 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -19,7 +19,7 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import os
 import logging
-from urllib import basejoin
+from six.moves.urllib_parse import urljoin
 from io import BytesIO
 from collections import defaultdict
 from xml.etree import ElementTree as ET
@@ -169,7 +169,7 @@ class SitemapEntry(object):
         if callable(lbl):
             lbl = lbl(app)
         if url is not None:
-            url = basejoin(app.url, url)
+            url = urljoin(app.url, url)
         return SitemapEntry(lbl, url,
                             [ch.bind_app(app) for ch in self.children],
                             className=self.className,
diff --git a/Allura/allura/model/multifactor.py b/Allura/allura/model/multifactor.py
index 433c246..1486a8e 100644
--- a/Allura/allura/model/multifactor.py
+++ b/Allura/allura/model/multifactor.py
@@ -40,7 +40,7 @@ class TotpKey(MappedClass):
 
     _id = FieldProperty(S.ObjectId)
     user_id = FieldProperty(S.ObjectId, required=True)
-    key = FieldProperty(bytes, required=True)
+    key = FieldProperty(str, required=True)
 
 
 class RecoveryCode(MappedClass):


[allura] 16/16: [#8354] modernize run_tests script

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e02b2e44b14a46e2af03db375af34414ad0f7f40
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Mar 10 11:56:05 2020 -0400

    [#8354] modernize run_tests script
---
 run_tests | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/run_tests b/run_tests
index d3b30b7..c4ff7b4 100755
--- a/run_tests
+++ b/run_tests
@@ -17,17 +17,20 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
+from __future__ import absolute_import
+from __future__ import print_function
 import argparse
-from copy import copy
 from glob import glob
 import multiprocessing
 from multiprocessing.pool import ThreadPool
 import subprocess
 import sys
 import threading
-import textwrap
 import os
 
+import six
+
 CPUS = multiprocessing.cpu_count()
 CONCURRENT_SUITES = (CPUS // 4) or CPUS
 CONCURRENT_TESTS = CPUS // CONCURRENT_SUITES
@@ -42,10 +45,14 @@ NOT_MULTIPROC_SAFE = [
     'ForgeSVN',
 ]
 
+# unless you want to mess with changing stdout's own encoding, this works well:
+#  py2 gets utf-8 encoded (binary) and py3 gets unicode text
+print_ensured = six.ensure_binary if six.PY2 else six.ensure_text
+
 
 def run_one(cmd, **popen_kwargs):
-    cmd_to_show = u'`{}` in {}'.format(cmd, popen_kwargs.get('cwd', '.'))
-    print u'{} running {}\n'.format(threading.current_thread(), cmd_to_show)
+    cmd_to_show = '`{}` in {}'.format(cmd, popen_kwargs.get('cwd', '.'))
+    print('{} running {}\n'.format(threading.current_thread(), cmd_to_show))
     sys.stdout.flush()
 
     all_popen_kwargs = dict(shell=True, stderr=subprocess.STDOUT,
@@ -56,15 +63,15 @@ def run_one(cmd, **popen_kwargs):
     proc = subprocess.Popen(cmd, **all_popen_kwargs)
     while proc.poll() is None:
         line = proc.stdout.readline()
-        sys.stdout.write(line)
-        if 'No data to combine' in line:
+        sys.stdout.write(print_ensured(line))
+        if b'No data to combine' in line:
             sys.stdout.write('^^ error from "coverage combine" command.  Make sure your package has a setup.cfg with coverage settings like other packages\n')
         sys.stdout.flush()
     # wait for completion and get remainder of output
     out_remainder, _ = proc.communicate()
-    sys.stdout.write(out_remainder)
+    sys.stdout.write(print_ensured(out_remainder))
     sys.stdout.flush()
-    print u'finished {}'.format(cmd_to_show)
+    print('finished {}'.format(cmd_to_show))
     sys.stdout.flush()
     return proc
 
@@ -108,7 +115,7 @@ def check_packages(packages):
         try:
             __import__(pkg.lower())
         except ImportError:
-            print "Not running tests for {}, since it isn't set up".format(pkg)
+            print("Not running tests for {}, since it isn't set up".format(pkg))
         else:
             yield pkg
 


[allura] 02/16: [#8354] replace webhelpers literal with Markup (already inherited from it, we already use it aka jinja2.Markup elsewhere too)

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 9ad951bd6a6f4c54abb1d88707962180579a4abe
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 12:23:56 2020 -0500

    [#8354] replace webhelpers literal with Markup (already inherited from it, we already use it aka jinja2.Markup elsewhere too)
---
 Allura/allura/config/app_cfg.py                  |  4 ++--
 Allura/allura/lib/app_globals.py                 | 14 +++++++-------
 Allura/allura/lib/markdown_extensions.py         |  4 +++-
 Allura/allura/lib/utils.py                       |  4 ++--
 Allura/allura/lib/widgets/forms.py               |  7 ++++---
 Allura/allura/tasks/mail_tasks.py                |  2 +-
 ForgeTracker/forgetracker/widgets/ticket_form.py |  6 +++---
 7 files changed, 22 insertions(+), 19 deletions(-)

diff --git a/Allura/allura/config/app_cfg.py b/Allura/allura/config/app_cfg.py
index e93b09b..fc9eb3e 100644
--- a/Allura/allura/config/app_cfg.py
+++ b/Allura/allura/config/app_cfg.py
@@ -40,7 +40,7 @@ from tg import app_globals as g
 from tg.renderers.jinja import JinjaRenderer
 import jinja2
 from tg.configuration import AppConfig, config
-from webhelpers.html import literal
+from markupsafe import Markup
 import ew
 
 import allura
@@ -133,7 +133,7 @@ class JinjaEngine(ew.TemplateEngine):
         context = self.context(context)
         with ew.utils.push_context(ew.widget_context, render_context=context):
             text = template.render(**context)
-            return literal(text)
+            return Markup(text)
 
 
 base_config = ForgeConfig()
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 3809ad8..5af35ea 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -83,7 +83,7 @@ class ForgeMarkdown(markdown.Markdown):
             # so we return it as a plain text
             log.info('Text is too big. Skipping markdown processing')
             escaped = cgi.escape(h.really_unicode(source))
-            return h.html.literal('<pre>%s</pre>' % escaped)
+            return Markup('<pre>%s</pre>' % escaped)
         try:
             return markdown.Markdown.convert(self, source)
         except Exception:
@@ -91,7 +91,7 @@ class ForgeMarkdown(markdown.Markdown):
                      ''.join(traceback.format_stack()), exc_info=True)
             escaped = h.really_unicode(source)
             escaped = cgi.escape(escaped)
-            return h.html.literal("""<p><strong>ERROR!</strong> The markdown supplied could not be parsed correctly.
+            return Markup("""<p><strong>ERROR!</strong> The markdown supplied could not be parsed correctly.
             Did you forget to surround a code snippet with "~~~~"?</p><pre>%s</pre>""" % escaped)
 
     def cached_convert(self, artifact, field_name):
@@ -117,7 +117,7 @@ class ForgeMarkdown(markdown.Markdown):
         if cache.md5 is not None:
             md5 = hashlib.md5(source_text.encode('utf-8')).hexdigest()
             if cache.md5 == md5 and getattr(cache, 'fix7528', False) == bugfix_rev:
-                return h.html.literal(cache.html)
+                return Markup(cache.html)
 
         # Convert the markdown and time the result.
         start = time.time()
@@ -418,8 +418,8 @@ class Globals(object):
     def highlight(self, text, lexer=None, filename=None):
         if not text:
             if lexer == 'diff':
-                return h.html.literal('<em>File contents unchanged</em>')
-            return h.html.literal('<em>Empty file</em>')
+                return Markup('<em>File contents unchanged</em>')
+            return Markup('<em>Empty file</em>')
         # Don't use line numbers for diff highlight's, as per [#1484]
         if lexer == 'diff':
             formatter = pygments.formatters.HtmlFormatter(cssclass='codehilite', linenos=False)
@@ -439,9 +439,9 @@ class Globals(object):
             # no highlighting, but we should escape, encode, and wrap it in
             # a <pre>
             text = cgi.escape(text)
-            return h.html.literal('<pre>' + text + '</pre>')
+            return Markup('<pre>' + text + '</pre>')
         else:
-            return h.html.literal(pygments.highlight(text, lexer, formatter))
+            return Markup(pygments.highlight(text, lexer, formatter))
 
     def forge_markdown(self, **kwargs):
         '''return a markdown.Markdown object on which you can call convert'''
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index 7575d1e..c2050f7 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -21,6 +21,7 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import re
 import logging
+
 from six.moves.urllib.parse import urljoin
 
 from tg import config
@@ -30,6 +31,7 @@ import html5lib.serializer
 import html5lib.filters.alphabeticalattributes
 import markdown
 import emoji
+from markupsafe import Markup
 
 from . import macro
 from . import helpers as h
@@ -431,7 +433,7 @@ class ForgeLinkTreeProcessor(markdown.treeprocessors.Treeprocessor):
 class MarkAsSafe(markdown.postprocessors.Postprocessor):
 
     def run(self, text):
-        return h.html.literal(text)
+        return Markup(text)
 
 
 class AddCustomClass(markdown.postprocessors.Postprocessor):
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 14c6575..2494c8b 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -50,7 +50,7 @@ from tg import redirect, app_globals as g
 from tg.decorators import before_validate
 from tg.controllers.util import etag_cache
 from paste.deploy.converters import asbool, asint
-from webhelpers.html import literal
+from markupsafe import Markup
 from webob import exc
 from pygments.formatters import HtmlFormatter
 from setproctitle import getproctitle
@@ -294,7 +294,7 @@ class AntiSpam(object):
         for fldno in range(self.num_honey):
             fld_name = self.enc('honey%d' % (fldno))
             fld_id = self.enc('honey%d%d' % (self.counter, fldno))
-            yield literal(self.honey_field_template.substitute(
+            yield Markup(self.honey_field_template.substitute(
                 honey_class=self.honey_class,
                 fld_id=fld_id,
                 fld_name=fld_name))
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index a528269..888baa5 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -29,6 +29,7 @@ from pytz import common_timezones, country_timezones, country_names
 from paste.deploy.converters import aslist, asint, asbool
 import tg
 from tg import config
+from markupsafe import Markup
 
 from allura.lib import validators as V
 from allura.lib import helpers as h
@@ -108,7 +109,7 @@ class ForgeForm(ew.SimpleForm):
             or ctx['name'])
         html = '<label for="%s">%s</label>' % (
             ctx['id'], label_text)
-        return h.html.literal(html)
+        return Markup(html)
 
     def context_for(self, field):
         ctx = super(ForgeForm, self).context_for(field)
@@ -122,7 +123,7 @@ class ForgeForm(ew.SimpleForm):
         if ctx['errors'] and field.show_errors and not ignore_errors:
             display = "%s<div class='error'>%s</div>" % (display,
                                                          ctx['errors'])
-        return h.html.literal(display)
+        return Markup(display)
 
 
 class ForgeFormResponsive(ForgeForm):
@@ -900,7 +901,7 @@ class NeighborhoodOverviewForm(ForgeForm):
                 display = "%s<div class='error'>%s</div>" % (display,
                                                              ctx['errors'])
 
-            return h.html.literal(display)
+            return Markup(display)
         else:
             return super(NeighborhoodOverviewForm, self).display_field(field, ignore_errors)
 
diff --git a/Allura/allura/tasks/mail_tasks.py b/Allura/allura/tasks/mail_tasks.py
index c2774af..779cd0f 100644
--- a/Allura/allura/tasks/mail_tasks.py
+++ b/Allura/allura/tasks/mail_tasks.py
@@ -44,7 +44,7 @@ def mail_meta_content(metalink):
     :param metalink:  url to the page the action button links to
     '''
 
-    return h.html.literal("""\
+    return markupsafe.Markup("""\
     <div itemscope itemtype="http://schema.org/EmailMessage">
     <div itemprop="action" itemscope itemtype="http://schema.org/ViewAction">
       <link itemprop="url" href="%s"></link>
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index 4778a36..f1f04e1 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.py
@@ -17,10 +17,10 @@
 
 from __future__ import unicode_literals
 from __future__ import absolute_import
+
 from tg import tmpl_context as c
 from formencode import validators as fev
-from webhelpers.html.builder import literal
-
+from markupsafe import Markup
 import ew as ew_core
 import ew.jinja2_ew as ew
 
@@ -81,7 +81,7 @@ class GenericTicketForm(ew.SimpleForm):
 
         display = field.display(**ctx)
         if ctx['errors'] and field.show_errors and not ignore_errors:
-            display += literal("<div class='error'>") + ctx['errors'] + literal("</div>")
+            display += Markup("<div class='error'>") + ctx['errors'] + Markup("</div>")
         return display
 
     def _add_current_value_to_user_field(self, field, user):


[allura] 12/16: [#8354] unicode/byte fixes encountered during nearly all tests setup

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit b5052ae9f125d456534c92ca6df743df1f0563ac
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:51:53 2020 -0500

    [#8354] unicode/byte fixes encountered during nearly all tests setup
---
 Allura/allura/app.py                          | 2 --
 Allura/allura/lib/custom_middleware.py        | 2 +-
 Allura/allura/lib/helpers.py                  | 7 ++++++-
 Allura/allura/lib/plugin.py                   | 4 ++--
 Allura/allura/lib/widgets/forms.py            | 2 +-
 Allura/allura/templates/jinja_master/lib.html | 4 ++--
 Allura/allura/tests/test_plugin.py            | 1 +
 7 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index 24ea6ff..effa59c 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -118,8 +118,6 @@ class SitemapEntry(object):
         """
         self.label = label
         self.className = className
-        if url is not None:
-            url = url.encode('utf-8')
         self.url = url
         self.small = small
         self.ui_icon = ui_icon
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 061d792..4ae9df1 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -241,7 +241,7 @@ class SSLMiddleware(object):
 
         try:
             request_uri = req.url
-            request_uri.decode('ascii')
+            six.ensure_binary(request_uri).decode('ascii')
         except UnicodeError:
             resp = exc.HTTPBadRequest()
         else:
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 79e57b3..3f537ad 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -433,8 +433,13 @@ def nonce(length=4):
 
 
 def cryptographic_nonce(length=40):
+    rand_bytes = os.urandom(length)
+    if six.PY2:
+        rand_ints = tuple(map(ord, rand_bytes))
+    else:
+        rand_ints = tuple(rand_bytes)
     hex_format = '%.2x' * length
-    return hex_format % tuple(map(ord, os.urandom(length)))
+    return hex_format % rand_ints
 
 
 def random_password(length=20, chars=string.ascii_uppercase + string.digits):
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index a1b2629..a79750c 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -577,8 +577,8 @@ class LocalAuthenticationProvider(AuthenticationProvider):
         if salt is None:
             salt = ''.join(chr(randint(1, 0x7f))
                            for i in range(M.User.SALT_LEN))
-        hashpass = sha256(salt + password.encode('utf-8')).digest()
-        return 'sha256' + salt + b64encode(hashpass)
+        hashpass = sha256((salt + password).encode('utf-8')).digest()
+        return 'sha256' + salt + six.ensure_text(b64encode(hashpass))
 
     def user_project_shortname(self, user):
         # "_" isn't valid for subdomains (which project names are used with)
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 888baa5..ab0ef32 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -82,7 +82,7 @@ class NeighborhoodProjectShortNameValidator(fev.FancyValidator):
         """
         if neighborhood is None:
             neighborhood = M.Neighborhood.query.get(name=state.full_dict['neighborhood'])
-        value = h.really_unicode(value or '').encode('utf-8')
+        value = h.really_unicode(value or '')
         self._validate_shortname(value, neighborhood, state)
         if check_allowed:
             self._validate_allowed(value, neighborhood, state)
diff --git a/Allura/allura/templates/jinja_master/lib.html b/Allura/allura/templates/jinja_master/lib.html
index 0a85183..9114986 100644
--- a/Allura/allura/templates/jinja_master/lib.html
+++ b/Allura/allura/templates/jinja_master/lib.html
@@ -18,13 +18,13 @@
 -#}
 
 {% macro csrf() -%}
-  {% if request -%}
+  {% if request is defined -%}
     {{ request.cookies['_session_id'] or request.environ['_session_id'] }}
   {%- endif %}
 {%- endmacro %}
 
 {% macro csrf_token() -%}
-  {% if request %}
+  {% if request is defined %}
     <input name="_session_id" type="hidden" value="{{csrf()}}">
   {% endif %}
 {%- endmacro %}
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 8e02010..ce23474 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -598,6 +598,7 @@ class TestLocalAuthenticationProvider(object):
         ep = self.provider._encode_password
         assert ep('test_pass') != ep('test_pass')
         assert ep('test_pass', '0000') == ep('test_pass', '0000')
+        assert_equal(ep('test_pass', '0000'), 'sha2560000j7pRjKKZ5L8G0jScZKja9ECmYF2zBV82Mi+E3wkop30=')
 
     def test_set_password_with_old_password(self):
         user = Mock()


[allura] 11/16: [#8354] make sure jinja2 config values are of the right type

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 670f81c2661f57425ea92321c6d0596e54b78ecf
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:33:10 2020 -0500

    [#8354] make sure jinja2 config values are of the right type
---
 Allura/allura/config/app_cfg.py    | 3 ++-
 Allura/allura/config/middleware.py | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/Allura/allura/config/app_cfg.py b/Allura/allura/config/app_cfg.py
index fc9eb3e..91f89cf 100644
--- a/Allura/allura/config/app_cfg.py
+++ b/Allura/allura/config/app_cfg.py
@@ -42,6 +42,7 @@ import jinja2
 from tg.configuration import AppConfig, config
 from markupsafe import Markup
 import ew
+from tg.support.converters import asint
 
 import allura
 # needed for tg.configuration to work
@@ -103,7 +104,7 @@ class AlluraJinjaRenderer(JinjaRenderer):
             auto_reload=config['auto_reload_templates'],
             autoescape=True,
             bytecode_cache=bcc,
-            cache_size=config.get('jinja_cache_size', -1),
+            cache_size=asint(config.get('jinja_cache_size', -1)),
             extensions=['jinja2.ext.do', 'jinja2.ext.i18n'])
         jinja2_env.install_gettext_translations(tg.i18n)
         jinja2_env.filters['datetimeformat'] = helpers.datetimeformat
diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index af52386..b46ee1b 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -175,9 +175,9 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
         # (the Allura [easy_widgets.engines] entry point is named "jinja" (not jinja2) but it doesn't need
         #  any settings since it is a class that uses the same jinja env as the rest of allura)
         **{
-            'jinja2.auto_reload': config['auto_reload_templates'],
+            'jinja2.auto_reload': asbool(config['auto_reload_templates']),
             'jinja2.bytecode_cache': AlluraJinjaRenderer._setup_bytecode_cache(),
-            'jinja2.cache_size': config.get('jinja_cache_size', -1),
+            'jinja2.cache_size': asint(config.get('jinja_cache_size', -1)),
         }
     )
     # Handle static files (by tool)


[allura] 10/16: [#8354] remove iteritems() usage in templates; assuming none of these are such huge lists that py2-non-iter will matter

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit b7db90c7335daf7dd490f3eaec7157da7d167b8d
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:15:08 2020 -0500

    [#8354] remove iteritems() usage in templates; assuming none of these are such huge lists that py2-non-iter will matter
---
 Allura/allura/ext/admin/templates/project_trove.html                  | 2 +-
 Allura/allura/templates/browse_trove_categories.html                  | 4 ++--
 Allura/allura/templates/reconfirm_auth.html                           | 2 +-
 Allura/allura/templates/repo/merge_request_changed.html               | 2 +-
 Allura/allura/templates/widgets/forge_form.html                       | 2 +-
 Allura/allura/templates/widgets/page_size.html                        | 2 +-
 Allura/allura/templates/widgets/search_help.html                      | 2 +-
 Allura/allura/templates/widgets/state_field.html                      | 2 +-
 Allura/allura/templates_responsive/widgets/forge_form.html            | 2 +-
 ForgeImporters/forgeimporters/templates/project_base.html             | 4 ++--
 ForgeTracker/forgetracker/templates/tracker/admin_fields.html         | 2 +-
 .../templates/tracker_widgets/custom_field_admin_detail.html          | 2 +-
 12 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/Allura/allura/ext/admin/templates/project_trove.html b/Allura/allura/ext/admin/templates/project_trove.html
index 23af654..e93aac4 100644
--- a/Allura/allura/ext/admin/templates/project_trove.html
+++ b/Allura/allura/ext/admin/templates/project_trove.html
@@ -50,7 +50,7 @@
   <div class="grid-19 trove_add_container">
     {% if trove_recommendations[base.shortname] %}
         Recommended to choose from:
-            {% for trove_id, label in trove_recommendations[base.shortname].iteritems() %}
+            {% for trove_id, label in trove_recommendations[base.shortname].items() %}
                 <a href="#" data-id="{{ trove_id }}" data-trove="{{ base.shortname }}" class="recommended_trove"><i class="fa fa-plus-circle"></i> {{ label }}</a>
             {% endfor %}
         <br>
diff --git a/Allura/allura/templates/browse_trove_categories.html b/Allura/allura/templates/browse_trove_categories.html
index 9e2ab07..cbf8c82 100644
--- a/Allura/allura/templates/browse_trove_categories.html
+++ b/Allura/allura/templates/browse_trove_categories.html
@@ -25,10 +25,10 @@
     <h3><a href="/categories">(back)</a></h3>
     <h3>Trove Categories</h3>
     <ul>
-        {% for key, value in tree.iteritems() recursive %}
+        {% for key, value in tree.items() recursive %}
             <li>{{ key }}</li>
             {% if value %}
-                <ul>{{ loop(value.iteritems()) }}</ul>
+                <ul>{{ loop(value.items()) }}</ul>
             {% endif %}
         {% endfor %}
     </ul>
diff --git a/Allura/allura/templates/reconfirm_auth.html b/Allura/allura/templates/reconfirm_auth.html
index 26d6fd2..a3ea322 100644
--- a/Allura/allura/templates/reconfirm_auth.html
+++ b/Allura/allura/templates/reconfirm_auth.html
@@ -35,7 +35,7 @@
     <input type="submit" value="Submit">
 
     {# include any post params again, so that their original form submit can go through successfully #}
-    {% for key, val in request.POST.iteritems() %}
+    {% for key, val in request.POST.items() %}
         {% if key != 'password' %}
             <input type="hidden" name="{{ key }}" value="{{ val }}">
         {% endif %}
diff --git a/Allura/allura/templates/repo/merge_request_changed.html b/Allura/allura/templates/repo/merge_request_changed.html
index dd4059c..479bab3 100644
--- a/Allura/allura/templates/repo/merge_request_changed.html
+++ b/Allura/allura/templates/repo/merge_request_changed.html
@@ -16,7 +16,7 @@
        specific language governing permissions and limitations
        under the License.
 -#}
-{% for field, values in changes.iteritems() %}
+{% for field, values in changes.items() %}
 {% if field == 'Description': %}
 - **{{ field }}**:
 
diff --git a/Allura/allura/templates/widgets/forge_form.html b/Allura/allura/templates/widgets/forge_form.html
index 1ff6702..4f182d1 100644
--- a/Allura/allura/templates/widgets/forge_form.html
+++ b/Allura/allura/templates/widgets/forge_form.html
@@ -25,7 +25,7 @@
   {% if style == 'wide' %}
     {% set extra_width = 4 %}
   {% endif %}
-  {% if errors and not errors.iteritems and show_errors %}
+  {% if errors and not errors.items and show_errors %}
   <div class="grid-{{19 + extra_width}}"><span {{widget.j2_attrs({'class':error_class})}}>{{errors|nl2br}}</span></div>
   {% endif %}
   {% for field in widget.fields %}
diff --git a/Allura/allura/templates/widgets/page_size.html b/Allura/allura/templates/widgets/page_size.html
index baed90a..623a087 100644
--- a/Allura/allura/templates/widgets/page_size.html
+++ b/Allura/allura/templates/widgets/page_size.html
@@ -17,7 +17,7 @@
        under the License.
 -#}
 <form method="get">
-  {% for k,v in widget.url_params.iteritems() %}
+  {% for k,v in widget.url_params.items() %}
     <input type="hidden" name="{{k}}" value="{{v}}"/>
   {% endfor %}
   {% if limit %}
diff --git a/Allura/allura/templates/widgets/search_help.html b/Allura/allura/templates/widgets/search_help.html
index a936e4c..23a85c1 100644
--- a/Allura/allura/templates/widgets/search_help.html
+++ b/Allura/allura/templates/widgets/search_help.html
@@ -24,7 +24,7 @@
 {% if fields %}
     <p>To search on specific fields, use these field names instead of a general text search.  You can group with <code>AND</code> or <code>OR</code>.</p>
     <ul>
-    {% for fld, descr in fields.iteritems() %}
+    {% for fld, descr in fields.items() %}
         <li><b>{{ fld }}:</b>{{ descr }}</li>
     {% endfor %}
     </ul>
diff --git a/Allura/allura/templates/widgets/state_field.html b/Allura/allura/templates/widgets/state_field.html
index 59f6843..3dca679 100644
--- a/Allura/allura/templates/widgets/state_field.html
+++ b/Allura/allura/templates/widgets/state_field.html
@@ -25,7 +25,7 @@
       <span class="{{ error_class }}">{{ ctx.errors }}</span><br/>
     {% endif %}
     {{ selector.display(css_class=selector_cls, **ctx) }}
-    {% for name, field in states.iteritems() %}
+    {% for name, field in states.items() %}
         {% set ctx = widget.context_for(field) %}
         <div class="{{ field_cls }}" data-name="{{ name }}">
             {% if field.show_label %}<label for="{{ ctx.name }}">{{ field.label }}</label><br/>{% endif %}
diff --git a/Allura/allura/templates_responsive/widgets/forge_form.html b/Allura/allura/templates_responsive/widgets/forge_form.html
index 43b72e7..1678a12 100644
--- a/Allura/allura/templates_responsive/widgets/forge_form.html
+++ b/Allura/allura/templates_responsive/widgets/forge_form.html
@@ -29,7 +29,7 @@
       {% if enctype %}enctype="{{enctype}}"{% endif %}
       {% if target %}target="{{target}}"{% endif %}
       action="{{action}}">
-  {% if errors and not errors.iteritems and show_errors %}
+  {% if errors and not errors.items and show_errors %}
   <div class=""><span {{widget.j2_attrs({'class':error_class})}}>{{errors|nl2br}}</span></div>
   {% endif %}
   {% for field in widget.fields %}
diff --git a/ForgeImporters/forgeimporters/templates/project_base.html b/ForgeImporters/forgeimporters/templates/project_base.html
index 09b3d80..65b33b8 100644
--- a/ForgeImporters/forgeimporters/templates/project_base.html
+++ b/ForgeImporters/forgeimporters/templates/project_base.html
@@ -113,14 +113,14 @@
         {% if c.form_errors['tools'] %}
         <div class="error">{{c.form_errors['tools']}}</div>
         {% endif %}
-        {% for name, tool_importer in importer.tool_importers.iteritems() %}
+        {% for name, tool_importer in importer.tool_importers.items() %}
         <div class="tool">
             <img src="{{ tool_importer.tool_icon(g.theme, 48) }}" alt="{{ tool_importer.tool_label }} icon">
             <label>
                 <input name="tools" value="{{name}}" type="checkbox"{% if not c.form_errors or name in c.form_values['tools'] %} checked="checked"{% endif %}/>
                 {{tool_importer.tool_label}}
             </label>
-            {% for option_name, option_label in tool_importer.tool_option.iteritems() %}
+            {% for option_name, option_label in tool_importer.tool_option.items() %}
               <label>
                 <input name="tool_option" value="{{option_name}}" type="checkbox"{% if not c.form_errors or name in c.form_values['tool_option'] %} checked="checked"{% endif %}/>
                 {{option_label}}
diff --git a/ForgeTracker/forgetracker/templates/tracker/admin_fields.html b/ForgeTracker/forgetracker/templates/tracker/admin_fields.html
index 33f05a7..751bb85 100644
--- a/ForgeTracker/forgetracker/templates/tracker/admin_fields.html
+++ b/ForgeTracker/forgetracker/templates/tracker/admin_fields.html
@@ -37,7 +37,7 @@
             <th>Show in list views (e.g. search results, milestone views)</th>
         </tr>
         </thead>
-        {%for column, full_name in columns.iteritems() %}
+        {%for column, full_name in columns.items() %}
         <tr>
             <td>{{full_name}}</td> <td><input type="checkbox" name="{{column}}" {%if globals.show_in_search[column]%}checked {%endif%}></td>
         </tr>
diff --git a/ForgeTracker/forgetracker/templates/tracker_widgets/custom_field_admin_detail.html b/ForgeTracker/forgetracker/templates/tracker_widgets/custom_field_admin_detail.html
index 4dab13d..ef6e5d0 100644
--- a/ForgeTracker/forgetracker/templates/tracker_widgets/custom_field_admin_detail.html
+++ b/ForgeTracker/forgetracker/templates/tracker_widgets/custom_field_admin_detail.html
@@ -19,7 +19,7 @@
 <div class="{{container_cls}}">
   {% set ctx=widget.context_for(selector) %}
   {{selector.display(css_class=selector_cls, **ctx)}}
-  {% for name, field in states.iteritems() %}
+  {% for name, field in states.items() %}
     {% set ctx=widget.context_for(field) %}
     <div class="{{field_cls}}" data-name="{{name}}">
       {{field.display(**ctx)}}


[allura] 06/16: [#8354] webhelpers.paginate -> standalone paginate package

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit de2d4a50bc6ed0202a419f9b0b0953a1466d651d
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 15:31:43 2020 -0500

    [#8354] webhelpers.paginate -> standalone paginate package
---
 Allura/allura/controllers/site_admin.py                   | 2 +-
 Allura/allura/lib/widgets/form_fields.py                  | 6 ++++--
 Allura/allura/templates/widgets/page_list.html            | 2 +-
 ForgeBlog/forgeblog/templates/blog_widgets/page_list.html | 4 ++--
 requirements.in                                           | 1 +
 requirements.txt                                          | 1 +
 6 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 14b5ffd..644c9c9 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -33,6 +33,7 @@ from tg import request
 from formencode import validators, Invalid
 from webob.exc import HTTPNotFound, HTTPFound
 from ming.odm import ThreadLocalORMSession
+import paginate
 
 from allura.app import SitemapEntry
 from allura.lib import helpers as h
@@ -50,7 +51,6 @@ from allura.scripts.delete_projects import DeleteProjects
 import allura
 
 from six.moves.urllib.parse import urlparse
-from webhelpers import paginate
 import six
 from six.moves import range
 from six.moves import map
diff --git a/Allura/allura/lib/widgets/form_fields.py b/Allura/allura/lib/widgets/form_fields.py
index 7610325..b60cee1 100644
--- a/Allura/allura/lib/widgets/form_fields.py
+++ b/Allura/allura/lib/widgets/form_fields.py
@@ -23,7 +23,7 @@ import json
 import logging
 
 from formencode import validators as fev
-from webhelpers import paginate
+import paginate
 
 import ew as ew_core
 import ew.jinja2_ew as ew
@@ -312,7 +312,9 @@ class PageList(ew_core.Widget):
             params['page'] = page - page_offset
             return url(request.path, params)
         return paginate.Page(list(range(count)), page + page_offset, int(limit),
-                             url=page_url)
+                             url=page_url,
+                             url_maker=lambda pagenum: '?page={}&limit={}'.format(pagenum-1, limit)
+                             )
 
     def prepare_context(self, context):
         context = super(PageList, self).prepare_context(context)
diff --git a/Allura/allura/templates/widgets/page_list.html b/Allura/allura/templates/widgets/page_list.html
index 2621601..fbe25a2 100644
--- a/Allura/allura/templates/widgets/page_list.html
+++ b/Allura/allura/templates/widgets/page_list.html
@@ -23,7 +23,7 @@
 {% if pager_output.strip() %}
 <div>
     <div class="page_list">
-      {{ pager_output }}
+      {{ pager_output|safe }}
     </div>
     <div class="clear"></div>
 </div>
diff --git a/ForgeBlog/forgeblog/templates/blog_widgets/page_list.html b/ForgeBlog/forgeblog/templates/blog_widgets/page_list.html
index bbc9725..0681277 100644
--- a/ForgeBlog/forgeblog/templates/blog_widgets/page_list.html
+++ b/ForgeBlog/forgeblog/templates/blog_widgets/page_list.html
@@ -19,10 +19,10 @@
 <div style="margin: 10px">
   {% set paginator = widget.paginator(count, page, limit) %}
   <div style="float: right">
-    {{paginator.pager('$link_previous', symbol_previous='Newer Entries >>')}}
+    {{paginator.pager('$link_previous', symbol_previous='Newer Entries >>')|safe}}
   </div>
   <div style="float: left">
-    {{paginator.pager('$link_next', symbol_next='<< Older Entries')}}
+    {{paginator.pager('$link_next', symbol_next='<< Older Entries')|safe}}
   </div>
   <div style="clear: both"></div>
 </div>
diff --git a/requirements.in b/requirements.in
index fdb3fd4..98e4dc7 100644
--- a/requirements.in
+++ b/requirements.in
@@ -21,6 +21,7 @@ MarkupSafe
 Ming==0.5.6
 # oauth2 doesn't have py3.6 support.  There's a fork with fixes but no pypi releases I can find.  https://github.com/joestump/python-oauth2/issues/223
 oauth2
+paginate
 Paste
 PasteDeploy
 PasteScript
diff --git a/requirements.txt b/requirements.txt
index 04c5a8e..04f722d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -51,6 +51,7 @@ mock==3.0.5
 nose==1.3.7
 oauth2==1.9.0.post1
 oauthlib==3.0.2           # via requests-oauthlib
+paginate==0.5.6
 paste==3.1.0
 pastedeploy==2.0.1
 pastescript==3.1.0


[allura] 15/16: [#8354] antispam fixes for py3

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 9aba823737f416d80735efa792dffed5333eb0f7
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Mon Mar 9 12:23:50 2020 -0400

    [#8354] antispam fixes for py3
---
 Allura/allura/lib/utils.py | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 2494c8b..81d3a26 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -213,14 +213,15 @@ class AntiSpam(object):
             self.timestamp = timestamp if timestamp else int(time.time())
             self.spinner = spinner if spinner else self.make_spinner()
             self.timestamp_text = str(self.timestamp)
-            self.spinner_text = self._wrap(self.spinner)
+            self.spinner_text = six.ensure_text(self._wrap(self.spinner))
         else:
             self.request = request
             self.timestamp_text = request.params['timestamp']
             self.spinner_text = request.params['spinner']
             self.timestamp = int(self.timestamp_text)
             self.spinner = self._unwrap(self.spinner_text)
-        self.spinner_ord = list(map(ord, self.spinner))
+        trans_fn = ord if six.PY2 else int
+        self.spinner_ord = list(map(trans_fn, self.spinner))
         self.random_padding = [random.randint(0, 255) for x in self.spinner]
         self.honey_class = self.enc(self.spinner_text, css_safe=True)
 
@@ -241,20 +242,21 @@ class AntiSpam(object):
         encoding doesn't use hyphens, underscores, colons, nor periods, so we'll
         use these characters to replace its plus, slash, equals, and newline.
         '''
-        s = base64.b64encode(s)
-        s = s.rstrip('=\n')
-        s = s.replace('+', '-').replace('/', '_')
-        s = 'X' + s
+        s = base64.b64encode(six.ensure_binary(s))
+        s = s.rstrip(b'=\n')
+        s = s.replace(b'+', b'-').replace(b'/', b'_')
+        s = b'X' + s
         return s
 
     @staticmethod
     def _unwrap(s):
         s = s[1:]
-        s = s.replace('-', '+').replace('_', '/')
+        s = six.ensure_binary(s)
+        s = s.replace(b'-', b'+').replace(b'_', b'/')
         i = len(s) % 4
         if i > 0:
-            s += '=' * (4 - i)
-        s = base64.b64decode(s + '\n')
+            s += b'=' * (4 - i)
+        s = base64.b64decode(s + b'\n')
         return s
 
     def enc(self, plain, css_safe=False):
@@ -275,6 +277,7 @@ class AntiSpam(object):
         enc = ''.join(six.unichr(p ^ s) for p, s in zip(plain, self.spinner_ord))
         enc = six.ensure_binary(enc)
         enc = self._wrap(enc)
+        enc = six.ensure_text(enc)
         if css_safe:
             enc = ''.join(ch for ch in enc if ch.isalpha())
         return enc
@@ -315,7 +318,7 @@ class AntiSpam(object):
         ip_chunk = '.'.join(octets[0:3])
         plain = '%d:%s:%s' % (
             timestamp, ip_chunk, tg.config.get('spinner_secret', 'abcdef'))
-        return hashlib.sha1(plain).digest()
+        return hashlib.sha1(six.ensure_binary(plain)).digest()
 
     @classmethod
     def validate_request(cls, request=None, now=None, params=None):
@@ -339,11 +342,11 @@ class AntiSpam(object):
                     raise ValueError('Post from the distant past')
                 if obj.spinner != expected_spinner:
                     raise ValueError('Bad spinner value')
-                for k in new_params.keys():
+                for k in list(new_params.keys()):
                     try:
                         new_params[obj.dec(k)] = new_params[k]
                         new_params.pop(k)
-                    except:
+                    except Exception as ex:
                         pass
                 for fldno in range(obj.num_honey):
                     try:


[allura] 04/16: [#8354] change StringIO uses that really are BytesIO

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 7ed67d7cfcf9db10421a33db622523cee96b99e5
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 13:11:00 2020 -0500

    [#8354] change StringIO uses that really are BytesIO
---
 Allura/allura/app.py                               | 10 ++++----
 Allura/allura/controllers/static.py                |  5 ++--
 Allura/allura/lib/decorators.py                    |  6 ++++-
 Allura/allura/lib/helpers.py                       |  5 ++--
 Allura/allura/lib/plugin.py                        |  4 +--
 Allura/allura/model/filesystem.py                  |  4 +--
 Allura/allura/tests/functional/test_admin.py       | 10 ++++----
 .../allura/tests/functional/test_neighborhood.py   |  6 ++---
 Allura/allura/tests/model/test_discussion.py       | 26 +++++++++----------
 Allura/allura/tests/model/test_filesystem.py       | 21 ++++++++-------
 ForgeBlog/forgeblog/tests/test_app.py              |  4 +--
 .../forgediscussion/tests/functional/test_forum.py |  6 ++---
 .../tests/functional/test_forum_admin.py           |  1 -
 ForgeDiscussion/forgediscussion/tests/test_app.py  |  4 +--
 ForgeImporters/forgeimporters/base.py              | 12 ++++-----
 ForgeImporters/forgeimporters/github/tracker.py    |  8 ++----
 .../forgeimporters/tests/github/test_extractor.py  | 30 ++++++++++------------
 .../forgeimporters/tests/github/test_tracker.py    |  2 +-
 ForgeSVN/forgesvn/model/svn.py                     |  4 +--
 ForgeSVN/forgesvn/tests/model/test_repository.py   |  9 +++----
 ForgeTracker/forgetracker/import_support.py        |  4 +--
 .../forgetracker/tests/functional/test_root.py     |  6 ++---
 ForgeTracker/forgetracker/tests/test_app.py        |  4 +--
 ForgeWiki/forgewiki/tests/functional/test_root.py  |  6 ++---
 ForgeWiki/forgewiki/tests/test_app.py              |  4 +--
 25 files changed, 98 insertions(+), 103 deletions(-)

diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index 17e4943..056c776 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -20,7 +20,7 @@ from __future__ import absolute_import
 import os
 import logging
 from urllib import basejoin
-from cStringIO import StringIO
+from io import BytesIO
 from collections import defaultdict
 from xml.etree import ElementTree as ET
 from copy import copy
@@ -50,7 +50,7 @@ from allura.lib.utils import permanent_redirect, ConfigProxy
 from allura import model as M
 from allura.tasks import index_tasks
 import six
-from io import open
+from io import open, BytesIO
 from six.moves import map
 
 log = logging.getLogger(__name__)
@@ -710,7 +710,7 @@ class Application(object):
         if message.get('filename'):
             # Special case - the actual post may not have been created yet
             log.info('Saving attachment %s', message['filename'])
-            fp = StringIO(message['payload'])
+            fp = BytesIO(six.ensure_binary(message['payload']))
             self.AttachmentClass.save_attachment(
                 message['filename'], fp,
                 content_type=message.get(
@@ -728,9 +728,9 @@ class Application(object):
                 message_id)
 
             try:
-                fp = StringIO(message['payload'].encode('utf-8'))
+                fp = BytesIO(message['payload'].encode('utf-8'))
             except UnicodeDecodeError:
-                fp = StringIO(message['payload'])
+                fp = BytesIO(message['payload'])
 
             post.attach(
                 'alternate', fp,
diff --git a/Allura/allura/controllers/static.py b/Allura/allura/controllers/static.py
index fd5404b..720ef77 100644
--- a/Allura/allura/controllers/static.py
+++ b/Allura/allura/controllers/static.py
@@ -17,8 +17,9 @@
 
 from __future__ import unicode_literals
 from __future__ import absolute_import
-from cStringIO import StringIO
+from io import BytesIO
 
+import six
 from tg import expose
 from tg.decorators import without_trailing_slash
 from webob import exc
@@ -55,4 +56,4 @@ class NewForgeController(object):
         """
         css, md5 = g.tool_icon_css
         return utils.serve_file(
-            StringIO(css), 'tool_icon_css', 'text/css', etag=md5)
+            BytesIO(six.ensure_binary(css)), 'tool_icon_css', 'text/css', etag=md5)
diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index 14a5e9c..a4b9bd2 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/decorators.py
@@ -21,7 +21,11 @@ import inspect
 import sys
 import json
 import logging
-from six.moves.http_cookiejar import Cookie
+import six
+if six.PY3:
+    from http.cookies import SimpleCookie as Cookie
+else:
+    from Cookie import Cookie
 from collections import defaultdict
 from six.moves.urllib.parse import unquote
 from datetime import datetime
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 13c14a8..d2506e7 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -23,6 +23,7 @@ import sys
 import os
 import os.path
 import difflib
+
 import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
 import re
 import unicodedata
@@ -37,7 +38,7 @@ from collections import defaultdict
 import shlex
 import socket
 from functools import partial
-from cStringIO import StringIO
+from io import BytesIO
 import cgi
 
 import emoji
@@ -1211,7 +1212,7 @@ def rate_limit(cfg_opt, artifact_count, start_date, exception=None):
 
 def base64uri(content_or_image, image_format='PNG', mimetype='image/png', windows_line_endings=False):
     if hasattr(content_or_image, 'save'):
-        output = StringIO()
+        output = BytesIO()
         content_or_image.save(output, format=image_format)
         content = output.getvalue()
     else:
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 32b6dae..a1b2629 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -29,7 +29,7 @@ import crypt
 import random
 from six.moves.urllib.request import urlopen
 from six.moves.urllib.parse import urlparse
-from cStringIO import StringIO
+from io import BytesIO
 from random import randint
 from hashlib import sha256
 from base64 import b64encode
@@ -1094,7 +1094,7 @@ class ProjectRegistrationProvider(object):
                     troves.append(
                         M.TroveCategory.query.get(trove_cat_id=trove_id)._id)
         if 'icon' in project_template:
-            icon_file = StringIO(urlopen(project_template['icon']['url']).read())
+            icon_file = BytesIO(urlopen(project_template['icon']['url']).read())
             p.save_icon(project_template['icon']['filename'], icon_file)
 
         if user_project:
diff --git a/Allura/allura/model/filesystem.py b/Allura/allura/model/filesystem.py
index 7bd726c..8392a92 100644
--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -19,7 +19,7 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import os
 import re
-from cStringIO import StringIO
+from io import BytesIO
 import logging
 
 import PIL
@@ -103,7 +103,7 @@ class File(MappedClass):
 
     @classmethod
     def from_data(cls, filename, data, **kw):
-        return cls.from_stream(filename, StringIO(data), **kw)
+        return cls.from_stream(filename, BytesIO(data), **kw)
 
     def delete(self):
         self._fs().delete(self.file_id)
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index be3a353..cb3a182 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import
 import os
 import allura
 import pkg_resources
-import StringIO
+from io import BytesIO
 import logging
 from io import open
 
@@ -384,12 +384,12 @@ class TestProjectAdmin(TestController):
                 short_description='A Test Project'),
                 upload_files=[upload])
         r = self.app.get('/p/test/icon')
-        image = PIL.Image.open(StringIO.StringIO(r.body))
+        image = PIL.Image.open(BytesIO(r.body))
         assert image.size == (48, 48)
 
         r = self.app.get('/p/test/icon?foo=bar')
         r = self.app.get('/p/test/icon?w=96')
-        image = PIL.Image.open(StringIO.StringIO(r.body))
+        image = PIL.Image.open(BytesIO(r.body))
         assert image.size == (96, 96)
         r = self.app.get('/p/test/icon?w=12345', status=404)
 
@@ -411,10 +411,10 @@ class TestProjectAdmin(TestController):
         filename = project.get_screenshots()[0].filename
         r = self.app.get('/p/test/screenshot/' + filename)
         uploaded = PIL.Image.open(file_path)
-        screenshot = PIL.Image.open(StringIO.StringIO(r.body))
+        screenshot = PIL.Image.open(BytesIO(r.body))
         assert uploaded.size == screenshot.size
         r = self.app.get('/p/test/screenshot/' + filename + '/thumb')
-        thumb = PIL.Image.open(StringIO.StringIO(r.body))
+        thumb = PIL.Image.open(BytesIO(r.body))
         assert thumb.size == (150, 150)
         # FIX: home pages don't currently support screenshots (now that they're a wiki);
         # reinstate this code (or appropriate) when we have a macro for that
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 7ebf528..1f7034c 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -20,7 +20,7 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import json
 import os
-from cStringIO import StringIO
+from io import BytesIO
 import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
 from io import open
 
@@ -276,7 +276,7 @@ class TestNeighborhood(TestController):
                                       homepage='# MozQ1'),
                           extra_environ=dict(username=str('root')), upload_files=[upload])
         r = self.app.get('/adobe/icon')
-        image = PIL.Image.open(StringIO(r.body))
+        image = PIL.Image.open(BytesIO(r.body))
         assert image.size == (48, 48)
 
         r = self.app.get('/adobe/icon?foo=bar')
@@ -894,7 +894,7 @@ class TestNeighborhood(TestController):
                          foo_id, extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/_admin/awards/%s/icon' %
                          foo_id, extra_environ=dict(username=str('root')))
-        image = PIL.Image.open(StringIO(r.body))
+        image = PIL.Image.open(BytesIO(r.body))
         assert image.size == (48, 48)
         self.app.post('/adobe/_admin/awards/grant',
                       params=dict(grant='FOO', recipient='adobe-1',
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index b4e5f22..3f9906f 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -22,7 +22,7 @@ Model tests for artifact
 """
 from __future__ import unicode_literals
 from __future__ import absolute_import
-from cStringIO import StringIO
+from io import BytesIO
 import time
 from datetime import datetime, timedelta
 from cgi import FieldStorage
@@ -178,14 +178,14 @@ def test_attachment_methods():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     p = t.post('This is a post')
-    p_att = p.attach('foo.text', StringIO('Hello, world!'),
+    p_att = p.attach('foo.text', BytesIO(b'Hello, world!'),
                      discussion_id=d._id,
                      thread_id=t._id,
                      post_id=p._id)
-    t_att = p.attach('foo2.text', StringIO('Hello, thread!'),
+    t_att = p.attach('foo2.text', BytesIO(b'Hello, thread!'),
                      discussion_id=d._id,
                      thread_id=t._id)
-    d_att = p.attach('foo3.text', StringIO('Hello, discussion!'),
+    d_att = p.attach('foo3.text', BytesIO(b'Hello, discussion!'),
                      discussion_id=d._id)
 
     ThreadLocalORMSession.flush_all()
@@ -202,7 +202,7 @@ def test_attachment_methods():
     fs.name = 'file_info'
     fs.filename = 'fake.txt'
     fs.type = 'text/plain'
-    fs.file = StringIO('this is the content of the fake file\n')
+    fs.file = BytesIO(b'this is the content of the fake file\n')
     p = t.post(text='test message', forum=None, subject='', file_info=fs)
     ThreadLocalORMSession.flush_all()
     n = M.Notification.query.get(
@@ -220,12 +220,12 @@ def test_multiple_attachments():
     test_file1.name = 'file_info'
     test_file1.filename = 'test1.txt'
     test_file1.type = 'text/plain'
-    test_file1.file = StringIO('test file1\n')
+    test_file1.file = BytesIO(b'test file1\n')
     test_file2 = FieldStorage()
     test_file2.name = 'file_info'
     test_file2.filename = 'test2.txt'
     test_file2.type = 'text/plain'
-    test_file2.file = StringIO('test file2\n')
+    test_file2.file = BytesIO(b'test file2\n')
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     test_post = t.post('test post')
@@ -243,7 +243,7 @@ def test_add_attachment():
     test_file.name = 'file_info'
     test_file.filename = 'test.txt'
     test_file.type = 'text/plain'
-    test_file.file = StringIO('test file\n')
+    test_file.file = BytesIO(b'test file\n')
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     test_post = t.post('test post')
@@ -262,12 +262,12 @@ def test_notification_two_attaches():
     fs1.name = 'file_info'
     fs1.filename = 'fake.txt'
     fs1.type = 'text/plain'
-    fs1.file = StringIO('this is the content of the fake file\n')
+    fs1.file = BytesIO(b'this is the content of the fake file\n')
     fs2 = FieldStorage()
     fs2.name = 'file_info'
     fs2.filename = 'fake2.txt'
     fs2.type = 'text/plain'
-    fs2.file = StringIO('this is the content of the fake file\n')
+    fs2.file = BytesIO(b'this is the content of the fake file\n')
     p = t.post(text='test message', forum=None, subject='', file_info=[fs1, fs2])
     ThreadLocalORMSession.flush_all()
     n = M.Notification.query.get(
@@ -285,7 +285,7 @@ def test_discussion_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     p = t.post('This is a post')
-    p.attach('foo.text', StringIO(''),
+    p.attach('foo.text', BytesIO(b''),
              discussion_id=d._id,
              thread_id=t._id,
              post_id=p._id)
@@ -302,7 +302,7 @@ def test_thread_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     p = t.post('This is a post')
-    p.attach('foo.text', StringIO(''),
+    p.attach('foo.text', BytesIO(b''),
              discussion_id=d._id,
              thread_id=t._id,
              post_id=p._id)
@@ -315,7 +315,7 @@ def test_post_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
     p = t.post('This is a post')
-    p.attach('foo.text', StringIO(''),
+    p.attach('foo.text', BytesIO(b''),
              discussion_id=d._id,
              thread_id=t._id,
              post_id=p._id)
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index d757a4f..3ff5727 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -21,7 +21,6 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import os
 from unittest import TestCase
-from cStringIO import StringIO
 from io import BytesIO
 
 from tg import tmpl_context as c
@@ -55,7 +54,7 @@ class TestFile(TestCase):
         self.db.fs.chunks.remove()
 
     def test_from_stream(self):
-        f = File.from_stream('test1.txt', StringIO('test1'))
+        f = File.from_stream('test1.txt', BytesIO(b'test1'))
         self.session.flush()
         assert self.db.fs.count() == 1
         assert self.db.fs.files.count() == 1
@@ -65,7 +64,7 @@ class TestFile(TestCase):
         self._assert_content(f, 'test1')
 
     def test_from_data(self):
-        f = File.from_data('test2.txt', 'test2')
+        f = File.from_data('test2.txt', b'test2')
         self.session.flush(f)
         assert self.db.fs.count() == 1
         assert self.db.fs.files.count() == 1
@@ -86,7 +85,7 @@ class TestFile(TestCase):
         assert text.startswith('# -*-')
 
     def test_delete(self):
-        f = File.from_data('test1.txt', 'test1')
+        f = File.from_data('test1.txt', b'test1')
         self.session.flush()
         assert self.db.fs.count() == 1
         assert self.db.fs.files.count() == 1
@@ -98,8 +97,8 @@ class TestFile(TestCase):
         assert self.db.fs.chunks.count() == 0
 
     def test_remove(self):
-        File.from_data('test1.txt', 'test1')
-        File.from_data('test2.txt', 'test2')
+        File.from_data('test1.txt', b'test1')
+        File.from_data('test2.txt', b'test2')
         self.session.flush()
         assert self.db.fs.count() == 2
         assert self.db.fs.files.count() == 2
@@ -111,7 +110,7 @@ class TestFile(TestCase):
         assert self.db.fs.chunks.count() == 1
 
     def test_overwrite(self):
-        f = File.from_data('test1.txt', 'test1')
+        f = File.from_data('test1.txt', b'test1')
         self.session.flush()
         assert self.db.fs.count() == 1
         assert self.db.fs.files.count() == 1
@@ -126,7 +125,7 @@ class TestFile(TestCase):
         self._assert_content(f, 'test2')
 
     def test_serve_embed(self):
-        f = File.from_data('te s\u0b6e1.txt', 'test1')
+        f = File.from_data('te s\u0b6e1.txt', b'test1')
         self.session.flush()
         with patch('allura.lib.utils.tg.request', Request.blank('/')), \
                 patch('allura.lib.utils.tg.response', Response()) as response, \
@@ -139,7 +138,7 @@ class TestFile(TestCase):
             assert 'Content-Disposition' not in response.headers
 
     def test_serve_embed_false(self):
-        f = File.from_data('te s\u0b6e1.txt', 'test1')
+        f = File.from_data('te s\u0b6e1.txt', b'test1')
         self.session.flush()
         with patch('allura.lib.utils.tg.request', Request.blank('/')), \
                 patch('allura.lib.utils.tg.response', Response()) as response, \
@@ -175,7 +174,7 @@ class TestFile(TestCase):
     def test_not_image(self):
         f, t = File.save_image(
             'file.txt',
-            StringIO('blah'),
+            BytesIO(b'blah'),
             thumbnail_size=(16, 16),
             square=True,
             save_original=True)
@@ -185,7 +184,7 @@ class TestFile(TestCase):
     def test_invalid_image(self):
         f, t = File.save_image(
             'bogus.png',
-            StringIO('bogus data here!'),
+            BytesIO(b'bogus data here!'),
             thumbnail_size=(16, 16),
             square=True,
             save_original=True)
diff --git a/ForgeBlog/forgeblog/tests/test_app.py b/ForgeBlog/forgeblog/tests/test_app.py
index 9f32613..26089b9 100644
--- a/ForgeBlog/forgeblog/tests/test_app.py
+++ b/ForgeBlog/forgeblog/tests/test_app.py
@@ -23,10 +23,10 @@ import tempfile
 import json
 import os
 from cgi import FieldStorage
+from io import BytesIO
 
 from nose.tools import assert_equal
 from tg import tmpl_context as c
-from cStringIO import StringIO
 from ming.orm import ThreadLocalORMSession
 
 from allura import model as M
@@ -112,7 +112,7 @@ class TestBulkExport(object):
             test_file1 = FieldStorage()
             test_file1.name = 'file_info'
             test_file1.filename = 'test_file'
-            test_file1.file = StringIO('test file1\n')
+            test_file1.file = BytesIO(b'test file1\n')
             p = post.discussion_thread.add_post(text='test comment')
             p.add_multiple_attachments(test_file1)
             ThreadLocalORMSession.flush_all()
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 8ad92f6..f4b59d6 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -212,13 +212,13 @@ class TestForumMessageHandling(TestController):
         assert_equal(FM.ForumPost.query.find().count(), 3)
 
     def test_attach(self):
-        self._post('testforum', 'Attachment Thread', 'This is a text file',
+        self._post('testforum', 'Attachment Thread', 'This is text attachment',
                    message_id='test.attach.100@domain.net',
                    filename='test.txt',
                    content_type='text/plain')
-        self._post('testforum', 'Test Thread', 'Nothing here',
+        self._post('testforum', 'Test Thread', b'Nothing here',
                    message_id='test.attach.100@domain.net')
-        self._post('testforum', 'Attachment Thread', 'This is a text file',
+        self._post('testforum', 'Attachment Thread', 'This is binary ¶¬¡™£¢¢•º™™¶'.encode('utf-8'),
                    message_id='test.attach.100@domain.net',
                    content_type='text/plain')
 
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
index 41682ec..127e2d2 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
@@ -19,7 +19,6 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import os
 import allura
-from StringIO import StringIO
 import logging
 
 import PIL
diff --git a/ForgeDiscussion/forgediscussion/tests/test_app.py b/ForgeDiscussion/forgediscussion/tests/test_app.py
index 4e842c3..30f9d90 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_app.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_app.py
@@ -26,10 +26,10 @@ import json
 import os
 from operator import attrgetter
 from cgi import FieldStorage
+from io import BytesIO
 
 from nose.tools import assert_equal
 from tg import tmpl_context as c
-from cStringIO import StringIO
 
 from forgediscussion.site_stats import posts_24hr
 from ming.orm import ThreadLocalORMSession
@@ -96,7 +96,7 @@ class TestBulkExport(TestDiscussionApiBase):
         test_file1 = FieldStorage()
         test_file1.name = 'file_info'
         test_file1.filename = 'test_file'
-        test_file1.file = StringIO('test file1\n')
+        test_file1.file = BytesIO(b'test file1\n')
         post.add_attachment(test_file1)
         ThreadLocalORMSession.flush_all()
 
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index f4e78c9..9897910 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -20,6 +20,8 @@ from __future__ import absolute_import
 import os
 import errno
 import logging
+from io import BytesIO
+
 import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
 import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
 from collections import defaultdict
@@ -29,10 +31,6 @@ from datetime import datetime
 import codecs
 from six.moves import filter
 import six
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
 
 from bs4 import BeautifulSoup
 from tg import expose, validate, flash, redirect, config
@@ -616,17 +614,17 @@ class ImportAdminExtension(AdminExtension):
         sidebar_links.append(link)
 
 
-def stringio_parser(page):
+def bytesio_parser(page):
     return {
         'content-type': page.info()['content-type'],
-        'data': StringIO(page.read()),
+        'data': BytesIO(page.read()),
     }
 
 
 class File(object):
 
     def __init__(self, url, filename=None):
-        extractor = ProjectExtractor(None, url, parser=stringio_parser)
+        extractor = ProjectExtractor(None, url, parser=bytesio_parser)
         self.url = url
         self.filename = filename or os.path.basename(urlparse(url).path)
         # try to get the mime-type from the filename first, because
diff --git a/ForgeImporters/forgeimporters/github/tracker.py b/ForgeImporters/forgeimporters/github/tracker.py
index 7db14e5..b796d3c 100644
--- a/ForgeImporters/forgeimporters/github/tracker.py
+++ b/ForgeImporters/forgeimporters/github/tracker.py
@@ -22,11 +22,7 @@ import logging
 from datetime import datetime
 from six.moves.urllib.error import HTTPError
 import six
-
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
+from io import BytesIO
 
 from formencode import validators as fev
 from tg import (
@@ -289,7 +285,7 @@ class Attachment(object):
     def get_file(self, extractor):
         try:
             fp_ish = extractor.urlopen(self.url)
-            fp = StringIO(fp_ish.read())
+            fp = BytesIO(fp_ish.read())
             return fp
         except HTTPError as e:
             if e.code == 404:
diff --git a/ForgeImporters/forgeimporters/tests/github/test_extractor.py b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
index 617e56b..2f31577 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
@@ -19,15 +19,13 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import json
 from unittest import TestCase
+from io import BytesIO
 import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
 
 from mock import patch, Mock
 
 from ... import github
 
-# Can't use cStringIO here, because we cannot set attributes or subclass it,
-# and this is needed in mocked_urlopen below
-from StringIO import StringIO
 from six.moves import zip
 
 
@@ -65,24 +63,24 @@ class TestGitHubProjectExtractor(TestCase):
     def mocked_urlopen(self, url):
         headers = {}
         if url.endswith('/test_project'):
-            response = StringIO(json.dumps(self.PROJECT_INFO))
+            response = BytesIO(json.dumps(self.PROJECT_INFO))
         elif url.endswith('/issues?state=closed'):
-            response = StringIO(json.dumps(self.CLOSED_ISSUES_LIST))
+            response = BytesIO(json.dumps(self.CLOSED_ISSUES_LIST))
         elif url.endswith('/issues?state=open'):
-            response = StringIO(json.dumps(self.OPENED_ISSUES_LIST))
+            response = BytesIO(json.dumps(self.OPENED_ISSUES_LIST))
             headers = {'Link': '</issues?state=open&page=2>; rel="next"'}
         elif url.endswith('/issues?state=open&page=2'):
-            response = StringIO(json.dumps(self.OPENED_ISSUES_LIST_PAGE2))
+            response = BytesIO(json.dumps(self.OPENED_ISSUES_LIST_PAGE2))
         elif url.endswith('/comments'):
-            response = StringIO(json.dumps(self.ISSUE_COMMENTS))
+            response = BytesIO(json.dumps(self.ISSUE_COMMENTS))
             headers = {'Link': '</comments?page=2>; rel="next"'}
         elif url.endswith('/comments?page=2'):
-            response = StringIO(json.dumps(self.ISSUE_COMMENTS_PAGE2))
+            response = BytesIO(json.dumps(self.ISSUE_COMMENTS_PAGE2))
         elif url.endswith('/events'):
-            response = StringIO(json.dumps(self.ISSUE_EVENTS))
+            response = BytesIO(json.dumps(self.ISSUE_EVENTS))
             headers = {'Link': '</events?page=2>; rel="next"'}
         elif url.endswith('/events?page=2'):
-            response = StringIO(json.dumps(self.ISSUE_EVENTS_PAGE2))
+            response = BytesIO(json.dumps(self.ISSUE_EVENTS_PAGE2))
 
         response.info = lambda: headers
         return response
@@ -166,9 +164,9 @@ class TestGitHubProjectExtractor(TestCase):
             'X-RateLimit-Remaining': '0',
             'X-RateLimit-Reset': '1382693522',
         }
-        response_limit_exceeded = StringIO('{}')
+        response_limit_exceeded = BytesIO(b'{}')
         response_limit_exceeded.info = lambda: limit_exceeded_headers
-        response_ok = StringIO('{}')
+        response_ok = BytesIO(b'{}')
         response_ok.info = lambda: {}
         urlopen.side_effect = [response_limit_exceeded, response_ok]
         e = github.GitHubProjectExtractor('test_project')
@@ -182,7 +180,7 @@ class TestGitHubProjectExtractor(TestCase):
         sleep.reset_mock()
         urlopen.reset_mock()
         log.warn.reset_mock()
-        response_ok = StringIO('{}')
+        response_ok = BytesIO(b'{}')
         response_ok.info = lambda: {}
         urlopen.side_effect = [response_ok]
         e.get_page('fake 2')
@@ -202,11 +200,11 @@ class TestGitHubProjectExtractor(TestCase):
         }
 
         def urlopen_side_effect(*a, **kw):
-            mock_resp = StringIO('{}')
+            mock_resp = BytesIO(b'{}')
             mock_resp.info = lambda: {}
             urlopen.side_effect = [mock_resp]
             raise six.moves.urllib.error.HTTPError(
-                'url', 403, 'msg', limit_exceeded_headers, StringIO('{}'))
+                'url', 403, 'msg', limit_exceeded_headers, BytesIO(b'{}'))
         urlopen.side_effect = urlopen_side_effect
         e = github.GitHubProjectExtractor('test_project')
         e.get_page('fake')
diff --git a/ForgeImporters/forgeimporters/tests/github/test_tracker.py b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
index 83da436..3c975a4 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
@@ -136,7 +136,7 @@ class TestTrackerImporter(TestCase):
     def test_get_attachments(self):
         importer = tracker.GitHubTrackerImporter()
         extractor = mock.Mock()
-        extractor.urlopen().read.return_value = 'data'
+        extractor.urlopen().read.return_value = b'data'
         body = 'hello\n' \
             '![cdbpzjc5ex4](https://f.cloud.github.com/assets/979771/1027411/a393ab5e-0e70-11e3-8a38-b93a3df904cf.jpg)\r\n' \
             '![screensh0t](http://f.cl.ly/items/13453x43053r2G0d3x0v/Screen%20Shot%202012-04-28%20at%2010.48.17%20AM.png)'
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index 7370104..b66e1fc 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -27,7 +27,7 @@ import time
 import operator as op
 from subprocess import Popen, PIPE
 from hashlib import sha1
-from cStringIO import StringIO
+from io import BytesIO
 from datetime import datetime
 import tempfile
 from shutil import rmtree
@@ -578,7 +578,7 @@ class SVNImplementation(M.RepositoryImplementation):
         data = self._svn.cat(
             self._url + blob.path(),
             revision=self._revision(blob.commit._id))
-        return StringIO(data)
+        return BytesIO(data)
 
     def blob_size(self, blob):
         try:
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 07b14a5..7ef9de7 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -26,8 +26,9 @@ import pkg_resources
 from itertools import count, product
 from datetime import datetime
 from zipfile import ZipFile
-
+from io import BytesIO
 from collections import defaultdict
+
 from tg import tmpl_context as c, app_globals as g
 import mock
 from nose.tools import assert_equal, assert_in
@@ -921,8 +922,7 @@ class TestCommit(_TestWithRepo):
             return counter.i
         counter.i = 0
         blobs = defaultdict(counter)
-        from cStringIO import StringIO
-        return lambda blob: StringIO(str(blobs[blob.path()]))
+        return lambda blob: BytesIO(str(blobs[blob.path()]))
 
     def test_diffs_file_renames(self):
         def open_blob(blob):
@@ -935,8 +935,7 @@ class TestCommit(_TestWithRepo):
                 # moved from /b/b and modified
                 '/b/a/z': 'Death Star will destroy you\nALL',
             }
-            from cStringIO import StringIO
-            return StringIO(blobs.get(blob.path(), ''))
+            return BytesIO(blobs.get(blob.path(), ''))
         self.repo._impl.open_blob = open_blob
 
         self.repo._impl.commit = mock.Mock(return_value=self.ci)
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index 0fb4103..efe011f 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import
 import logging
 import json
 from datetime import datetime
-from cStringIO import StringIO
+from io import BytesIO
 
 # Non-stdlib imports
 from tg import tmpl_context as c
@@ -67,7 +67,7 @@ class ResettableStream(object):
     def _read_header(self):
         if self.buf is None:
             data = self.fp.read(self.buf_size)
-            self.buf = StringIO(data)
+            self.buf = BytesIO(data)
             self.buf_len = len(data)
             self.stream_pos = self.buf_len
 
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 3ff8512..a5d9fe8 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -23,7 +23,7 @@ import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
 import os
 import time
 import json
-import StringIO
+from io import BytesIO
 import allura
 import mock
 from io import open
@@ -979,11 +979,11 @@ class TestFunctionalController(TrackerTestController):
 
         uploaded = PIL.Image.open(file_path)
         r = self.app.get('/bugs/1/attachment/' + filename)
-        downloaded = PIL.Image.open(StringIO.StringIO(r.body))
+        downloaded = PIL.Image.open(BytesIO(r.body))
         assert uploaded.size == downloaded.size
         r = self.app.get('/bugs/1/attachment/' + filename + '/thumb')
 
-        thumbnail = PIL.Image.open(StringIO.StringIO(r.body))
+        thumbnail = PIL.Image.open(BytesIO(r.body))
         assert thumbnail.size == (100, 100)
 
     def test_sidebar_static_page(self):
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index f1cfd2f..5053990 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -21,11 +21,11 @@ import tempfile
 import json
 import operator
 import os
+from io import BytesIO
 
 from nose.tools import assert_equal, assert_true
 from tg import tmpl_context as c
 from cgi import FieldStorage
-from cStringIO import StringIO
 
 from alluratest.controller import setup_basic_test
 from ming.orm import ThreadLocalORMSession
@@ -102,7 +102,7 @@ class TestBulkExport(TrackerTestController):
         test_file1 = FieldStorage()
         test_file1.name = 'file_info'
         test_file1.filename = 'test_file'
-        test_file1.file = StringIO('test file1\n')
+        test_file1.file = BytesIO(b'test file1\n')
         self.post.add_attachment(test_file1)
         ThreadLocalORMSession.flush_all()
 
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index b2a6022..77e87ef 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -21,7 +21,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import absolute_import
 import os
-import StringIO
+from io import BytesIO
 import allura
 import json
 from io import open
@@ -550,11 +550,11 @@ class TestRootController(TestController):
 
         uploaded = PIL.Image.open(file_path)
         r = self.app.get('/wiki/TEST/attachment/' + filename)
-        downloaded = PIL.Image.open(StringIO.StringIO(r.body))
+        downloaded = PIL.Image.open(BytesIO(r.body))
         assert uploaded.size == downloaded.size
         r = self.app.get('/wiki/TEST/attachment/' + filename + '/thumb')
 
-        thumbnail = PIL.Image.open(StringIO.StringIO(r.body))
+        thumbnail = PIL.Image.open(BytesIO(r.body))
         assert thumbnail.size == (100, 100)
 
         # Make sure thumbnail is absent
diff --git a/ForgeWiki/forgewiki/tests/test_app.py b/ForgeWiki/forgewiki/tests/test_app.py
index bd58982..d7f80e8 100644
--- a/ForgeWiki/forgewiki/tests/test_app.py
+++ b/ForgeWiki/forgewiki/tests/test_app.py
@@ -22,8 +22,8 @@ import tempfile
 import json
 import operator
 import os
+from io import BytesIO
 
-from cStringIO import StringIO
 from nose.tools import assert_equal
 from tg import tmpl_context as c
 from ming.orm import ThreadLocalORMSession
@@ -96,7 +96,7 @@ class TestBulkExport(object):
         self.page.text = 'test_text'
         self.page.mod_date = datetime.datetime(2013, 7, 5)
         self.page.labels = ['test_label1', 'test_label2']
-        self.page.attach('some/path/test_file', StringIO('test string'))
+        self.page.attach('some/path/test_file', BytesIO(b'test string'))
         ThreadLocalORMSession.flush_all()
 
     def test_bulk_export_with_attachmetns(self):


[allura] 13/16: [#8354] avoid error: dictionary changed size during iteration

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 2f3961b4e796391d93c56d69085939f556393a43
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 17:52:12 2020 -0500

    [#8354] avoid error: dictionary changed size during iteration
---
 Allura/allura/websetup/bootstrap.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 46c8832..9280175 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -290,7 +290,7 @@ def wipe_database():
                 continue
             log.info('Wiping database %s', database)
             db = conn[database]
-            for coll in db.collection_names():
+            for coll in list(db.collection_names()):
                 if coll.startswith('system.'):
                     continue
                 log.info('Dropping collection %s:%s', database, coll)
@@ -304,7 +304,7 @@ def clear_all_database_tables():
     conn = M.main_doc_session.bind.conn
     for db in conn.database_names():
         db = conn[db]
-        for coll in db.collection_names():
+        for coll in list(db.collection_names()):
             if coll == 'system.indexes':
                 continue
             db.drop_collection(coll)


[allura] 03/16: [#8354] fix six cookie import

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8354
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 0c530a438675a2937ca49a19078cc2e63bad1618
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Fri Mar 6 12:29:23 2020 -0500

    [#8354] fix six cookie import
---
 Allura/allura/lib/decorators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index 6a33304..14a5e9c 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/decorators.py
@@ -21,7 +21,7 @@ import inspect
 import sys
 import json
 import logging
-from six.moves.http_cookies import Cookie
+from six.moves.http_cookiejar import Cookie
 from collections import defaultdict
 from six.moves.urllib.parse import unquote
 from datetime import datetime