diff options
author | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-15 04:04:55 (GMT) |
---|---|---|
committer | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-15 04:04:55 (GMT) |
commit | b4560a8504bc2d3daffe302ca2be1c0e50acb9a7 (patch) | |
tree | 8504e2137f5d40f785468b220feb278635704f0f | |
parent | 0b3bc7dabbe9c3bf344174539fdcef608a894bbf (diff) |
Use util from ad module
-rw-r--r-- | restful_document/__init__.py | 2 | ||||
-rw-r--r-- | restful_document/document.py | 2 | ||||
-rw-r--r-- | restful_document/env.py | 8 | ||||
-rw-r--r-- | restful_document/metadata.py | 7 | ||||
-rw-r--r-- | restful_document/router.py | 5 | ||||
-rw-r--r-- | restful_document/server.py | 5 | ||||
-rw-r--r-- | restful_document/user.py | 5 | ||||
-rw-r--r-- | restful_document/util.py | 809 | ||||
-rwxr-xr-x | tests/units/user.py | 5 |
9 files changed, 27 insertions, 821 deletions
diff --git a/restful_document/__init__.py b/restful_document/__init__.py index 22f427e..4ceff8f 100644 --- a/restful_document/__init__.py +++ b/restful_document/__init__.py @@ -13,7 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from active_document import util from restful_document.document import Document, restful_method from restful_document.metadata import Metadata, Method from restful_document.router import Router from restful_document.user import User +from restful_document.env import principal, request diff --git a/restful_document/document.py b/restful_document/document.py index 2f9adca..d3cae55 100644 --- a/restful_document/document.py +++ b/restful_document/document.py @@ -16,10 +16,10 @@ from gettext import gettext as _ import active_document as ad +enforce = ad.util.enforce from restful_document import env from restful_document.metadata import restful_method -from restful_document.util import enforce class Document(ad.Document): diff --git a/restful_document/env.py b/restful_document/env.py index 52f04d7..a1fb97f 100644 --- a/restful_document/env.py +++ b/restful_document/env.py @@ -18,8 +18,9 @@ import threading from urlparse import parse_qsl from gettext import gettext as _ -from restful_document import util -from restful_document.util import enforce +import active_document as ad +enforce = ad.util.enforce +util = ad.util _default = object() @@ -187,9 +188,12 @@ class Principal(threading.local): """Authenticated user.""" _cache = {} + _user = None @property def user(self): + if self._user is not None: + return self._user enforce('sugar_user' in request and 'sugar_user_signature' in request, Unauthorized, _('Sugar user credentials were not specified')) signature = request['sugar_user_signature'] diff --git a/restful_document/metadata.py b/restful_document/metadata.py index 9fa0669..8cd9e47 100644 --- a/restful_document/metadata.py +++ b/restful_document/metadata.py @@ -16,8 +16,11 @@ import types from gettext import gettext as _ -from restful_document import util, env -from restful_document.util import enforce +import active_document as ad +enforce = ad.util.enforce +util = ad.util + +from restful_document import env def restful_method(**kwargs): diff --git a/restful_document/router.py b/restful_document/router.py index 367e335..1725e15 100644 --- a/restful_document/router.py +++ b/restful_document/router.py @@ -20,10 +20,11 @@ import logging from gettext import gettext as _ import active_document as ad +enforce = ad.util.enforce +util = ad.util -from restful_document import util, env +from restful_document import env from restful_document.metadata import Metadata -from restful_document.util import enforce _logger = logging.getLogger('rd.router') diff --git a/restful_document/server.py b/restful_document/server.py index caa79b7..28562a2 100644 --- a/restful_document/server.py +++ b/restful_document/server.py @@ -26,9 +26,10 @@ from gevent.wsgi import WSGIServer import active_document as ad import restful_document as rd +enforce = ad.util.enforce +util = ad.util -from restful_document import env, util, printf -from restful_document.util import enforce +from restful_document import env, printf class Server(object): diff --git a/restful_document/user.py b/restful_document/user.py index 300c0ea..6135328 100644 --- a/restful_document/user.py +++ b/restful_document/user.py @@ -20,11 +20,12 @@ from gettext import gettext as _ from M2Crypto import DSA import active_document as ad +enforce = ad.util.enforce +util = ad.util -from restful_document import env, util +from restful_document import env from restful_document.metadata import restful_method from restful_document.document import Document -from restful_document.util import enforce class User(Document): diff --git a/restful_document/util.py b/restful_document/util.py deleted file mode 100644 index aebd86e..0000000 --- a/restful_document/util.py +++ /dev/null @@ -1,809 +0,0 @@ -# Copyright (C) 2011, Aleksey Lim -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -"""Swiss knife module. - -$Repo: git://git.sugarlabs.org/alsroot/codelets.git$ -$File: src/util.py$ -$Data: 2012-01-05$ - -""" - -import os -import re -import sys -import fcntl -import shutil -import atexit -import logging -import tempfile -import datetime -import subprocess -from os.path import exists, join, islink, isdir, dirname, basename, lexists -from os.path import abspath, expanduser -from gettext import gettext as _ - -try: - import json - if not hasattr(json, 'dumps'): - raise ImportError() -except ImportError: - import simplejson as json - - -def enforce(condition, error=None, *args): - """Make an assertion in runtime. - - In comparing with `assert`, it will all time present in the code. - Just a bit of syntax sugar. - - :param condition: - the condition to assert; if not False then return, - otherse raise an RuntimeError exception - :param error: - error message to pass to RuntimeError object - or Exception class to raise - :param args: - optional '%' arguments for the `error` - - """ - if condition: - return - - if isinstance(error, type): - exception_class = error - if args: - error = args[0] - args = args[1:] - else: - error = None - else: - exception_class = RuntimeError - - if args: - error = error % args - elif not error: - # pylint: disable-msg=W0212 - frame = sys._getframe(1) - error = _('Runtime assertion failed at %s:%s') % \ - (frame.f_globals['__file__'], frame.f_lineno - 1) - - raise exception_class(error) - - -def exception(*args): - """Log about exception on low log level. - - That might be useful for non-critial exception. Input arguments are the - same as for `logging.exception` function. - - :param args: - optional arguments to pass to logging function; - the first argument might be a `logging.Logger` to use instead of - using direct `logging` calls - - """ - if args and isinstance(args[0], logging.Logger): - logger = args[0] - args = args[1:] - else: - logger = logging - - klass, error, tb = sys.exc_info() - - import traceback - tb = [i.rstrip() for i in traceback.format_exception(klass, error, tb)] - - error = str(error) or _('Something weird happened') - if args: - if len(args) == 1: - message = args[0] - else: - message = args[0] % args[1:] - error = '%s: %s' % (message, error) - - logger.error(error) - logger.debug('\n'.join(tb)) - - -def assert_call(cmd, stdin=None, **kwargs): - """Variant of `call` method with raising exception of errors. - - :param cmd: - commad to execute, might be string or argv list - :param stdin: - text that will be used as an input for executed process - - """ - return call(cmd, stdin=stdin, asserts=True, **kwargs) - - -def call(cmd, stdin=None, asserts=False, raw=False, error_cb=None, **kwargs): - """Convenient wrapper around subprocess call. - - Note, this function is intended for processes that output finite - and not big amount of text. - - :param cmd: - commad to execute, might be string or argv list - :param stdin: - text that will be used as an input for executed process - :param asserts: - whether to raise `RuntimeError` on fail execution status - :param error_cb: - call callback(stderr) on getting error exit status from the process - :returns: - `None` on errors, otherwise `str` value of stdout - - """ - stdout, stderr = None, None - returncode = 1 - try: - logging.debug('Exec %r', cmd) - process = subprocess.Popen(cmd, stderr=subprocess.PIPE, - stdout=subprocess.PIPE, stdin=subprocess.PIPE, **kwargs) - if stdin is not None: - process.stdin.write(stdin) - process.stdin.close() - # Avoid using Popen.communicate() - # http://bugs.python.org/issue4216#msg77582 - process.wait() - stdout = _nb_read(process.stdout) - stderr = _nb_read(process.stderr) - if not raw: - stdout = stdout.strip() - stderr = stderr.strip() - returncode = process.returncode - enforce(returncode == 0, _('Exit status is an error')) - logging.debug('Successfully executed stdout=%r stderr=%r', - stdout.split('\n'), stderr.split('\n')) - return stdout - except Exception, error: - logging.debug('Failed to execute error="%s" stdout=%r stderr=%r', - error, str(stdout).split('\n'), str(stderr).split('\n')) - if asserts: - raise RuntimeError(_('Failed to execute %r command: %s') % \ - (cmd, error)) - elif error_cb is not None: - error_cb(returncode, stdout, stderr) - - -def rmtree(path, ignore_errors=True, **kwargs): - """Remove directory with all its content. - - Function will check if owner has permissions for removing directories - (it makes sense for 0install implementaion caches). - - :param path: - path to the directory to remove - :param ignore_errors: - ignore all errors while removing - :returns: - `None` on errors, otherwise `str` value of stdout - - """ - if isdir(path): - - def fix_dir(path): - # 0install removes owner permissions - stat = os.stat(path).st_mode & 0777 - if stat & 0700 != 0700: - os.chmod(path, stat | 0700) - - fix_dir(path) - for root, dirs, __ in os.walk(path): - for i in dirs: - fix_dir(join(root, i)) - - shutil.rmtree(path, ignore_errors=ignore_errors, **kwargs) - elif lexists(path): - os.unlink(path) - - -def cptree(src, dst): - """Efficient version of copying directories. - - Function will try to make hard links for copying files at first and - will fallback to regular copying overwise. - - :param src: - path to the source directory - :param dst: - path to the new directory - - """ - if abspath(src) == abspath(dst): - return - - do_copy = [] - - def link(src, dst): - if not exists(dirname(dst)): - os.makedirs(dirname(dst)) - - if islink(src): - link_to = os.readlink(src) - os.symlink(link_to, dst) - elif isdir(src): - cptree(src, dst) - else: - if do_copy: - shutil.copy(src, dst) - else: - try: - os.link(src, dst) - except OSError: - do_copy.append(True) - shutil.copy(src, dst) - shutil.copystat(src, dst) - - if isdir(src): - for root, __, files in os.walk(src): - dst_root = join(dst, root[len(src):].lstrip(os.sep)) - if not exists(dst_root): - os.makedirs(dst_root) - for i in files: - link(join(root, i), join(dst_root, i)) - else: - link(src, dst) - - -def new_file(path, mode=None): - """Atomic new file creation. - - Method will create temporaty file in the same directory as the specified - one. When file object associated with this temporaty file will be closed, - temporaty file will be renamed to the final destination. - - :param path: - path to save final file to - :param mode: - mode for new file - :returns: - file object - - """ - tmp_path = TempFilePath(dir=dirname(path), prefix=basename(path)) - - result = _NewFile(tmp_path, 'w') - if mode: - os.fchmod(result.fileno(), mode) - result.tmp_path = tmp_path - result.dst_path = path - - return result - - -def get_frame(frame_no): - """Return Python call stack frame. - - The reason to have this wrapper is that this stack information is a private - data and might depend on Python implementaion. - - :param frame_no: - number of stack frame starting from caller's stack position - :returns: - frame object - - """ - # +1 since the calling `get_frame` adds one more frame - # pylint: disable-msg=W0212 - return sys._getframe(frame_no + 1) - - -def utcnow(): - """Return local time in UTC. - - Support testing workflow on multi processes level. - - :returns: - `datetime.datetime.utcnow()` value - - """ - direct_time_path = '/tmp/.utcnow' - if exists(direct_time_path): - ts = os.stat(direct_time_path).st_mtime - return datetime.datetime.fromtimestamp(ts) - else: - return datetime.datetime.utcnow() - - -def _set_utcnow(value): - direct_time_path = '/tmp/.utcnow' - file(direct_time_path, 'w').close() - os.utime(direct_time_path, (value, value)) - - -def _unset_utcnow(): - direct_time_path = '/tmp/.utcnow' - if exists(direct_time_path): - os.unlink(direct_time_path) - - -class Option(object): - """Configuration option. - - `Option` object will be used as command-line argument and - configuration file option. All these objects will be automatically - collected from `sugar_server.env` module and from `etc` module from - all services. - - """ - #: Collected by `Option.seek()` options in original order. - unsorted_items = [] - #: Collected by `Option.seek()` options by name. - items = {} - #: Collected by `Option.seek()` options by section. - sections = {} - _config = None - - def __init__(self, description=None, default=None, short_option=None, - type_cast=None, type_repr=None, action=None): - """ - :param description: - description string - :param default: - default value for the option - :param short_option: - value in for of `-<char>` to use as a short option for command-line - parser - :param type_cast: - function that will be uses to type cast to option type - while setting option value - :param type_repr: - function that will be uses to type cast from option type - while converting option value to string - :param action: - value for `action` argument of `OptionParser.add_option()` - - """ - if default is not None and type_cast is not None: - default = type_cast(default) - self._value = default - self.description = description - self.type_cast = type_cast - self.type_repr = type_repr - self.short_option = short_option or '' - self.action = action - self.section = None - self.name = None - self.attr_name = None - - @property - def long_option(self): - """Long command-line argument name.""" - return '--%s' % self.name - - # pylint: disable-msg=E0202 - @property - def value(self): - """Get option raw value.""" - return self._value - - # pylint: disable-msg=E1101, E0102, E0202 - @value.setter - def value(self, x): - """Set option value. - - The `Option.type_cast` function will be used for type casting specified - value to option. - - """ - if x is None: - self._value = None - elif self.type_cast is not None: - self._value = self.type_cast(x) - else: - self._value = str(x) or None - - @staticmethod - def seek(section, mod=None): - """Collect `Option` objects. - - Function will populate `Option.unsorted_items`, `Option.items` and - `Option.sections` values. Call this function before any usage - of `Option` objects. - - :param section: - arbitrary name to group options per section - :param mod: - mdoule object to search for `Option` objects; - if omited, use caller's module - - """ - if mod is None: - mod_name = get_frame(1).f_globals['__name__'] - mod = sys.modules[mod_name] - - for name in sorted(dir(mod)): - attr = getattr(mod, name) - # Options might be from different `util` modules - if not (type(attr).__name__ == 'Option' and \ - type(attr).__module__.split('.')[-1] == 'util'): - continue - - attr.attr_name = name - attr.name = name.replace('_', '-') - attr.module = mod - attr.section = section - - Option.unsorted_items.append(attr) - Option.items[attr.name] = attr - if section not in Option.sections: - Option.sections[section] = {} - Option.sections[section][attr.name] = attr - - @staticmethod - def bind(parser, config_files=None, notice=None): - """Initilize option usage. - - Call this function after invoking `Option.seek()`. - - :param parser: - if not `None`, `OptionParser` object to export, - collected by `Option.seek` options, to - :param config_files: - list of paths to files that will be used to read default - option values; this value will initiate `Option.config` variable - :param notice: - optional notice to print with arguments' description - - """ - if config_files: - Option._config = Option() - Option._config.name = 'config' - Option._config.attr_name = 'config' - Option._config.description = \ - _('colon separated list of paths to alternative ' \ - 'configuration file(s)') - Option._config.short_option = '-c' - Option._config.type_cast = \ - lambda x: [i for i in re.split('[\s:;,]+', x) if i] - Option._config.type_repr = \ - lambda x: ':'.join(x) - Option._config.value = ':'.join(config_files) - - for prop in [Option._config] + Option.items.values(): - desc = prop.description - if prop.value is not None: - desc += ' [%s]' % prop - if notice: - desc += '; ' + notice - if parser is not None: - parser.add_option(prop.short_option, prop.long_option, - action=prop.action, help=desc) - - @staticmethod - def merge(options, config_files=None): - """Combine default values with command-line arguments and config files. - - Call this function after invoking `Option.bind()`. - - :param options: - the first value from a tuple returned by - `OptionParser.parse_args()` function - :param config_files: - list of paths to files that will be used to read default - option values instead of reusing `--config` value - - """ - from ConfigParser import ConfigParser - - if config_files is None: - if Option._config is None: - raise RuntimeError(_('Method Option.merge was not called or ' \ - 'its config_files argument was None')) - config_files = Option._config.value - - config = ConfigParser() - for i in config_files: - if exists(expanduser(i)): - config.read(expanduser(i)) - - for prop in Option.items.values(): - if hasattr(options, prop.attr_name) and \ - getattr(options, prop.attr_name) is not None: - prop.value = getattr(options, prop.attr_name) - elif config.has_option(prop.section, prop.name): - prop.value = config.get(prop.section, prop.name) - - @staticmethod - def export(): - """Current configuration in human readable form. - - :returns: - list of lines - - """ - import textwrap - - lines = [] - sections = set() - - for prop in Option.unsorted_items: - if prop.section not in sections: - if sections: - lines.append('') - lines.append('[%s]' % prop.section) - sections.add(prop.section) - lines.append('\n'.join( - ['# %s' % i for i in textwrap.wrap(prop.description, 78)])) - value = '\n\t'.join(str(prop).split('\n')) - lines.append('%s = %s' % (prop.name, value)) - - return lines - - @staticmethod - def bool_cast(x): - if not x or str(x).strip().lower() in ['', 'false', 'none']: - return False - else: - return bool(x) - - @staticmethod - def list_cast(x): - if isinstance(x, str) or isinstance(x, unicode): - return [i for i in x.strip().split(':') if i] - else: - return x - - @staticmethod - def list_repr(x): - return ':'.join(x) - - def __str__(self): - if self.value is None: - return '' - else: - if self.type_repr is None: - return str(self.value) - else: - return self.type_repr(self.value) - - def __unicode__(self): - return self.__str__() - - -class Command(object): - """Service command. - - `Command` is a way to have custom sub-commands in services. All these - objects will be automatically collected from `etc` module - from all services. - - """ - #: Collected by `Command.seek()` commands by name. - items = {} - #: Collected by `Command.seek()` commands by section. - sections = {} - - def __init__(self, description=None, cmd_format=None): - """ - :param description: - command description - :param cmd_format: - part of description to explain additional command arguments - - """ - self.description = description or '' - self.cmd_format = cmd_format or '' - self.name = None - self.attr_name = None - - @staticmethod - def seek(section, mod=None): - """Collect `Command` objects. - - Function will populate `Command.items` and `Command.sections` values. - Call this function before any usage of `Command` objects. - - :param section: - arbitrary name to group options per section - :param mod: - mdoule object to search for `Option` objects; - if omited, use caller's module - - """ - if mod is None: - mod_name = get_frame(1).f_globals['__name__'] - mod = sys.modules[mod_name] - - for name in sorted(dir(mod)): - attr = getattr(mod, name) - # Commands might be from different `util` modules - if not (type(attr).__name__ == 'Command' and \ - type(attr).__module__.split('.')[-1] == 'util'): - continue - - attr.name = name.replace('_', '-') - attr.attr_name = name - attr.module = mod - attr.section = section - - Command.items[attr.name] = attr - if section not in Command.sections: - Command.sections[section] = {} - Command.sections[section][attr.name] = attr - - @staticmethod - def call(mod, name, *args, **kwargs): - """Call the command. - - Specfied module should contain a function with a name - `CMD_<command-name>()`. All additional `Command.call()` arguments - will be passed as-is to command implementaion function. - - :param mod: - module to search for command implementaion - :param name: - command name - :returns: - what command implementaion returns - - """ - cmd = Command.items.get(name) - enforce(cmd is not None, _('No such command, %s'), name) - - func_name = 'CMD_%s' % cmd.attr_name - if not hasattr(mod, func_name): - raise RuntimeError(_('No such command, %s, in module %s') % \ - (name, mod.__name__)) - getattr(mod, func_name)(*args, **kwargs) - - def __str__(self): - return self.name - - def __unicode__(self): - return self.__str__() - - -class TempFilePath(unicode): - """Auto removed temporary file. - - Right after creating `TempFilePath` object, temporaty file will be - created. On `TempFilePath` object deleting, this file will be removed. - The key difference with `tempfile.NamedTemporaryFile` is that - `TempFilePath` doesn't keep open file descriptor with removing file - right after closing it (though starting form Python 2.6, - `tempfile.NamedTemporaryFile` supports `delete` argument). - - """ - - def __new__(cls, path=None, text=None, **kwargs): - """ - Function supports the same arguments as `tempfile.mkstemp`. - - :param path: - instead of generating temporary file name, exact `path` value - will be used - :param text: - content for newly created file - - """ - if path: - if not exists(dirname(path)): - os.makedirs(dirname(path)) - fd = None - else: - if 'dir' in kwargs: - dir_value = kwargs['dir'] - if not exists(dir_value): - os.makedirs(dir_value) - fd, path = tempfile.mkstemp(**kwargs) - - _temp_file_paths.add(path) - - if text is not None: - if fd is None: - fd = os.open(path, os.O_WRONLY | os.O_CREAT) - os.write(fd, text) - - if fd is not None: - os.close(fd) - - return unicode.__new__(cls, path) - - def __del__(self): - if _temp_file_paths and self in _temp_file_paths: - _temp_file_paths.remove(self) - if exists(self): - os.unlink(self) - - -class TempDir(unicode): - """Auto removed temporary directory. - - Right after creating `TempDir` object, temporaty directory will be - created. On `TempDir` object deleting, this directory will be removed. - - """ - - def __new__(cls, path=None, **kwargs): - """ - Function supports the same arguments as `tempfile.mkdtemp`. - - :param path: - instead of generating temporary file name, exact `path` value - will be used - - """ - if path is not None: - if not exists(path): - os.makedirs(path) - else: - if 'dir' in kwargs: - dir_value = kwargs['dir'] - if not exists(dir_value): - os.makedirs(dir_value) - path = tempfile.mkdtemp(**kwargs) - - _temp_dirs.add(path) - - return unicode.__new__(cls, path) - - def __del__(self): - if _temp_dirs and self in _temp_dirs: - _temp_dirs.remove(self) - if exists(self): - rmtree(self) - - def persist(self): - """Do not auto remove this directory.""" - if self in _temp_dirs: - _temp_dirs.remove(self) - - -class _NewFile(file): - - dst_path = None - tmp_path = None - - def close(self): - file.close(self) - if self.tmp_path is not None: - os.rename(self.tmp_path, self.dst_path) - self.tmp_path = None - - def __del__(self): - self.tmp_path = None - - -def _cleanup_temp_files(): - for path in _temp_file_paths: - if exists(path): - os.unlink(path) - - for path in _temp_dirs: - if exists(path): - rmtree(path) - - -_temp_file_paths = set() -_temp_dirs = set() -atexit.register(_cleanup_temp_files) - - -def _nb_read(stream): - if stream is None: - return '' - fd = stream.fileno() - orig_flags = fcntl.fcntl(fd, fcntl.F_GETFL) - try: - fcntl.fcntl(fd, fcntl.F_SETFL, orig_flags | os.O_NONBLOCK) - return stream.read() - except Exception: - return '' - finally: - fcntl.fcntl(fd, fcntl.F_SETFL, orig_flags) diff --git a/tests/units/user.py b/tests/units/user.py index f436aed..e91d1b5 100755 --- a/tests/units/user.py +++ b/tests/units/user.py @@ -11,7 +11,10 @@ from __init__ import tests from tests import Resource import restful_document as rd -from restful_document import env, util +import active_document as ad +util = ad.util + +from restful_document import env class UserTest(tests.Test): |