aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/tools
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r--mediagoblin/tools/crypto.py12
-rw-r--r--mediagoblin/tools/exif.py15
-rw-r--r--mediagoblin/tools/federation.py91
-rw-r--r--mediagoblin/tools/mail.py20
-rw-r--r--mediagoblin/tools/metadata.py3
-rw-r--r--mediagoblin/tools/pagination.py6
-rw-r--r--mediagoblin/tools/processing.py15
-rw-r--r--mediagoblin/tools/response.py3
-rw-r--r--mediagoblin/tools/routing.py28
-rw-r--r--mediagoblin/tools/staticdirect.py4
-rw-r--r--mediagoblin/tools/template.py22
-rw-r--r--mediagoblin/tools/timesince.py14
-rw-r--r--mediagoblin/tools/transition.py21
-rw-r--r--mediagoblin/tools/translate.py17
-rw-r--r--mediagoblin/tools/url.py4
-rw-r--r--mediagoblin/tools/workbench.py10
16 files changed, 213 insertions, 72 deletions
diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py
index b219a484..1107e200 100644
--- a/mediagoblin/tools/crypto.py
+++ b/mediagoblin/tools/crypto.py
@@ -36,7 +36,7 @@ try:
except AttributeError:
getrandbits = random.getrandbits
-
+# TODO: This should be attached to the MediaGoblinApp
__itsda_secret = None
@@ -51,7 +51,7 @@ def load_key(filename):
def create_key(key_dir, key_filepath):
global __itsda_secret
- old_umask = os.umask(077)
+ old_umask = os.umask(0o77)
key_file = None
try:
if not os.path.isdir(key_dir):
@@ -60,7 +60,7 @@ def create_key(key_dir, key_filepath):
key = str(getrandbits(192))
key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin',
delete=False)
- key_file.write(key)
+ key_file.write(key.encode('ascii'))
key_file.flush()
os.rename(key_file.name, key_filepath)
key_file.close()
@@ -73,13 +73,13 @@ def create_key(key_dir, key_filepath):
_log.info("Saved new key for It's Dangerous")
-def setup_crypto():
+def setup_crypto(app_config):
global __itsda_secret
- key_dir = mg_globals.app_config["crypto_path"]
+ key_dir = app_config["crypto_path"]
key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin')
try:
load_key(key_filepath)
- except IOError, error:
+ except IOError as error:
if error.errno != errno.ENOENT:
raise
create_key(key_dir, key_filepath)
diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py
index 50f1aabf..ec83f43c 100644
--- a/mediagoblin/tools/exif.py
+++ b/mediagoblin/tools/exif.py
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import six
+
from exifread import process_file
from exifread.utils import Ratio
@@ -75,7 +77,7 @@ def extract_exif(filename):
Returns EXIF tags found in file at ``filename``
"""
try:
- with file(filename) as image:
+ with open(filename, 'rb') as image:
return process_file(image, details=False)
except IOError:
raise BadMediaFail(_('Could not read the image file.'))
@@ -94,7 +96,7 @@ def clean_exif(exif):
'Thumbnail JPEGInterchangeFormat']
return dict((key, _ifd_tag_to_dict(value)) for (key, value)
- in exif.iteritems() if key not in disabled_tags)
+ in six.iteritems(exif) if key not in disabled_tags)
def _ifd_tag_to_dict(tag):
@@ -110,7 +112,7 @@ def _ifd_tag_to_dict(tag):
'field_length': tag.field_length,
'values': None}
- if isinstance(tag.printable, str):
+ if isinstance(tag.printable, six.binary_type):
# Force it to be decoded as UTF-8 so that it'll fit into the DB
data['printable'] = tag.printable.decode('utf8', 'replace')
@@ -118,7 +120,7 @@ def _ifd_tag_to_dict(tag):
data['values'] = [_ratio_to_list(val) if isinstance(val, Ratio) else val
for val in tag.values]
else:
- if isinstance(tag.values, str):
+ if isinstance(tag.values, six.binary_type):
# Force UTF-8, so that it fits into the DB
data['values'] = tag.values.decode('utf8', 'replace')
else:
@@ -132,7 +134,8 @@ def _ratio_to_list(ratio):
def get_useful(tags):
- return dict((key, tag) for (key, tag) in tags.iteritems())
+ from collections import OrderedDict
+ return OrderedDict((key, tag) for (key, tag) in six.iteritems(tags))
def get_gps_data(tags):
@@ -149,7 +152,7 @@ def get_gps_data(tags):
'latitude': tags['GPS GPSLatitude'],
'longitude': tags['GPS GPSLongitude']}
- for key, dat in dms_data.iteritems():
+ for key, dat in six.iteritems(dms_data):
gps_data[key] = (
lambda v:
float(v[0].num) / float(v[0].den) \
diff --git a/mediagoblin/tools/federation.py b/mediagoblin/tools/federation.py
new file mode 100644
index 00000000..6c2d27da
--- /dev/null
+++ b/mediagoblin/tools/federation.py
@@ -0,0 +1,91 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2014 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.models import Activity, Generator, User
+
+def create_generator(request):
+ """
+ This creates a Generator object based on the Client associated with the
+ OAuth credentials used. If the request has invalid OAuth credentials or
+ no OAuth credentials None is returned.
+ """
+ if not hasattr(request, "access_token"):
+ return None
+
+ client = request.access_token.get_requesttoken.get_client
+
+ # Check if there is a generator already
+ generator = Generator.query.filter_by(
+ name=client.application_name,
+ object_type="client"
+ ).first()
+ if generator is None:
+ generator = Generator(
+ name=client.application_name,
+ object_type="client"
+ )
+ generator.save()
+
+ return generator
+
+
+
+def create_activity(verb, obj, actor, target=None, generator=None):
+ """
+ This will create an Activity object which for the obj if possible
+ and save it. The verb should be one of the following:
+ add, author, create, delete, dislike, favorite, follow
+ like, post, share, unfollow, unfavorite, unlike, unshare,
+ update, tag.
+
+ If none of those fit you might not want/need to create an activity for
+ the object. The list is in mediagoblin.db.models.Activity.VALID_VERBS
+ """
+ # exception when we try and generate an activity with an unknow verb
+ # could change later to allow arbitrary verbs but at the moment we'll play
+ # it safe.
+
+ if verb not in Activity.VALID_VERBS:
+ raise ValueError("A invalid verb type has been supplied.")
+
+ if generator is None:
+ # This should exist as we're creating it by the migration for Generator
+ generator = Generator.query.filter_by(name="GNU MediaGoblin").first()
+ if generator is None:
+ generator = Generator(
+ name="GNU MediaGoblin",
+ object_type="service"
+ )
+ generator.save()
+
+ activity = Activity(verb=verb)
+ activity.set_object(obj)
+
+ if target is not None:
+ activity.set_target(target)
+
+ # If they've set it override the actor from the obj.
+ activity.actor = actor.id if isinstance(actor, User) else actor
+
+ activity.generator = generator.id
+ activity.save()
+
+ # Sigh want to do this prior to save but I can't figure a way to get
+ # around relationship() not looking up object when model isn't saved.
+ if activity.generate_content():
+ activity.save()
+
+ return activity
diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py
index ab355835..ab3e0eaa 100644
--- a/mediagoblin/tools/mail.py
+++ b/mediagoblin/tools/mail.py
@@ -14,11 +14,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import print_function, unicode_literals
+
import six
import smtplib
import sys
-from email.MIMEText import MIMEText
from mediagoblin import mg_globals, messages
+from mediagoblin._compat import MIMEText
from mediagoblin.tools import common
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -130,12 +132,12 @@ def send_email(from_addr, to_addrs, subject, message_body):
EMAIL_TEST_INBOX.append(message)
elif mg_globals.app_config['email_debug_mode']:
- print u"===== Email ====="
- print u"From address: %s" % message['From']
- print u"To addresses: %s" % message['To']
- print u"Subject: %s" % message['Subject']
- print u"-- Body: --"
- print message.get_payload(decode=True)
+ print("===== Email =====")
+ print("From address: %s" % message['From'])
+ print("To addresses: %s" % message['To'])
+ print("Subject: %s" % message['Subject'])
+ print("-- Body: --")
+ print(message.get_payload(decode=True))
return mhost.sendmail(from_addr, to_addrs, message.as_string())
@@ -162,5 +164,5 @@ def email_debug_message(request):
if mg_globals.app_config['email_debug_mode']:
# DEBUG message, no need to translate
messages.add_message(request, messages.DEBUG,
- u"This instance is running in email debug mode. "
- u"The email will be on the console of the server process.")
+ "This instance is running in email debug mode. "
+ "The email will be on the console of the server process.")
diff --git a/mediagoblin/tools/metadata.py b/mediagoblin/tools/metadata.py
index bfefcac9..aeb4f829 100644
--- a/mediagoblin/tools/metadata.py
+++ b/mediagoblin/tools/metadata.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from io import open
import os
import copy
import json
@@ -102,7 +103,7 @@ def load_resource(package, resource_path):
os.path.sep.
"""
filename = resource_filename(package, os.path.sep.join(resource_path))
- return file(filename).read()
+ return open(filename, encoding="utf-8").read()
def load_resource_json(package, resource_path):
"""
diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py
index 855878e0..a525caf7 100644
--- a/mediagoblin/tools/pagination.py
+++ b/mediagoblin/tools/pagination.py
@@ -17,9 +17,11 @@
import urllib
import copy
from math import ceil, floor
-from itertools import izip, count
+from itertools import count
from werkzeug.datastructures import MultiDict
+from six.moves import zip
+
PAGINATION_DEFAULT_PER_PAGE = 30
@@ -52,7 +54,7 @@ class Pagination(object):
if jump_to_id:
cursor = copy.copy(self.cursor)
- for (doc, increment) in izip(cursor, count(0)):
+ for (doc, increment) in list(zip(cursor, count(0))):
if doc.id == jump_to_id:
self.page = 1 + int(floor(increment / self.per_page))
diff --git a/mediagoblin/tools/processing.py b/mediagoblin/tools/processing.py
index 2abe6452..26d7bb9b 100644
--- a/mediagoblin/tools/processing.py
+++ b/mediagoblin/tools/processing.py
@@ -18,8 +18,7 @@ import logging
import json
import traceback
-from urllib2 import urlopen, Request, HTTPError
-from urllib import urlencode
+from six.moves.urllib import request, parse
_log = logging.getLogger(__name__)
@@ -37,10 +36,10 @@ def create_post_request(url, data, **kw):
data_parser: The parser function that is used to parse the `data`
argument
'''
- data_parser = kw.get('data_parser', urlencode)
+ data_parser = kw.get('data_parser', parse.urlencode)
headers = kw.get('headers', {})
- return Request(url, data_parser(data), headers=headers)
+ return request.Request(url, data_parser(data), headers=headers)
def json_processing_callback(entry):
@@ -48,12 +47,12 @@ def json_processing_callback(entry):
Send an HTTP post to the registered callback url, if any.
'''
if not entry.processing_metadata:
- _log.debug('No processing callback for {0}'.format(entry))
+ _log.debug('No processing callback URL for {0}'.format(entry))
return
url = entry.processing_metadata[0].callback_url
- _log.debug('Sending processing callback for {0} ({1})'.format(
+ _log.debug('Sending processing callback for {0} to {1}'.format(
entry,
url))
@@ -76,11 +75,11 @@ def json_processing_callback(entry):
data_parser=json.dumps)
try:
- urlopen(request)
+ request.urlopen(request)
_log.debug('Processing callback for {0} sent'.format(entry))
return True
- except HTTPError:
+ except request.HTTPError:
_log.error('Failed to send callback: {0}'.format(
traceback.format_exc()))
diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py
index 88270265..889938a8 100644
--- a/mediagoblin/tools/response.py
+++ b/mediagoblin/tools/response.py
@@ -16,6 +16,7 @@
import json
+import six
import werkzeug.utils
from werkzeug.wrappers import Response as wz_Response
from mediagoblin.tools.template import render_template
@@ -153,7 +154,7 @@ def json_response(serializable, _disable_cors=False, *args, **kw):
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'}
- for key, value in cors_headers.iteritems():
+ for key, value in six.iteritems(cors_headers):
response.headers.set(key, value)
return response
diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py
index a15795fe..ae7c7154 100644
--- a/mediagoblin/tools/routing.py
+++ b/mediagoblin/tools/routing.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
+import urlparse
import six
from werkzeug.routing import Map, Rule
@@ -27,15 +28,22 @@ url_map = Map()
class MGRoute(Rule):
- def __init__(self, endpoint, url, controller):
+ def __init__(self, endpoint, url, controller, match_slash=True):
Rule.__init__(self, url, endpoint=endpoint)
self.gmg_controller = controller
+ self.match_slash = match_slash
def empty(self):
new_rule = Rule.empty(self)
new_rule.gmg_controller = self.gmg_controller
return new_rule
+ def match(self, path, *args, **kwargs):
+ if not (self.match_slash or path.endswith("/")):
+ path = path + "/"
+
+ return super(MGRoute, self).match(path, *args, **kwargs)
+
def endpoint_to_controller(rule):
endpoint = rule.endpoint
@@ -51,11 +59,11 @@ def endpoint_to_controller(rule):
return view_func
-def add_route(endpoint, url, controller):
+def add_route(endpoint, url, controller, *args, **kwargs):
"""
Add a route to the url mapping
"""
- url_map.add(MGRoute(endpoint, url, controller))
+ url_map.add(MGRoute(endpoint, url, controller, *args, **kwargs))
def mount(mountpoint, routes):
@@ -65,3 +73,17 @@ def mount(mountpoint, routes):
for endpoint, url, controller in routes:
url = "%s/%s" % (mountpoint.rstrip('/'), url.lstrip('/'))
add_route(endpoint, url, controller)
+
+def extract_url_arguments(url, urlmap):
+ """
+ This extracts the URL arguments from a given URL
+ """
+ parsed_url = urlparse.urlparse(url)
+ map_adapter = urlmap.bind(
+ server_name=parsed_url.netloc,
+ script_name=parsed_url.path,
+ url_scheme=parsed_url.scheme,
+ path_info=parsed_url.path
+ )
+
+ return map_adapter.match()[1]
diff --git a/mediagoblin/tools/staticdirect.py b/mediagoblin/tools/staticdirect.py
index 8381b8b6..881dd20e 100644
--- a/mediagoblin/tools/staticdirect.py
+++ b/mediagoblin/tools/staticdirect.py
@@ -24,6 +24,8 @@
import logging
+import six
+
_log = logging.getLogger(__name__)
@@ -48,7 +50,7 @@ class StaticDirect(object):
def __init__(self, domains):
self.domains = dict(
[(key, value.rstrip('/'))
- for key, value in domains.iteritems()])
+ for key, value in six.iteritems(domains)])
self.cache = {}
def __call__(self, filepath, domain=None):
diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py
index e5acdf45..f2619808 100644
--- a/mediagoblin/tools/template.py
+++ b/mediagoblin/tools/template.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import six
import jinja2
from jinja2.ext import Extension
@@ -28,16 +29,14 @@ from mediagoblin import _version
from mediagoblin.tools import common
from mediagoblin.tools.translate import is_rtl
from mediagoblin.tools.translate import set_thread_locale
-from mediagoblin.tools.translate import get_locale_from_request
from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
from mediagoblin.tools.timesince import timesince
from mediagoblin.meddleware.csrf import render_csrf_form_token
-
SETUP_JINJA_ENVS = {}
-def get_jinja_env(template_loader, locale):
+def get_jinja_env(app, template_loader, locale):
"""
Set up the Jinja environment,
@@ -54,7 +53,7 @@ def get_jinja_env(template_loader, locale):
# The default config does not require a [jinja2] block.
# You may create one if you wish to enable additional jinja2 extensions,
# see example in config_spec.ini
- jinja2_config = mg_globals.global_config.get('jinja2', {})
+ jinja2_config = app.global_config.get('jinja2', {})
local_exts = jinja2_config.get('extensions', [])
# jinja2.StrictUndefined will give exceptions on references
@@ -66,9 +65,12 @@ def get_jinja_env(template_loader, locale):
'jinja2.ext.i18n', 'jinja2.ext.autoescape',
TemplateHookExtension] + local_exts)
- template_env.install_gettext_callables(
- mg_globals.thread_scope.translations.ugettext,
- mg_globals.thread_scope.translations.ungettext)
+ if six.PY2:
+ template_env.install_gettext_callables(mg_globals.thread_scope.translations.ugettext,
+ mg_globals.thread_scope.translations.ungettext)
+ else:
+ template_env.install_gettext_callables(mg_globals.thread_scope.translations.gettext,
+ mg_globals.thread_scope.translations.ngettext)
# All templates will know how to ...
# ... fetch all waiting messages and remove them from the queue
@@ -76,10 +78,10 @@ def get_jinja_env(template_loader, locale):
# ... have access to the global and app config
# ... determine if the language is rtl or ltr
template_env.globals['fetch_messages'] = messages.fetch_messages
- template_env.globals['app_config'] = mg_globals.app_config
- template_env.globals['global_config'] = mg_globals.global_config
+ template_env.globals['app_config'] = app.app_config
+ template_env.globals['global_config'] = app.global_config
template_env.globals['version'] = _version.__version__
- template_env.globals['auth'] = mg_globals.app.auth
+ template_env.globals['auth'] = app.auth
template_env.globals['is_rtl'] = is_rtl(locale)
template_env.filters['urlencode'] = url_quote_plus
diff --git a/mediagoblin/tools/timesince.py b/mediagoblin/tools/timesince.py
index b761c1be..7a8b3ff0 100644
--- a/mediagoblin/tools/timesince.py
+++ b/mediagoblin/tools/timesince.py
@@ -33,18 +33,6 @@ import pytz
from mediagoblin.tools.translate import pass_to_ugettext, lazy_pass_to_ungettext as _
-"""UTC time zone as a tzinfo instance."""
-utc = pytz.utc if pytz else UTC()
-
-def is_aware(value):
- """
- Determines if a given datetime.datetime is aware.
-
- The logic is described in Python's docs:
- http://docs.python.org/library/datetime.html#datetime.tzinfo
- """
- return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
-
def timesince(d, now=None, reversed=False):
"""
Takes two datetime objects and returns the time between d and now
@@ -73,7 +61,7 @@ def timesince(d, now=None, reversed=False):
now = datetime.datetime(now.year, now.month, now.day)
if not now:
- now = datetime.datetime.now(utc if is_aware(d) else None)
+ now = datetime.datetime.utcnow()
delta = (d - now) if reversed else (now - d)
# ignore microseconds
diff --git a/mediagoblin/tools/transition.py b/mediagoblin/tools/transition.py
new file mode 100644
index 00000000..a8041b89
--- /dev/null
+++ b/mediagoblin/tools/transition.py
@@ -0,0 +1,21 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import os
+
+# one global to disable them all
+DISABLE_GLOBALS = os.environ.get("DISABLE_GLOBALS", "false").lower() == "true"
diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py
index f77351b5..a5e56cfe 100644
--- a/mediagoblin/tools/translate.py
+++ b/mediagoblin/tools/translate.py
@@ -17,6 +17,7 @@
import gettext
import pkg_resources
+import six
from babel import localedata
from babel.support import LazyProxy
@@ -52,9 +53,9 @@ class ReallyLazyProxy(LazyProxy):
"""
Like LazyProxy, except that it doesn't cache the value ;)
"""
- @property
- def value(self):
- return self._func(*self._args, **self._kwargs)
+ def __init__(self, func, *args, **kwargs):
+ super(ReallyLazyProxy, self).__init__(func, *args, **kwargs)
+ object.__setattr__(self, '_is_cache_enabled', False)
def __repr__(self):
return "<%s for %s(%r, %r)>" % (
@@ -146,8 +147,9 @@ def pass_to_ugettext(*args, **kwargs):
The reason we can't have a global ugettext method is because
mg_globals gets swapped out by the application per-request.
"""
- return mg_globals.thread_scope.translations.ugettext(
- *args, **kwargs)
+ if six.PY2:
+ return mg_globals.thread_scope.translations.ugettext(*args, **kwargs)
+ return mg_globals.thread_scope.translations.gettext(*args, **kwargs)
def pass_to_ungettext(*args, **kwargs):
"""
@@ -156,8 +158,9 @@ def pass_to_ungettext(*args, **kwargs):
The reason we can't have a global ugettext method is because
mg_globals gets swapped out by the application per-request.
"""
- return mg_globals.thread_scope.translations.ungettext(
- *args, **kwargs)
+ if six.PY2:
+ return mg_globals.thread_scope.translations.ungettext(*args, **kwargs)
+ return mg_globals.thread_scope.translations.ngettext(*args, **kwargs)
def lazy_pass_to_ugettext(*args, **kwargs):
diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py
index 657c0373..4d97247a 100644
--- a/mediagoblin/tools/url.py
+++ b/mediagoblin/tools/url.py
@@ -17,6 +17,8 @@
import re
from unidecode import unidecode
+import six
+
_punct_re = re.compile(r'[\t !"#:$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
@@ -27,4 +29,4 @@ def slugify(text, delim=u'-'):
result = []
for word in _punct_re.split(text.lower()):
result.extend(unidecode(word).split())
- return unicode(delim.join(result))
+ return six.text_type(delim.join(result))
diff --git a/mediagoblin/tools/workbench.py b/mediagoblin/tools/workbench.py
index 0bd4096b..f1ad6414 100644
--- a/mediagoblin/tools/workbench.py
+++ b/mediagoblin/tools/workbench.py
@@ -18,10 +18,15 @@ import os
import shutil
import tempfile
+import six
+
+from mediagoblin._compat import py2_unicode
# Actual workbench stuff
# ----------------------
+
+@py2_unicode
class Workbench(object):
"""
Represent the directory for the workbench
@@ -36,11 +41,8 @@ class Workbench(object):
"""
self.dir = dir
- def __unicode__(self):
- return unicode(self.dir)
-
def __str__(self):
- return str(self.dir)
+ return six.text_type(self.dir)
def __repr__(self):
try: