diff options
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r-- | mediagoblin/tools/crypto.py | 12 | ||||
-rw-r--r-- | mediagoblin/tools/exif.py | 15 | ||||
-rw-r--r-- | mediagoblin/tools/federation.py | 91 | ||||
-rw-r--r-- | mediagoblin/tools/mail.py | 20 | ||||
-rw-r--r-- | mediagoblin/tools/metadata.py | 3 | ||||
-rw-r--r-- | mediagoblin/tools/pagination.py | 6 | ||||
-rw-r--r-- | mediagoblin/tools/processing.py | 15 | ||||
-rw-r--r-- | mediagoblin/tools/response.py | 3 | ||||
-rw-r--r-- | mediagoblin/tools/routing.py | 28 | ||||
-rw-r--r-- | mediagoblin/tools/staticdirect.py | 4 | ||||
-rw-r--r-- | mediagoblin/tools/template.py | 22 | ||||
-rw-r--r-- | mediagoblin/tools/timesince.py | 14 | ||||
-rw-r--r-- | mediagoblin/tools/transition.py | 21 | ||||
-rw-r--r-- | mediagoblin/tools/translate.py | 17 | ||||
-rw-r--r-- | mediagoblin/tools/url.py | 4 | ||||
-rw-r--r-- | mediagoblin/tools/workbench.py | 10 |
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: |