{ "info": { "author": "Martijn Faassen", "author_email": "faassen@startifact.com", "bugtrack_url": null, "classifiers": [], "description": "hurry.resource\n**************\n\n.. Important::\n hurry.resource has been superseded by the\n `Fanstatic `_ project!\n\nIntroduction\n============\n\nResources are files that are used as resources in the display of a web\npage, such as CSS files, Javascript files and images. Resources\npackaged together in a directory to be published as such are called a\nresource *library*.\n\nWhen a resource is included in the ``head`` section of a HTML page, we\ncall this a resource *inclusion*. An inclusion is of a particular\nresource in a particular library. There are two forms of this kind of\ninclusion in HTML: javascript is included using the ``script`` tag,\nand CSS (and KSS) are included using a ``link`` tag.\n\nInclusions may depend on other inclusions. A javascript resource may\nfor instance be built on top of another javascript resource. This\nmeans both of them should be loaded when the page displays.\n\nPage components may actually require a certain inclusion in order to\nbe functional. A widget may for instance expect a particular\nJavascript library to loaded. We call this an *inclusion requirement*\nof the component.\n\n``hurry.resource`` provides a simple API to specify resource\nlibraries, inclusion and inclusion requirements.\n\nA resource library\n==================\n\nWe define a library ``foo``. It takes two arguments, the name of the\nlibrary as it should be published under in a URL and uniquely identify\nit, and a path to the root of the resources (rootpath) that this\nlibrary publishes::\n\n >>> from hurry.resource import Library\n >>> foo = Library('foo', 'dummy')\n\nThe full path to the directory with the resources is reconstructed from\nthe package that the Library is defined in::\n\n >>> foo.path #doctest: +ELLIPSIS\n '.../hurry.resource/src/hurry/resource/dummy'\n\nEntry points\n============\n\nLibraries can be exposed for registration by whatever web framework\nthat hurry.resource is integrated with. This web framework can then\nexpose the library path on a URL somewhere. This is done using the\n``hurry.resource.libraries`` entry point. To register ``Library``\ninstances ``foo`` and ``bar`` in your package as entry points include\na section like this in your ``setup.py``::\n\n entry_points={\n 'hurry.resource.libraries': [\n 'foo = mypackage.foomodule:foo',\n 'bar = mypackage.barmodule:bar',\n ],\n }\n\nThere is an API to help you obtain all registered libraries::\n\n >>> from hurry.resource import libraries\n\nNothing is registered however::\n\n >>> list(libraries())\n []\n\nIt would be nice to now have some tests that see whether entry points\nactually get picked up so, but that would require an involved test\nsetup that we find difficult to construct.\n\nInclusion\n=========\n\nWe now create an inclusion of a particular resource in a library. This\ninclusion needs ``a.js`` from ``library`` and ``b.js`` as well::\n\n >>> from hurry.resource import ResourceInclusion\n >>> x1 = ResourceInclusion(foo, 'a.js')\n >>> x2 = ResourceInclusion(foo, 'b.css')\n\nLet's now make an inclusion ``y1`` that depends on ``x1`` and ``x2``::\n\n >>> y1 = ResourceInclusion(foo, 'c.js', depends=[x1, x2])\n\nInclusion requirements\n======================\n\nWhen rendering a web page we want to require the inclusion of a\nresource anywhere within the request handling process. We might for\ninstance have a widget that takes care of rendering its own HTML but\nalso needs a resource to be included in the page header.\n\nWe have a special object that represents the needed inclusions during\na certain request cycle::\n\n >>> from hurry.resource import NeededInclusions\n >>> needed = NeededInclusions()\n\nWe state that a resource is needed by calling the ``need`` method on\nthis object::\n\n >>> needed.need(y1)\n\nLet's now see what resources are needed by this inclusion::\n\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nAs you can see, ``css`` resources are sorted before ``js`` resources.\n\nGrouping resources\n==================\n\nIt is also possible to define a group that doesn't get rendered\nitself, but groups other resources together that should be rendered::\n\n >>> from hurry.resource import GroupInclusion\n >>> group = GroupInclusion([x1, x2])\n\nWhen we need a group, we'll get all inclusions referenced in it::\n\n >>> needed = NeededInclusions()\n >>> needed.need(group)\n >>> group.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ]\n\nA group can also be depended on; it won't show up in the list of\ninclusions directly::\n\n >>> more_stuff = ResourceInclusion(foo, 'more_stuff.js', depends=[group])\n >>> more_stuff.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nA convenience spelling\n======================\n\nWhen specifying that we want a resource inclusion to be rendered, we\nnow need access to the current ``NeededInclusions`` object and the\nresource inclusion itself.\n\nLet's introduce a more convenient spelling of needs now::\n\n y1.need()\n\nWe can require a resource without reference to the needed inclusions\nobject directly as there is typically only a single set of needed\ninclusions that is generated during the rendering of a page.\n\nSo let's try out this spelling to see it fail::\n\n >>> y1.need()\n Traceback (most recent call last):\n ...\n NotImplementedError: need to implement plugin.get_current_needed_inclusions()\n\nWe get an error because we haven't configured the framework yet. The\nsystem says we need to implement\n``plugin.get_current_needed_inclusions()`` first. This is a method\nthat we need to implement so we can tell the system how to obtain the\ncurrent ``NeededInclusions`` object.\n\nThis needed inclusions should be maintained on an object that is going\nto be present throughout the request/response cycle that generates the\nweb page that has the inclusions on them. One place where\nwe can maintain the needed inclusions is the request object\nitself, if we indeed have global access to it. Alternatively you could\nstore the currently needed inclusions in a thread local variable.\n\nLet's introduce a simple request object (your mileage may vary in your\nown web framework)::\n\n >>> class Request(object):\n ... def __init__(self):\n ... self.needed = NeededInclusions()\n\nWe now make a request, imitating what happens during a typical\nrequest/response cycle in a web framework::\n\n >>> request = Request()\n\nWe now define a plugin class that implements the\n``get_current_needed_inclusions()`` method by obtaining it from the\nrequest::\n\n >>> class Plugin(object):\n ... def get_current_needed_inclusions(self):\n ... return request.needed\n\nWe now need to register this plugin with the framework::\n\n >>> from hurry.resource import register_plugin\n >>> register_plugin(Plugin())\n\nThere is an API to retrieve the current needed inclusions, so let's\ncheck which resources our request needs currently::\n\n >>> from hurry.resource import get_current_needed_inclusions\n >>> get_current_needed_inclusions().inclusions()\n []\n\nNothing yet. We now make ``y1`` needed using our simplified spelling::\n\n >>> y1.need()\n\nThe resource inclusion will now indeed be needed::\n\n >>> get_current_needed_inclusions().inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nLet's go back to the original spelling of ``needed.need(y)``\nnow. While this is a bit more cumbersome to use in application code, it is\neasier to read for the purposes of this document.\n\nA note on optimization\n======================\n\nThere are various optimizations for resource inclusion that\n``hurry.resource`` supports. Because some optimizations can make\ndebugging more difficult, the optimizations are disabled by default.\n\nWe will summarize the optimization features here and tell you how to\nenable them. Later sections below go into more details.\n\n* minified resources. Resources can specify minified versions using\n the mode system. You can use ``hurry.resource.mode('minified')``\n somewhere in the request handling of your application. This will\n make sure that resources included on your page are supplied as\n minified versions, if these are available.\n\n* rolling up of resources. Resource libraries can specify rollup\n resources that combine multiple resources into one. This reduces the\n amount of server requests to be made by the web browser, and can\n help with caching. To enable rolling up, call\n ``hurry.resource.rollup`` somewhere in your request handling.\n\n* javascript inclusions at the bottom of the web page. If your\n framework integration uses the special ``render_topbottom`` method,\n you can enable the inclusion of javascript files at the bottom by\n calling ``hurry.resource.bottom()``. This will only include\n resources at the bottom that have explicitly declared themselves to\n be *bottom-safe*. You can declare a resource bottom safe by passing\n ``bottom=True`` when constructing a ``ResourceInclusion``. If you\n want to force all javascript to be including at the bottom of the\n page by default, you can call ``hurry.resource.bottom(force=True)``.\n\nTo find out more about these and other optimizations, please read this\n`best practices article`_ that describes some common optimizations to\nspeed up page load times.\n\n.. _`best practices article`: http://developer.yahoo.com/performance/rules.html\n\nMultiple requirements\n=====================\n\nIn this section, we will show what happens in various scenarios where\nwe requiring multiple ``ResourceInclusion`` objects.\n\nWe create a new set of needed inclusions::\n\n >>> needed = NeededInclusions()\n >>> needed.inclusions()\n []\n\nWe need ``y1`` again::\n\n >>> needed.need(y1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nNeeding the same inclusion twice won't make any difference for the\nresources needed. So when we need ``y1`` again, we see no difference\nin the needed resources::\n\n >>> needed.need(y1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nNeeding ``x1`` or ``x2`` won't make any difference either, as ``y1``\nalready required ``x1`` and ``x2``::\n\n >>> needed.need(x1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n >>> needed.need(x2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nLet's do it in reverse, and require the ``x1`` and ``x2`` resources\nbefore we need those in ``y1``. Again this makes no difference::\n\n >>> needed = NeededInclusions()\n >>> needed.need(x1)\n >>> needed.need(x2)\n >>> needed.need(y1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nLet's try it with more complicated dependency structures now::\n\n >>> needed = NeededInclusions()\n >>> a1 = ResourceInclusion(foo, 'a1.js')\n >>> a2 = ResourceInclusion(foo, 'a2.js', depends=[a1])\n >>> a3 = ResourceInclusion(foo, 'a3.js', depends=[a2])\n >>> a4 = ResourceInclusion(foo, 'a4.js', depends=[a1])\n >>> needed.need(a3)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n >>> needed.need(a4)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ,\n ]\n\nIf we reverse the requirements for ``a4`` and ``a3``, we get the following\ninclusion structure, based on the order in which need was expressed::\n\n >>> needed = NeededInclusions()\n >>> needed.need(a4)\n >>> needed.need(a3)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ,\n ]\n\nLet's look at the order in which resources are listed when we need\nsomething that ends up depending on everything::\n\n >>> a5 = ResourceInclusion(foo, 'a5.js', depends=[a4, a3])\n >>> needed = NeededInclusions()\n >>> needed.need(a5)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ,\n ,\n ]\n\nWhen we introduce the extra inclusion of ``a3`` earlier on, we still\nget a valid list of inclusions given the dependency structure, even\nthough the sorting order is different::\n\n >>> needed = NeededInclusions()\n >>> needed.need(a3)\n >>> needed.need(a5)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ,\n ,\n ]\n\nModes\n=====\n\nA resource can optionally exist in several modes, such as for instance\na minified and a debug version. Let's define a resource that exists in\ntwo modes (a main one and a debug alternative)::\n\n >>> k1 = ResourceInclusion(foo, 'k.js', debug='k-debug.js')\n\nLet's need this resource::\n\n >>> needed = NeededInclusions()\n >>> needed.need(k1)\n\nBy default, we get ``k.js``::\n\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nWe can however also get the resource for mode ``debug`` and get\n``k-debug.js``::\n\n >>> needed.mode('debug')\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nModes can also be specified fully with a resource inclusion, which allows\nyou to specify a different ``library`` argumnent::\n\n >>> k2 = ResourceInclusion(foo, 'k2.js',\n ... debug=ResourceInclusion(foo, 'k2-debug.js'))\n >>> needed = NeededInclusions()\n >>> needed.need(k2)\n\nBy default we get ``k2.js``::\n\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nWe can however also get the resource for mode ``debug`` and get\n``k2-debug.js``::\n\n >>> needed.mode('debug')\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nNote that modes are assumed to be identical in dependency structure;\nthey functionally should do the same.\n\nIf you request a mode and a resource doesn't support it, it will\nreturn its default resource instead::\n\n >>> needed = NeededInclusions()\n >>> needed.mode('minified')\n >>> needed.need(k1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\n``hurry.resource`` suggests resource libraries follow the following\nconventions for modes:\n\n * default - the original source text, non-minified, and without any\n special extra debugging functionality.\n\n * debug - an optional version of the source text that offers more\n debugging support, such as logging.\n\n * minified - an optional minified (compressed) form of the resource.\n\nIn the case of rollups, several resources can be consolidated into one\nlarger one for optimization purposes. A library might only offer a\nminified version of a rollup resource; if the developer wants to\ndebug, it is expected he uses the resources in non-rolledup format.\nIn this case you should make a resource inclusion where the default\nmode is equal to the minified mode, like this::\n\n >>> example = ResourceInclusion(foo, 'k.js', minified='k.js')\n\nIf the developer wants to debug, he will need to disable rolling up\n(by calling ``hurry.resource.rollup(disable=True)``, or by simply\nnever calling ``hurry.resource.rollup()`` in the request cycle).\n\nMode convenience\n================\n\nLike for ``need``, there is also a convenience spelling for\n``mode``. It uses ``ICurrentNeededInclusions``, which we've already\nset up to look at the ``request.needed`` variable. Let's set up\na new request::\n\n >>> request = Request()\n\nLet's set up a resource and need it::\n\n >>> l1 = ResourceInclusion(foo, 'l1.js', debug='l1-debug.js')\n >>> l1.need()\n\nLet's look at the resources needed by default::\n\n >>> c = get_current_needed_inclusions()\n >>> c.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nLet's now change the mode using the convenience\n``hurry.resource.mode`` spelling::\n\n >>> from hurry.resource import mode\n >>> mode('debug')\n\nWhen we request the resources now, we get them in the ``debug`` mode::\n\n >>> c.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\n\"Rollups\"\n=========\n\nFor performance reasons it's often useful to consolidate multiple\nresources into a single, larger resource, a so-called\n\"rollup\". Multiple javascript files could for instance be offered in a\nsingle, larger one. These consolidations can be specified as a\nresource::\n\n >>> b1 = ResourceInclusion(foo, 'b1.js')\n >>> b2 = ResourceInclusion(foo, 'b2.js')\n >>> giant = ResourceInclusion(foo, 'giant.js', supersedes=[b1, b2])\n\nRolling up of resources is not enabled by default, as sometimes a\nlibrary only offers these rollups in minified form, and automatically\nrolling up would not be nice during debugging. It's therefore a\nperformance feature you can enable.\n\nWithout rollups enabled nothing special happens::\n\n >>> needed = NeededInclusions()\n >>> needed.need(b1)\n >>> needed.need(b2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [, ]\n\nLet's enable rollups::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n\nThe convenience spelling to enable rollups during request handling\nlooks like this::\n\n hurry.resource.rollup()\n\nIf we now find multiple resources that are also part of a\nconsolidation, the system automatically collapses them::\n\n >>> needed.need(b1)\n >>> needed.need(b2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nThe system will by default only consolidate exactly. That is, if only a single\nresource out of two is present, the consolidation will not be triggered::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(b1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nLet's look at this with a larger consolidation of 3 resources::\n\n >>> c1 = ResourceInclusion(foo, 'c1.css')\n >>> c2 = ResourceInclusion(foo, 'c2.css')\n >>> c3 = ResourceInclusion(foo, 'c3.css')\n >>> giantc = ResourceInclusion(foo, 'giantc.css', supersedes=[c1, c2, c3])\n\nIt will not roll up one resource::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(c1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nNeither will it roll up two resources::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(c1)\n >>> needed.need(c2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ]\n\nIt will however roll up three resources::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(c1)\n >>> needed.need(c2)\n >>> needed.need(c3)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nThe default behavior is to play it safe: we cannot be certain that we\ndo not include too much if we were to include ``giantc.css`` if only\nc1 and c2 are required. This is especially important with CSS\nlibraries: if only ``c1.css`` and ``c2.css`` are to be included in a\npage, including ``giantc.css`` is not appropriate as that also\nincludes the content of ``c3.css``, which might override and extend\nthe behavior of ``c1.css`` and ``c2.css``.\n\nThe situation is sometimes different with Javascript libraries, which\ncan be written in such a way that a larger rollup will just include\nmore functions, but will not actually affect page behavior. If we have\na rollup resource that we don't mind kicking in even if part of the\nrequirements have been met, we can indicate this::\n\n >>> d1 = ResourceInclusion(foo, 'd1.js')\n >>> d2 = ResourceInclusion(foo, 'd2.js')\n >>> d3 = ResourceInclusion(foo, 'd3.js')\n >>> giantd = ResourceInclusion(foo, 'giantd.js', supersedes=[d1, d2, d3],\n ... eager_superseder=True)\n\nWe will see ``giantd.js`` kick in even if we only require ``d1`` and\n``d2``::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(d1)\n >>> needed.need(d2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nIn fact even if we only need a single resource the eager superseder will\nshow up instead::\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(d1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nIf there are two potential eager superseders, the biggest one will\nbe taken::\n\n >>> d4 = ResourceInclusion(foo, 'd4.js')\n >>> giantd_bigger = ResourceInclusion(foo, 'giantd-bigger.js',\n ... supersedes=[d1, d2, d3, d4], eager_superseder=True)\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(d1)\n >>> needed.need(d2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nIf there is a potential non-eager superseder and an eager one, the eager one\nwill be taken::\n\n >>> giantd_noneager = ResourceInclusion(foo, 'giantd-noneager.js',\n ... supersedes=[d1, d2, d3, d4])\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(d1)\n >>> needed.need(d2)\n >>> needed.need(d3)\n >>> needed.need(d4)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nA resource can be part of multiple rollups. In this case the rollup\nthat rolls up the most resources is used. So, if there are two\npotential non-eager superseders, the one that rolls up the most\nresources will be used::\n\n >>> e1 = ResourceInclusion(foo, 'e1.js')\n >>> e2 = ResourceInclusion(foo, 'e2.js')\n >>> e3 = ResourceInclusion(foo, 'e3.js')\n >>> giante_two = ResourceInclusion(foo, 'giante-two.js',\n ... supersedes=[e1, e2])\n >>> giante_three = ResourceInclusion(foo, 'giante-three.js',\n ... supersedes=[e1, e2, e3])\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(e1)\n >>> needed.need(e2)\n >>> needed.need(e3)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nConsolidation also works with modes::\n\n >>> f1 = ResourceInclusion(foo, 'f1.js', debug='f1-debug.js')\n >>> f2 = ResourceInclusion(foo, 'f2.js', debug='f2-debug.js')\n >>> giantf = ResourceInclusion(foo, 'giantf.js', supersedes=[f1, f2],\n ... debug='giantf-debug.js')\n\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(f1)\n >>> needed.need(f2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n >>> needed.mode('debug')\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nWhat if the rolled up resources have no mode but the superseding resource\ndoes? In this case the superseding resource's mode has no meaning, so\nmodes have no effect::\n\n >>> g1 = ResourceInclusion(foo, 'g1.js')\n >>> g2 = ResourceInclusion(foo, 'g2.js')\n >>> giantg = ResourceInclusion(foo, 'giantg.js', supersedes=[g1, g2],\n ... debug='giantg-debug.js')\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(g1)\n >>> needed.need(g2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n >>> needed.mode('debug')\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nWhat if the rolled up resources have a mode but the superseding resource\ndoes not? Let's look at that scenario::\n\n >>> h1 = ResourceInclusion(foo, 'h1.js', debug='h1-debug.js')\n >>> h2 = ResourceInclusion(foo, 'h2.js', debug='h2-debug.js')\n >>> gianth = ResourceInclusion(foo, 'gianth.js', supersedes=[h1, h2])\n >>> needed = NeededInclusions()\n >>> needed.rollup()\n >>> needed.need(h1)\n >>> needed.need(h2)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n []\n\nSince there is no superseder for the debug mode, we will get the two\nresources, not rolled up::\n\n >>> needed.mode('debug')\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ]\n\nRendering resources\n===================\n\nLet's define some needed resource inclusions::\n\n >>> needed = NeededInclusions()\n >>> needed.need(y1)\n >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ]\n\nNow let's try to render these inclusions::\n\n >>> print needed.render()\n Traceback (most recent call last):\n ...\n AttributeError: 'Plugin' object has no attribute 'get_library_url'\n\nThat didn't work. In order to render an inclusion, we need to tell\n``hurry.resource`` how to get the URL for a resource inclusion. We\nalready know the relative URL, so we need to specify how to get a URL\nto the library itself that the relative URL can be added to.\n\nWe'll extend the existing plugin that already knows how to obtain the\ncurrent needed inclusions. For the purposes of this document, we\ndefine a function that renders resources as some static URL on\nlocalhost::\n\n >>> class NewPlugin(Plugin):\n ... def get_library_url(self, library):\n ... return 'http://localhost/static/%s' % library.name\n\nLet's register the plugin::\n\n >>> register_plugin(NewPlugin())\n\nRendering the inclusions now will result in the HTML fragments we\nneed to include on the top of our page (just under the ```` tag\nfor instance)::\n\n >>> print needed.render()\n \n \n \n\nLet's set this a currently needed inclusions::\n\n >>> request.needed = needed\n\nThere is a function available as well for rendering the resources for\nthe currently needed inclusion::\n\n >>> from hurry import resource\n >>> print resource.render()\n \n \n \n\nInserting resources in HTML\n===========================\n\nWhen you have the HTML it can be convenient to have a way to insert\nresources directly into some HTML.\n\nThe insertion system assumes a HTML text that has a ```` tag in it::\n\n >>> html = \"something more\"\n\nTo insert the resources directly in HTML we can use ``render_into_html``\non ``needed``::\n\n >>> print needed.render_into_html(html)\n \n \n \n \n something more\n\nThe top-level convenience function does this for the currently needed\nresources::\n\n >>> print resource.render_into_html(html)\n \n \n \n \n something more\n\nSee below for a way to insert into HTML when bottom fragments are\ninvolved.\n\nTop and bottom fragments\n========================\n\nIt's also possible to render the resource inclusions into two\nfragments, some to be included just after the ```` tag, but some\nto be included at the very bottom of the HTML page, just before the\n```` tag. This is useful as it can `speed up page load times`_.\n\n.. _`speed up page load times`: http://developer.yahoo.com/performance/rules.html\n\nLet's look at the same resources, now rendered separately into ``top``\nand ``bottom`` fragments::\n\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n \n \n >>> print bottom\n \n\nThere is effectively no change; all the resources are still on the\ntop. We can enable bottom rendering by calling the ``bottom`` method before\nwe render::\n\n >>> needed.bottom()\n\nSince none of the resources indicated it was safe to render them at\nthe bottom, even this explicit call will not result in any changes::\n\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n \n \n >>> print bottom\n \n\n``bottom(force=True)`` will however force all javascript inclusions to be\nrendered in the bottom fragment::\n\n >>> needed.bottom(force=True)\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n >>> print bottom\n \n \n\nLet's now introduce a javascript resource that says it is safe to be\nincluded on the bottom::\n\n >>> y2 = ResourceInclusion(foo, 'y2.js', bottom=True)\n\nWhen we start over without ``bottom`` enabled, we get this resource\nshow up in the top fragment after all::\n\n >>> needed = NeededInclusions()\n >>> needed.need(y1)\n >>> needed.need(y2)\n\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n \n \n \n >>> print bottom\n \n\nWe now tell the system that it's safe to render inclusions at the bottom::\n\n >>> needed.bottom()\n\nWe now see the resource ``y2`` show up in the bottom fragment::\n\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n \n \n >>> print bottom\n \n\nThere's also a convenience function for the currently needed inclusion::\n\n >>> request.needed = needed\n >>> top, bottom = resource.render_topbottom()\n >>> print top\n \n \n \n >>> print bottom\n \n\nWhen we force bottom rendering of Javascript, there is no effect of\nmaking a resource bottom-safe: all ``.js`` resources will be rendered\nat the bottom anyway::\n\n >>> needed.bottom(force=True)\n >>> top, bottom = needed.render_topbottom()\n >>> print top\n \n >>> print bottom\n \n \n \n\nNote that if ``bottom`` is enabled, it makes no sense to have a\nresource inclusion ``b`` that depends on a resource inclusion ``a``\nwhere ``a`` is bottom-safe and ``b``, that depends on it, is not\nbottom-safe. In this case ``a`` would be included on the page at the\nbottom *after* ``b`` in the ```` section, and this might lead to\nordering problems. Likewise a rollup resource shouldn't combine\nresources where some are bottom-safe and others aren't.\n\nThe system makes no sanity checks for misconfiguration of\nbottom-safety however; it could be the user simply never enables\n``bottom`` mode at all and doesn't care about this issue. In this case\nthe user will want to write Javascript code that isn't safe to be\nincluded at the bottom of the page and still be able to depend on\nJavascript code that is.\n\nInserting top and bottom resources in HTML\n==========================================\n\nYou can also insert top and bottom fragments into HTML. This assumes a\nHTML text that has a ```` tag in it as well as a ````\ntag::\n\n >>> html = \"rest of headrest of body\"\n\nTo insert the resources directly in HTML we can use\n``render_topbottom_into_html`` on ``needed``::\n\n >>> print needed.render_topbottom_into_html(html)\n \n \n rest of headrest of body\n \n \n\nThere's also a function available to do this for the currently needed\nresources::\n\n >>> print resource.render_topbottom_into_html(html)\n \n \n rest of headrest of body\n \n \n\nUsing WSGI middleware to insert into HTML\n=========================================\n\nThere is also a WSGI middleware available to insert the top (and bottom)\ninto the HTML. We are using WebOb to create a response object that will\nserve as our WSGI application.\n\nWe create a simple WSGI application. In our application we declare that\nwe need a resource (``y1``) and put that in the WSGI ``environ`` under the\nkey ``hurry.resource.needed``::\n\n >>> def app(environ, start_response):\n ... start_response('200 OK', [])\n ... needed = environ['hurry.resource.needed'] = NeededInclusions()\n ... needed.need(y1)\n ... return ['']\n\nWe now wrap this in our middleware, so that the middleware is activated::\n\n >>> from hurry.resource.wsgi import Middleware\n >>> wrapped_app = Middleware(app)\n\nNow we make a request (using webob for convenience)::\n\n >>> import webob\n >>> req = webob.Request.blank('/')\n >>> res = req.get_response(wrapped_app)\n\nWe can now see that the resources are added to the HTML by the middleware::\n\n >>> print res.body\n \n \n \n \n \n\nWhen we set the response Content-Type to non-HTML, the middleware\nwon't be active even if we need things and the body appears to contain\nHTML::\n\n >>> def app(environ, start_response):\n ... start_response('200 OK', [('Content-Type', 'text/plain')])\n ... needed = environ['hurry.resource.needed'] = NeededInclusions()\n ... needed.need(y1)\n ... return ['']\n >>> wrapped_app = Middleware(app)\n >>> req = webob.Request.blank('/')\n >>> res = req.get_response(wrapped_app)\n >>> res.body\n ''\n\nbottom convenience\n==================\n\nLike for ``need`` and ``mode``, there is also a convenience spelling for\n``bottom``::\n\n >>> request = Request()\n >>> l1 = ResourceInclusion(foo, 'l1.js', bottom=True)\n >>> l1.need()\n\nLet's look at the resources needed by default::\n\n >>> c = get_current_needed_inclusions()\n >>> top, bottom = c.render_topbottom()\n >>> print top\n \n >>> print bottom\n \n\nLet's now change the bottom mode using the convenience\n``hurry.resource.bottom`` spelling::\n\n >>> from hurry.resource import bottom\n >>> bottom()\n\nRe-rendering will show it's honoring the bottom setting::\n\n >>> top, bottom = c.render_topbottom()\n >>> print top\n \n >>> print bottom\n \n\nGenerating resource code\n========================\n\nSometimes it is useful to generate code that expresses a complex\nresource dependency structure. One example of that is in\n``hurry.yui``. We can use the ``generate_code`` function to render resource\ninclusions::\n\n >>> i1 = ResourceInclusion(foo, 'i1.js')\n >>> i2 = ResourceInclusion(foo, 'i2.js', depends=[i1])\n >>> i3 = ResourceInclusion(foo, 'i3.js', depends=[i2])\n >>> i4 = ResourceInclusion(foo, 'i4.js', depends=[i1])\n >>> i5 = ResourceInclusion(foo, 'i5.js', depends=[i4, i3])\n\n >>> from hurry.resource import generate_code\n >>> print generate_code(i1=i1, i2=i2, i3=i3, i4=i4, i5=i5)\n from hurry.resource import Library, ResourceInclusion\n \n foo = Library('foo', 'dummy')\n \n i1 = ResourceInclusion(foo, 'i1.js')\n i2 = ResourceInclusion(foo, 'i2.js', depends=[i1])\n i3 = ResourceInclusion(foo, 'i3.js', depends=[i2])\n i4 = ResourceInclusion(foo, 'i4.js', depends=[i1])\n i5 = ResourceInclusion(foo, 'i5.js', depends=[i4, i3])\n\nLet's look at a more complicated example with modes and superseders::\n\n >>> j1 = ResourceInclusion(foo, 'j1.js', debug='j1-debug.js')\n >>> j2 = ResourceInclusion(foo, 'j2.js', debug='j2-debug.js')\n >>> giantj = ResourceInclusion(foo, 'giantj.js', supersedes=[j1, j2],\n ... debug='giantj-debug.js')\n\n >>> print generate_code(j1=j1, j2=j2, giantj=giantj)\n from hurry.resource import Library, ResourceInclusion\n \n foo = Library('foo', 'dummy')\n \n j1 = ResourceInclusion(foo, 'j1.js', debug='j1-debug.js')\n j2 = ResourceInclusion(foo, 'j2.js', debug='j2-debug.js')\n giantj = ResourceInclusion(foo, 'giantj.js', supersedes=[j1, j2], debug='giantj-debug.js')\n\nWe can control the name the inclusion will get in the source code by\nusing keyword parameters::\n\n >>> print generate_code(hoi=i1)\n from hurry.resource import Library, ResourceInclusion\n \n foo = Library('foo', 'dummy')\n \n hoi = ResourceInclusion(foo, 'i1.js')\n\n >>> print generate_code(hoi=i1, i2=i2)\n from hurry.resource import Library, ResourceInclusion\n \n foo = Library('foo', 'dummy')\n \n hoi = ResourceInclusion(foo, 'i1.js')\n i2 = ResourceInclusion(foo, 'i2.js', depends=[hoi])\n\nSorting inclusions by dependency\n================================\n\nThis is more a footnote than something that you should be concerned\nabout. In case assumptions in this library are wrong or there are\nother reasons you would like to sort resource inclusions that come in\nsome arbitrary order into one where the dependency relation makes\nsense, you can use ``sort_inclusions_topological``::\n\n >>> from hurry.resource import sort_inclusions_topological\n\nLet's make a list of resource inclusions not sorted by dependency::\n\n >>> i = [a5, a3, a1, a2, a4]\n >>> sort_inclusions_topological(i) #doctest: +NORMALIZE_WHITESPACE\n [,\n ,\n ,\n ,\n ]\n\n\nInclusion renderers\n===================\n\nThe HTML fragments for inclusions are rendered by ``inclusion renderers``\nthat are simple functions registered per extension.\n\nRenderers are registered in the ``inclusion_renderers`` dictionary:\n\n >>> from hurry.resource.core import inclusion_renderers\n >>> sorted(inclusion_renderers)\n ['.css', '.js', '.kss']\n\nRenderers render HTML fragments using given resource URL:\n\n >>> inclusion_renderers['.js']('http://localhost/script.js')\n ''\n\nLet's create an inclusion of unknown resource:\n\n >>> a6 = ResourceInclusion(foo, 'nothing.unknown')\n\n >>> from hurry.resource.core import render_inclusions\n >>> render_inclusions([a6]) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\n Traceback (most recent call last):\n ...\n UnknownResourceExtension: Unknown resource extension .unknown for resource\n inclusion: \n\nNow let's add a renderer for our \".unknown\" extension and try again:\n\n >>> def render_unknown(url):\n ... return '' % url\n >>> inclusion_renderers['.unknown'] = render_unknown\n\n >>> render_inclusions([a6])\n ''\n\nCHANGES\n*******\n\n.. Important::\n hurry.resource has been superseded by the\n `Fanstatic `_ project!\n\n0.10 (2010-07-24)\n=================\n\n* The WSGI Middleware depends on WebOb, which is an optional\n dependency. Don't expose it into the ``__init__.py`` therefore, as\n that needs to be independent of WebOb. To use the middleware import\n it directly from ``hurry.resource.wsgi``.\n\n* The ``Library`` factory now has to be called with a rootpath\n argument as the second argument. This is a path to the actual\n resource directory that the library represents, relative to the\n directory of the package. This is needed to make `` hurry.resource``\n useful for frameworks not based on ``zope.configuration``.\n\n This breaks backwards compatibility with previous versions of\n ``hurry.resource``; all code that uses ``Library`` should be\n adjusted. Please also update their ``setup.py`` to depend on\n ``hurry.resource >= 0.10``.\n\n* Packages should register their ``Library`` instances with\n ``hurry.resource`` using the ``hurry.resource.libraries`` entry\n point.\n\n0.9 (2010-07-13)\n================\n\n* WSGI support: ``hurry.resource.Middleware`` can be used to wrap WSGI\n applications. If the application supplies a ``NeededInclusions``\n object in ``environ`` with the key ``hurry.resource.needed``, the\n middleware will pick up on this and insert the needed inclusions.\n\n The WebOb library is needed to make this work and depending on\n ``hurry.resource [wsgi]`` will pull in the required dependency.\n\n* Fixed some typos in README.txt.\n\n0.4.1 (2009-12-16)\n==================\n\n* Fix restructured text.\n\n0.4 (2009-12-16)\n================\n\n* Expose a ``GroupInclusion`` that only exists to depend on other\n inclusions. This way it becomes possible to expose a set of\n different resources together without there being a central resource\n that depends on them. The GroupInclusion itself is therefore not\n rendered.\n\n* Create ``render_into_html`` and ``render_topbottom_into_html``\n functionality, which insert the rendered inclusions into HTML.\n\n* Expose ``render``, ``render_into_html``, ``render_topbottom`` and\n ``render_topbottom_into_html`` as top-level functions in\n ``hurry.resource``.\n\n* Fix NameError when trying to render an inclusion with unknown extension.\n\n* Document inclusion renderers mechanism in the README.txt.\n\n0.3 (2008-10-15)\n================\n\nFeatures added\n--------------\n\n* Consolidating resources into rollups is now disabled by\n default. This is to help developers with debugging; rollups are\n typically minified for optimization purposes, and libraries such as\n YUI do not offer rollups in non-minified form. Since rollups must\n now be explicitly enabled by the application developer\n (``hurry.resource.rollup()``), an application developer who needs to\n debug can choose not to call it (or call\n ``hurry.resource.rollup(disable=True``).\n\n* Added an optimization section to the documentation.\n\n* Added some more details about how modes are expected to be used to\n the documentation.\n\nBug fixes\n---------\n\n* ``hurry.resource.bottom`` now takes a second optional ``disable``\n parameter.\n\n0.2 (2008-10-13)\n================\n\n* Changed the API to set the mode. Instead of passing it to ``render``\n and ``inclusions``, the requested mode can be set with the ``mode``\n method on ``INeededInclusions``. For convenience there is a ``mode``\n function as well that can be imported directly from\n ``hurry.resource`` that sets the mode for the current needed\n inclusions.\n\n* Added support for rendering resources into two fragments, one to\n be included at the top of the HTML page in the ```` section,\n the other to be included just before the ```` section. In\n some circumstances doing this can `speed up page load time`_.\n\n .. _`speed up page load time`: http://developer.yahoo.net/blog/archives/2007/07/high_performanc_5.html\n\n0.1 (2008-10-07)\n================\n\n* Initial public release.\n\nDownload\n********", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/hurry.resource", "keywords": "", "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "hurry.resource", "package_url": "https://pypi.org/project/hurry.resource/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/hurry.resource/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://pypi.python.org/pypi/hurry.resource" }, "release_url": "https://pypi.org/project/hurry.resource/0.10/", "requires_dist": null, "requires_python": null, "summary": "Flexible resources for web applications.", "version": "0.10" }, "last_serial": 793131, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "3446bd6219a01dffb965bd511e185540", "sha256": "d722bd836131f23d62f45859ca2ba6c8adb46584c5f9fb33dddc7a6cf656ce46" }, "downloads": -1, "filename": "hurry.resource-0.1.tar.gz", "has_sig": false, "md5_digest": "3446bd6219a01dffb965bd511e185540", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21316, "upload_time": "2008-10-07T15:41:25", "url": "https://files.pythonhosted.org/packages/aa/98/ce9e83b43d37a406964b6a0afd3259101b88553f20a7090e1cbfef18960a/hurry.resource-0.1.tar.gz" } ], "0.10": [ { "comment_text": "", "digests": { "md5": "0187bee8cbb72f30d73ccf54446e3137", "sha256": "bb9690b11ab9b4f814f04503b24e75587160138008be0a927326e8cea249d3b7" }, "downloads": -1, "filename": "hurry.resource-0.10.tar.gz", "has_sig": false, "md5_digest": "0187bee8cbb72f30d73ccf54446e3137", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 42650, "upload_time": "2010-07-24T18:13:58", "url": "https://files.pythonhosted.org/packages/e3/54/703c36d94eddac4227c86ae1559db59c45129fe58867136944999ddd5661/hurry.resource-0.10.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "958d546262ef55b91a80f88cb81da3d3", "sha256": "03cb6cab71d43636207e48e7b366ac724d3d4774e8f79b6e41971a4dcd480190" }, "downloads": -1, "filename": "hurry.resource-0.2.tar.gz", "has_sig": false, "md5_digest": "958d546262ef55b91a80f88cb81da3d3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26737, "upload_time": "2008-10-13T19:52:20", "url": "https://files.pythonhosted.org/packages/e6/b6/2d9643bfe7dc87eec24049cf213b0ad64df171a581bc90f06e591e73aaab/hurry.resource-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "fbe1cb31608bad1c2aee63f4910db6cb", "sha256": "60ef6fad302135bbf9fad169be7db571f21e960db45c9f81220d360f55aa2a7c" }, "downloads": -1, "filename": "hurry.resource-0.3.tar.gz", "has_sig": false, "md5_digest": "fbe1cb31608bad1c2aee63f4910db6cb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30796, "upload_time": "2008-10-15T17:26:14", "url": "https://files.pythonhosted.org/packages/d7/82/54fcd5dd9474fa8f8b930029ea58598002f02717fbad027054b6c91961e3/hurry.resource-0.3.tar.gz" } ], "0.4": [], "0.4.1": [ { "comment_text": "", "digests": { "md5": "410158acccf162bce0583468c4b0a1fa", "sha256": "310d06bd204c4dd29559e7495edc81d72a95a64109ee8103b4e4102e0e5e2730" }, "downloads": -1, "filename": "hurry.resource-0.4.1.tar.gz", "has_sig": false, "md5_digest": "410158acccf162bce0583468c4b0a1fa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 36062, "upload_time": "2009-12-16T18:39:58", "url": "https://files.pythonhosted.org/packages/b8/0b/31444bc3f6170496673926b26680394ce8858c3f2c81f1f527da663a2980/hurry.resource-0.4.1.tar.gz" } ], "0.9": [ { "comment_text": "", "digests": { "md5": "75bf27ce09c574d32b5ad341c46c8926", "sha256": "388f415c9131788c71e869cc2747d1db58f2324640e17e72d447dac5475620d1" }, "downloads": -1, "filename": "hurry.resource-0.9.tar.gz", "has_sig": false, "md5_digest": "75bf27ce09c574d32b5ad341c46c8926", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 40356, "upload_time": "2010-07-13T17:38:26", "url": "https://files.pythonhosted.org/packages/cc/47/07ce867631e506893d93d8aff8145ea27bbb38ead0b5745029a1fe38e1ba/hurry.resource-0.9.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "ba069d277e024c58defe4b3a85bcb30f", "sha256": "f9c005d3c9d06aaf0a45aa795f89b8b24a36ecfe57131d7922ddb2ba7de71e69" }, "downloads": -1, "filename": "hurry.resource-0.9.1.tar.gz", "has_sig": false, "md5_digest": "ba069d277e024c58defe4b3a85bcb30f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41116, "upload_time": "2011-02-15T10:54:28", "url": "https://files.pythonhosted.org/packages/09/20/a09ab6d0ad0dd60d26d5432a906470135c238619e4589512ec4974562b56/hurry.resource-0.9.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0187bee8cbb72f30d73ccf54446e3137", "sha256": "bb9690b11ab9b4f814f04503b24e75587160138008be0a927326e8cea249d3b7" }, "downloads": -1, "filename": "hurry.resource-0.10.tar.gz", "has_sig": false, "md5_digest": "0187bee8cbb72f30d73ccf54446e3137", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 42650, "upload_time": "2010-07-24T18:13:58", "url": "https://files.pythonhosted.org/packages/e3/54/703c36d94eddac4227c86ae1559db59c45129fe58867136944999ddd5661/hurry.resource-0.10.tar.gz" } ] }