0001"""Configuration setup for templating systems and Paste error
0002middleware
0003
0004This module supplies PylonsConfig which handles setting up defaults
0005for templating systems, Paste errorware, and prefixing Routes if
0006necessary.
0007"""
0008import copy
0009import logging
0010import os
0011import warnings
0012
0013from paste.config import DispatchingConfig
0014from paste.deploy.converters import asbool
0015
0016import pylons.legacy
0017import pylons.templating
0018
0019
0020default_template_engine = 'mako'
0021request_defaults = dict(charset='utf-8', errors='replace',
0022 decode_param_names=False, language='en-us')
0023response_defaults = dict(content_type='text/html',
0024 charset='utf-8', errors='strict',
0025 headers={'Cache-Control': 'no-cache',
0026 'Pragma': 'no-cache'})
0027
0028log = logging.getLogger(__name__)
0029
0030
0031class PylonsConfig(DispatchingConfig):
0032 """Pylons configuration object
0033
0034 The Pylons configuration object is a per-application instance object
0035 that retains the information regarding the global and app conf's as
0036 well as per-application instance specific data such as the mapper, the
0037 paths for this instance, and the myghty configuration.
0038
0039 The config object is available in your application as the Pylons global
0040 ``pylons.config``. An example usage:
0041
0042 .. code-block :: Python
0043
0044 from pylons import config
0045
0046 template_paths = config['pylons.paths']['templates']
0047
0048 There's several useful keys of the config object most people will be
0049 interested in:
0050
0051 ``pylons.template_options``
0052 Full dict of template options that any TG compatible plugin should
0053 be able to parse. Comes with basic config needed for Myghty, Kid,
0054 and Mako.
0055 ``pylons.paths``
0056 A dict of absolute paths that were defined in the applications
0057 ``config/environment.py`` module.
0058 ``pylons.environ_config``
0059 Dict of environ keys for where in the environ to pickup various
0060 objects for registering with Pylons. If these are present then
0061 PylonsApp will use them from environ rather than using default
0062 middleware from Beaker. Valid keys are: ``session, cache``
0063 ``pylons.template_engines``
0064 List of template engines to configure. The first one in the list will
0065 be configured as the default template engine. Each item in the list is
0066 a dict indicating how to configure the template engine with keys:
0067 ``engine``, ``template_root``, ``template_options``, and ``alias``
0068 ``pylons.default_charset``
0069 Deprecated: Use the response_settings dict instead.
0070 Default character encoding specified to the browser via the
0071 'charset' parameter of the HTTP response's Content-Type header.
0072 ``pylons.strict_c``
0073 Whether or not the ``c`` object should throw an attribute error when
0074 access is attempted to an attribute that doesn't exist.
0075 ``pylons.request_options``
0076 A dict of Content-Type related default settings for new instances of
0077 ``paste.wsgiwrappers.WSGIRequest``. May contain the values ``charset``
0078 and ``errors`` and ``decode_param_names``. Overrides the Pylons default
0079 values specified by the ``request_defaults`` dict.
0080 ``pylons.response_options``
0081 A dict of Content-Type related default settings for new instances of
0082 ``pylons.Response``. May contain the values ``content_type``,
0083 ``charset`` and ``errors``. Overrides the Pylons default values
0084 specified by the ``response_defaults`` dict.
0085 ``routes.map``
0086 Mapper object used for Routing. Yes, it is possible to add routes
0087 after your application has started running.
0088 """
0089 defaults = {
0090 'debug': False,
0091 'pylons.package': None,
0092 'pylons.paths': {'root': None,
0093 'controllers': None,
0094 'templates': [],
0095 'static_files': None},
0096 'pylons.db_engines': {},
0097 'pylons.environ_config': {},
0098 'pylons.g': None,
0099 'pylons.h': None,
0100 'pylons.request_options': request_defaults.copy(),
0101 'pylons.response_options': response_defaults.copy(),
0102 'pylons.strict_c': False,
0103 'buffet.template_engines': [],
0104 'buffet.template_options': {},
0105 }
0106
0107 def __getattr__(self, name):
0108 # Backwards compatibility
0109 if name == 'Config':
0110 class FakeConfig(object):
0111 def __init__(this, *args, **kwargs):
0112 self.load_environment(*args, **kwargs)
0113 def __getattr__(this, name):
0114 return getattr(self, name)
0115 def __setattr__(this, name, value):
0116 setattr(self, name, value)
0117 return FakeConfig
0118 else:
0119 conf_dict = self.current_conf()
0120
0121 # Backwards compat for when the option is now in the dict, and
0122 # access was attempted via attribute
0123 for prefix in ('', 'pylons.', 'buffet.', 'routes.'):
0124 full_name = prefix + name
0125 if full_name in conf_dict:
0126 warnings.warn(pylons.legacy.config_attr_moved % (name, full_name), DeprecationWarning, 3)
0128 return conf_dict[full_name]
0129 if name == 'request_defaults':
0130 return request_defaults
0131 elif name == 'response_defaults':
0132 return response_defaults
0133 return getattr(conf_dict, name)
0134
0135 def load_environment(self, tmpl_options=None, map=None, paths=None,
0136 environ_config=None, default_charset=None,
0137 strict_c=False, request_settings=None,
0138 response_settings=None):
0139 """Load the environment options
0140
0141 Deprecated functionality for pre-0.9.6 projects.
0142 """
0143 warnings.warn(pylons.legacy.config_load_environment,
0144 DeprecationWarning, 3)
0145
0146 conf = copy.deepcopy(PylonsConfig.defaults)
0147 if tmpl_options:
0148 conf['buffet.template_options'] = tmpl_options
0149
0150 if request_settings:
0151 conf['pylons.request_options'].update(request_settings)
0152
0153 if response_settings:
0154 conf['pylons.response_options'].update(response_settings)
0155
0156 conf['routes.map'] = map
0157 conf['pylons.paths'] = paths or {}
0158 conf['pylons.environ_config'] = environ_config or {}
0159 conf['pylons.strict_c'] = strict_c
0160
0161 if default_charset:
0162 warnings.warn(pylons.legacy.default_charset_warning % dict(klass='Config', charset=default_charset),
0164 DeprecationWarning, 2)
0165 conf['pylons.response_options']['charset'] = default_charset
0166 self['environment_load'] = conf
0167
0168 def add_template_engine(self, engine, root, options=None, alias=None):
0169 """Add additional template engines for configuration on Pylons WSGI
0170 init.
0171
0172 ``engine``
0173 The name of the template engine
0174
0175 ``root``
0176 Template root for the engine
0177
0178 ``options``
0179 Dict of additional options used during engine initialization, if
0180 not provided, default to using the template_options dict.
0181
0182 ``alias``
0183 Name engine should respond to when actually used. This allows for
0184 multiple configurations of the same engine and lets you alias the
0185 additional ones to other names.
0186
0187 Example of Kid addition:
0188
0189 .. code-block:: Python
0190
0191 # In yourproj/middleware.py
0192 # ...
0193 config.init_app(global_conf, app_conf, package='yourproj')
0194
0195 # Load additional template engines
0196 kidopts = {'kid.assume_encoding':'utf-8', 'kid.encoding':'utf-8'}
0197 config.add_template_engine('kid', 'yourproj.kidtemplates', kidopts)
0198
0199 Example of changing the default template engine:
0200
0201 .. code-block:: Python
0202
0203 # In yourproj/middleware.py
0204 # ...
0205 config.init_app(global_conf, app_conf, package='yourproj')
0206
0207 # Remove existing template engine
0208 old_default = config.template_engines.pop()
0209
0210 # Load additional template engines
0211 kidopts = {'kid.assume_encoding':'utf-8', 'kid.encoding':'utf-8'}
0212 config.add_template_engine('kid', 'yourproj.kidtemplates', kidopts)
0213
0214 # Add old default as additional engine
0215 config.template_engines.append(old_default)
0216 """
0217 if not options:
0218 options = self['buffet.template_options']
0219 config = dict(engine=engine, template_root=root,
0220 template_options=options, alias=alias)
0221 log.debug("Adding %s engine with alias %s and %s options", engine,
0222 alias, options)
0223 self['buffet.template_engines'].append(config)
0224
0225 def init_app(self, global_conf, app_conf, package=None,
0226 template_engine=default_template_engine, paths=None):
0227 """Initialize configuration for the application
0228
0229 .. note
0230 This *must* be called at least once, as soon as possible tosetup
0231 all the configuration options.
0232
0233 ``global_config``
0234 Several options are expected to be set for a Pylons web
0235 application. They will be loaded from the global_config which has
0236 the main Paste options. If ``debug`` is not enabled as a global
0237 config option, the following option *must* be set:
0238
0239 * error_to - The email address to send the debug error to
0240
0241 The optional config options in this case are:
0242
0243 * smtp_server - The SMTP server to use, defaults to 'localhost'
0244 * error_log - A logfile to write the error to
0245 * error_subject_prefix - The prefix of the error email subject
0246 * from_address - Whom the error email should be from
0247 ``app_conf``
0248 Defaults supplied via the [app:main] section from the Paste
0249 config file. ``load_config`` only cares about whether a 'prefix'
0250 option is set, if so it will update Routes to ensure URL's take
0251 that into account.
0252 ``package``
0253 The name of the application package, to be stored in the app_conf.
0254 ``template_engine``
0255 Declare the default template engine to setup. Choices are kid,
0256 genshi, mako (the default), and pylonsmyghty.
0257 """
0258 log.debug("Initializing configuration, package: '%s'", package)
0259 conf = global_conf.copy()
0260 conf.update(app_conf)
0261 conf.update(dict(app_conf=app_conf, global_conf=global_conf))
0262 conf.update(self.pop('environment_load', {}))
0263
0264 if paths:
0265 conf['pylons.paths'] = paths
0266
0267 # XXX Legacy: More backwards compatibility locations for the package
0268 # name
0269 conf['pylons.package'] = conf['package'] = conf['app_conf']['package'] = package
0271
0272 if 'debug' in conf:
0273 conf['debug'] = asbool(conf['debug'])
0274
0275 if paths and 'root_path' in paths:
0276 warnings.warn(pylons.legacy.root_path, DeprecationWarning, 2)
0277 paths['root'] = paths['root_path']
0278
0279 log.debug("Pushing process configuration")
0280 self.push_process_config(conf)
0281 self.set_defaults(template_engine)
0282
0283 def set_defaults(self, template_engine):
0284 conf = self.current_conf()
0285
0286 # Ensure all the keys from defaults are present, load them if not
0287 for key, val in copy.deepcopy(PylonsConfig.defaults).iteritems():
0288 conf.setdefault(key, val)
0289
0290 # Setup the prefix to override the routes if necessary.
0291 prefix = conf.get('prefix')
0292 if prefix:
0293 warnings.warn(pylons.legacy.prefix_warning % prefix,
0294 DeprecationWarning, 3)
0295 map = conf.get('routes.map')
0296 if map:
0297 map.prefix = prefix
0298 map._created_regs = False
0299
0300 # Load the errorware configuration from the Paste configuration file
0301 # These all have defaults, and emails are only sent if configured and
0302 # if this application is running in production mode
0303 errorware = {}
0304 errorware['debug'] = asbool(conf.get('debug'))
0305 if not errorware['debug']:
0306 errorware['debug'] = False
0307 errorware['error_email'] = conf.get('email_to')
0308 errorware['error_log'] = conf.get('error_log', None)
0309 errorware['smtp_server'] = conf.get('smtp_server',
0310 'localhost')
0311 errorware['error_subject_prefix'] = conf.get(
0312 'error_subject_prefix', 'WebApp Error: ')
0313 errorware['from_address'] = conf.get(
0314 'from_address', conf.get('error_email_from',
0315 'pylons@yourapp.com'))
0316 errorware['error_message'] = conf.get('error_message',
0317 'An internal server error occurred')
0318
0319 # Standard Pylons configuration directives for Myghty
0320 myghty_defaults = {}
0321
0322 # Raise a complete error for the error middleware to catch
0323 myghty_defaults['raise_error'] = True
0324 myghty_defaults['output_encoding'] = conf['pylons.response_options']['charset']
0326 myghty_defaults['component_root'] = [{os.path.basename(path): path} for path in conf['pylons.paths']['templates']]
0328
0329 # Merge additional globals
0330 myghty_defaults.setdefault('allow_globals',
0331 []).extend(pylons.templating.PYLONS_VARS)
0332
0333 myghty_template_options = {}
0334 if 'myghty_data_dir' in conf:
0335 warnings.warn("Old config option found in ini file, replace "
0336 "'myghty_data_dir' option with 'data_dir'",
0337 DeprecationWarning, 3)
0338 myghty_defaults['data_dir'] = conf['myghty_data_dir']
0339 elif 'cache_dir' in conf:
0340 myghty_defaults['data_dir'] = os.path.join(conf['cache_dir'],
0341 'templates')
0342
0343 # Copy in some defaults
0344 if 'cache_dir' in conf:
0345 conf.setdefault('beaker.session.data_dir',
0346 os.path.join(conf['cache_dir'], 'sessions'))
0347 conf.setdefault('beaker.cache.data_dir',
0348 os.path.join(conf['cache_dir'], 'cache'))
0349
0350 # Copy Myghty defaults and options into template options
0351 for k, v in myghty_defaults.iteritems():
0352 myghty_template_options['myghty.'+k] = v
0353
0354 # Legacy copy of session and cache settings into conf
0355 if k.startswith('session_') or k.startswith('cache_'):
0356 conf[k] = v
0357
0358 # Copy old session/cache config to new keys for Beaker 0.7+
0359 for key, val in conf.items():
0360 if key.startswith('cache_'):
0361 conf['cache.'+key[6:]] = val
0362 elif key.startswith('session_'):
0363 conf['session.'+key[8:]] = val
0364
0365 # Setup the main template options dict
0366 conf['buffet.template_options'].update(myghty_template_options)
0367
0368 # Setup several defaults for various template languages
0369 defaults = {}
0370
0371 # Rearrange template options as default for Mako
0372 defaults['mako.directories'] = conf['pylons.paths']['templates']
0373 defaults['mako.filesystem_checks'] = True
0374 defaults['mako.output_encoding'] = conf['pylons.response_options']['charset']
0376 if 'cache_dir' in conf:
0377 defaults['mako.module_directory'] = os.path.join(conf['cache_dir'], 'templates')
0379
0380 # Setup kid defaults
0381 defaults['kid.assume_encoding'] = 'utf-8'
0382 defaults['kid.encoding'] = conf['pylons.response_options']['charset']
0383
0384 # Merge template options into defaults
0385 defaults.update(conf['buffet.template_options'])
0386 conf['buffet.template_options'] = defaults
0387
0388 # Prepare our default template engine
0389 if template_engine == 'pylonsmyghty':
0390 self.add_template_engine('pylonsmyghty', None,
0391 myghty_template_options)
0392 elif template_engine == 'mako':
0393 self.add_template_engine('mako', '')
0394 elif template_engine in ['genshi', 'kid']:
0395 self.add_template_engine(template_engine,
0396 conf['pylons.package'] + '.templates')
0397 elif template_engine == 'cheetah':
0398 self.add_template_engine(template_engine, '%s.templates' %
0399 conf['pylons.package'])
0400
0401 log.debug("Loaded %s template engine as the default template renderer", template_engine)
0402
0403 conf['pylons.cache_dir'] = conf.pop('cache_dir',
0404 conf['app_conf'].get('cache_dir'))
0405 # Save our errorware values
0406 conf['pylons.errorware'] = errorware
0407
0408
0409config = PylonsConfig()
0410
0411
0412# Push an empty config so all accesses to config at import time have something
0413# to look at and modify. This config will be merged with the app's when it's
0414# built in the paste.app_factory entry point.
0415initial_config = copy.deepcopy(PylonsConfig.defaults)
0416config.push_process_config(initial_config)