1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Session service
19 ===============
20
21 This service provides an API and a basic cookie based storage. The
22 storage is exchangable, though.
23 """
24 __author__ = u"Andr\xe9 Malo"
25 __docformat__ = "restructuredtext en"
26
27 from wtf import services as _services
28 from wtf import util as _util
29
30
32 """
33 Interface for storage classes
34
35 :IVariables:
36 - `has_cookie`: Is a cookie involved by this storage?
37
38 :Types:
39 - `has_cookie`: ``bool``
40 """
41
42 - def set(self, name, value):
43 """
44 Set an attribute
45
46 :Parameters:
47 - `name`: Attribute name
48 - `value`: Attribute value
49
50 :Types:
51 - `name`: ``str``
52 - `value`: any
53 """
54
55 - def get(self, name):
56 """
57 Retrieve attribute
58
59 :Parameters:
60 - `name`: Attribute name
61
62 :Types:
63 - `name`: ``str``
64
65 :return: Attribute value
66 :rtype: any
67
68 :Exceptions:
69 - `KeyError`: Attribute not found
70 """
71
73 """
74 Delete attribute
75
76 :Parameters:
77 - `name`: Attribute name
78
79 :Types:
80 - `name`: ``str``
81
82 :Exceptions:
83 - `KeyError`: Attribute not found
84 """
85
87 """
88 Determine whether an attribute exists
89
90 :Parameters:
91 - `name`: Attribute name to check
92
93 :Types:
94 - `name`: ``str``
95
96 :return: Does it exist?
97 :rtype: ``bool``
98 """
99
101 """
102 Determine whether the storage needs a cookie to be set
103
104 :return: Does it need one?
105 :rtype: ``bool``
106 """
107
109 """
110 Determine whether the storage needs a store back
111
112 :return: Does it?
113 :rtype: ``bool``
114 """
115
117 """
118 Create a cookie for the session
119
120 :return: The cookie string
121 :rtype: ``str``
122 """
123
125 """ Persist the session data """
126
128 """ Wipe out session data """
129
130
132 """ Interface for storage factories """
133
134 - def __init__(self, config, opts, args):
135 """
136 Initialization
137
138 :Parameters:
139 - `config`: Configuration
140 - `opts`: Command line options
141 - `args`: Positional command line arguments
142
143 :Types:
144 - `config`: `wtf.config.Config`
145 - `opts`: ``optparse.OptionContainer``
146 - `args`: ``list``
147 """
148
150 """
151 Factory function: Create request-bound storage object
152
153 :Parameters:
154 - `request`: Request object
155
156 :Types:
157 - `request`: `wtf.app.request.Request`
158
159 :return: The new storage instance
160 :rtype: `StorageInterface`
161 """
162
164 """
165 Factory function: Create sid-bound storage object
166
167 :Parameters:
168 - `sid`: Session ID
169
170 :Types:
171 - `sid`: ``str``
172
173 :return: The new storage instance
174 :rtype: `StorageInterface`
175 """
176
178 """
179 Fetch status information from storage factory
180
181 :return: Status information, see specific implementation for
182 details
183 :rtype: any
184 """
185
186
188 """
189 Session object
190
191 :IVariables:
192 - `_adapter`: Storage adapter
193
194 :Types:
195 - `_adapter`: `StorageAdapter`
196 """
197 __slots__ = ['__weakref__', '_adapter']
198
200 """
201 Initialization
202
203 :Parameters:
204 - `adapter`: Storage adapter
205
206 :Types:
207 - `adapter`: `StorageAdapter`
208 """
209 self._adapter = adapter
210
212 """
213 Resolve magic sesison attributes magically
214
215 :Parameters:
216 - `name`: The magic name to resolve
217
218 :Types:
219 - `name`: ``str``
220
221 :return: The value of the magic attribute
222 :rtype: any
223
224 :Exceptions:
225 - `AttributeError`: Attribute not found
226 """
227 name = "__%s__" % name
228 if self._adapter.contains(name):
229 return self._adapter.get(name)
230 raise AttributeError(name)
231
233 """
234 Delete a session attribute
235
236 If the attribute did not exist before, this is not an error
237 (just a no-op).
238
239 :Parameters:
240 - `name`: Name of the attribute
241
242 :Types:
243 - `name`: ``str``
244 """
245 self._adapter.delete(name)
246
248 """
249 Set or replace a session attribute
250
251 :Parameters:
252 - `name`: Attribute name
253 - `value`: Attribute value (must be picklable)
254
255 :Types:
256 - `name`: ``str``
257 - `value`: any
258 """
259 self._adapter.set(name, value)
260
262 """
263 Determine the value of an attribute
264
265 :Parameters:
266 - `name`: Attribute name
267
268 :Types:
269 - `name` ``str``
270
271 :return: Attribute value
272 :rtype: any
273
274 :Exceptions:
275 - `KeyError`: The attribute did not exist
276 """
277 return self._adapter.get(name)
278
279 - def get(self, name, default=None):
280 """
281 Determine the value of an attribute
282
283 :Parameters:
284 - `name`: The attribute name
285 - `default`: Default value to return in case the attribute does not
286 exist
287
288 :Types:
289 - `name`: ``str``
290 - `default`: any
291 """
292 try:
293 return self._adapter.get(name)
294 except KeyError:
295 return default
296
298 """
299 Determine if an attribute exists in the session
300
301 :Parameters:
302 - `name`: Attribute name to check
303
304 :Types:
305 - `name`: ``str``
306
307 :return: Does it exist?
308 :rtype: ``bool``
309 """
310 return self._adapter.contains(name)
311 has_key = __contains__
312
313 - def new(self, **kwargs):
314 """
315 Create a new session
316
317 :Parameters:
318 - `kwargs`: Initial attributes (``{'name': value, ...}``)
319
320 :Types:
321 - `kwargs`: ``dict``
322 """
323 self._adapter.new()
324 for key, val in kwargs.iteritems():
325 self._adapter.set(key, val)
326
328 """
329 Invalidate the session
330
331 The session is not cleared, but will be unusable with the next
332 request. Call `new` if you need to create a fresh, empty session after
333 invalidating.
334 """
335 self._adapter.set('__is_valid__', False)
336
337
339 """
340 Storage adapter
341
342 This class acts a glue code between the session object, middleware and
343 storage implementations. It provides all interfaces needed both by the
344 public session object and the middleware. The lazyness of session
345 initialization and store back are also implemented here.
346
347 :IVariables:
348 - `_dirty`: Is the session dirty (need store back)?
349 - `_factory`: Tuple of storage factory and request object. When the
350 storage is initialized, this attribute becomes ``None``
351 - `_storage`: Lazily initialized storage instance (on first access)
352
353 :Types:
354 - `_dirty`: ``bool``
355 - `_factory`: ``tuple``
356 - `_storage`: ``StorageInterface``
357 """
358 _dirty, _stored = False, False
359
360 - def __init__(self, factory, request=None):
361 """
362 Initialization
363
364 :Parameters:
365 - `factory`: Storage factory
366 - `request`: Request object
367
368 :Types:
369 - `factory`: `StorageFactoryInterface`
370 - `request`: `wtf.app.request.Request`
371 """
372 self._factory = factory, request
373
375 """
376 Resolve unknown attributes
377
378 Effectively resolve the ``_storage`` attribute lazily. The attribute
379 will be cached in the instance, so this method is called only once
380 (for ``_storage``).
381
382 :Parameters:
383 - `name`: The attribute name to resolve
384
385 :Types:
386 - `name`: ``str``
387
388 :return: The resolved attribute value
389 :rtype: any
390
391 :Exceptions:
392 - `AttributeError`: Attribute not found
393 """
394 if name == '_storage':
395 factory, request = self._factory
396 self._stored, self._storage = True, factory(request)
397 if self.contains('__is_valid__') and not self.get('__is_valid__'):
398 self._storage = factory(None)
399 self.set('__is_valid__', True)
400 return super(StorageAdapter, self).__getattribute__(name)
401
403 """ Create a fresh session """
404
405
406 factory, _ = self._factory
407 self._dirty, self._storage = True, factory(None)
408 self.set('__is_valid__', True)
409
410 - def get(self, name):
411 """
412 Retrieve a attribute value
413
414 :Parameters:
415 - `name`: The attribute name to look up
416
417 :Types:
418 - `name`: ``str``
419
420 :return: attribute value
421 :rtype: any
422
423 :Exceptions:
424 - `KeyError`: Attribute not found
425 """
426 return self._storage.get(name)
427
429 """
430 Determine whether an attribute exists
431
432 :Parameters:
433 - `name`: The name to check
434
435 :Types:
436 - `name`: ``str``
437
438 :return: Does the attribute exist?
439 :rtype: ``bool``
440 """
441 return self._storage.contains(name)
442
443 - def set(self, name, value):
444 """
445 Set or replace an attribute
446
447 :Parameters:
448 - `name`: Attribute name
449 - `value`: Attribute value (must be pickleable)
450
451 :Types:
452 - `name`: ``str``
453 - `value`: any
454 """
455 self._dirty, _ = True, self._storage.set(name, value)
456
458 """
459 Delete an attribute
460
461 :Parameters:
462 - `name`: The name of the attribute to delete
463
464 :Types:
465 - `name`: ``str``
466 """
467 try:
468 self._dirty, _ = True, self._storage.delete(name)
469 except KeyError:
470 pass
471
473 """
474 Store back the session
475
476 :Note: The session is only stored back, if it's marked dirty.
477 """
478 if self._dirty or (self._stored and self._storage.need_store()):
479 return self._storage.store_back()
480
482 """ Wipe session in storage """
483 return self._storage.wipe()
484
486 """
487 Ask the storage for a cookie if necessary
488
489 :return: The cookie string or ``None``
490 :rtype: ``str``
491 """
492 if self._stored and self._storage.need_cookie():
493 return self._storage.make_cookie()
494 return None
495
496
498 """
499 Intermediate session object factory
500
501 :IVariables:
502 - `_storage`: Storage factory (takes the request object)
503 - `_start_response`: original WSGI start_response callable
504 - `_adapter`: Storage adapter (maybe ``None`` if the session is
505 not queried)
506
507 :Types:
508 - `_storage`: ``callable``
509 - `_start_response`: ``callable``
510 - `_adapter`: ``StorageAdapter``
511 """
512 _adapter = None
513
514 - def __init__(self, storage, start_response):
515 """
516 Initialization
517
518 :Parameters:
519 - `storage`: Storage factory (takes the request object)
520 - `start_response`: Original start_response callable
521
522 :Types:
523 - `storage`: ``callable``
524 - `start_response`: ``callable``
525 """
526 self._storage = storage
527 self._start_response = start_response
528
530 """
531 Factory function
532
533 :Parameters:
534 - `request`: The request object
535
536 :Types:
537 - `request`: `wtf.app.request.Request`
538
539 :return: Session object
540 :rtype: `Session`
541 """
542 self._adapter = StorageAdapter(self._storage, request)
543 return Session(self._adapter)
544
546 """
547 Session dealing start_response callable
548
549 :See: `WSGI spec`_
550
551 .. _WSGI spec: http://www.python.org/dev/peps/pep-0333/
552 """
553 adapter = self._adapter
554 if adapter is not None:
555 adapter.store_back()
556 cookie = adapter.cookie()
557 if cookie is not None:
558 for idx, (name, value) in enumerate(response_headers):
559 if name.lower() == 'cache-control':
560 values = set(item.strip().replace('"', '').lower()
561 for item in value.split(','))
562 toadd = []
563 if 'no-cache=set-cookie' not in values:
564 toadd.append("no-cache=Set-Cookie")
565 if 'private' not in values:
566 toadd.append("private")
567 if toadd:
568 response_headers[idx] = (
569 name, ", ".join([value] + toadd)
570 )
571 break
572 else:
573 response_headers.append((
574 'Cache-Control', 'private, no-cache=Set-Cookie'
575 ))
576 response_headers.append(('Set-Cookie', cookie))
577 return self._start_response(status, response_headers, exc_info)
578
579
581 """
582 Session middleware
583
584 :IVariables:
585 - `_storage`: Storage factory
586 - `_func`: Next WSGI handler
587
588 :Types:
589 - `_storage`: `StorageFactoryInterface`
590 - `_func`: ``callable``
591 """
592
594 """
595 Initialization
596
597 :Parameters:
598 - `storage`: Storage factory
599 - `func`: WSGI callable to wrap
600
601 :Types:
602 - `storage`: `StorageFactoryInterface`
603 - `func`: ``callable``
604 """
605 self._storage, self._func = storage, func
606
607 - def __call__(self, environ, start_response):
608 """
609 Middleware handler
610
611 :Parameters:
612 - `environ`: WSGI environment
613 - `start_response`: Start response callable
614
615 :Types:
616 - `environ`: ``dict``
617 - `start_response`: ``callable``
618
619 :return: WSGI response iterable
620 :rtype: ``iterable``
621 """
622 factory = SessionFactory(self._storage, start_response)
623 environ['wtf.request.session'] = factory
624 return self._func(environ, factory.start_response)
625
626
628 """
629 Global session service
630
631 :IVariables:
632 - `status`: Status requester
633
634 :Types:
635 - `status`: ``callable``
636 """
637
639 """
640 Initialization
641
642 :Parameters:
643 - `storage`: Storage factory
644
645 :Types:
646 - `storage`: `StorageFactoryInterface`
647 """
648 self.status = storage.status
649
650
652 """
653 Session service
654
655 This service provides a middleware which automatically handles
656 session persistance and APIs for the application.
657
658 :IVariables:
659 - `_storage`: Storage factory
660
661 :Types:
662 - `_storage`: `StorageFactoryInterface`
663 """
664 __implements__ = [_services.ServiceInterface]
665
666 - def __init__(self, config, opts, args):
667 """ :See: `wtf.services.ServiceInterface.__init__` """
668 if 'session' in config and not config.session('enable', True):
669 self._storage = None
670 else:
671 if 'session' in config and 'storage' in config.session:
672 storage = config.session.storage
673 else:
674 storage = 'wtf.app.services.session_storage.cookie.Cookie'
675 self._storage = _util.load_dotted(storage)(config, opts, args)
676
678 """ :See: `wtf.services.ServiceInterface.shutdown` """
679 pass
680
682 """ :See: `wtf.services.ServiceInterface.global_service` """
683 if self._storage is None:
684 return None
685 return 'wtf.session', GlobalSession(self._storage)
686
688 """ :See: `wtf.services.ServiceInterface.middleware` """
689 if self._storage is None:
690 return func
691 return Middleware(self._storage, func)
692