Esempio n. 1
0
 def template_for_uri(self, uri, exc_if_not_found=True):
     """
 :return: template for the uri provided 
 :rtype:  Template
 """
     try:
         template = self.instances[uri]
         if config.get("smisk.mvc.template.autoreload", config.get("smisk.autoreload")):
             if template is not None:
                 template = self._check(uri, template)
             else:
                 raise KeyError("check again")
         if exc_if_not_found and template is None:
             raise exceptions.TopLevelLookupException("Failed to locate template for uri '%s'" % uri)
         return template
     except KeyError:
         u = re.sub(r"^\/+", "", uri)
         for dn in self.directories:
             srcfile = posixpath.normpath(posixpath.join(dn, u))
             if os.access(srcfile, os.F_OK):
                 return self._load(srcfile, uri)
         else:
             self.instances[uri] = None
             if exc_if_not_found:
                 raise exceptions.TopLevelLookupException("Failed to locate template for uri '%s'" % uri)
             return None
Esempio n. 2
0
    def _load(self, filename, uri, text=None):
        try:
            if filename is not None:
                filename = posixpath.normpath(filename)

            encoding_errors = "replace"
            if len(uri) > 4 and (uri[-5:].lower() == ".html" or uri[-4:].lower() == ".xml"):
                encoding_errors = "htmlentityreplace"

            cache_type = config.get("smisk.mvc.template.cache_type", "memory")

            self.instances[uri] = Template(
                uri=uri,
                filename=filename,
                text=text,
                lookup=self,
                module_filename=None,
                format_exceptions=config.get("smisk.mvc.template.format_exceptions", True),
                input_encoding=config.get("smisk.mvc.template.input_encoding", "utf-8"),
                output_encoding=smisk.mvc.Response.charset,
                encoding_errors=encoding_errors,
                cache_type=cache_type,
                default_filters=config.get("smisk.mvc.template.default_filters", ["unicode"]),
                imports=self.imports,
            )
            if log.level <= logging.DEBUG and cache_type != "file":
                code = self.instances[uri].code
                log.debug("Compiled %s into %d bytes of python code:\n%s", uri, len(code), code)
            return self.instances[uri]
        except:
            self.instances.pop(uri, None)
            raise
Esempio n. 3
0
 def _update_config_files_list(self):
   config_files = set()
   if config.get('smisk.autoreload.config', config.get('smisk.autoreload')):
     for path,conf in config.sources:
       if path[0] != '<':
         config_files.add(path)
   self.config_files = config_files
Esempio n. 4
0
 def reset_cache(self):
     limit = config.get("smisk.mvc.template.cache_limit", -1)
     if limit == -1:
         self.instances = {}
         self._uri_cache = {}
     else:
         self.instances = LRUCache(limit)
         self._uri_cache = LRUCache(limit)
Esempio n. 5
0
 def run(self):
   '''Reload the process if registered files have been modified.'''
   sysfiles = set()
   
   if config.get('smisk.autoreload.modules', config.get('smisk.autoreload')):
     for k, m in sys.modules.items():
       if self.match is None or self.match.match(k):
         if hasattr(m, '__loader__'):
           if hasattr(m.__loader__, 'archive'):
             k = m.__loader__.archive
         k = getattr(m, '__file__', None)
         sysfiles.add(k)
   
   for path in sysfiles | self.config_files:
     if path:
       if path.endswith('.pyc') or path.endswith('.pyo'):
         path = path[:-1]
       
       oldtime = self.mtimes.get(path, 0)
       if oldtime is None:
         # Module with no .py file. Skip it.
         continue
       
       #self.log.info('Checking %r' % sysfiles)
       
       try:
         mtime = os.stat(path).st_mtime
       except OSError:
         # Either a module with no .py file, or it's been deleted.
         mtime = None
       
       if path not in self.mtimes:
         # If a module has no .py file, this will be None.
         self.mtimes[path] = mtime
       else:
         #self.log.info("checking %s", path)
         if mtime is None or mtime > oldtime:
           if path.endswith(config.filename_ext) and path in [k for k,d in config.sources]:
             self.on_config_modified(path)
             self.mtimes[path] = mtime
           else:
             self.on_module_modified(path)
           return
Esempio n. 6
0
File: main.py Progetto: rsms/smisk
	def run(self, bind=None, application=None, forks=None, handle_errors=False):
		'''Helper for running an application.
		'''
		# Write PID
		if self.pidfile:
			flags = os.O_WRONLY | os.O_APPEND
			if hasattr(os, 'O_EXLOCK'):
				flags = flags | os.O_EXLOCK
			fd = os.open(self.pidfile, flags)
			try:
				os.write(fd, '%d\n' % os.getpid())
			finally:
				os.close(fd)
		
		# Make sure we have an application
		application = absapp(application)
		
		# Bind
		if bind is not None:
			os.environ['SMISK_BIND'] = bind
		if 'SMISK_BIND' in os.environ:
			smisk.core.bind(os.environ['SMISK_BIND'])
			log.info('Listening on %s', smisk.core.listening())
		
		# Enable auto-reloading if any of these are True:
		if _config.get('smisk.autoreload.modules') \
		or _config.get('smisk.autoreload.config', _config.get('smisk.autoreload')):
			from smisk.autoreload import Autoreloader
			ar = Autoreloader()
			ar.start()
		
		# Forks
		if isinstance(forks, int):
			application.forks = forks
		
		# Call app.run()
		if handle_errors:
			return handle_errors_wrapper(application.run)
		else:
			return application.run()
Esempio n. 7
0
 def send_response(self, rsp):
   '''Send the response to the current client, finalizing the current HTTP
   transaction.
   '''
   # Empty rsp
   if rsp is None:
     # The leaf might have sent content using low-level functions,
     # so we need to confirm the response has not yet started and 
     # a custom content length header has not been set.
     if not self.response.has_begun:
       self.response.adjust_status(False)
     return
   
   # Add headers if the response has not yet begun
   if not self.response.has_begun:
     # Add Content-Length header
     if self.response.find_header('Content-Length:') == -1:
       self.response.headers.append('Content-Length: %d' % len(rsp))
     # Add Content-Type header
     self.response.serializer.add_content_type_header(self.response, self.response.charset)
     # Has content or not?
     if len(rsp) > 0:
       # Make sure appropriate status is set, if needed
       self.response.adjust_status(True)
       # Add ETag if enabled
       etag = config.get('smisk.mvc.etag')
       if etag is not None and self.response.find_header('ETag:') == -1:
         h = etag(''.join(self.response.headers))
         h.update(rsp)
         self.response.headers.append('ETag: "%s"' % h.hexdigest())
     else:
       # Make sure appropriate status is set, if needed
       self.response.adjust_status(False)
   
   # Debug print
   if log.level <= logging.DEBUG:
     self._log_debug_sending_rsp(rsp)
   
   # Send headers
   self.response.begin()
   
   # Head does not contain a payload, but all the headers should be exactly
   # like they would with a GET. (Including Content-Length)
   if self.request.method != 'HEAD':
     # Send body
     if __debug__:
       assert isinstance(rsp, str), 'type(rsp) == %s' % type(rsp)
     self.response.write(rsp)
Esempio n. 8
0
    def render_error(self, status, params={}, format="html"):
        # Compile body from template
        errors = config.get("smisk.mvc.template.errors", {})
        if status.code in errors:
            template = self.template_for_uri("%s.%s" % (errors[status.code], format), False)
        elif status in errors:
            template = self.template_for_uri("%s.%s" % (errors[status], format), False)
        elif 0 in errors:
            template = self.template_for_uri("%s.%s" % (errors[0], format), False)
        else:
            template = None

        # We can't render this error because we did not find a suiting template.
        if template is None:
            return None

        # Render template
        return template.render(**params)
Esempio n. 9
0
	def configure(self, config_key='smisk.mvc.routes'):
		filters = config.get(config_key, [])
		if not isinstance(filters, (list, tuple)):
			raise TypeError('configuration parameter %r must be a list' % config_key)
		for filter in filters:
			try:
				# Convert a list or tuple mapping
				if isinstance(filter, (tuple, list)):
					if len(filter) > 2:
						filter = {'methods':filter[0], 'pattern': filter[1], 'destination': filter[2]}
					else:
						filter = {'pattern': filter[0], 'destination': filter[1]}
				# Create a filter from the mapping
				dest = URL(filter['destination'])
				self.filter(filter['pattern'], dest, match_on_full_url=dest.scheme,
										methods=filter.get('methods', None))
			except TypeError, e:
				e.args = ('configuration parameter %r must contain dictionaries or lists' % config_key,)
				raise
			except IndexError, e:
				e.args = ('%r in configuration parameter %r' % (e.message, config_key),)
				raise
Esempio n. 10
0
 def setup(self):
   '''Setup application state
   '''
   # Setup ETag
   etag = config.get('smisk.mvc.etag')
   if etag is not None and isinstance(etag, basestring):
     import hashlib
     config.set_default('smisk.mvc.etag', getattr(hashlib, etag))
   
   # Check templates config
   if self.templates:
     if not self.templates.directories:
       path = os.path.join(os.environ['SMISK_APP_DIR'], 'templates')
       if os.path.isdir(path):
         self.templates.directories = [path]
         log.debug('using template directories: %s', ', '.join(self.templates.directories))
       else:
         log.info('template directory not found -- disabling templates.')
         self.templates.directories = []
         self.templates = None
   
   # Set fallback serializer
   if isinstance(Response.fallback_serializer, basestring):
     Response.fallback_serializer = serializers.find(Response.fallback_serializer)
   if Response.fallback_serializer not in serializers:
     # Might have been unregistered and need to be reconfigured
     Response.fallback_serializer = None
   if Response.fallback_serializer is None:
     try:
       Response.fallback_serializer = serializers.extensions['html']
     except KeyError:
       try:
         Response.fallback_serializer = serializers[0]
       except IndexError:
         Response.fallback_serializer = None
   
   # Create tables if needed and setup any models
   if model.metadata.bind:
     model.setup_all(True)
Esempio n. 11
0
 def apply_leaf_restrictions(self):
   '''Applies any restrictions set by the current leaf/destination.
   
   :rtype: None
   '''
   # Method restrictions
   try:
     log.debug('applying method restrictions for leaf %r', self.destination.leaf)
     leaf_methods = self.destination.leaf.methods
     method = self.request.method
     log.debug('leaf allows %r, request is %r', leaf_methods, method)
     
     if leaf_methods is not None:
       method_not_allowed = method not in leaf_methods
       is_opts_and_refl = (method == 'OPTIONS'  and  control.enable_reflection)
       
       if method_not_allowed  and  method == 'HEAD' and 'GET' in leaf_methods:
         # HEAD is always allowed as long as GET is allowed.
         # We perform the check here in order to give the user the possibility
         # to explicitly @expose a leaf with OPTIONS included in the methods 
         # argument. (Same reason with OPTIONS further down here)
         method_not_allowed = False
       elif method_not_allowed  or  method == 'OPTIONS':
         # HTTP 1.1 requires us to specify allowed methods in a 405 response
         # and we should also include Allow for OPTIONS requests.
         if is_opts_and_refl and method_not_allowed:
           # OPTIONS was not in leaf_methods, so add it through copy (not appending)
           leaf_methods = leaf_methods + ['OPTIONS']
         if 'HEAD' not in leaf_methods  and  'GET' in leaf_methods:
           # HEAD was not in leaf_methods, but GET is, so add it through copy (not appending)
           leaf_methods = leaf_methods + ['HEAD']
         self.response.headers.append('Allow: ' + ', '.join(leaf_methods))
       
       if method_not_allowed:
         # If OPTIONS request and control.enable_reflection is True, respond
         # with leaf relfection. Placing the check here, inside method_not_allowed,
         # allows the application designer to explicitly @expose a leaf with
         # OPTIONS included in the methods argument, in order for her to handle
         # a OPTIONS request, rather than Smisk taking over.
         if is_opts_and_refl:
           class LeafReflectionDestination(Destination):
             def _call_leaf(self, *args, **params):
               return control.leaf_reflection(self.leaf)
           self.destination = LeafReflectionDestination(self.destination.leaf)
         else:
           # Method not allowed
           raise http.MethodNotAllowed("The requested method %s is not allowed for the URI %s." %\
             (method, self.request.url.uri))
   except AttributeError:
     # self.destination.leaf does not have any method restrictions
     pass
   
   # Format restrictions
   try:
     leaf_formats = self.destination.leaf.formats
     for ext in self.response.serializer.extensions:
       if ext not in leaf_formats:
         self.response.serializer = None
         break
     if self.response.serializer is None:
       log.warn('client requested a response type which is not available for the current leaf')
       if self.response.format is not None:
         raise http.NotFound('Resource not available as %r' % self.response.format)
       elif config.get('smisk.mvc.strict_tcn', True) or len(leaf_formats) == 0:
         raise http.NotAcceptable()
       else:
         try:
           self.response.serializer = serializers.extensions[leaf_formats[0]]
         except KeyError:
           raise http.NotAcceptable()
   except AttributeError:
     # self.destination.leaf.formats does not exist -- no restrictions apply
     pass
Esempio n. 12
0
 def parse_request(self):
   '''
   Parses the request, involving appropriate serializer if needed.
   
   :returns: (list arguments, dict parameters)
   :rtype:   tuple
   '''
   args = []
   params = {}
   log.debug('parsing request')
   
   # Look at Accept-Charset header and set self.response.charset accordingly
   accept_charset = self.request.env.get('HTTP_ACCEPT_CHARSET', False)
   if accept_charset:
     self.response.charsets, highqs, partials, accept_any = parse_qvalue_header(accept_charset.lower())
     if accept_any:
       self.response.charsets = []
     else:
       alt_cs = None
       for cq in self.response.charsets:
         c = cq[0]
         try:
           char_codecs.lookup(c)
           alt_cs = c
           break
         except LookupError:
           pass
     
       if alt_cs is not None:
         self.response.charset = alt_cs
       else:
         # If an Accept-Charset header is present, and if the server cannot send a response 
         # which is acceptable according to the Accept-Charset header, then the server 
         # SHOULD send an error response with the 406 (not acceptable) status code, though 
         # the sending of an unacceptable response is also allowed. [RFC 2616]
         log.info('client demanded charset(s) we can not respond using. "Accept-Charset: %s"',
           accept_charset)
         if config.get('smisk.mvc.strict_tcn', True):
           raise http.NotAcceptable()
     
       if log.level <= logging.DEBUG:
         log.debug('using alternate response character encoding: %r (requested by client)',
           self.response.charset)
   
   # Handle params
   try:
     if self.charset:
       # trigger build-up and thus decoding of text data
       self.request.post
       self.request.cookies
       params.update(self.request.get)
     else:
       for k,v in self.request.get.items():
         if isinstance(v, str):
           v = v.decode('latin_1', self.unicode_errors)
         params[k] = v
   except UnicodeDecodeError:
     # We do not speak about latin-1 in this message since in the case of URL-escaped 
     # bytes we can never fail to decode bytes as latin-1.
     raise http.BadRequest('Unable to decode text data. '\
       'Please encode text using the %s character set.' % self.charset)
   
   # Parse body if POST request
   if self.request.method in ('POST', 'PUT'):
     path_ext_serializer = self._serializer_for_request_path_ext()
     content_type = self.request.env.get('CONTENT_TYPE', '').lower()
     content_charset = None
     
     p = content_type.find(';')
     if p != -1:
       for k,v in [kv.split('=') for kv in content_type[p+1:].strip().split(';')]:
         if k == 'charset':
           content_charset = v
       content_type = content_type[:p]
     
     if path_ext_serializer is not None and not content_type:
       content_type = path_ext_serializer.media_types[0]
     
     if content_type == 'application/x-www-form-urlencoded' or len(content_type) == 0:
       # Standard urlencoded content
       params.update(self.request.post)
     elif not content_type.startswith('multipart/'):
       # Multiparts are parsed by smisk.core, so let's try to
       # decode the body only if it's of another type.
       try:
         if content_type:
           self.request.serializer = serializers.media_types[content_type]
         elif path_ext_serializer is not None:
           self.request.serializer = path_ext_serializer
         if not self.request.serializer or not self.request.serializer.can_unserialize:
           # If we can not decode the payload, raise a KeyError in order to
           # generate a UnsupportedMediaType response (see further down...)
           raise KeyError()
         log.debug('decoding request payload using %s', self.request.serializer)
         content_length = int(self.request.env.get('CONTENT_LENGTH', -1))
         (eargs, eparams) = self.request.serializer.unserialize(self.request.input, content_length, content_charset)
         if eargs is not None:
           args.extend(eargs)
         if eparams is not None:
           params.update(eparams)
       except KeyError:
         log.error('unable to parse request -- no serializer able to decode %r', content_type)
         raise http.UnsupportedMediaType()
   
   return (args, params)
Esempio n. 13
0
 def response_serializer(self, no_http_exc=False):
   '''
   Return the most appropriate serializer for handling response encoding.
   
   :param no_http_exc: If true, HTTP statuses are never rised when no acceptable 
                       serializer is found. Instead a fallback serializer will be returned:
                       First we try to return a serializer for format html, if that
                       fails we return the first registered serializer. If that also
                       fails there is nothing more left to do but return None.
                       Primarily used by `error()`.
   :type  no_http_exc: bool
   :return: The most appropriate serializer
   :rtype:  Serializer
   '''
   # Overridden by explicit response.format?
   if self.response.format is not None:
     # Should fail if not exists
     return serializers.extensions[self.response.format]
   
   # Overridden internally by explicit Content-Type header?
   p = self.response.find_header('Content-Type:')
   if p != -1:
     content_type = self.response.headers[p][13:].strip("\t ").lower()
     p = content_type.find(';')
     if p != -1:
       content_type = content_type[:p].rstrip("\t ")
     try:
       return serializers.media_types[content_type]
     except KeyError:
       if no_http_exc:
         return Response.fallback_serializer
       else:
         raise http.InternalServerError('Content-Type response header is set to type %r '\
           'which does not have any valid serializer associated with it.' % content_type)
   
   # Try filename extension
   fallback = None
   if no_http_exc:
     fallback = Response.fallback_serializer
   serializer = self._serializer_for_request_path_ext(fallback=fallback)
   if serializer is not None:
     return serializer
   
   # Try media type
   accept_types = self.request.env.get('HTTP_ACCEPT', None)
   if accept_types is not None and len(accept_types):
     if log.level <= logging.DEBUG:
       log.debug('client accepts: %r', accept_types)
     
     # Parse the qvalue header
     tqs, highqs, partials, accept_any = parse_qvalue_header(accept_types)
     
     # If the default serializer exists in the highest quality accept types, return it
     if Response.serializer is not None:
       for t in Response.serializer.media_types:
         if t in highqs:
           if '*' not in t and self.response.find_header('Content-Type:') == -1:
             self.response.headers.append('Content-Type: '+t)
           return Response.serializer
     
     # Find a serializer matching any accept type, ordered by qvalue
     available_types = serializers.media_types.keys()
     for tq in tqs:
       t = tq[0]
       if t in available_types:
         if '*' not in t and self.response.find_header('Content-Type:') == -1:
           self.response.headers.append('Content-Type: '+t)
         return serializers.media_types[t]
     
     # Accepts */* which is far more common than accepting partials, so we test this here
     # and simply return Response.serializer if the client accepts anything.
     if accept_any:
       if Response.serializer is not None:
         return Response.serializer
       else:
         return Response.fallback_serializer
     
     # If the default serializer matches any partial, return it (the likeliness of 
     # this happening is so small we wait until now)
     if Response.serializer is not None:
       for t in Response.serializer.media_types:
         if t[:t.find('/', 0)] in partials:
           return Response.serializer
     
     # Test the rest of the partials
     for t, serializer in serializers.media_types.items():
       if t[:t.find('/', 0)] in partials:
         return serializer
     
     # If an Accept header field is present, and if the server cannot send a response which 
     # is acceptable according to the combined Accept field value, then the server SHOULD 
     # send a 406 (not acceptable) response. [RFC 2616]
     log.info('client demanded content type(s) we can not respond in. "Accept: %s"', accept_types)
     if config.get('smisk.mvc.strict_tcn', True):
       raise http.NotAcceptable()
   
   # The client did not ask for any type in particular
   
   # Strict TCN
   if Response.serializer is None:
     if no_http_exc or len(serializers) < 2:
       return Response.fallback_serializer
     else:
       raise http.MultipleChoices(self.request.cn_url)
     
   # Return the default serializer
   return Response.serializer
Esempio n. 14
0
File: main.py Progetto: rsms/smisk
def handle_errors_wrapper(fnc, error_cb=sys.exit, abort_cb=None, *args, **kwargs):
	'''Call `fnc` catching any errors and writing information to ``error.log``.
	
	``error.log`` will be written to, or appended to if it aldready exists,
	``ENV["SMISK_LOG_DIR"]/error.log``. If ``SMISK_LOG_DIR`` is not set,
	the file will be written to ``ENV["SMISK_APP_DIR"]/error.log``.
	
	* ``KeyboardInterrupt`` is discarded/passed, causing a call to `abort_cb`,
		if set, without any arguments.
	
	* ``SystemExit`` is passed on to Python and in normal cases causes a program
		termination, thus this function will not return.
	
	* Any other exception causes ``error.log`` to be written to and finally
		a call to `error_cb` with a single argument; exit status code.
	
	:param	error_cb:	 Called after an exception was caught and info 
	                   has been written to ``error.log``. Receives a
	                   single argument: Status code as an integer.
	                   Defaults to ``sys.exit`` causing normal program
	                   termination. The returned value of this callable
	                   will be returned by `handle_errors_wrapper` itself.
	:type	 error_cb:	 callable
	:param	abort_cb:	 Like `error_cb` but instead called when
											``KeyboardInterrupt`` was raised.
	:type	 abort_cb:	 callable
	:rtype: object
	'''
	try:
		# Run the wrapped callable
		return fnc(*args, **kwargs)
	except KeyboardInterrupt:
		if abort_cb:
			return abort_cb()
	except SystemExit:
		raise
	except:
		# Write to error.log
		try:
			logfile = os.environ.get('SMISK_LOG_DIR', os.environ.get(os.environ['SMISK_APP_DIR'], '.'))
			logfile = os.path.join(logfile, 'error.log')
			logfile = os.path.abspath(_config.get('smisk.emergency_logfile', logfile))
			f = open(logfile, 'a')
			try:
				from traceback import print_exc
				from datetime import datetime
				f.write(datetime.now().isoformat())
				f.write(" [%d] " % os.getpid())
				print_exc(1000, f)
			finally:
				f.close()
				try:
					print_exc(1000, sys.stderr)
				except:
					pass
				sys.stderr.write('Wrote emergency log to %s\n' % logfile)
		except Exception, e:
			try:
				sys.stderr.write('Failed to write emergency log to %s: %s\n' % (logfile, e))
			except:
				pass
		# Call error callback
		if error_cb:
			return error_cb(1)
Esempio n. 15
0
File: app.py Progetto: rsms/smisk
 def __init__(self):
   # If persistent evaluates to True, the contents of the shared 
   # dict will be flushed to disk on shutdown and read from disk 
   # on startup, thus providing a persistent set of data.
   self.entries = shared_dict(persistent=config.get('persistent'))