Package wtf :: Package app :: Package services :: Module session
[hide private]
[frames] | no frames]

Source Code for Module wtf.app.services.session

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2007-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 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   
31 -class StorageInterface(object):
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
72 - def delete(self, name):
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
86 - def contains(self, name):
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 107
108 - def need_store(self):
109 """ 110 Determine whether the storage needs a store back 111 112 :return: Does it? 113 :rtype: ``bool`` 114 """
115 123
124 - def store_back(self):
125 """ Persist the session data """
126
127 - def wipe(self):
128 """ Wipe out session data """
129 130
131 -class StorageFactoryInterface(object):
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
149 - def __call__(self, request):
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
163 - def from_sid(self, sid):
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
177 - def status(self):
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
187 -class Session(object):
188 """ 189 Session object 190 191 :IVariables: 192 - `_adapter`: Storage adapter 193 194 :Types: 195 - `_adapter`: `StorageAdapter` 196 """ 197 __slots__ = ['__weakref__', '_adapter'] 198
199 - def __init__(self, adapter):
200 """ 201 Initialization 202 203 :Parameters: 204 - `adapter`: Storage adapter 205 206 :Types: 207 - `adapter`: `StorageAdapter` 208 """ 209 self._adapter = adapter
210
211 - def __getattr__(self, name):
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
232 - def __delitem__(self, name):
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
247 - def __setitem__(self, name, value):
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
261 - def __getitem__(self, name):
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
297 - def __contains__(self, name):
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
327 - def invalidate(self):
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
338 -class StorageAdapter(object):
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
374 - def __getattr__(self, name):
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
402 - def new(self):
403 """ Create a fresh session """ 404 # pylint: disable = W0201 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
428 - def contains(self, name):
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
457 - def delete(self, name):
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
472 - def store_back(self):
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
481 - def wipe(self):
482 """ Wipe session in storage """ 483 return self._storage.wipe()
484
485 - def cookie(self):
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
497 -class SessionFactory(object):
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
529 - def __call__(self, request):
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
545 - def start_response(self, status, response_headers, exc_info=None):
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
580 -class Middleware(object):
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
593 - def __init__(self, storage, func):
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
627 -class GlobalSession(object):
628 """ 629 Global session service 630 631 :IVariables: 632 - `status`: Status requester 633 634 :Types: 635 - `status`: ``callable`` 636 """ 637
638 - def __init__(self, storage):
639 """ 640 Initialization 641 642 :Parameters: 643 - `storage`: Storage factory 644 645 :Types: 646 - `storage`: `StorageFactoryInterface` 647 """ 648 self.status = storage.status
649 650
651 -class SessionService(object):
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
677 - def shutdown(self):
678 """ :See: `wtf.services.ServiceInterface.shutdown` """ 679 pass
680
681 - def global_service(self):
682 """ :See: `wtf.services.ServiceInterface.global_service` """ 683 if self._storage is None: 684 return None 685 return 'wtf.session', GlobalSession(self._storage)
686
687 - def middleware(self, func):
688 """ :See: `wtf.services.ServiceInterface.middleware` """ 689 if self._storage is None: 690 return func 691 return Middleware(self._storage, func)
692