1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/OpenSecurity/install/web.py-0.37/web/application.py Mon Dec 02 14:02:05 2013 +0100
1.3 @@ -0,0 +1,687 @@
1.4 +"""
1.5 +Web application
1.6 +(from web.py)
1.7 +"""
1.8 +import webapi as web
1.9 +import webapi, wsgi, utils
1.10 +import debugerror
1.11 +import httpserver
1.12 +
1.13 +from utils import lstrips, safeunicode
1.14 +import sys
1.15 +
1.16 +import urllib
1.17 +import traceback
1.18 +import itertools
1.19 +import os
1.20 +import types
1.21 +from exceptions import SystemExit
1.22 +
1.23 +try:
1.24 + import wsgiref.handlers
1.25 +except ImportError:
1.26 + pass # don't break people with old Pythons
1.27 +
1.28 +__all__ = [
1.29 + "application", "auto_application",
1.30 + "subdir_application", "subdomain_application",
1.31 + "loadhook", "unloadhook",
1.32 + "autodelegate"
1.33 +]
1.34 +
1.35 +class application:
1.36 + """
1.37 + Application to delegate requests based on path.
1.38 +
1.39 + >>> urls = ("/hello", "hello")
1.40 + >>> app = application(urls, globals())
1.41 + >>> class hello:
1.42 + ... def GET(self): return "hello"
1.43 + >>>
1.44 + >>> app.request("/hello").data
1.45 + 'hello'
1.46 + """
1.47 + def __init__(self, mapping=(), fvars={}, autoreload=None):
1.48 + if autoreload is None:
1.49 + autoreload = web.config.get('debug', False)
1.50 + self.init_mapping(mapping)
1.51 + self.fvars = fvars
1.52 + self.processors = []
1.53 +
1.54 + self.add_processor(loadhook(self._load))
1.55 + self.add_processor(unloadhook(self._unload))
1.56 +
1.57 + if autoreload:
1.58 + def main_module_name():
1.59 + mod = sys.modules['__main__']
1.60 + file = getattr(mod, '__file__', None) # make sure this works even from python interpreter
1.61 + return file and os.path.splitext(os.path.basename(file))[0]
1.62 +
1.63 + def modname(fvars):
1.64 + """find name of the module name from fvars."""
1.65 + file, name = fvars.get('__file__'), fvars.get('__name__')
1.66 + if file is None or name is None:
1.67 + return None
1.68 +
1.69 + if name == '__main__':
1.70 + # Since the __main__ module can't be reloaded, the module has
1.71 + # to be imported using its file name.
1.72 + name = main_module_name()
1.73 + return name
1.74 +
1.75 + mapping_name = utils.dictfind(fvars, mapping)
1.76 + module_name = modname(fvars)
1.77 +
1.78 + def reload_mapping():
1.79 + """loadhook to reload mapping and fvars."""
1.80 + mod = __import__(module_name, None, None, [''])
1.81 + mapping = getattr(mod, mapping_name, None)
1.82 + if mapping:
1.83 + self.fvars = mod.__dict__
1.84 + self.init_mapping(mapping)
1.85 +
1.86 + self.add_processor(loadhook(Reloader()))
1.87 + if mapping_name and module_name:
1.88 + self.add_processor(loadhook(reload_mapping))
1.89 +
1.90 + # load __main__ module usings its filename, so that it can be reloaded.
1.91 + if main_module_name() and '__main__' in sys.argv:
1.92 + try:
1.93 + __import__(main_module_name())
1.94 + except ImportError:
1.95 + pass
1.96 +
1.97 + def _load(self):
1.98 + web.ctx.app_stack.append(self)
1.99 +
1.100 + def _unload(self):
1.101 + web.ctx.app_stack = web.ctx.app_stack[:-1]
1.102 +
1.103 + if web.ctx.app_stack:
1.104 + # this is a sub-application, revert ctx to earlier state.
1.105 + oldctx = web.ctx.get('_oldctx')
1.106 + if oldctx:
1.107 + web.ctx.home = oldctx.home
1.108 + web.ctx.homepath = oldctx.homepath
1.109 + web.ctx.path = oldctx.path
1.110 + web.ctx.fullpath = oldctx.fullpath
1.111 +
1.112 + def _cleanup(self):
1.113 + # Threads can be recycled by WSGI servers.
1.114 + # Clearing up all thread-local state to avoid interefereing with subsequent requests.
1.115 + utils.ThreadedDict.clear_all()
1.116 +
1.117 + def init_mapping(self, mapping):
1.118 + self.mapping = list(utils.group(mapping, 2))
1.119 +
1.120 + def add_mapping(self, pattern, classname):
1.121 + self.mapping.append((pattern, classname))
1.122 +
1.123 + def add_processor(self, processor):
1.124 + """
1.125 + Adds a processor to the application.
1.126 +
1.127 + >>> urls = ("/(.*)", "echo")
1.128 + >>> app = application(urls, globals())
1.129 + >>> class echo:
1.130 + ... def GET(self, name): return name
1.131 + ...
1.132 + >>>
1.133 + >>> def hello(handler): return "hello, " + handler()
1.134 + ...
1.135 + >>> app.add_processor(hello)
1.136 + >>> app.request("/web.py").data
1.137 + 'hello, web.py'
1.138 + """
1.139 + self.processors.append(processor)
1.140 +
1.141 + def request(self, localpart='/', method='GET', data=None,
1.142 + host="0.0.0.0:8080", headers=None, https=False, **kw):
1.143 + """Makes request to this application for the specified path and method.
1.144 + Response will be a storage object with data, status and headers.
1.145 +
1.146 + >>> urls = ("/hello", "hello")
1.147 + >>> app = application(urls, globals())
1.148 + >>> class hello:
1.149 + ... def GET(self):
1.150 + ... web.header('Content-Type', 'text/plain')
1.151 + ... return "hello"
1.152 + ...
1.153 + >>> response = app.request("/hello")
1.154 + >>> response.data
1.155 + 'hello'
1.156 + >>> response.status
1.157 + '200 OK'
1.158 + >>> response.headers['Content-Type']
1.159 + 'text/plain'
1.160 +
1.161 + To use https, use https=True.
1.162 +
1.163 + >>> urls = ("/redirect", "redirect")
1.164 + >>> app = application(urls, globals())
1.165 + >>> class redirect:
1.166 + ... def GET(self): raise web.seeother("/foo")
1.167 + ...
1.168 + >>> response = app.request("/redirect")
1.169 + >>> response.headers['Location']
1.170 + 'http://0.0.0.0:8080/foo'
1.171 + >>> response = app.request("/redirect", https=True)
1.172 + >>> response.headers['Location']
1.173 + 'https://0.0.0.0:8080/foo'
1.174 +
1.175 + The headers argument specifies HTTP headers as a mapping object
1.176 + such as a dict.
1.177 +
1.178 + >>> urls = ('/ua', 'uaprinter')
1.179 + >>> class uaprinter:
1.180 + ... def GET(self):
1.181 + ... return 'your user-agent is ' + web.ctx.env['HTTP_USER_AGENT']
1.182 + ...
1.183 + >>> app = application(urls, globals())
1.184 + >>> app.request('/ua', headers = {
1.185 + ... 'User-Agent': 'a small jumping bean/1.0 (compatible)'
1.186 + ... }).data
1.187 + 'your user-agent is a small jumping bean/1.0 (compatible)'
1.188 +
1.189 + """
1.190 + path, maybe_query = urllib.splitquery(localpart)
1.191 + query = maybe_query or ""
1.192 +
1.193 + if 'env' in kw:
1.194 + env = kw['env']
1.195 + else:
1.196 + env = {}
1.197 + env = dict(env, HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, QUERY_STRING=query, HTTPS=str(https))
1.198 + headers = headers or {}
1.199 +
1.200 + for k, v in headers.items():
1.201 + env['HTTP_' + k.upper().replace('-', '_')] = v
1.202 +
1.203 + if 'HTTP_CONTENT_LENGTH' in env:
1.204 + env['CONTENT_LENGTH'] = env.pop('HTTP_CONTENT_LENGTH')
1.205 +
1.206 + if 'HTTP_CONTENT_TYPE' in env:
1.207 + env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE')
1.208 +
1.209 + if method not in ["HEAD", "GET"]:
1.210 + data = data or ''
1.211 + import StringIO
1.212 + if isinstance(data, dict):
1.213 + q = urllib.urlencode(data)
1.214 + else:
1.215 + q = data
1.216 + env['wsgi.input'] = StringIO.StringIO(q)
1.217 + if not env.get('CONTENT_TYPE', '').lower().startswith('multipart/') and 'CONTENT_LENGTH' not in env:
1.218 + env['CONTENT_LENGTH'] = len(q)
1.219 + response = web.storage()
1.220 + def start_response(status, headers):
1.221 + response.status = status
1.222 + response.headers = dict(headers)
1.223 + response.header_items = headers
1.224 + response.data = "".join(self.wsgifunc()(env, start_response))
1.225 + return response
1.226 +
1.227 + def browser(self):
1.228 + import browser
1.229 + return browser.AppBrowser(self)
1.230 +
1.231 + def handle(self):
1.232 + fn, args = self._match(self.mapping, web.ctx.path)
1.233 + return self._delegate(fn, self.fvars, args)
1.234 +
1.235 + def handle_with_processors(self):
1.236 + def process(processors):
1.237 + try:
1.238 + if processors:
1.239 + p, processors = processors[0], processors[1:]
1.240 + return p(lambda: process(processors))
1.241 + else:
1.242 + return self.handle()
1.243 + except web.HTTPError:
1.244 + raise
1.245 + except (KeyboardInterrupt, SystemExit):
1.246 + raise
1.247 + except:
1.248 + print >> web.debug, traceback.format_exc()
1.249 + raise self.internalerror()
1.250 +
1.251 + # processors must be applied in the resvere order. (??)
1.252 + return process(self.processors)
1.253 +
1.254 + def wsgifunc(self, *middleware):
1.255 + """Returns a WSGI-compatible function for this application."""
1.256 + def peep(iterator):
1.257 + """Peeps into an iterator by doing an iteration
1.258 + and returns an equivalent iterator.
1.259 + """
1.260 + # wsgi requires the headers first
1.261 + # so we need to do an iteration
1.262 + # and save the result for later
1.263 + try:
1.264 + firstchunk = iterator.next()
1.265 + except StopIteration:
1.266 + firstchunk = ''
1.267 +
1.268 + return itertools.chain([firstchunk], iterator)
1.269 +
1.270 + def is_generator(x): return x and hasattr(x, 'next')
1.271 +
1.272 + def wsgi(env, start_resp):
1.273 + # clear threadlocal to avoid inteference of previous requests
1.274 + self._cleanup()
1.275 +
1.276 + self.load(env)
1.277 + try:
1.278 + # allow uppercase methods only
1.279 + if web.ctx.method.upper() != web.ctx.method:
1.280 + raise web.nomethod()
1.281 +
1.282 + result = self.handle_with_processors()
1.283 + if is_generator(result):
1.284 + result = peep(result)
1.285 + else:
1.286 + result = [result]
1.287 + except web.HTTPError, e:
1.288 + result = [e.data]
1.289 +
1.290 + result = web.safestr(iter(result))
1.291 +
1.292 + status, headers = web.ctx.status, web.ctx.headers
1.293 + start_resp(status, headers)
1.294 +
1.295 + def cleanup():
1.296 + self._cleanup()
1.297 + yield '' # force this function to be a generator
1.298 +
1.299 + return itertools.chain(result, cleanup())
1.300 +
1.301 + for m in middleware:
1.302 + wsgi = m(wsgi)
1.303 +
1.304 + return wsgi
1.305 +
1.306 + def run(self, *middleware):
1.307 + """
1.308 + Starts handling requests. If called in a CGI or FastCGI context, it will follow
1.309 + that protocol. If called from the command line, it will start an HTTP
1.310 + server on the port named in the first command line argument, or, if there
1.311 + is no argument, on port 8080.
1.312 +
1.313 + `middleware` is a list of WSGI middleware which is applied to the resulting WSGI
1.314 + function.
1.315 + """
1.316 + return wsgi.runwsgi(self.wsgifunc(*middleware))
1.317 +
1.318 + def stop(self):
1.319 + """Stops the http server started by run.
1.320 + """
1.321 + if httpserver.server:
1.322 + httpserver.server.stop()
1.323 + httpserver.server = None
1.324 +
1.325 + def cgirun(self, *middleware):
1.326 + """
1.327 + Return a CGI handler. This is mostly useful with Google App Engine.
1.328 + There you can just do:
1.329 +
1.330 + main = app.cgirun()
1.331 + """
1.332 + wsgiapp = self.wsgifunc(*middleware)
1.333 +
1.334 + try:
1.335 + from google.appengine.ext.webapp.util import run_wsgi_app
1.336 + return run_wsgi_app(wsgiapp)
1.337 + except ImportError:
1.338 + # we're not running from within Google App Engine
1.339 + return wsgiref.handlers.CGIHandler().run(wsgiapp)
1.340 +
1.341 + def load(self, env):
1.342 + """Initializes ctx using env."""
1.343 + ctx = web.ctx
1.344 + ctx.clear()
1.345 + ctx.status = '200 OK'
1.346 + ctx.headers = []
1.347 + ctx.output = ''
1.348 + ctx.environ = ctx.env = env
1.349 + ctx.host = env.get('HTTP_HOST')
1.350 +
1.351 + if env.get('wsgi.url_scheme') in ['http', 'https']:
1.352 + ctx.protocol = env['wsgi.url_scheme']
1.353 + elif env.get('HTTPS', '').lower() in ['on', 'true', '1']:
1.354 + ctx.protocol = 'https'
1.355 + else:
1.356 + ctx.protocol = 'http'
1.357 + ctx.homedomain = ctx.protocol + '://' + env.get('HTTP_HOST', '[unknown]')
1.358 + ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
1.359 + ctx.home = ctx.homedomain + ctx.homepath
1.360 + #@@ home is changed when the request is handled to a sub-application.
1.361 + #@@ but the real home is required for doing absolute redirects.
1.362 + ctx.realhome = ctx.home
1.363 + ctx.ip = env.get('REMOTE_ADDR')
1.364 + ctx.method = env.get('REQUEST_METHOD')
1.365 + ctx.path = env.get('PATH_INFO')
1.366 + # http://trac.lighttpd.net/trac/ticket/406 requires:
1.367 + if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
1.368 + ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath)
1.369 + # Apache and CherryPy webservers unquote the url but lighttpd doesn't.
1.370 + # unquote explicitly for lighttpd to make ctx.path uniform across all servers.
1.371 + ctx.path = urllib.unquote(ctx.path)
1.372 +
1.373 + if env.get('QUERY_STRING'):
1.374 + ctx.query = '?' + env.get('QUERY_STRING', '')
1.375 + else:
1.376 + ctx.query = ''
1.377 +
1.378 + ctx.fullpath = ctx.path + ctx.query
1.379 +
1.380 + for k, v in ctx.iteritems():
1.381 + # convert all string values to unicode values and replace
1.382 + # malformed data with a suitable replacement marker.
1.383 + if isinstance(v, str):
1.384 + ctx[k] = v.decode('utf-8', 'replace')
1.385 +
1.386 + # status must always be str
1.387 + ctx.status = '200 OK'
1.388 +
1.389 + ctx.app_stack = []
1.390 +
1.391 + def _delegate(self, f, fvars, args=[]):
1.392 + def handle_class(cls):
1.393 + meth = web.ctx.method
1.394 + if meth == 'HEAD' and not hasattr(cls, meth):
1.395 + meth = 'GET'
1.396 + if not hasattr(cls, meth):
1.397 + raise web.nomethod(cls)
1.398 + tocall = getattr(cls(), meth)
1.399 + return tocall(*args)
1.400 +
1.401 + def is_class(o): return isinstance(o, (types.ClassType, type))
1.402 +
1.403 + if f is None:
1.404 + raise web.notfound()
1.405 + elif isinstance(f, application):
1.406 + return f.handle_with_processors()
1.407 + elif is_class(f):
1.408 + return handle_class(f)
1.409 + elif isinstance(f, basestring):
1.410 + if f.startswith('redirect '):
1.411 + url = f.split(' ', 1)[1]
1.412 + if web.ctx.method == "GET":
1.413 + x = web.ctx.env.get('QUERY_STRING', '')
1.414 + if x:
1.415 + url += '?' + x
1.416 + raise web.redirect(url)
1.417 + elif '.' in f:
1.418 + mod, cls = f.rsplit('.', 1)
1.419 + mod = __import__(mod, None, None, [''])
1.420 + cls = getattr(mod, cls)
1.421 + else:
1.422 + cls = fvars[f]
1.423 + return handle_class(cls)
1.424 + elif hasattr(f, '__call__'):
1.425 + return f()
1.426 + else:
1.427 + return web.notfound()
1.428 +
1.429 + def _match(self, mapping, value):
1.430 + for pat, what in mapping:
1.431 + if isinstance(what, application):
1.432 + if value.startswith(pat):
1.433 + f = lambda: self._delegate_sub_application(pat, what)
1.434 + return f, None
1.435 + else:
1.436 + continue
1.437 + elif isinstance(what, basestring):
1.438 + what, result = utils.re_subm('^' + pat + '$', what, value)
1.439 + else:
1.440 + result = utils.re_compile('^' + pat + '$').match(value)
1.441 +
1.442 + if result: # it's a match
1.443 + return what, [x for x in result.groups()]
1.444 + return None, None
1.445 +
1.446 + def _delegate_sub_application(self, dir, app):
1.447 + """Deletes request to sub application `app` rooted at the directory `dir`.
1.448 + The home, homepath, path and fullpath values in web.ctx are updated to mimic request
1.449 + to the subapp and are restored after it is handled.
1.450 +
1.451 + @@Any issues with when used with yield?
1.452 + """
1.453 + web.ctx._oldctx = web.storage(web.ctx)
1.454 + web.ctx.home += dir
1.455 + web.ctx.homepath += dir
1.456 + web.ctx.path = web.ctx.path[len(dir):]
1.457 + web.ctx.fullpath = web.ctx.fullpath[len(dir):]
1.458 + return app.handle_with_processors()
1.459 +
1.460 + def get_parent_app(self):
1.461 + if self in web.ctx.app_stack:
1.462 + index = web.ctx.app_stack.index(self)
1.463 + if index > 0:
1.464 + return web.ctx.app_stack[index-1]
1.465 +
1.466 + def notfound(self):
1.467 + """Returns HTTPError with '404 not found' message"""
1.468 + parent = self.get_parent_app()
1.469 + if parent:
1.470 + return parent.notfound()
1.471 + else:
1.472 + return web._NotFound()
1.473 +
1.474 + def internalerror(self):
1.475 + """Returns HTTPError with '500 internal error' message"""
1.476 + parent = self.get_parent_app()
1.477 + if parent:
1.478 + return parent.internalerror()
1.479 + elif web.config.get('debug'):
1.480 + import debugerror
1.481 + return debugerror.debugerror()
1.482 + else:
1.483 + return web._InternalError()
1.484 +
1.485 +class auto_application(application):
1.486 + """Application similar to `application` but urls are constructed
1.487 + automatiacally using metaclass.
1.488 +
1.489 + >>> app = auto_application()
1.490 + >>> class hello(app.page):
1.491 + ... def GET(self): return "hello, world"
1.492 + ...
1.493 + >>> class foo(app.page):
1.494 + ... path = '/foo/.*'
1.495 + ... def GET(self): return "foo"
1.496 + >>> app.request("/hello").data
1.497 + 'hello, world'
1.498 + >>> app.request('/foo/bar').data
1.499 + 'foo'
1.500 + """
1.501 + def __init__(self):
1.502 + application.__init__(self)
1.503 +
1.504 + class metapage(type):
1.505 + def __init__(klass, name, bases, attrs):
1.506 + type.__init__(klass, name, bases, attrs)
1.507 + path = attrs.get('path', '/' + name)
1.508 +
1.509 + # path can be specified as None to ignore that class
1.510 + # typically required to create a abstract base class.
1.511 + if path is not None:
1.512 + self.add_mapping(path, klass)
1.513 +
1.514 + class page:
1.515 + path = None
1.516 + __metaclass__ = metapage
1.517 +
1.518 + self.page = page
1.519 +
1.520 +# The application class already has the required functionality of subdir_application
1.521 +subdir_application = application
1.522 +
1.523 +class subdomain_application(application):
1.524 + """
1.525 + Application to delegate requests based on the host.
1.526 +
1.527 + >>> urls = ("/hello", "hello")
1.528 + >>> app = application(urls, globals())
1.529 + >>> class hello:
1.530 + ... def GET(self): return "hello"
1.531 + >>>
1.532 + >>> mapping = (r"hello\.example\.com", app)
1.533 + >>> app2 = subdomain_application(mapping)
1.534 + >>> app2.request("/hello", host="hello.example.com").data
1.535 + 'hello'
1.536 + >>> response = app2.request("/hello", host="something.example.com")
1.537 + >>> response.status
1.538 + '404 Not Found'
1.539 + >>> response.data
1.540 + 'not found'
1.541 + """
1.542 + def handle(self):
1.543 + host = web.ctx.host.split(':')[0] #strip port
1.544 + fn, args = self._match(self.mapping, host)
1.545 + return self._delegate(fn, self.fvars, args)
1.546 +
1.547 + def _match(self, mapping, value):
1.548 + for pat, what in mapping:
1.549 + if isinstance(what, basestring):
1.550 + what, result = utils.re_subm('^' + pat + '$', what, value)
1.551 + else:
1.552 + result = utils.re_compile('^' + pat + '$').match(value)
1.553 +
1.554 + if result: # it's a match
1.555 + return what, [x for x in result.groups()]
1.556 + return None, None
1.557 +
1.558 +def loadhook(h):
1.559 + """
1.560 + Converts a load hook into an application processor.
1.561 +
1.562 + >>> app = auto_application()
1.563 + >>> def f(): "something done before handling request"
1.564 + ...
1.565 + >>> app.add_processor(loadhook(f))
1.566 + """
1.567 + def processor(handler):
1.568 + h()
1.569 + return handler()
1.570 +
1.571 + return processor
1.572 +
1.573 +def unloadhook(h):
1.574 + """
1.575 + Converts an unload hook into an application processor.
1.576 +
1.577 + >>> app = auto_application()
1.578 + >>> def f(): "something done after handling request"
1.579 + ...
1.580 + >>> app.add_processor(unloadhook(f))
1.581 + """
1.582 + def processor(handler):
1.583 + try:
1.584 + result = handler()
1.585 + is_generator = result and hasattr(result, 'next')
1.586 + except:
1.587 + # run the hook even when handler raises some exception
1.588 + h()
1.589 + raise
1.590 +
1.591 + if is_generator:
1.592 + return wrap(result)
1.593 + else:
1.594 + h()
1.595 + return result
1.596 +
1.597 + def wrap(result):
1.598 + def next():
1.599 + try:
1.600 + return result.next()
1.601 + except:
1.602 + # call the hook at the and of iterator
1.603 + h()
1.604 + raise
1.605 +
1.606 + result = iter(result)
1.607 + while True:
1.608 + yield next()
1.609 +
1.610 + return processor
1.611 +
1.612 +def autodelegate(prefix=''):
1.613 + """
1.614 + Returns a method that takes one argument and calls the method named prefix+arg,
1.615 + calling `notfound()` if there isn't one. Example:
1.616 +
1.617 + urls = ('/prefs/(.*)', 'prefs')
1.618 +
1.619 + class prefs:
1.620 + GET = autodelegate('GET_')
1.621 + def GET_password(self): pass
1.622 + def GET_privacy(self): pass
1.623 +
1.624 + `GET_password` would get called for `/prefs/password` while `GET_privacy` for
1.625 + `GET_privacy` gets called for `/prefs/privacy`.
1.626 +
1.627 + If a user visits `/prefs/password/change` then `GET_password(self, '/change')`
1.628 + is called.
1.629 + """
1.630 + def internal(self, arg):
1.631 + if '/' in arg:
1.632 + first, rest = arg.split('/', 1)
1.633 + func = prefix + first
1.634 + args = ['/' + rest]
1.635 + else:
1.636 + func = prefix + arg
1.637 + args = []
1.638 +
1.639 + if hasattr(self, func):
1.640 + try:
1.641 + return getattr(self, func)(*args)
1.642 + except TypeError:
1.643 + raise web.notfound()
1.644 + else:
1.645 + raise web.notfound()
1.646 + return internal
1.647 +
1.648 +class Reloader:
1.649 + """Checks to see if any loaded modules have changed on disk and,
1.650 + if so, reloads them.
1.651 + """
1.652 +
1.653 + """File suffix of compiled modules."""
1.654 + if sys.platform.startswith('java'):
1.655 + SUFFIX = '$py.class'
1.656 + else:
1.657 + SUFFIX = '.pyc'
1.658 +
1.659 + def __init__(self):
1.660 + self.mtimes = {}
1.661 +
1.662 + def __call__(self):
1.663 + for mod in sys.modules.values():
1.664 + self.check(mod)
1.665 +
1.666 + def check(self, mod):
1.667 + # jython registers java packages as modules but they either
1.668 + # don't have a __file__ attribute or its value is None
1.669 + if not (mod and hasattr(mod, '__file__') and mod.__file__):
1.670 + return
1.671 +
1.672 + try:
1.673 + mtime = os.stat(mod.__file__).st_mtime
1.674 + except (OSError, IOError):
1.675 + return
1.676 + if mod.__file__.endswith(self.__class__.SUFFIX) and os.path.exists(mod.__file__[:-1]):
1.677 + mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
1.678 +
1.679 + if mod not in self.mtimes:
1.680 + self.mtimes[mod] = mtime
1.681 + elif self.mtimes[mod] < mtime:
1.682 + try:
1.683 + reload(mod)
1.684 + self.mtimes[mod] = mtime
1.685 + except ImportError:
1.686 + pass
1.687 +
1.688 +if __name__ == "__main__":
1.689 + import doctest
1.690 + doctest.testmod()