summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Bicking <ian@ianbicking.org>2005-08-22 23:27:44 +0000
committerIan Bicking <ian@ianbicking.org>2005-08-22 23:27:44 +0000
commit36d1c63f5e12f1dfd6d5187359093a96a2fd2de4 (patch)
treeb7a5def485a3dc9f4a6d78d7846aa9893f96f8f2
parent683bab67fba28b7afa7aeca693590c45fefd284b (diff)
downloadpaste-git-36d1c63f5e12f1dfd6d5187359093a96a2fd2de4.tar.gz
Moved wareweb to separate project
-rw-r--r--paste/wareweb/__init__.py5
-rw-r--r--paste/wareweb/cgifields.py82
-rw-r--r--paste/wareweb/conf_setup.py5
-rw-r--r--paste/wareweb/cookiewriter.py41
-rw-r--r--paste/wareweb/dispatch.py138
-rw-r--r--paste/wareweb/docs/wareweb.txt149
-rw-r--r--paste/wareweb/event.py34
-rw-r--r--paste/wareweb/interfaces.py196
-rw-r--r--paste/wareweb/notify.py67
-rw-r--r--paste/wareweb/packing.py213
-rw-r--r--paste/wareweb/servlet.py218
-rw-r--r--paste/wareweb/tests/__init__.py1
-rw-r--r--paste/wareweb/tests/test_unpack.py103
-rw-r--r--paste/wareweb/timeinterval.py70
14 files changed, 0 insertions, 1322 deletions
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
-<http://webwareforpython.org>`_. 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 ``<app_name>.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('<div class="message">%s</div>\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 '<br>'.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']