Package gluon :: Module globals
[hide private]
[frames] | no frames]

Source Code for Module gluon.globals

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework 
  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  8   
  9  Contains the classes for the global used variables: 
 10   
 11  - Request 
 12  - Response 
 13  - Session 
 14   
 15  """ 
 16   
 17  from storage import Storage, List 
 18  from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE 
 19  from xmlrpc import handler 
 20  from contenttype import contenttype 
 21  from html import xmlescape, TABLE, TR, PRE, URL 
 22  from http import HTTP, redirect 
 23  from fileutils import up 
 24  from serializers import json, custom_json 
 25  import settings 
 26  from utils import web2py_uuid, secure_dumps, secure_loads 
 27  from settings import global_settings 
 28  import hashlib 
 29  import portalocker 
 30  import cPickle 
 31  import cStringIO 
 32  import datetime 
 33  import re 
 34  import Cookie 
 35  import os 
 36  import sys 
 37  import traceback 
 38  import threading 
 39   
 40  FMT = '%a, %d-%b-%Y %H:%M:%S PST' 
 41  PAST = 'Sat, 1-Jan-1971 00:00:00' 
 42  FUTURE = 'Tue, 1-Dec-2999 23:59:59' 
 43   
 44  try: 
 45      from gluon.contrib.minify import minify 
 46      have_minify = True 
 47  except ImportError: 
 48      have_minify = False 
 49   
 50  regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$') 
 51   
 52  __all__ = ['Request', 'Response', 'Session'] 
 53   
 54  current = threading.local()  # thread-local storage for request-scope globals 
 55   
 56  css_template = '<link href="%s" rel="stylesheet" type="text/css" />' 
 57  js_template = '<script src="%s" type="text/javascript"></script>' 
 58  coffee_template = '<script src="%s" type="text/coffee"></script>' 
 59  typescript_template = '<script src="%s" type="text/typescript"></script>' 
 60  less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />' 
 61  css_inline = '<style type="text/css">\n%s\n</style>' 
 62  js_inline = '<script type="text/javascript">\n%s\n</script>' 
 63   
 64   
65 -class Request(Storage):
66 67 """ 68 defines the request object and the default values of its members 69 70 - env: environment variables, by gluon.main.wsgibase() 71 - cookies 72 - get_vars 73 - post_vars 74 - vars 75 - folder 76 - application 77 - function 78 - args 79 - extension 80 - now: datetime.datetime.today() 81 - restful() 82 """ 83
84 - def __init__(self):
85 Storage.__init__(self) 86 self.wsgi = Storage() # hooks to environ and start_response 87 self.env = Storage() 88 self.cookies = Cookie.SimpleCookie() 89 self.get_vars = Storage() 90 self.post_vars = Storage() 91 self.vars = Storage() 92 self.folder = None 93 self.application = None 94 self.function = None 95 self.args = List() 96 self.extension = 'html' 97 self.now = datetime.datetime.now() 98 self.utcnow = datetime.datetime.utcnow() 99 self.is_restful = False 100 self.is_https = False 101 self.is_local = False 102 self.global_settings = settings.global_settings
103
104 - def compute_uuid(self):
105 self.uuid = '%s/%s.%s.%s' % ( 106 self.application, 107 self.client.replace(':', '_'), 108 self.now.strftime('%Y-%m-%d.%H-%M-%S'), 109 web2py_uuid()) 110 return self.uuid
111
112 - def user_agent(self):
113 from gluon.contrib import user_agent_parser 114 session = current.session 115 user_agent = session._user_agent or \ 116 user_agent_parser.detect(self.env.http_user_agent) 117 if session: 118 session._user_agent = user_agent 119 user_agent = Storage(user_agent) 120 for key, value in user_agent.items(): 121 if isinstance(value, dict): 122 user_agent[key] = Storage(value) 123 return user_agent
124
125 - def requires_https(self):
126 """ 127 If request comes in over HTTP, redirect it to HTTPS 128 and secure the session. 129 """ 130 cmd_opts = global_settings.cmd_options 131 #checking if this is called within the scheduler or within the shell 132 #in addition to checking if it's not a cronjob 133 if ((cmd_opts and (cmd_opts.shell or cmd_opts.scheduler)) 134 or global_settings.cronjob or self.is_https): 135 current.session.secure() 136 else: 137 current.session.forget() 138 redirect(URL(scheme='https', args=self.args, vars=self.vars))
139 140 141
142 - def restful(self):
143 def wrapper(action, self=self): 144 def f(_action=action, _self=self, *a, **b): 145 self.is_restful = True 146 method = _self.env.request_method 147 if len(_self.args) and '.' in _self.args[-1]: 148 _self.args[- 149 1], _self.extension = _self.args[-1].rsplit('.', 1) 150 current.response.headers['Content-Type'] = \ 151 contenttype(_self.extension.lower()) 152 if not method in ['GET', 'POST', 'DELETE', 'PUT']: 153 raise HTTP(400, "invalid method") 154 rest_action = _action().get(method, None) 155 if not rest_action: 156 raise HTTP(400, "method not supported") 157 try: 158 return rest_action(*_self.args, **_self.vars) 159 except TypeError, e: 160 exc_type, exc_value, exc_traceback = sys.exc_info() 161 if len(traceback.extract_tb(exc_traceback)) == 1: 162 raise HTTP(400, "invalid arguments") 163 else: 164 raise e
165 f.__doc__ = action.__doc__ 166 f.__name__ = action.__name__ 167 return f
168 return wrapper 169 170
171 -class Response(Storage):
172 173 """ 174 defines the response object and the default values of its members 175 response.write( ) can be used to write in the output html 176 """ 177
178 - def __init__(self):
179 Storage.__init__(self) 180 self.status = 200 181 self.headers = dict() 182 self.headers['X-Powered-By'] = 'web2py' 183 self.body = cStringIO.StringIO() 184 self.session_id = None 185 self.cookies = Cookie.SimpleCookie() 186 self.postprocessing = [] 187 self.flash = '' # used by the default view layout 188 self.meta = Storage() # used by web2py_ajax.html 189 self.menu = [] # used by the default view layout 190 self.files = [] # used by web2py_ajax.html 191 self.generic_patterns = [] # patterns to allow generic views 192 self.delimiters = ('{{', '}}') 193 self._vars = None 194 self._caller = lambda f: f() 195 self._view_environment = None 196 self._custom_commit = None 197 self._custom_rollback = None
198
199 - def write(self, data, escape=True):
200 if not escape: 201 self.body.write(str(data)) 202 else: 203 self.body.write(xmlescape(data))
204
205 - def render(self, *a, **b):
206 from compileapp import run_view_in 207 if len(a) > 2: 208 raise SyntaxError( 209 'Response.render can be called with two arguments, at most') 210 elif len(a) == 2: 211 (view, self._vars) = (a[0], a[1]) 212 elif len(a) == 1 and isinstance(a[0], str): 213 (view, self._vars) = (a[0], {}) 214 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read): 215 (view, self._vars) = (a[0], {}) 216 elif len(a) == 1 and isinstance(a[0], dict): 217 (view, self._vars) = (None, a[0]) 218 else: 219 (view, self._vars) = (None, {}) 220 self._vars.update(b) 221 self._view_environment.update(self._vars) 222 if view: 223 import cStringIO 224 (obody, oview) = (self.body, self.view) 225 (self.body, self.view) = (cStringIO.StringIO(), view) 226 run_view_in(self._view_environment) 227 page = self.body.getvalue() 228 self.body.close() 229 (self.body, self.view) = (obody, oview) 230 else: 231 run_view_in(self._view_environment) 232 page = self.body.getvalue() 233 return page
234
235 - def include_meta(self):
236 s = '\n'.join( 237 '<meta name="%s" content="%s" />\n' % (k, xmlescape(v)) 238 for k, v in (self.meta or {}).iteritems()) 239 self.write(s, escape=False)
240
241 - def include_files(self, extensions=None):
242 243 """ 244 Caching method for writing out files. 245 By default, caches in ram for 5 minutes. To change, 246 response.cache_includes = (cache_method, time_expire). 247 Example: (cache.disk, 60) # caches to disk for 1 minute. 248 """ 249 from gluon import URL 250 251 files = [] 252 has_js = has_css = False 253 for item in self.files: 254 if extensions and not item.split('.')[-1] in extensions: 255 continue 256 if item in files: 257 continue 258 if item.endswith('.js'): 259 has_js = True 260 if item.endswith('.css'): 261 has_css = True 262 files.append(item) 263 264 if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)): 265 # cache for 5 minutes by default 266 key = hashlib.md5(repr(files)).hexdigest() 267 268 cache = self.cache_includes or (current.cache.ram, 60 * 5) 269 270 def call_minify(files=files): 271 return minify.minify(files, 272 URL('static', 'temp'), 273 current.request.folder, 274 self.optimize_css, 275 self.optimize_js)
276 if cache: 277 cache_model, time_expire = cache 278 files = cache_model('response.files.minified/' + key, 279 call_minify, 280 time_expire) 281 else: 282 files = call_minify() 283 s = '' 284 for item in files: 285 if isinstance(item, str): 286 f = item.lower().split('?')[0] 287 if self.static_version: 288 item = item.replace( 289 '/static/', '/static/_%s/' % self.static_version, 1) 290 if f.endswith('.css'): 291 s += css_template % item 292 elif f.endswith('.js'): 293 s += js_template % item 294 elif f.endswith('.coffee'): 295 s += coffee_template % item 296 elif f.endswith('.ts'): 297 # http://www.typescriptlang.org/ 298 s += typescript_template % item 299 elif f.endswith('.less'): 300 s += less_template % item 301 elif isinstance(item, (list, tuple)): 302 f = item[0] 303 if f == 'css:inline': 304 s += css_inline % item[1] 305 elif f == 'js:inline': 306 s += js_inline % item[1] 307 self.write(s, escape=False)
308
309 - def stream( 310 self, 311 stream, 312 chunk_size=DEFAULT_CHUNK_SIZE, 313 request=None, 314 attachment=False, 315 filename=None 316 ):
317 """ 318 if a controller function:: 319 320 return response.stream(file, 100) 321 322 the file content will be streamed at 100 bytes at the time 323 324 Optional kwargs: 325 (for custom stream calls) 326 attachment=True # Send as attachment. Usually creates a 327 # pop-up download window on browsers 328 filename=None # The name for the attachment 329 330 Note: for using the stream name (filename) with attachments 331 the option must be explicitly set as function parameter(will 332 default to the last request argument otherwise) 333 """ 334 335 headers = self.headers 336 # for attachment settings and backward compatibility 337 keys = [item.lower() for item in headers] 338 if attachment: 339 if filename is None: 340 attname = "" 341 else: 342 attname = filename 343 headers["Content-Disposition"] = \ 344 "attachment;filename=%s" % attname 345 346 if not request: 347 request = current.request 348 if isinstance(stream, (str, unicode)): 349 stream_file_or_304_or_206(stream, 350 chunk_size=chunk_size, 351 request=request, 352 headers=headers, 353 status=self.status) 354 355 # ## the following is for backward compatibility 356 if hasattr(stream, 'name'): 357 filename = stream.name 358 359 if filename and not 'content-type' in keys: 360 headers['Content-Type'] = contenttype(filename) 361 if filename and not 'content-length' in keys: 362 try: 363 headers['Content-Length'] = \ 364 os.path.getsize(filename) 365 except OSError: 366 pass 367 368 env = request.env 369 # Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled 370 if request.is_https and isinstance(env.http_user_agent, str) and \ 371 not re.search(r'Opera', env.http_user_agent) and \ 372 re.search(r'MSIE [5-8][^0-9]', env.http_user_agent): 373 headers['Pragma'] = 'cache' 374 headers['Cache-Control'] = 'private' 375 376 if request and env.web2py_use_wsgi_file_wrapper: 377 wrapped = env.wsgi_file_wrapper(stream, chunk_size) 378 else: 379 wrapped = streamer(stream, chunk_size=chunk_size) 380 return wrapped
381
382 - def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True, download_filename=None):
383 """ 384 example of usage in controller:: 385 386 def download(): 387 return response.download(request, db) 388 389 downloads from http://..../download/filename 390 """ 391 392 current.session.forget(current.response) 393 394 if not request.args: 395 raise HTTP(404) 396 name = request.args[-1] 397 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\ 398 .match(name) 399 if not items: 400 raise HTTP(404) 401 (t, f) = (items.group('table'), items.group('field')) 402 try: 403 field = db[t][f] 404 except AttributeError: 405 raise HTTP(404) 406 try: 407 (filename, stream) = field.retrieve(name,nameonly=True) 408 except IOError: 409 raise HTTP(404) 410 headers = self.headers 411 headers['Content-Type'] = contenttype(name) 412 if download_filename == None: 413 download_filename = filename 414 if attachment: 415 headers['Content-Disposition'] = \ 416 'attachment; filename="%s"' % download_filename.replace('"','\"') 417 return self.stream(stream, chunk_size=chunk_size, request=request)
418
419 - def json(self, data, default=None):
420 return json(data, default=default or custom_json)
421
422 - def xmlrpc(self, request, methods):
423 """ 424 assuming:: 425 426 def add(a, b): 427 return a+b 428 429 if a controller function \"func\":: 430 431 return response.xmlrpc(request, [add]) 432 433 the controller will be able to handle xmlrpc requests for 434 the add function. Example:: 435 436 import xmlrpclib 437 connection = xmlrpclib.ServerProxy( 438 'http://hostname/app/contr/func') 439 print connection.add(3, 4) 440 441 """ 442 443 return handler(request, self, methods)
444
445 - def toolbar(self):
446 from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A 447 BUTTON = TAG.button 448 admin = URL("admin", "default", "design", 449 args=current.request.application) 450 from gluon.dal import DAL 451 dbstats = [] 452 dbtables = {} 453 infos = DAL.get_instances() 454 for k,v in infos.iteritems(): 455 dbstats.append(TABLE(*[TR(PRE(row[0]),'%.2fms' % 456 (row[1]*1000)) 457 for row in v['dbstats']])) 458 dbtables[k] = dict(defined=v['dbtables']['defined'] or '[no defined tables]', 459 lazy=v['dbtables']['lazy'] or '[no lazy tables]') 460 u = web2py_uuid() 461 backtotop = A('Back to top', _href="#totop-%s" % u) 462 return DIV( 463 BUTTON('design', _onclick="document.location='%s'" % admin), 464 BUTTON('request', 465 _onclick="jQuery('#request-%s').slideToggle()" % u), 466 BUTTON('response', 467 _onclick="jQuery('#response-%s').slideToggle()" % u), 468 BUTTON('session', 469 _onclick="jQuery('#session-%s').slideToggle()" % u), 470 BUTTON('db tables', 471 _onclick="jQuery('#db-tables-%s').slideToggle()" % u), 472 BUTTON('db stats', 473 _onclick="jQuery('#db-stats-%s').slideToggle()" % u), 474 DIV(BEAUTIFY(current.request), backtotop, 475 _class="hidden", _id="request-%s" % u), 476 DIV(BEAUTIFY(current.session), backtotop, 477 _class="hidden", _id="session-%s" % u), 478 DIV(BEAUTIFY(current.response), backtotop, 479 _class="hidden", _id="response-%s" % u), 480 DIV(BEAUTIFY(dbtables), backtotop, _class="hidden", 481 _id="db-tables-%s" % u), 482 DIV(BEAUTIFY( 483 dbstats), backtotop, _class="hidden", _id="db-stats-%s" % u), 484 SCRIPT("jQuery('.hidden').hide()"), _id="totop-%s" % u 485 )
486 487
488 -class Session(Storage):
489 490 """ 491 defines the session object and the default values of its members (None) 492 """ 493
494 - def connect( 495 self, 496 request=None, 497 response=None, 498 db=None, 499 tablename='web2py_session', 500 masterapp=None, 501 migrate=True, 502 separate=None, 503 check_client=False, 504 cookie_key=None, 505 cookie_expires=None, 506 compression_level=None 507 ):
508 """ 509 separate can be separate=lambda(session_name): session_name[-2:] 510 and it is used to determine a session prefix. 511 separate can be True and it is set to session_name[-2:] 512 """ 513 if request is None: 514 request = current.request 515 if response is None: 516 response = current.response 517 if separate is True: 518 separate = lambda session_name: session_name[-2:] 519 self._unlock(response) 520 if not masterapp: 521 masterapp = request.application 522 response.session_id_name = 'session_id_%s' % masterapp.lower() 523 response.session_data_name = 'session_data_%s' % masterapp.lower() 524 response.session_cookie_expires = cookie_expires 525 526 # Load session data from cookie 527 cookies = request.cookies 528 529 # check if there is a session_id in cookies 530 if response.session_id_name in cookies: 531 response.session_id = \ 532 cookies[response.session_id_name].value 533 else: 534 response.session_id = None 535 536 # check if there is session data in cookies 537 if response.session_data_name in cookies: 538 session_cookie_data = cookies[response.session_data_name].value 539 else: 540 session_cookie_data = None 541 542 # if we are supposed to use cookie based session data 543 if cookie_key: 544 response.session_storage_type = 'cookie' 545 response.session_cookie_key = cookie_key 546 response.session_cookie_compression_level = compression_level 547 if session_cookie_data: 548 data = secure_loads(session_cookie_data, cookie_key, 549 compression_level=compression_level) 550 if data: 551 self.update(data) 552 # else if we are supposed to use file based sessions 553 elif not db: 554 response.session_storage_type = 'file' 555 if global_settings.db_sessions is True \ 556 or masterapp in global_settings.db_sessions: 557 return 558 response.session_new = False 559 client = request.client and request.client.replace(':', '.') 560 if response.session_id: 561 if regex_session_id.match(response.session_id): 562 response.session_filename = \ 563 os.path.join(up(request.folder), masterapp, 564 'sessions', response.session_id) 565 else: 566 response.session_id = None 567 # do not try load the data from file is these was data in cookie 568 if response.session_id and not session_cookie_data: 569 # os.path.exists(response.session_filename): 570 try: 571 response.session_file = \ 572 open(response.session_filename, 'rb+') 573 try: 574 portalocker.lock(response.session_file, 575 portalocker.LOCK_EX) 576 response.session_locked = True 577 self.update(cPickle.load(response.session_file)) 578 response.session_file.seek(0) 579 oc = response.session_filename.split('/')[-1]\ 580 .split('-')[0] 581 if check_client and client != oc: 582 raise Exception("cookie attack") 583 except: 584 response.session_id = None 585 finally: 586 pass 587 #This causes admin login to break. Must find out why. 588 #self._close(response) 589 except: 590 response.session_file = None 591 if not response.session_id: 592 uuid = web2py_uuid() 593 response.session_id = '%s-%s' % (client, uuid) 594 if separate: 595 prefix = separate(response.session_id) 596 response.session_id = '%s/%s' % \ 597 (prefix, response.session_id) 598 response.session_filename = \ 599 os.path.join(up(request.folder), masterapp, 600 'sessions', response.session_id) 601 response.session_new = True 602 # else the session goes in db 603 else: 604 response.session_storage_type = 'db' 605 if global_settings.db_sessions is not True: 606 global_settings.db_sessions.add(masterapp) 607 if response.session_file: 608 self._close(response) 609 if settings.global_settings.web2py_runtime_gae: 610 # in principle this could work without GAE 611 request.tickets_db = db 612 if masterapp == request.application: 613 table_migrate = migrate 614 else: 615 table_migrate = False 616 tname = tablename + '_' + masterapp 617 table = db.get(tname, None) 618 Field = db.Field 619 if table is None: 620 db.define_table( 621 tname, 622 Field('locked', 'boolean', default=False), 623 Field('client_ip', length=64), 624 Field('created_datetime', 'datetime', 625 default=request.now), 626 Field('modified_datetime', 'datetime'), 627 Field('unique_key', length=64), 628 Field('session_data', 'blob'), 629 migrate=table_migrate, 630 ) 631 table = db[tname] # to allow for lazy table 632 try: 633 634 # Get session data out of the database 635 (record_id, unique_key) = response.session_id.split(':') 636 if record_id == '0': 637 raise Exception('record_id == 0') 638 # Select from database 639 if not session_cookie_data: 640 rows = db(table.id == record_id).select() 641 # Make sure the session data exists in the database 642 if len(rows) == 0 or rows[0].unique_key != unique_key: 643 raise Exception('No record') 644 # rows[0].update_record(locked=True) 645 # Unpickle the data 646 session_data = cPickle.loads(rows[0].session_data) 647 self.update(session_data) 648 except Exception: 649 record_id = None 650 unique_key = web2py_uuid() 651 session_data = {} 652 response.session_id = '%s:%s' % (record_id, unique_key) 653 response.session_db_table = table 654 response.session_db_record_id = record_id 655 response.session_db_unique_key = unique_key 656 rcookies = response.cookies 657 rcookies[response.session_id_name] = response.session_id 658 rcookies[response.session_id_name]['path'] = '/' 659 if cookie_expires: 660 rcookies[response.session_id_name][ 661 'expires'] = cookie_expires.strftime(FMT) 662 # if not cookie_key, but session_data_name in cookies 663 # expire session_data_name from cookies 664 if session_cookie_data: 665 rcookies[response.session_data_name] = 'expired' 666 rcookies[response.session_data_name]['path'] = '/' 667 rcookies[response.session_data_name]['expires'] = PAST 668 if self.flash: 669 (response.flash, self.flash) = (self.flash, None)
670
671 - def clear(self):
672 previous_session_hash = self.pop('_session_hash', None) 673 Storage.clear(self) 674 if previous_session_hash: 675 self._session_hash = previous_session_hash
676
677 - def is_new(self):
678 if self._start_timestamp: 679 return False 680 else: 681 self._start_timestamp = datetime.datetime.today() 682 return True
683
684 - def is_expired(self, seconds=3600):
685 now = datetime.datetime.today() 686 if not self._last_timestamp or \ 687 self._last_timestamp + datetime.timedelta(seconds=seconds) > now: 688 self._last_timestamp = now 689 return False 690 else: 691 return True
692
693 - def secure(self):
694 self._secure = True
695
696 - def forget(self, response=None):
697 self._close(response) 698 self._forget = True
699 713
714 - def _unchanged(self):
715 previous_session_hash = self.pop('_session_hash', None) 716 if not previous_session_hash and not \ 717 any(value is not None for value in self.itervalues()): 718 return True 719 session_pickled = cPickle.dumps(dict(self)) 720 session_hash = hashlib.md5(session_pickled).hexdigest() 721 if previous_session_hash == session_hash: 722 return True 723 else: 724 self._session_hash = session_hash 725 return False
726
727 - def _try_store_in_db(self, request, response):
728 # don't save if file-based sessions, 729 # no session id, or session being forgotten 730 # or no changes to session 731 if response.session_storage_type != 'db' or not response.session_id \ 732 or self._forget or self._unchanged(): 733 return False 734 735 table = response.session_db_table 736 record_id = response.session_db_record_id 737 unique_key = response.session_db_unique_key 738 739 dd = dict(locked=False, 740 client_ip=request.client.replace(':', '.'), 741 modified_datetime=request.now, 742 session_data=cPickle.dumps(dict(self)), 743 unique_key=unique_key) 744 if record_id: 745 table._db(table.id == record_id).update(**dd) 746 else: 747 record_id = table.insert(**dd) 748 749 cookies, session_id_name = response.cookies, response.session_id_name 750 cookies[session_id_name] = '%s:%s' % (record_id, unique_key) 751 cookies[session_id_name]['path'] = '/' 752 return True
753 758
759 - def _try_store_in_file(self, request, response):
760 if response.session_storage_type != 'file': 761 return False 762 try: 763 if not response.session_id or self._forget or self._unchanged(): 764 return False 765 if response.session_new or not response.session_file: 766 # Tests if the session sub-folder exists, if not, create it 767 session_folder = os.path.dirname(response.session_filename) 768 if not os.path.exists(session_folder): 769 os.mkdir(session_folder) 770 response.session_file = open(response.session_filename, 'wb') 771 portalocker.lock(response.session_file, portalocker.LOCK_EX) 772 response.session_locked = True 773 774 if response.session_file: 775 cPickle.dump(dict(self), response.session_file) 776 response.session_file.truncate() 777 finally: 778 self._close(response) 779 return True
780
781 - def _unlock(self, response):
782 if response and response.session_file and response.session_locked: 783 try: 784 portalocker.unlock(response.session_file) 785 response.session_locked = False 786 except: # this should never happen but happens in Windows 787 pass
788
789 - def _close(self, response):
790 if response and response.session_file: 791 self._unlock(response) 792 try: 793 response.session_file.close() 794 del response.session_file 795 except: 796 pass
797