From 36d1c63f5e12f1dfd6d5187359093a96a2fd2de4 Mon Sep 17 00:00:00 2001 From: Ian Bicking Date: Mon, 22 Aug 2005 23:27:44 +0000 Subject: Moved wareweb to separate project --- paste/wareweb/__init__.py | 5 - paste/wareweb/cgifields.py | 82 -------------- paste/wareweb/conf_setup.py | 5 - paste/wareweb/cookiewriter.py | 41 ------- paste/wareweb/dispatch.py | 138 ----------------------- paste/wareweb/docs/wareweb.txt | 149 ------------------------- paste/wareweb/event.py | 34 ------ paste/wareweb/interfaces.py | 196 --------------------------------- paste/wareweb/notify.py | 67 ------------ paste/wareweb/packing.py | 213 ------------------------------------ paste/wareweb/servlet.py | 218 ------------------------------------- paste/wareweb/tests/__init__.py | 1 - paste/wareweb/tests/test_unpack.py | 103 ------------------ paste/wareweb/timeinterval.py | 70 ------------ 14 files changed, 1322 deletions(-) delete mode 100644 paste/wareweb/__init__.py delete mode 100644 paste/wareweb/cgifields.py delete mode 100644 paste/wareweb/conf_setup.py delete mode 100644 paste/wareweb/cookiewriter.py delete mode 100644 paste/wareweb/dispatch.py delete mode 100644 paste/wareweb/docs/wareweb.txt delete mode 100644 paste/wareweb/event.py delete mode 100644 paste/wareweb/interfaces.py delete mode 100644 paste/wareweb/notify.py delete mode 100644 paste/wareweb/packing.py delete mode 100644 paste/wareweb/servlet.py delete mode 100644 paste/wareweb/tests/__init__.py delete mode 100644 paste/wareweb/tests/test_unpack.py delete mode 100644 paste/wareweb/timeinterval.py diff --git a/paste/wareweb/__init__.py b/paste/wareweb/__init__.py deleted file mode 100644 index f94de14..0000000 --- a/paste/wareweb/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from servlet import * -from packing import * -from dispatch import * -from notify import * - diff --git a/paste/wareweb/cgifields.py b/paste/wareweb/cgifields.py deleted file mode 100644 index f838447..0000000 --- a/paste/wareweb/cgifields.py +++ /dev/null @@ -1,82 +0,0 @@ -import cgi -from UserDict import UserDict - -def parse_fields(environ): - fs = cgi.FieldStorage( - environ['wsgi.input'], - environ=environ, - keep_blank_values=True, - strict_parsing=False) - try: - keys = fs.keys() - except TypeError: - # Maybe an XML-RPC request - keys = [] - d = {} - for key in keys: - value = fs[key] - if not isinstance(value, list): - if not value.filename: - # Turn the MiniFieldStorage into a string: - value = value.value - else: - value = [v.value for v in value] - d[key] = value - if environ['REQUEST_METHOD'].upper() == 'POST': - # Then we must also parse GET variables - getfields = cgi.parse_qs( - environ.get('QUERY_STRING', ''), - keep_blank_values=True, - strict_parsing=False) - for name, value in getfields.items(): - if not d.has_key(name): - if isinstance(value, list) and len(value) == 1: - # parse_qs always returns a list of lists, - # while FieldStorage only uses lists for - # keys that actually repeat; this fixes that. - value = value[0] - d[name] = value - return d - -class Fields(UserDict): - - def __init__(self, field_dict): - self.data = field_dict - - def __getattr__(self, attr): - # @@: I don't like this. Should it give a KeyError? - # should it exist at all? - if attr.startswith('_'): - raise AttributeError - return self.data.get(attr) - - def __contains__(self, key): - return key in self.data - - def __iter__(self): - return iter(self.data) - - def getlist(self, name): - """ - Return the named item as a list ([] if name not found, - [self[name]] if only one field passed in). - """ - v = self.data.get(name, []) - if isinstance(v, list): - return v - return [v] - - def itemlist(self): - """ - Return a list of (name, [values...]). Like .items(), - except all values becomes a list (like .getlist()). - """ - items = [] - for name, value in self.iteritems(): - if isinstance(value, list): - items.append((name, value)) - else: - items.append((name, [value])) - return items - - __str__ = UserDict.__repr__ diff --git a/paste/wareweb/conf_setup.py b/paste/wareweb/conf_setup.py deleted file mode 100644 index ccd4751..0000000 --- a/paste/wareweb/conf_setup.py +++ /dev/null @@ -1,5 +0,0 @@ -from paste.webkit import conf_setup as webkit_conf_setup - -def build_application(conf): - conf['install_fake_webware'] = False - return webkit_conf_setup.build_application(conf) diff --git a/paste/wareweb/cookiewriter.py b/paste/wareweb/cookiewriter.py deleted file mode 100644 index eb315c1..0000000 --- a/paste/wareweb/cookiewriter.py +++ /dev/null @@ -1,41 +0,0 @@ -from Cookie import SimpleCookie -import timeinterval - -class Cookie(object): - - def __init__(self, name, value, path, expires=None, secure=False): - self.name = name - self.value = value - self.path = path - self.secure = secure - if expires == 'ONCLOSE' or not expires: - expires = None - elif expires == 'NOW' or expires == 'NEVER': - expires = time.gmtime(time.time()) - if expires == 'NEVER': - expires = (expires[0] + 10,) + expires[1:] - expires = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", expires) - else: - if isinstance(expires, (str, unicode)) and expires.startswith('+'): - interval = timeinterval.time_decode(expires[1:]) - expires = time.time() + interval - if isinstance(expires, (int, long, float)): - expires = time.gmtime(expires) - if isinstance(expires, (tuple, time.struct_time)): - expires = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", expires) - self.expires = expires - - def __repr__(self): - return '<%s %s=%r>' % ( - self.__class__.__name__, self.name, self.value) - - def header(self): - c = SimpleCookie() - c[self.name] = value - c[self.name]['path'] = self.path - if self.expires is not None: - c[self.name]['expires'] = expires - if self.secure: - c[self.name]['secure'] = True - return str(c).split(':')[1].strip() - diff --git a/paste/wareweb/dispatch.py b/paste/wareweb/dispatch.py deleted file mode 100644 index e907ec5..0000000 --- a/paste/wareweb/dispatch.py +++ /dev/null @@ -1,138 +0,0 @@ -from paste import httpexceptions -import event - -__all__ = ['public', 'ActionDispatch', 'PathDispatch'] - -def public(func): - func.public = True - return func - -class MethodDispatch(object): - - """ - This is an *abstract* class. It implements generic dispatching - to servlet methods. See ``ActionDispatch`` and ``PathDispatch`` - for implementations. - - Methods are considered public if their ``public`` attribute is - true (you can use the ``@public`` decorator to set this) or if the - method name is prefixed appropriately. (E.g., if ``prefix`` is - ``action_`` then ``action_meth()`` will be considered the public - method by the name ``meth`` -- the prefix is added automatically!) - """ - - prefix = None - - def __addtoclass__(self, attr, cls): - if self.__class__ is MethodDispatch: - raise NotImplementedError( - "MethodDispatch is an abstract class, and cannot be " - "used directly (use one of its subclasses)") - cls.listeners.append(self.respond_event) - - def respond_event(self, name, servlet, *args, **kw): - if name == 'end_awake': - result = self.find_method(servlet, *args, **kw) - if result is None: - return event.Continue - else: - return result - return event.Continue - - def get_method(self, servlet, action): - if self.prefix: - try: - return (self.prefix + action, - getattr(servlet, self.prefix + action)) - except AttributeError: - pass - try: - return action, getattr(servlet, action) - except AttributeError: - pass - return None, None - - def valid_method(self, name, method): - if getattr(method, 'public', False): - return True - if self.prefix and name.startswith(self.prefix): - return True - return False - -class ActionDispatch(MethodDispatch): - - """ - This dispatches to a method based on a URL variable (GET or POST - -- remember that you can also include GET variables in your form's - ``action`` even if the form is being POSTed). - - The URL variable indicates a method to run; if no variable is - found then ``default_action`` is assumed (if you pass in a default - action). - - The URL variable is given with ``action_name`` and defaults to - ``'_action_'``. You can pass in the action either as - ``_action_=method_name`` or ``_action_method_name=anything`` - (useful with submit buttons, where the value is part of the UI). - """ - - prefix = 'action_' - - def __init__(self, action_name='_action_', default_action=None): - self.action_name = action_name - self.default_action = default_action - - def find_method(self, servlet, ret_value, **kw): - possible_actions = [] - for name, value in servlet.fields.items(): - if name == self.action_name: - possible_actions.append(value) - elif name.startswith(self.action_name): - possible_actions.append(name[len(self.action_name):]) - if not possible_actions: - if self.default_action: - possible_actions = [self.default_action] - else: - return event.Continue - if len(possible_actions) > 1: - raise httpexceptions.HTTPBadRequest( - "More than one action received: %s" - % ', '.join(map(repr, possible_actions))) - action = possible_actions[0] - name, method = self.get_method(servlet, action) - if name is None: - raise httpexceptions.HTTPForbidden( - "Action method not found: %r" % action) - if not self.valid_method(name, method): - raise httpexceptions.HTTPForbidden( - "Method not allowed: %r" % action) - return method() - -class PathDispatch(MethodDispatch): - - """ - This dispatches to a method based on the ``PATH_INFO``. Thus - ``/path/to/servlet/meth`` will call the ``meth`` method. - - @@: This should probably adjust SCRIPT_NAME and PATH_INFO - @@: Should these all use the same prefix? - """ - - prefix = 'path_' - - def find_method(self, servlet, ret_value, **kw): - parts = servlet.path_parts - if not parts: - action = 'index' - else: - action = parts[0] - servlet.path_parts = parts[1:] - name, method = self.get_method(servlet, action) - if name is None: - raise httpexceptions.HTTPForbidden( - "Method not found: %r" % action) - if not self.valid_method(name, method): - raise httpexceptions.HTTPForbidden( - "Method not allowed: %r" % action) - return method() - diff --git a/paste/wareweb/docs/wareweb.txt b/paste/wareweb/docs/wareweb.txt deleted file mode 100644 index ba554fd..0000000 --- a/paste/wareweb/docs/wareweb.txt +++ /dev/null @@ -1,149 +0,0 @@ -Wareweb -======= - -.. contents:: - -Introduction ------------- - -Wareweb is a rethinking of the ideas behind `Webware -`_. Webware is a fairly old framework, -written back in the days of Python 1.5.2, and some of the API -decisions behind it now show their age. Also, many of the -abstractions in Webware didn't turn out to be useful, while some other -abstractions are missing. - -Wareweb tries to keep the basic model of Webware, while simplifying -many portions and adding some new techniques. It's not backward -compatible, but it doesn't require major revamping to apply to a -Webware application. - -Wareweb also requires Python 2.4; as long as we're updating the API, I -want to make it as modern as possible. Decorators in particular are -widely used in the framework. - -Interface ---------- - -See ``paste/wareweb/interfaces.py`` for a description of the -``IServlet`` interface. - -Components and Events ---------------------- - -There are a couple fairly simple metaprogramming techniques added to -``Servlet`` to make it easier to add functionality. - -Active Attributes -~~~~~~~~~~~~~~~~~ - -When you create a class, attributes can be "active". That is, simply -assigning the attribute can cause changes to the class. - -This is implemented by looking for a special method ``__addtoclass__`` -in all new attributes. The dispatchers, for instance, add themselves -as "event listeners," so that simply by putting a dispatcher in your -class definition somewhere you'll have changed the functionality of -the class. - -Events -~~~~~~ - -Many methods throw events (with ``paste.wareweb.event.raise_event``). -An event is sent to each listener on the object, and the handler can -do nothing (returning ``event.Continue``) or can override or respond -to the event in some way. - -Dispatching objects listen for the ``end_awake`` event (called at the -end of ``awake()``), and may call an extra method on the servlet based -on some criteria (like ``_awake_`` or ``path_info``). - -Templating systems can use an event to write a template. ZPTKit -overrides ``start_respond`` to write the template. - -Unpacking Methods -~~~~~~~~~~~~~~~~~ - -On a per-method basis you can use the algorithms in -``paste.wareweb.unpack`` to unpack your request. E.g., something -like: - - dispatch = MethodDispatch() - - @public - @unpack - def edit_item(self, item_id_int_path, name): - Item.get(id=item_id_int_path).name = name - -This unpacks a request like ``servlet_name/edit_item/1?name=new_name`` -into the function call. Note that ``MethodDispatch`` makes -``/edit_item`` call the ``edit_item()`` method. ``@public`` declares -that the method is actually available to the public. ``@unpack`` uses -the method signature to identify the parts -- ``item_id_int_path`` -ends with ``_path``, and so will be taken from ``path_info``; and has -``_int`` and so will be coerced into an integer. - -Automatic Properties -~~~~~~~~~~~~~~~~~~~~ - -Methods that end with ``__get``, ``__set`` and ``__del`` will -automatically be turned into properties. Subclasses can selectively -replace just one of these implementations, while leaving the others in -place. - -Differences from Webware ------------------------- - -This is a rough summary of the differences from Webware. There's lots -of little things that are just plain gone; this doesn't enumerate all -of them. - -No Request And Response -~~~~~~~~~~~~~~~~~~~~~~~ - -There are no longer request and response objects. There is only the -servlet. The servlet contains all the useful methods from these -objects. - -From the response, several methods: - -* set_cookie -* set_header -* add_header -* write -* redirect - -From the request, several attributes (note: no methods): - -* environ -* config -* request_method -* app_url -* path_info -* path_parts -* fields - -For more exact details, see ``paste.wareweb.interfaces.IServlet`` - -Only One Servlet Class -~~~~~~~~~~~~~~~~~~~~~~ - -There's one ``Servlet`` for everyone -- no ``Servlet``, -``HTTPServlet``, and ``Page``. All servlets instances are *not* -threadsafe, because they contain information about the request and -response in their instance variables. In practice no Webware servlets -were threadsafe, nor was there anything to be gained by that. - -Also, reusable functionality is not meant to be added with subclasses -of ``Servlet``. Subclassing is for the end-developer to add -application-specific functionality; there's other techniques for -adding reusable functionality. - -No _action_ by default -~~~~~~~~~~~~~~~~~~~~~~ - -URL dispatching is handled more generically; several dispatchers are -available, one which implements the ``_action_` parsing of Webware. -Dispatchers for XML-RPC and JSON-RPC are also possible, and they have -their own built-in methods of indicating methods. Other forms of URL -parsing are also implemented. diff --git a/paste/wareweb/event.py b/paste/wareweb/event.py deleted file mode 100644 index 5376d63..0000000 --- a/paste/wareweb/event.py +++ /dev/null @@ -1,34 +0,0 @@ -class Continue: - """ - This class is a singleton (never meant to be instantiated) that - represents a kind of no-op return from a class. - """ - def __init__(self): - assert False, ( - "Continue cannot be instantiated (use the class object " - "itself)") - -def wrap_func(func): - name = func.__name__ - def replacement_func(actor, *args, **kw): - # @@: It's really more of an "inner_value" than "next_method" - kw['next_method'] = func - result = raise_event('start_%s' % name, actor, *args, **kw) - del kw['next_method'] - if result is Continue: - value = func(actor, *args, **kw) - else: - return result - result = raise_event('end_%s' % name, actor, value, *args, **kw) - if result is Continue: - return value - return result - replacement_func.__name__ = name - return replacement_func - -def raise_event(name, actor, *args, **kw): - for listener in actor.listeners: - value = listener(name, actor, *args, **kw) - if value is not Continue: - return value - return Continue diff --git a/paste/wareweb/interfaces.py b/paste/wareweb/interfaces.py deleted file mode 100644 index 9cd39b5..0000000 --- a/paste/wareweb/interfaces.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -Descriptions of the main interfaces in Wareweb. These aren't formal -interfaces, they are just here for documentation purposes. -""" - -class IServlet: - - """ - Response method sequence - ------------------------ - - These methods are called roughly in order to produce the response. - """ - - def __call__(environ, start_response): - """ - Implementation of the WSGI application interface. Instances - of IServlet are WSGI applications. - - Calls event ``call`` with ``environ`` and ``start_response``, - which may return (status_headers, app_iter) and thus abort the - rest of the call. - """ - - def run(): - """ - 'Runs' the request. This typically just calls ``awake()``, - ``respond()`` and ``sleep()``. Error handlers could be added - here by subclasses. - - Wrapped as event (wrapped methods call ``start_method_name`` - and ``end_method_name``). - """ - - def awake(call_setup=True): - """ - Called at beginning of request. SHOULD NOT produce output. - If ``call_setup`` is true then ``.setup()`` will be called. - ``SitePage`` classes (that is, abstract subclasses of - ``Servlet``) should override this method and not ``setup``. - - Wrapped as event. - """ - - def setup(): - """ - Called at beginning of requests. Subclasses do not need to - call the superclass implementation. This is where individual - (non-abstract) servlets typically do their setup. - """ - - def respond(): - """ - Called after ``.awake()``, this typically produces the body of - the response. Some components may intercept this method - (e.g., components that want to take over the body of the - response, like templating components). - - Wrapped as event. - """ - - def sleep(call_teardown=True): - """ - Called at the end of a request, to clean up resources. Called - regardless of exceptions in ``.awake()`` or ``.respond()``, so - implementations should be careful not to assume all resources - have been successfully set up. Like ``awake`` this calls - ``.teardown()`` if ``call_teardown`` is true; abstract classes - should override this method and not ``teardown``. - - Wrapped as event. - """ - - def teardown(): - """ - Resource cleanup at the end of request. Subclasses do not - need to call the superclass implementation. - """ - - """ - Request Attributes - ------------------ - - These attributes describe the request - """ - - environ = """ - The WSGI environment. Contains typical CGI variables, in addition - to any WSGI extensions. - """ - - config = """ - The Paste configuration object; a dictionary-like object. - """ - - app_url = """ - The URL (usually not fully qualified) of this application. This - looks in the environmental variable ``.base_url``. - The default ``app_name`` is ``"app"``, and this requires a - ``urlparser_hook`` in ``__init__.py`` to set accurately, which - would look like:: - - app_name = 'app' - def urlparse_hook(environ): - key = '%s.base_url' % app_name - if not key in environ: - environ[key] = environ['SCRIPT_NAME'] - """ - - path_info = """ - The value of environ['PATH_INFO'] -- all the URL that comes after - this servlet's location. - """ - - path_parts = """ - A list of path parts; essentially ``path_info.split('/')`` - """ - - fields = """ - A dictionary-like object of all the request fields (both GET and - POST variables, folded together). - - You can also get variables as attributes (which default to None). - - Also has a ``.getlist(name)`` method, which returns the variable - as a list (i.e., if missing the return value is ``[]``; if one - string value the return value is ``[value]``) - """ - - """ - Response Methods - ---------------- - - These methods are used to create the response - """ - - title = """ - Attribute that returns the title of this page. Default - implementation returns the class's name. - """ - - session = """ - The session object. This is a dictionary-like object where values - are persisted through multiple requests (using a cookie). - """ - - def set_cookie(cookie_name, value, path='/', expires='ONCLOSE', - secure=False): - """ - Creates the named cookie in the response. - - ``expires`` can be: - * ``ONCLOSE`` (default: expires when browser is closed) - * ``NOW`` (for immediate deletion) - * ``NEVER`` (some time far in the future) - * A integer value (expiration in seconds) - * A time.struct_time object - * A string that starts with ``+`` and describes the time, like - ``+1w`` (1 week) or ``+1b`` (1 month) - """ - - def set_header(header_name, header_value): - """ - Sets the named header; overwriting any header that previously - existed. The ``Status`` header is specially used to change - the response status. - """ - - def add_header(header_name, header_value): - """ - Adds the named header, appending to any previous header that - might have been set. - """ - - def write(*objs): - """ - Writes the objects. ``None`` is written as the empty string, - and unicode objects are encoded with UTF-8. - """ - - def redirect(url, **query_vars): - """ - Redirects to the given URL. If the URL is relative, then it - will be resolved to be absolute with respect to - ``self.app_url``. - - The status is 303 by default; a status keyword argument can be - passed to override this. - - Other variables are appended to the query string, e.g.:: - - self.redirect('edit', id=5) - - Will redirect to ``edit?id=5`` - """ - diff --git a/paste/wareweb/notify.py b/paste/wareweb/notify.py deleted file mode 100644 index c4ced66..0000000 --- a/paste/wareweb/notify.py +++ /dev/null @@ -1,67 +0,0 @@ -import event - -__all__ = ['Notify'] - -class Notify(object): - - """ - This allows you to queue messages to be displayed to the user on - the next page the user reads. This allows you to give messages in - response to POST requests, then redirect to a another page and - display that message (e.g, 'event added'). - - Usage:: - - class SitePage(Servlet): - message = Notify() - - class MyPage(SitePage): - def setup(self): - if something_done: - self.message.write('All good!') - self.redirect(...) - def respond(self): - write header... - if self.message: - self.write('
%s
\n' - % self.message.html) - """ - - def __addtoclass__(self, attr, cls): - cls.listeners.append(self.servlet_event) - self.name = attr - - def servlet_event(self, name, servlet, *args, **kw): - if name == 'start_awake': - setattr(servlet, self.name, Message(servlet)) - elif name == 'end_sleep': - if getattr(servlet, '_notify_written', False): - if 'notify.messages' in servlet.session: - del servlet.session['notify.messages'] - return event.Continue - -class Message(object): - - def __init__(self, servlet): - self.servlet = servlet - - def write(self, message): - session = self.servlet.session - messages = session.get('notify.messages') or [] - messages.append(message) - session['notify.messages'] = messages - - def __nonzero__(self): - return bool(self.servlet.session.get('notify.messages')) - - def html__get(self): - messages = self.servlet.session.get('notify.messages') or [] - self.servlet._notify_written = True - return self.peek - html = property(html__get) - - def peek__get(self): - messages = self.servlet.session.get('notify.messages') or [] - return '
'.join(messages) - peek = property(peek__get) - diff --git a/paste/wareweb/packing.py b/paste/wareweb/packing.py deleted file mode 100644 index 9cf080b..0000000 --- a/paste/wareweb/packing.py +++ /dev/null @@ -1,213 +0,0 @@ -import inspect -import xmlrpclib -import traceback -from paste.httpexceptions import HTTPBadRequest -json = None - -__all__ = ['unpack', 'unpack_xmlrpc', 'unpack_json'] - -def unpack(func): - argspec = FunctionArgSpec(func) - def replacement_func(self): - args, kw = argspec.unpack_args(self.path_parts, self.fields) - return func(self, *args, **kw) - replacement_func.__doc__ = func.__doc__ - replacement_func.__name__ = func.__name__ - return replacement_func - -def unpack_xmlrpc(func): - def replacement_func(self): - assert self.environ['CONTENT_TYPE'].startswith('text/xml') - data = self.environ['wsgi.input'].read() - xmlargs, method_name = xmlrpclib.loads(data) - if method_name: - kw = {'method_name': method_name} - else: - kw = {} - self.set_header('content-type', 'text/xml; charset=UTF-8') - try: - result = func(self, *xmlargs, **kw) - except: - body = make_rpc_exception(environ, sys.exc_info()) - body = xmlrpclib.dumps( - xmlrpclib.Fault(1, fault), encoding='utf-8') - else: - if not isinstance(result, tuple): - result = (result,) - body = xmlrpclib.dumps( - result, methodresponse=True, encoding='utf-8') - self.write(body) - replacement_func.__doc__ = func.__doc__ - replacement_func.__name__ = func.__name__ - return replacement_func - -def make_rpc_exception(environ, exc_info): - config = environ['paste.config'] - rpc_exception = config.get('rpc_exception', None) - if rpc_exception not in (None, 'occurred', 'exception', 'traceback'): - environ['wsgi.errors'].write( - "Bad 'rpc_exception' setting: %r\n" % rpc_exception) - rpc_exception = None - if rpc_exception is None: - if config.get('debug'): - rpc_exception = 'traceback' - else: - rpc_exception = 'exception' - if rpc_exception == 'occurred': - fault = 'unhandled exception' - elif rpc_exception == 'exception': - fault = str(e) - elif rpc_exception == 'traceback': - out = StringIO() - traceback.print_exception(*exc_info, **{'file': out}) - fault = out.getvalue() - return fault - -def unpack_json(func): - global json - if json is None: - import json - def replacement_func(self): - data = self.environ['wsgi.input'].read() - jsonrpc = json.jsonToObj(data) - method = jsonrpc['method'] - params = jsonrpc['params'] - id = jsonrpc['id'] - if method: - kw = {'method_name': method} - else: - kw = {} - self.set_header('content-type', 'text/plain; charset: UTF-8') - try: - result = func(self, *params, **kw) - except: - body = make_rpc_exception(environ, sys.exc_info()) - response = { - 'result': None, - 'error': body, - 'id': id} - else: - response = { - 'result': result, - 'error': None, - 'id': id} - self.write(json.objToJson(response)) - replacement_func.__doc__ = func.__doc__ - replacement_func.__name__ = func.__name__ - return replacement_func - - -class FunctionArgSpec(object): - - def __init__(self, func): - self.funcargs, self.varargs, self.varkw, self.defaults = ( - inspect.getargspec(func)) - self.positional = [] - self.optional_pos = [] - self.coersions = self.collect_coersions(self.funcargs) - while self.funcargs and self.funcargs[0].endswith('_path'): - if len(self.defaults or ()) == len(self.funcargs): - # This is an optional path segment - self.optional_pos.append(self.funcargs.pop(0)) - self.defaults = self.defaults[1:] - else: - self.positional.append(self.funcargs.pop(0)) - self.reqargs = [] - if not self.defaults: - self.reqargs = self.funcargs - else: - self.reqargs = self.funcargs[:-len(self.defaults)] - - def unpack_args(self, path_parts, fields): - args = [] - kw = {} - if len(self.positional) > len(path_parts): - raise HTTPBadRequest( - "Not enough parameters on the URL (expected %i more " - "path segments)" % (len(self.positional)-len(path_parts))) - if (not self.varargs - and (len(self.positional)+len(self.optional_pos)) - < len(path_parts)): - raise HTTPBadRequest( - "Too many parameters on the URL (expected %i less path " - "segments)" % (len(path_parts)-len(self.positional) - -len(self.optional_pos))) - for name, value in fields.iteritems(): - if not self.varkw and name not in self.coersions: - raise HTTPBadRequest( - "Variable %r not expected" % name) - if name not in self.coersions: - kw[name] = value - continue - orig_name, coercer = self.coersions[name] - if coercer: - try: - value = coercer(value) - except (ValueError, TypeError), e: - raise HTTPBadRequest( - "Bad variable %r: %s" % (name, e)) - kw[orig_name] = value - for arg in self.reqargs: - if arg not in kw: - raise HTTPBadRequest( - "Variable %r required" % arg) - return args, kw - - def collect_coersions(self, funcargs): - coersions = {} - for name in funcargs: - coercer = normal - orig = name - while 1: - if name.endswith('_int'): - coercer = self.add_coercer(coercer, make_int) - name = name[:-4] - elif name.endswith('_list'): - coercer = self.add_coercer(coercer, make_list) - name = name[:-5] - elif name.endswith('_float'): - coercer = self.add_coercer(coercer, make_float) - name = name[:-6] - elif name.endswith('_req'): - coercer = self.add_coercer(coercer, make_required) - name = name[:-4] - else: - break - coersions[name] = (orig, coercer) - return coersions - - def add_coercer(self, coercer, new_coercer): - if not coercer or coercer is normal: - return new_coercer - else: - def coerce(val): - return new_coercer(coercer(val)) - return coerce - -def make_int(v): - if isinstance(v, list): - return map(int, v) - else: - return int(v) - -def make_float(v): - if isinstance(v, list): - return map(float, v) - else: - return float(v) - -def make_list(v): - if isinstance(v, list): - return v - else: - return [v] - -def make_required(s): - if s is None: - raise TypeError - return s - -def normal(v): - if isinstance(v, list): - raise ValueError("List not expected") - return v diff --git a/paste/wareweb/servlet.py b/paste/wareweb/servlet.py deleted file mode 100644 index f0be178..0000000 --- a/paste/wareweb/servlet.py +++ /dev/null @@ -1,218 +0,0 @@ -import re -import urllib -import traceback -import time -from UserDict import UserDict -from Cookie import SimpleCookie -from paste.util import classinit -from paste import httpexceptions -import timeinterval -import cgifields -import event -import cookiewriter - -__all__ = ['Servlet'] - -class ForwardRequest(Exception): - def __init__(self, app): - self.application = app - -class Servlet(object): - - app_name = 'app' - listeners = [] - _title = None - _html_title = None - - __metaclass__ = classinit.ClassInitMeta - - def __classinit__(cls, new_attrs): - if not new_attrs.has_key('listeners'): - cls.listeners = cls.listeners[:] - for attr, value in new_attrs.items(): - cls.add_attr(attr, value, set=False) - classinit.build_properties(cls, new_attrs) - - @classmethod - def add_attr(cls, attr, value, set=True): - if set: - setattr(cls, attr, value) - if hasattr(value, '__addtoclass__'): - value.__addtoclass__(attr, cls) - - def __call__(self, environ, start_response): - try: - status, headers, app_iter = self._process( - environ, start_response) - except ForwardRequest, e: - return e.application(environ, start_response) - else: - start_response(status, headers) - return app_iter - - def _process(self, environ, start_response): - value = event.raise_event('call', self, environ, start_response) - if value is not event.Continue: - status, headers, app_iter = value - return status, headers, app_iter - self.environ = environ - self.config = self.environ['paste.config'] - if self.config.get('app_name'): - self.app_name = self.config['app_name'] - self._cached_output = [] - self.headers_out = { - 'content-type': 'text/html; charset=UTF-8'} - self.status = '200 OK' - self._cookies_out = {} - self.request_method = environ['REQUEST_METHOD'].upper() - self.app_url = self.environ.get('%s.base_url' % self.app_name, '') - self.app_static_url = self.config.get( - 'static_url', self.app_url + '/static') - self.path_info = self.environ.get('PATH_INFO', '') - if self.path_info: - self.path_parts = filter(None, self.path_info[1:].split('/')) - else: - # Note that you have to look at self.path_info to - # distinguish between '' and '/' - self.path_parts = [] - self.fields = cgifields.Fields(cgifields.parse_fields(environ)) - self.cookies = {} - if 'HTTP_COOKIE' in environ: - cookies = SimpleCookie() - try: - cookies.load(environ['HTTP_COOKIE']) - except: - traceback.print_exc(file=self._environ['wsgi.errors']) - for key in cookies.keys(): - self.cookies[key] = cookies[key].value - self.run() - headers = [] - for name, value in self.headers_out.items(): - if isinstance(value, list): - for v in value: - headers.append((name, v)) - else: - headers.append((name, value)) - for cookie in self._cookies_out.values(): - headers.append(('Set-Cookie', cookie.header())) - return self.status, headers, self._cached_output - - @event.wrap_func - def run(self): - __traceback_hide__ = 'before_and_this' - try: - event.wrap_func(self.awake.im_func)(self) - event.wrap_func(self.respond.im_func)(self) - finally: - event.wrap_func(self.sleep.im_func)(self) - - def awake(self, call_setup=True): - if call_setup: - self.setup() - - def setup(self): - pass - - def respond(self): - pass - - def sleep(self, call_teardown=True): - if call_teardown: - self.teardown() - - def teardown(self): - pass - - ############################################################ - ## Request - ############################################################ - - def session__get(self): - if 'paste.session.factory' in self.environ: - sess = self.environ['paste.session.factory']() - elif 'paste.flup_session_service' in self.environ: - sess = self.environ['paste.flup_session_service'].session - self.__dict__['session'] = sess - return sess - - ############################################################ - ## Response - ############################################################ - - def title__get(self): - return self._title or self.__class__.__name__ - - def title__set(self, value): - self._title = value - - def html_title__get(self): - return self._html_title or self.title - - def html_title__set(self, value): - self._html_title = value - - def set_cookie(self, cookie_name, value, path='/', - expires='ONCLOSE', secure=False): - c = cookiewriter.Cookie(cookie_name, value, path=path, - expires=expires, secure=secure) - self._cookies_out[cookie_name] = c - - def set_header(self, header_name, header_value): - header_name = header_name.lower() - if header_name == 'status': - self.status = header_value - return - self.headers_out[header_name] = header_value - - def add_header(self, header_name, header_value): - header_name = header_name.lower() - if header_name == 'status': - self.status = header_value - return - if self.headers_out.has_key(header_name): - if not isinstance(self.headers_out[header_name], list): - self.headers_out[header_name] = [self.headers_out[header_name], - header_value] - else: - self.headers_out[header_name].append(header_value) - else: - self.headers_out[header_name] = header_value - - def write(self, *obj): - for v in obj: - if v is None: - continue - elif isinstance(v, str): - self._cached_output.append(v) - elif isinstance(v, unicode): - self._cached_output.append(v.encode('utf-8')) - else: - self._cached_output.append(unicode(v).encode('utf-8')) - - def redirect(self, url, **query_vars): - url = str(url) - if not url.startswith('/') and not abs_regex.search(url): - url = self.app_url + '/' + url - if 'status' in query_vars: - status = query_vars.pop('status') - if isinstance(status, (str, unicode)): - status = int(status.split()[0]) - else: - status = 303 - if query_vars: - if '?' in url: - url += '&' - else: - url += '?' - url += urllib.urlencode(query_vars) - raise httpexceptions.get_exception(status)( - "This resource has been redirected", - headers={'Location': url}) - - def forward_to_wsgiapp(self, app): - """ - Forwards the request to the given WSGI application - """ - raise ForwardRequest(app) - -abs_regex = re.compile(r'^[a-zA-Z]+:') diff --git a/paste/wareweb/tests/__init__.py b/paste/wareweb/tests/__init__.py deleted file mode 100644 index 792d600..0000000 --- a/paste/wareweb/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/paste/wareweb/tests/test_unpack.py b/paste/wareweb/tests/test_unpack.py deleted file mode 100644 index b44b99b..0000000 --- a/paste/wareweb/tests/test_unpack.py +++ /dev/null @@ -1,103 +0,0 @@ -from paste.wareweb import packing -from paste.wareweb import cgifields -from cStringIO import StringIO - -test_funcs = [] - -def unpack_test(query, *args, **kw): - def decorate(func): - test_funcs.append((func, query, args, kw)) - return func - return decorate - -def test_functions(): - for a1, a2, a3, a4 in test_funcs: - yield function_test, a1, a2, a3, a4 - -def function_test(func, query, expect_args, expect_kw): - spec = packing.FunctionArgSpec(func) - if '?' in query: - path_info, query = query.split('?') - else: - path_info = '' - assert not path_info or path_info.startswith('/') - path_parts = filter(None, path_info[1:].split('/')) - fields = cgifields.parse_fields({ - 'REQUEST_METHOD': 'GET', - 'QUERY_STRING': query, - 'wsgi.input': StringIO()}) - try: - args, kw = spec.unpack_args(path_parts, fields) - except packing.HTTPBadRequest, e: - print fields - print path_parts - if not expect_kw and len(expect_args) == 1: - print e - assert expect_args[0] == str(e) - return - raise - bad_args = [] - if len(expect_args) > args: - for arg in expect_args[len(args):]: - bad_args.append('%s: missing' % arg) - if len(args) > expect_args: - for arg in args[len(expect_args):]: - bad_args.append('Bad input positional arg: %s' % arg) - for i, (got, expected) in enumerate(zip(args, expect_args)): - if got != expected: - bad_args.append('Arg %i; got %r != %r' - % (i, got, expected)) - for name, value in kw.iteritems(): - if name not in expect_kw: - bad_args.append('kw %s: not expected' % name) - elif expect_kw[name] != value: - bad_args.append('kw %s; got %r != %r' - % (name, value, expect_kw[name])) - for name in expect_kw: - if name not in kw: - bad_args.append('kw %s; expected, not gotten' % name) - if bad_args: - print "Expected:" - print expect_args - print expect_kw - print "Got:" - print args - print kw - for line in bad_args: - print line - assert 0, "Bad arguments" - - -@unpack_test('name=bob&age=5', name='bob', age_int=5) -@unpack_test('name=joe&age=ten', "Bad variable 'age': invalid literal for int(): ten") -@unpack_test('age=10', "Variable 'name' required") -@unpack_test('name=name&bob=bob', "Variable 'bob' not expected") -@unpack_test('name=name1&name=name2&age=10', "Bad variable 'name': List not expected") -def t(name, age_int): - pass - - -@unpack_test('name1=x&name2=x&name3=x', name1='x', name2='x', name3='x') -@unpack_test('/test/this/out?stuff', 'test', 'this', 'out', stuff='') -def t2(*args, **kw): - pass - -@unpack_test('/this/here?x', 'this', 'here', x='') -@unpack_test('/this?x', 'this', x='') -@unpack_test('/?x', 'Not enough parameters on the URL (expected 1 more path segments)') -@unpack_test('/this/here/bad?x', 'Too many parameters on the URL (expected 1 less path segments)') -def t3(arg1_path, arg2_path=None, x=None): - pass - -@unpack_test('a=1', a_list_int=[1]) -@unpack_test('') -@unpack_test('a=1&a=2', a_list_int=[1, 2]) -@unpack_test('a=b', "Bad variable 'a': invalid literal for int(): b") -def t4(a_list_int=[]): - pass - -@unpack_test('/1/2', '1', 2) -@unpack_test('/xxx/5', 'xxx', 5) -@unpack_test('/1/x', 'Not enough parameters on the URL (expected 2 more path segments)') -def t5(first_path, second_int_path): - pass diff --git a/paste/wareweb/timeinterval.py b/paste/wareweb/timeinterval.py deleted file mode 100644 index f2d14b8..0000000 --- a/paste/wareweb/timeinterval.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -timeinterval - -Convert interval strings (in the form of 1w2d, etc) to -seconds, and back again. Is not exactly about months or -years (leap years in particular). - -Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd. - -Exports only time_encode and time_decode functions. -""" - -import re - -second = 1 -minute = second*60 -hour = minute*60 -day = hour*24 -week = day*7 -month = day*30 -year = day*365 -time_values = { - 'y': year, - 'b': month, - 'w': week, - 'd': day, - 'h': hour, - 'm': minute, - 's': second, - } -time_ordered = time_values.items() -time_ordered.sort(lambda a, b: -cmp(a[1], b[1])) - -def time_encode(seconds): - """Encodes a number of seconds (representing a time interval) - into a form like 1h2d3s.""" - s = '' - for char, amount in time_ordered: - if seconds >= amount: - i, seconds = divmod(seconds, amount) - s += '%i%s' % (i, char) - return s - -time_re = re.compile(r'[0-9]+[a-zA-Z]') -def time_decode(s): - """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes) - into a number of seconds""" - time = 0 - for match in all_matches(s, time_re): - char = match.group(0)[-1].lower() - if not time_values.has_key(char): - # @@: should signal error - continue - time += int(match.group(0)[:-1]) * time_values[char] - return time - -def all_matches(source, regex): - """ - Return a list of matches for regex in source - """ - pos = 0 - end = len(source) - rv = [] - match = regex.search(source, pos) - while match: - rv.append(match) - match = regex.search(source, match.end() ) - return rv - -__all__ = ['time_encode', 'time_decode'] -- cgit v1.2.1