| | 6 | |
|---|
| | 7 | |
|---|
| | 8 | class pipeline(list): |
|---|
| | 9 | """An ordered list of configurable WSGI middleware. |
|---|
| | 10 | |
|---|
| | 11 | self: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a |
|---|
| | 12 | constructor that takes an initial, positional wsgiapp argument, |
|---|
| | 13 | plus optional keyword arguments, and returns a WSGI application |
|---|
| | 14 | (that takes environ and start_response arguments). The 'name' can |
|---|
| | 15 | be any you choose, and will correspond to keys in self.config. |
|---|
| | 16 | config: a dict whose keys match names listed in the pipeline. Each |
|---|
| | 17 | value is a further dict which will be passed to the corresponding |
|---|
| | 18 | named WSGI callable (from the pipeline) as keyword arguments. |
|---|
| | 19 | """ |
|---|
| | 20 | |
|---|
| | 21 | def __new__(cls, app, members=None, key="wsgi"): |
|---|
| | 22 | return list.__new__(cls) |
|---|
| | 23 | |
|---|
| | 24 | def __init__(self, app, members=None, key="wsgi"): |
|---|
| | 25 | self.app = app |
|---|
| | 26 | if members: |
|---|
| | 27 | self.extend(members) |
|---|
| | 28 | self.head = None |
|---|
| | 29 | self.tail = None |
|---|
| | 30 | self.config = {} |
|---|
| | 31 | self.key = key |
|---|
| | 32 | app.namespaces[key] = self.namespace_handler |
|---|
| | 33 | app.wsgi_pipeline = self |
|---|
| | 34 | |
|---|
| | 35 | def namespace_handler(self, k, v): |
|---|
| | 36 | """Config handler for our namespace.""" |
|---|
| | 37 | if k == "pipeline": |
|---|
| | 38 | # Note this allows multiple entries to be aggregated (but also |
|---|
| | 39 | # note dicts are essentially unordered). It should also allow |
|---|
| | 40 | # developers to set default middleware in code (passed to |
|---|
| | 41 | # pipeline.__init__) that deployers can add to but not remove. |
|---|
| | 42 | self.extend(v) |
|---|
| | 43 | |
|---|
| | 44 | if self: |
|---|
| | 45 | # If self is empty, there's no need to replace app.wsgiapp. |
|---|
| | 46 | # Also note we're grabbing app.wsgiapp, not app.__call__, |
|---|
| | 47 | # so we can "play nice" with other Application-manglers |
|---|
| | 48 | # (hopefully, they'll do the same). |
|---|
| | 49 | self.tail = self.app.wsgiapp |
|---|
| | 50 | self.app.wsgiapp = self.__call__ |
|---|
| | 51 | else: |
|---|
| | 52 | name, arg = k.split(".", 1) |
|---|
| | 53 | bucket = self.config.setdefault(name, {}) |
|---|
| | 54 | bucket[arg] = v |
|---|
| | 55 | |
|---|
| | 56 | def __call__(self, environ, start_response): |
|---|
| | 57 | if not self.head: |
|---|
| | 58 | # This class may be used without calling namespace_handler, |
|---|
| | 59 | # in which case self.tail may still be None. |
|---|
| | 60 | self.head = self.tail or self.app.wsgiapp |
|---|
| | 61 | pipe = self[:] |
|---|
| | 62 | pipe.reverse() |
|---|
| | 63 | for name, callable in pipe: |
|---|
| | 64 | conf = self.config.get(name, {}) |
|---|
| | 65 | self.head = callable(self.head, **conf) |
|---|
| | 66 | return self.head(environ, start_response) |
|---|
| | 67 | |
|---|
| | 68 | def __repr__(self): |
|---|
| | 69 | return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, |
|---|
| | 70 | list(self)) |
|---|