mvc =========================================================== .. module:: smisk.mvc .. versionadded:: 1.1.0 Model-View-Controller-based sub-framework. This module and it's sub-modules constitutes the most common way of using Smisk, mapping URLs to the *control tree* – an actual class tree, growing from :class:`smisk.mvc.control.Controller`. **Key members** * :func:`main()` is a helper function which facilitates the most common use case: Setting up an application, configuring it, running it and logging uncaught exceptions. * The :class:`Application` class is the type of application. Normally you do not subclass :class:`Application`, but rather configure it and its different components. * The :mod:`smisk.mvc.console` module is an interactive console, aiding in development and management. * The :mod:`smisk.mvc.control` module contains many useful functions for inspecting the *control tree*. **Example**:: from smisk.mvc import * class root(Controller): def __call__(self, *args, **params): return {'message': 'Hello World!'} main() Configuration parameters ------------------------------------------------- .. describe:: smisk.mvc.response.serializer Name of a default response serializer. The value should be the name of a serializer *extension* (the value should be a valid key in the :attr:`smisk.serialization.serializers.extensions ` dictionary). If not set, the :attr:`Response.fallback_serializer` will be used for clients not requesting any particular content type. :default: :samp:`None` :type: str .. describe:: smisk.mvc.etag Enables adding an ETag header to all buffered responses. The value needs to be either the name of a valid hash function in the ``hashlib`` module (i.e. "md5"), or a something respoding in the same way as the hash functions in hashlib. i.e. need to return a hexadecimal string rep when: .. code-block:: python h = etag(data) h.update(more_data) etag_value = h.hexdigest() Enabling this is generally *not recommended* as it introduces a small to moderate performance hit, because a checksum need to be calculated for each response, and because of the nature of the data Smisk does not know about all stakes in a transaction, thus constructing a valid ETag might somethimes be impossible. :default: :samp:`None` :type: str .. describe:: smisk.mvc.strict_tcn Controls whether or not this application is strict about transparent content negotiation. For example, if this is ``True`` and a client accepts a character encoding which is not available, a 206 Not Acceptable response is sent. If the value would have been ``False``, the response would be sent using a data encoder default character set. This affects ``Accept*`` request headers which demands can not be met. As HTTP 1.1 (RFC 2616) allows fallback to defaults (though not recommended) we provide the option of turning off the 206 response. Setting this to false will cause Smisk to encode text responses using a best-guess character encoding. :default: :samp:`True` :type: bool Leaf filters ------------------------------------------------- A *leaf filter* is basically code that is run prior to and after calling a leaf on the controller tree, allowing manipulation of input, output and the states of things. Let's have a look at an example filter which gives the ability to say "this leaf requires a logged in user":: from smisk.mvc.decorators import leaf_filter @leaf_filter def require_login(leaf, *va, **kw): if not request.session or not request.session.get('userid'): redirect_to(root.login) return leaf(*va, **kw) This filter requires a valid session and the key ``userid`` to be set in the session (in this case things are simplified for educational reasons --- in reality we would probably check the type of the session object also). And here is how you would use the filter above:: class root(Controller): @require_login def protected(self): return {'secret launch codes': [u'abc', 123]} def login(self): pass # (actual auth code here) # curl localhost:8080/protected # 302 Found -> http://localhost:8080/login Note that the :samp:`from smisk.mvc.decorators import leaf_filter` is not needed if you are importing everything from :mod:`smisk.mvc` since ``leaf_filter`` is also exported from :mod:`smisk.mvc`. For more information and examples see :mod:`smisk.mvc.filters` and :func:`smisk.mvc.decorators.leaf_filter`. Functions ------------------------------------------------- .. function:: environment() -> string Name of the current environment. Returns the value of ``SMISK_ENVIRONMENT`` environment value and defaults to "``stable``". .. function:: main(application=None, appdir=None, bind=None, forks=None, handle_errors=True, cli=True, *args, **kwargs) -> object Helper for setting up and running an application. This function is not a true function, but rather an instance of :class:`Main`. See documentation of :func:`smisk.util.main.main()` for more information. .. function:: run(bind=None, application=None, forks=None, handle_errors=False) -> object Helper for running an application. Note that because of the nature of ``libfcgi`` an application can not be started, stopped and then started again. That said, you can only start your application once per process. (Details: OS_ShutdownPending sets a process-wide flag causing any call to accept to bail out) **Environment variables** .. envvar:: SMISK_BIND If set and not empty, a call to ``smisk.core.bind`` will occur, passing the value to bind, effectively starting a stand-alone process. :param bind: Bind to address (and port). Note that this overrides ``SMISK_BIND``. :param application: An application type or instance. :param forks: Number of child processes to spawn. :param handle_errors: Handle any errors by wrapping calls in :func:`smisk.util.main.handle_errors_wrapper()` :Returns: Anything returned by :meth:`Application.run()` :rtype: object :See: :func:`setup()`, :func:`main()` .. function:: setup(application=None, appdir=None, *args, **kwargs) -> Application :deprecated: You should use :meth:`smisk.util.main.Main.setup()` instead. Classes ------------------------------------------------- .. class:: Main(smisk.util.main.Main) :see: Documentation of :class:`smisk.util.main.Main` .. class:: Application(smisk.core.Application) MVC application :see: :class:`smisk.core.Application` .. attribute:: templates Templates handler. If this evaluates to false, templates are disabled. :see: `__init__()` :type: Templates .. attribute:: routes Router. :type: Router .. attribute:: serializer Used during runtime. Here because we want to use it in error() :type: Serializer .. attribute:: destination Used during runtime. Available in actions, serializers and templates. :type: routing.Destination .. attribute:: template Used during runtime. :type: mako.template.Template .. attribute:: unicode_errors How to handle unicode conversions. Possible values: ``strict, ignore, replace, xmlcharrefreplace, backslashreplace`` :type: string .. attribute:: autoclear_model_session Automatically clear the model session cache before each request is handled. You should not disable this unless any of the following statements apply: * You do not use smisk.model or SQLAlchemy at all (no database). * You run only one process (safe to cache). * Your application only reads a set of data that never changes. * Your model does not involve entity relations. Disabling this means entities stay in the local memory cache between sessions. (Relations and their content etc are cached, but not the actual data in the entities) Consider this model:: class Department(Entity): people = OneToMany('Person') class Person(Entity): department = ManyToOne('Department') Now, imagine we have two processes running; process A and process B. Process A gets a request to add a new person to an existing department. Afterwards, process B is requested to list all people in the same department. Now, if autoclear_model_session where set to False, the previously added person would no show up in the listing of people in the department which process B queried. This is because which people is "contained within" what department has been implicitly cached by SQLAlchemy. :type: ``bool`` :default: ``True`` .. attribute:: leaf_filters Application-global leaf filters, applied to all leafs in the controller tree. The filter are applied in the order they appear in this list. .. code-block:: python from smisk.mvc import * @leaf_filter def ensure_page_title(leaf, *va, **kw): rsp = leaf(*va, **kw) rsp.setdefault('title', u'The Title') return rsp class App(Application): def application_will_start(self): self.leaf_filters.append(ensure_page_title) return Application.application_will_start(self) :type: ``list`` .. versionadded:: 1.1.3 .. method:: __init__(router=None, templates=None, show_traceback=False, *args, **kwargs) TODO .. method:: run() TODO .. method:: application_did_stop() TODO .. method:: application_will_start() TODO .. method:: apply_leaf_restrictions() Applies any restrictions set by the current action. .. method:: autoload_configuration(config_mod_name='config') Automatically load configuration from application sub-module named *config_mod_name*. :param config_mod_name: Name of the application configuration sub-module .. method:: call_action(args, params) -> dict Resolves and calls the appropriate action, passing args and params to it. :Returns: Response structure or None .. method:: encode_response(rsp) -> str Encode the response object *rsp* :param rsp: Must be a string, a dict or None :Returns: *rsp* encoded as a series of bytes :See: :meth:`send_response()` .. method:: error(typ, val, tb) Handle an error and produce an appropriate response. :param typ: Exception type :param val: Exception value :param tb: Traceback .. method:: parse_request() Parses the request, involving appropriate serializer if needed. :Returns: (list arguments, dict parameters) :rtype: tuple .. method:: response_serializer(no_http_exc=False) -> Serializer 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()*. :Returns: The most appropriate serializer :rtype: :class:`smisk.serialization.Serializer` .. method:: send_response(rsp) Send the response to the current client, finalizing the current HTTP transaction. :param rsp: Response body :See: :meth:`encode_response()` .. method:: service() Manages the life of a HTTP transaction. **Summary** #. Reset current shared *request*, *response* and *self*. #. Aquire response serializer from *response_serializer()*. #. Try looking at ``response.format``, if set. #. Try looking at any explicitly set ``Content-Type`` in *response*. #. Try looking at request filename extension, derived from ``request.url.path``. #. Try looking at media types in request ``Accept`` header. #. Use *Response.fallback_serializer* or raise *http.MultipleChoices*, depending on value of ``no_http_exc`` method argument. #. Parse request using *parse_request()*. #. Update request parameters with any query string parameters. #. Register for the client acceptable character encodings by looking at any ``Accept-Charset`` header. #. Update request parameters and arguments with any POST body, possibly by using a serializer to decode the request body. #. Resolve *controller leaf* by calling *routes*. #. Apply any route filters. #. Resolve *leaf* on the *controller tree*. #. Apply any format restrictions defined by the current *controller leaf*. #. Append ``Vary`` header to response, with the value ``negotiate, accept, accept-charset``. #. Call the *controller leaf* which will return a *response object*. #. Applies any "before" filters. #. Calls the *controller leaf* #. Applies any "after" filters. #. Flush the model/database session, if started or modified, committing any modifications. #. If a templates are used, and the current *controller leaf* is associated with a template – aquire the template object for later use in *encode_response()*. #. Encode the *response object* using *encode_response()*, resulting in a string of opaque bytes which constitutes the response body, or payload. #. If the *response object* is ``None``, either render the current template (if any) without any parameters or fall back to *encode_response()* returning ``None``. #. If the *response object* is a string, encode it if needed and simply return the string, resulting in the input to *encode_response()* compares equally to the output. #. If a template object has been deduced from previous algorithms, serialize the *response object* using that template object. #. Otherwise, if no template is used, serialize the *response object* using the previously deduced response serializer. #. Complete (or commit) the current HTTP transaction by sending the response by calling *send_response()*. #. Set ``Content-Length`` and other response headers, unless the response has already begun. #. Calculate *ETag* if enabled through the *etag* attribute. #. Write the response body. .. method:: setup() Setup application state. Can be called multiple times and is automatically called, just after calling *autoload_configuration()*, by *setup()* and *application_will_start()*. **Outline** 1. If *etag* is enabled and is a string, replaces *etag* with the named hashing algorithm from hashlib. 2. If *templates* are enabled but ``templates.directories`` evaluates to false, set ``templates.directories`` to the default ``[SMISK_APP_DIR + "templates"]``. 3. Make sure *Response.fallback_serializer* has a valid serializer as it's value. 4. Setup any models. .. method:: template_for_path(path) -> Template Aquire template URI for *path*. :param path: A relative path :rtype: :class:`template.Template` .. method:: template_for_uri(uri) -> Template Aquire template for *uri*. :param uri: Path :rtype: :class:`template.Template` .. method:: template_uri_for_path(path) -> string Get template URI for *path*. :param path: A relative path :rtype: string .. ------------------------------------------------------------------------------------------ .. class:: Request(smisk.core.Request) .. attribute:: serializer Serializer used for decoding request payload. Available during a HTTP transaction. :type: smisk.serialization.Serializer .. attribute:: cn_url .. versionadded:: 1.1.3 URL with any filename extension removed, for use with Content Negotiation. If the request did not contain a filename extension, this is the same object as :attr:`smisk.core.Request.url`. :type: :class:`smisk.core.URL` .. ------------------------------------------------------------------------------------------ .. class:: Response(smisk.core.Response) .. attribute:: format Any value which is a valid key of the :attr:`smisk.serializers.extensions` dict. :type: string .. attribute:: serializer Serializer to use for encoding the response. The class attribute value serves as the application default serializer, used in cases where we need to encode the response, but the client is not specific about which serializer to use. If None, strict `TCN `__ applies. :see: :attr:`fallback_serializer` :type: :attr:`smisk.serialization.Serializer` .. attribute:: fallback_serializer Last-resort serializer, used for error responses and etc. If None when `Application.application_will_start` is called, this will be set to a HTML-serializer, and if none is available, simply the first registered serializer will be used. The class property is the only one used, the instance property has no meaning and no effect, thus if you want to modify this during runtime, you should do this ``Response.fallback_serializer = my_serializer`` instead of this ``app.response.fallback_serializer = my_serializer``. :type: :attr:`smisk.serialization.Serializer` .. attribute:: charset Character encoding used to encode the response body. The value of ``Response.charset`` (class property value) serves as the application default charset. :type: string .. method:: send_file(path) Send a file to the client by using the host server sendfile-header technique. Automatically sets :samp:`Content-Type` header, using `mimetypes.guess_type `_ Calling this method implicitly commits the current HTTP transaction, sending the response immedately. :param path: If this is a relative path, the host server defines the behaviour. :type path: string :raises EnvironmentError: If smisk does not know how to perform *sendfile* through the current host server. :raises EnvironmentError: If response has already started. :raises IOError: .. method:: adjust_status(has_content) .. versionadded:: 1.1.1 Make sure appropriate status is set for the response. .. method:: remove_header(name) .. versionadded:: 1.1.1 Remove any instance of header named or prefixed *name*. .. method:: remove_headers(*names) .. versionadded:: 1.1.1 Remove any instance of headers named or prefixed *\*names*. .. method:: replace_header(header) .. versionadded:: 1.1.1 Replace any instances of the same header type with *header*. >>> response.headers [] >>> response.headers.append('Content-Type: text/humbug') >>> response.headers.append('X-Name: Fuffens') >>> response.headers ['Content-Type: text/humbug', 'X-Name: Fuffens'] >>> response.replace_header('Content-type: text/plain') >>> response.headers ['X-Name: Fuffens', 'Content-type: text/plain'] .. ------------------------------------------------------------------------------------------ .. class:: Main(smisk.util.main.Main) .. attribute:: default_app_type Defaults to :class:`Application` .. method:: setup(application=None, appdir=None, *args, **kwargs) TODO Modules ------------------------------------------------- .. toctree:: :maxdepth: 1 smisk.mvc.console smisk.mvc.control smisk.mvc.decorators smisk.mvc.filters smisk.mvc.helpers smisk.mvc.http smisk.mvc.model smisk.mvc.routing smisk.mvc.template