Permalink
Browse files

Updates.

  • Loading branch information...
1 parent 828d102 commit 8f6c27704b27ecc7fd349115c754efad00cb2f35 @amcgregor amcgregor committed Jan 27, 2012
View
14 web/core/application.py
@@ -88,7 +88,7 @@ def __call__(self, environ):
# If the current return value isn't of the expceted type, invalidate the cache.
# or, if the previous handler can't process the current result, invalidate the cache.
- if type(result) is not kind or not handler(context, result):
+ if not isinstance(result, kind) or not handler(context, result):
raise KeyError('Invalidating.')
# Reset the cache miss counter.
@@ -114,10 +114,16 @@ def __call__(self, environ):
context.response = exc
except Exception as exc:
- raise
+ for ext in self._after:
+ if ext(context, exc):
+ exc = None
+
+ if exc:
+ raise
- for ext in self._after:
- ext(context, exc)
+ else:
+ for ext in self._after:
+ ext(context, None)
result = context.response(environ)
View
73 web/ext/auth/__init__.py
@@ -1,5 +1,7 @@
# encoding: utf-8
+from functools import partial
+
class AuthenticationExtension(object):
uses = ['template']
@@ -15,6 +17,8 @@ def prepare(self, context):
context.acl = []
context.namespace.user = context.user
context.namespace.auth = predicates
+
+ context.authenticate = partial(self.authenticate, context)
def dispatch(self, context, consumed, handler, is_endpoint):
acl = getattr(handler, '__acl__', [])
@@ -23,3 +27,72 @@ def dispatch(self, context, consumed, handler, is_endpoint):
def before(self, context):
"""Validate the ACL."""
pass
+
+ def authenticate(self, context, identifier, password=None, force=False):
+ """Authenticate a user.
+
+ Sets the current user in the session. You can optionally omit a password
+ and force the authentication to authenticate as any user.
+
+ If successful, the web.auth.user variable is immediately available.
+
+ Returns True on success, False otherwise.
+ """
+
+ if force:
+ result = (identifier, self._lookup(identifier))
+ else:
+ result = self._authenticate(identifier, password)
+
+ if result is None or result[1] is None:
+ return False
+
+ context.session[self._name] = result[0]
+ context.session.save()
+
+ context.user = result[1]
+
+ return True
+
+ def deauthenticate(self, context, nuke=False):
+ """Force logout.
+
+ The web.auth.user variable is immediately deleted and session variable cleared.
+
+ Additionally, this function can also completely erase the Beaker session.
+ """
+
+ session = context.session
+
+ if nuke:
+ session.invalidate()
+
+ session[self._name] = None
+
+ if not session.autocommit:
+ session.save()
+
+ context.user = None
+
+
+def authorize(predicate):
+ """Decorator to enforce predicates.
+
+ Evaluate predicates directly (using the bool callable) and raise a
+ 401 Not Authorized error if you want to do this by hand.
+ """
+
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kw):
+ if not bool(predicate):
+ raise web.core.http.HTTPUnauthorized()
+
+ return func(*args, **kw)
+
+ # Match wrapped function argspec.
+ wrapper.__func_argspec__ = getattr(func, '__func_argspec__', inspect.getargspec(func))
+
+ return wrapper
+
+ return decorator
View
3 web/ext/base/__init__.py
@@ -54,3 +54,6 @@ def dispatch(self, context, consumed, handler, is_endpoint):
if not is_endpoint:
context.environ['web.controller'] = str(context.request.path)
+
+ def after(self, context, exc=None):
+ pass
View
78 web/ext/base/helpers.py
@@ -1,11 +1,12 @@
# encoding: utf-8
import weakref
+import math
from marrow.util.url import URL
-__all__ = ['URLGenerator']
+__all__ = ['URLGenerator', 'Pager']
class URLGenerator(object):
@@ -115,3 +116,78 @@ def _partial(self, path, params, anchor):
url.fragment = anchor
return str(url)
+
+
+class Pager(object):
+ def __init__(self, iterable, page, per, total=None):
+ self.iterable = iterable
+ self.page = page
+ self.per = per
+
+ try:
+ self.total = total or len(iterable)
+ except TypeError:
+ self.total = None # indeterminate maximum
+
+ self._iterable = iterable[self.slice]
+
+ def __len__(self):
+ try:
+ return len(self._iterable)
+ except TypeError:
+ if self.total is None:
+ return self.per
+ else:
+ return min(self.total, self.per)
+
+ @property
+ def count(self):
+ return int(math.ceil(self.total / float(self.per)))
+
+ def prev(self):
+ return Pager(self.iterable, max(0, self.page - 1), self.per, self.total)
+
+ def next(self):
+ if self.total is not None:
+ return Pager(self.iterable, self.page + 1, self.per, self.total)
+
+ return Pager(self.iterable, min(len(self), self.page + 1), self.per, self.total)
+
+ def __iter__(self):
+ return self._iterable.__iter__()
+
+ @property
+ def slice(self):
+ return slice(self.per * (self.page - 1), (self.per * self.page) - 1)
+
+ def __getitem__(self, item):
+ return self._iterable.__getitem__(item)
+
+ def pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2):
+ """Iterates over the page numbers in the pagination. The four
+ parameters control the thresholds how many numbers should be produced
+ from the sides. Skipped page numbers are represented as `None`.
+ This is how you could render such a pagination in the templates:
+ """
+ last = 0
+ page = self.page
+ pages = self.count
+ for num in range(1, pages + 1):
+ if num <= left_edge or \
+ (num > page - left_current - 1 and \
+ num < page + right_current) or \
+ num > pages - right_edge:
+ if last + 1 != num:
+ yield None
+ yield num
+ last = num
+
+
+if __name__ == '__main__':
+ stuff = [1,2,3,4,5,6,7,8,9]
+ p = Pager(stuff, 1, 2)
+
+ print(p.page, p.per, p.total, p.count)
+ print(len(p), p.slice)
+ print(list(p.pages()))
+ print(list(p))
View
44 web/ext/session/__init__.py
@@ -1,23 +1,63 @@
# encoding: utf-8
+
+class BeakerSessionProvider(object):
+ uses = []
+ needs = []
+
+ def __init__(self, config=None):
+ """Called during startup."""
+
+ config = config or dict()
+
+ self.options = dict(
+ invalidate_corrupt=True,
+ type=None,
+ data_dir=None,
+ key='beaker.session.id',
+ timeout=None,
+ secret=None,
+ log_file=None
+ )
+
+ self.options.update(dict(config))
+
+ pass
+
+ def stop(self):
+ """Called during shutdown."""
+ pass
+
+ def __call__(self, context):
+ """Called during preparation."""
+ pass
+
+
+
+
class SessionExtension(object):
uses = ['database']
provides = ['session']
+ defaults = dict(
+
+ )
+
def __init__(self, config):
"""Executed to configure the extension."""
super(SessionExtension, self).__init__()
# TODO: Look this up w/ sane (tempfile) default.
- self.Provider = SessionProvider
+ self.Provider = BeakerSessionProvider
+ self.config = config
self.uses.extend(getattr(self.Provider, 'uses', [])
self.needs.extend(getattr(self.Provider, 'needs', [])
def start(self):
"""Executed during application startup just after binding the server."""
- self.provider = self.Provider()
+ self.provider = self.Provider(self.config)
def stop(self):
"""Executed during application shutdown after the last request has been served."""
View
36 web/ext/session/obj.py
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+from marrow.util.bunch import Bunch
+
+
+class Session(Bunch):
+ def __init__(self, identifier):
+ self._loaded = False
+
+ def save(self):
+ """Ensure the session is saved before continuing."""
+ pass
+
+ def delete(self):
+ """The current session should be deleted. No further session access is possible."""
+ pass
+
+ def invalidate(self):
+ """The current session should be deleted and a new session begun."""
+ pass
+
+ @property
+ def id(self):
+ pass
+
+ @property
+ def loaded(self):
+ pass
+
+ @property
+ def locked(self):
+ pass
+
+
+
+
View
7 web/ext/template.py
@@ -57,6 +57,13 @@ def stop(self):
"""Executed during application shutdown after the last request has been served."""
pass
+ def graceful(self):
+ """Called when a SIGHUP is sent to the application.
+
+ Allows your code to re-load configuration and your code should close then re-open sockets and files.
+ """
+ pass
+
def prepare(self, context):
"""Executed during request set-up to populate the @context@ attribute-access dictionary.

0 comments on commit 8f6c277

Please sign in to comment.