[[PageOutline]] [This document is complete to rev 1460.] = What's new in CherryPy 3.0 = This document only describes new features in CherryPy 3.0. A detailed "How To Upgrade" document is at [wiki:UpgradeTo30 UpgradeTo30]. == Speed == CherryPy 3 is much faster than CherryPy 2 (as much as three times faster in benchmarks). == Config == === _cp_config: attaching config to handlers === In CP 2, you could only specify "config" in a config file or dict, where it was always keyed by URL. For example: {{{ [/path/to/page] methods_with_bodies = ("POST", "PUT", "PROPPATCH") }}} It's obvious that the extra method is the norm for that path; in fact, the code could be considered broken without it. In CherryPy 3, you can attach that bit of config directly on the page handler: {{{ def page(self): return "Hello, world!" page.exposed = True page._cp_config = {"request.methods_with_bodies": ("POST", "PUT", "PROPPATCH")} }}} This can be done at any point in the cherrypy tree; for example, we could have attached that config to a class which contains the page method: {{{ class SetOPages: _cp_config = {"request.methods_with_bodies": ("POST", "PUT", "PROPPATCH")} def page(self): return "Hullo, Werld!" page.exposed = True }}} This technique allows you to: * Put config near where it's used for improved readability and maintainability. * Attach config to objects instead of URL's. This allows multiple URL's to point to the same object, yet you only need to define the config once. * Provide defaults which are still overridable in a config file. === Separate configuration scopes === CherryPy 2 used a single config dict for global, per-application, and per-path config. CherryPy 3 separates these scopes in a couple of ways: First, and most '''important''', {{{cherrypy.config}}} now only holds global config data; that is, config entries which affects all mounted applications. Each Application object keeps its own config in {{{app.config}}}. You must pass global config to {{{cherrypy.config.update}}}, and per-application config to {{{cherrypy.tree.mount}}}. You ''may'' use a single config file and hand the same file (or filename) to both methods; put your global config in a [global] section to signal {{{cherrypy.config.update}}} which entries to grab. Second, when a request is processed, these two config sources (global and per-application) are merged and collapsed to form a single config dict stored inside {{{cherrypy.request.config}}}. This dict contains only those config entries which apply to the given request; that is, per-path config. Note that when you do an InternalRedirect, this config is recalculated for the new path. === Configuration namespaces === In CherryPy 2, config entries were somewhat haphazard about their naming and scope. They were always inspected as late as possible, often multiple times, and their default values were locked away inside the source code. In CherryPy 3, all config entries (except "environment") are now prefixed with a namespace. When you provide a config entry, it is now bound as early as possible to the actual object referenced by the namespace; for example, CP 2's "stream_response" is now "response.stream", and actually sets the "stream" attribute of cherrypy.response. In this way, you can easily determine the default value by firing up a python interpreter and typing: {{{ >>> import cherrypy >>> cherrypy.response.stream False }}} This also means that some objects (the Request class in particular) have grown a number of new attributes, to avoid the need for config.get(). Entries from each namespace may be allowed in the global, application root ("/") or per-path config, or a combination: ||Scope||Global||Application Root||App Path|| ||engine||X|| || || ||hooks||X||X||X|| ||log||X||X|| || ||request||X||X||X|| ||response||X||X||X|| ||server||X|| || || ||tools||X||X||X|| ==== Custom config namespaces ==== You can define your own namespaces if you like, and they can do far more than simply set attributes. The {{{test/test_config}}} module, for example, shows an example of a custom namespace that coerces incoming params and outgoing body content. The {{{_cpwsgi}}} module includes an additional, builtin namespace for invoking WSGI middleware. In essence, a config namespace handler is just a function, that gets passed any config entries in its namespace. You add it to a namespaces registry (a dict), where keys are namespace names and values are handler functions. When a config entry for your namespace is encountered, the corresponding handler function will be called, passing the config key and value; that is, {{{namespaces[namespace](k, v)}}}. For example, if you write: {{{ def db_namespace(k, v): if k == 'connstring': orm.connect(v) cherrypy.config.namespaces['db'] = db_namespace }}} ...then {{{cherrypy.config.update({"db.connstring": "Oracle:host=1.10.100.200;sid=TEST"})}}} will call {{{db_namespace('connstring', 'Oracle:host=1.10.100.200;sid=TEST')}}}. The point at which your namespace handler is called depends on where you add it: ||Namespace ||Handler is called in || ||config.namespaces ||cherrypy.config.update|| ||Application.namespaces ||Application.merge (which is called by cherrypy.tree.mount)|| ||engine.request_class.namespaces||Request.configure (called for each request, after the handler is looked up)|| If you need additional code to run when all your namespace keys are collected, you can supply a callable context manager in place of a normal function for the handler. Context managers are defined in [http://www.python.org/dev/peps/pep-0343/ PEP 343]. == Tools == === Using builtin tools === Filters are gone! In their place are Tools, which allow for much more flexibility. If your favorite builtin filter has changed to a tool, it's easy to convert your code. See [wiki:UpgradeTo30 UpgradeTo30] for a complete list of name changes. Instead of this: {{{ [/docroot] static_filter.on: True static_filter.root: "/path/to/app" static_filter.dir: 'static' }}} ...use the "tools" namespace like this: {{{ [/docroot] tools.staticdir.on: True tools.staticdir.root: "/path/to/app" tools.staticdir.dir: 'static' }}} We can also use our new friend {{{_cp_config}}} (see above): {{{ class docroot(object): _cp_config = {'tools.staticdir.on': True, 'tools.staticdir.root: "/path/to/app", 'tools.staticdir.dir': 'static'} }}} But we can do even better by using the '''builtin decorator support''' that all Tools have: {{{ class docroot(object): @tools.staticdir(root="/path/to/app", dir='static') def page(self): ... }}} ...and in this case, we can do even '''better''' because tools.staticdir is a 'HandlerTool', and therefore can be used directly as a page handler: {{{ class docroot(object): static = tools.staticdir.handler(section='static', root="/path/to/app", dir='static') }}} Finally, you can use (most) Tools directly, by calling the function they wrap. They expose this via the 'callable' attribute: {{{ def page(self): tools.response_headers.callable([('Content-Language', 'fr')]) return "Bonjour, le Monde!" page.exposed = True }}} Because the underlying function is wrapped in a tool, you need to call help(tools.whatevertool.callable) if you want the docstring for it. Using help(tools.whatevertool) will give you help on how to use it as a Tool (for example, as a decorator). Tools also are also '''inspectable''' automatically. They expose their own arguments as attributes: {{{ >>> dir(cherrypy.tools.session_auth) [..., 'anonymous', 'callable', 'check_username_and_password', 'do_check', 'do_login', 'do_logout', 'handler', 'login_screen', 'on_check', 'on_login', 'on_logout', 'run', 'session_key'] }}} This makes IDE calltips especially useful, even when writing config files! === New and improved builtin tools === ==== tools.proxy ==== This replaces and enhances the old baseurl_filter. The old way: {{{ baseurl_filter.base_url = "http://myhost" baseurl_filter.use_x_forwarded_host = False }}} The new way: {{{ tools.proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For', scheme='X-Forwarded-Proto') }}} This changes the base URL (scheme://host[:port][/path]), and is most useful when running a CP server behind Apache or some other webserver. {{{tools.proxy.local}}} defines the request header which will be used to auto-fill the new request.base. If you want the new request.base to include path info (not just the host), you must explicitly set base to the full base path, and ALSO set {{{tools.proxy.local}}} to "" (empty string), so that the X-Forwarded-Host request header (which never includes path info) does not override it. New in CP 3: cherrypy.request.remote.ip (the IP address of the client) will be rewritten if the header specified by {{{tools.proxy.remote}}} is valid. By default, 'remote' is set to 'X-Forwarded-For'. If you do not want to rewrite remote.ip, set the 'remote' arg to an empty string. ==== tools.log_tracebacks ==== This replaces the CP 2 feature: "server.log_tracebacks". ==== tools.log_headers ==== This replaces the CP 2 feature: "server.log_request_headers". ==== tools.err_redirect ==== Turn this tool on to redirect all unhandled errors to a different page. Supply the new URL via {{{tools.err_redirect.url}}}. By default, this raises InternalRedirect. To use HTTPRedirect, set {{{tools.err_redirect.internal}}} to False. ==== tools.etags ==== This new tool validates the current ETag response header against If-Match and If-None-Match headers, and raises "304 Not Modified" or "412 Precondition Failed" as needed. If {{{tools.etags.autotags}}} is True, an ETag response-header value will be provided from an MD5 hash of the response body (unless some other code has already provided an ETag header). If False (the default), the ETag will not be automatic. ==== tools.expires ==== A tool for influencing cache mechanisms using the 'Expires' header. {{{tools.expires.secs}}} must be either an int or a datetime.timedelta, and indicates the number of seconds between response.time and when the response should expire. The 'Expires' header will be set to (response.time + secs). If zero (the default), the following "cache prevention" headers are also set: {{{ 'Pragma': 'no-cache' 'Cache-Control': 'no-cache' }}} If {{{tools.expires.force}}} is False (the default), the following headers are checked: 'Etag', 'Last-Modified', 'Age', 'Expires'. If any are already present, none of the above response headers are set. ==== tools.basic_auth ==== A tool for doing basic authentication. It takes a "realm" setting (a string) and a "users" dict of {username: password} pairs (or a callable which returns that dict). If authentication fails, 401 Unauthorized is raised. ==== tools.digest_auth ==== A tool for doing Digest authentication (RFC 2617). It takes a "realm" setting (a string) and a "users" dict of {username: password} pairs (or a callable which returns that dict). If authentication fails, 401 Unauthorized is raised. ==== tools.trailing_slash ==== A tool that lets you control whether URL's with a missing or extra trailing slash should raise HTTPRedirect. It's on by default, with these settings: {{{ tools.trailing_slash.on = True tools.trailing_slash.missing = True tools.trailing_slash.extra = False }}} That is, if a trailing slash is missing for an index handler, HTTPRedirect is raised. But if a non-index handler has an extra slash, it's not redirected by default. ==== tools.accept ==== A tool for verifying that the client is willing to accept the Content-Type of the response. {{{tools.accept.media}}}, if provided, should be the Content-Type value (as a string) or values (as a list or tuple of strings) which the current request can emit. The client's acceptable media ranges (as declared in the Accept request header) will be matched in order to these Content-Type values; the first such string is returned. That is, the return value will always be one of the strings provided in the 'media' arg (or None if 'media' is None). The return value doesn't mean anything when used as a Tool, but you can call {{{tools.accept.callable(media)}}} directly to dispatch based on the client's preferred Content-Type: {{{ def select(self): mtype = tools.accept.callable(['text/html', 'text/plain']) if mtype == 'text/html': return "