[Checkins] SVN: hurry.resource/branches/janjaapdriessen-resource-publisher/ Add tests for publisher, explain hashes in URLs. Update changelog

Jan-Jaap Driessen jdriessen at thehealthagency.com
Tue Nov 16 10:19:11 EST 2010


Log message for revision 118430:
  Add tests for publisher, explain hashes in URLs. Update changelog

Changed:
  U   hurry.resource/branches/janjaapdriessen-resource-publisher/CHANGES.txt
  U   hurry.resource/branches/janjaapdriessen-resource-publisher/src/hurry/resource/README.txt

-=-
Modified: hurry.resource/branches/janjaapdriessen-resource-publisher/CHANGES.txt
===================================================================
--- hurry.resource/branches/janjaapdriessen-resource-publisher/CHANGES.txt	2010-11-16 15:18:14 UTC (rev 118429)
+++ hurry.resource/branches/janjaapdriessen-resource-publisher/CHANGES.txt	2010-11-16 15:19:11 UTC (rev 118430)
@@ -19,11 +19,15 @@
 
 - Add hashing functionality that calculates a checksum of a resource library.
 
-- Hurry.resource.publisher knows about publishing resources.
+- Hurry.resource.wsgi knows about publishing resources.
 
 - The hurry.resource.wsgi injector middleware informs the wrapped application
   of the presence of a resource publisher.
 
+- Do not serve hidden files in hurry.resource.publisher.
+
+- Publisher only sets cache control header on successful responses.
+
 0.10 (2010-07-24)
 =================
 

Modified: hurry.resource/branches/janjaapdriessen-resource-publisher/src/hurry/resource/README.txt
===================================================================
--- hurry.resource/branches/janjaapdriessen-resource-publisher/src/hurry/resource/README.txt	2010-11-16 15:18:14 UTC (rev 118429)
+++ hurry.resource/branches/janjaapdriessen-resource-publisher/src/hurry/resource/README.txt	2010-11-16 15:19:11 UTC (rev 118430)
@@ -31,19 +31,21 @@
 A resource library
 ==================
 
-We define a library ``foo``. It takes two arguments, the name of the
-library as it should be published under in a URL and uniquely identify
+A hurry.resource Library takes two arguments: the name of the library
+as it should be published under in a URL and uniquely identify
 it, and a path to the root of the resources (rootpath) that this
-library publishes::
+library publishes. In the ``mypackage``, which was installed during
+setup of this test, we define a resource ``foo``::
 
-  >>> from hurry.resource import Library
-  >>> foo = Library('foo', 'dummy')
+  >>> from mypackage import foo
+  >>> foo.name
+  'foo'
 
 The full path to the directory with the resources is reconstructed from
 the package that the Library is defined in::
 
-  >>> foo.path #doctest: +ELLIPSIS
-  '.../hurry.resource/src/hurry/resource/dummy'
+  >>> foo.path
+  '...test-installs/mypackage-1.0dev.../mypackage/resources'
 
 Entry points
 ============
@@ -65,16 +67,9 @@
 There is an API to help you obtain all registered libraries::
 
   >>> from hurry.resource import libraries
-
-Nothing is registered however::
-
   >>> list(libraries())
-  []
+  [<hurry.resource.core.Library object at ...>]
 
-It would be nice to now have some tests that see whether entry points
-actually get picked up so, but that would require an involved test
-setup that we find difficult to construct.
-
 Inclusion
 =========
 
@@ -110,7 +105,7 @@
 
 Let's now see what resources are needed by this inclusion::
 
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -130,7 +125,7 @@
 
   >>> needed = NeededInclusions()
   >>> needed.need(group)
-  >>> group.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> group.inclusions()
   [<ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'b.css' in library 'foo'>]
 
@@ -138,7 +133,7 @@
 inclusions directly::
 
   >>> more_stuff = ResourceInclusion(foo, 'more_stuff.js', depends=[group])
-  >>> more_stuff.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> more_stuff.inclusions()
   [<ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'more_stuff.js' in library 'foo'>]
@@ -216,7 +211,7 @@
 
 The resource inclusion will now indeed be needed::
 
-  >>> get_current_needed_inclusions().inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> get_current_needed_inclusions().inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -278,7 +273,7 @@
 We need ``y1`` again::
 
   >>> needed.need(y1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -288,7 +283,7 @@
 in the needed resources::
 
   >>> needed.need(y1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -297,12 +292,12 @@
 already required ``x1`` and ``x2``::
 
   >>> needed.need(x1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
   >>> needed.need(x2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -314,7 +309,7 @@
   >>> needed.need(x1)
   >>> needed.need(x2)
   >>> needed.need(y1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -327,12 +322,12 @@
   >>> a3 = ResourceInclusion(foo, 'a3.js', depends=[a2])
   >>> a4 = ResourceInclusion(foo, 'a4.js', depends=[a1])
   >>> needed.need(a3)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
    <ResourceInclusion 'a3.js' in library 'foo'>]
   >>> needed.need(a4)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
    <ResourceInclusion 'a3.js' in library 'foo'>,
@@ -344,7 +339,7 @@
   >>> needed = NeededInclusions()
   >>> needed.need(a4)
   >>> needed.need(a3)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a4.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
@@ -356,7 +351,7 @@
   >>> a5 = ResourceInclusion(foo, 'a5.js', depends=[a4, a3])
   >>> needed = NeededInclusions()
   >>> needed.need(a5)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a4.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
@@ -370,7 +365,7 @@
   >>> needed = NeededInclusions()
   >>> needed.need(a3)
   >>> needed.need(a5)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
    <ResourceInclusion 'a3.js' in library 'foo'>,
@@ -393,14 +388,14 @@
 
 By default, we get ``k.js``::
 
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'k.js' in library 'foo'>]
 
 We can however also get the resource for mode ``debug`` and get
 ``k-debug.js``::
 
   >>> needed.mode('debug')
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'k-debug.js' in library 'foo'>]
 
 Modes can also be specified fully with a resource inclusion, which allows
@@ -413,14 +408,14 @@
 
 By default we get ``k2.js``::
 
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'k2.js' in library 'foo'>]
 
 We can however also get the resource for mode ``debug`` and get
 ``k2-debug.js``::
 
   >>> needed.mode('debug')
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'k2-debug.js' in library 'foo'>]
 
 Note that modes are assumed to be identical in dependency structure;
@@ -432,7 +427,7 @@
   >>> needed = NeededInclusions()
   >>> needed.mode('minified')
   >>> needed.need(k1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'k.js' in library 'foo'>]
 
 ``hurry.resource`` suggests resource libraries follow the following
@@ -477,7 +472,7 @@
 Let's look at the resources needed by default::
 
   >>> c = get_current_needed_inclusions()
-  >>> c.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> c.inclusions()
   [<ResourceInclusion 'l1.js' in library 'foo'>]
 
 Let's now change the mode using the convenience
@@ -488,7 +483,7 @@
 
 When we request the resources now, we get them in the ``debug`` mode::
 
-  >>> c.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> c.inclusions()
   [<ResourceInclusion 'l1-debug.js' in library 'foo'>]
 
 "Rollups"
@@ -514,7 +509,7 @@
   >>> needed = NeededInclusions()
   >>> needed.need(b1)
   >>> needed.need(b2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b1.js' in library 'foo'>, <ResourceInclusion 'b2.js' in library 'foo'>]
 
 Let's enable rollups::
@@ -532,7 +527,7 @@
 
   >>> needed.need(b1)
   >>> needed.need(b2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giant.js' in library 'foo'>]
 
 The system will by default only consolidate exactly. That is, if only a single
@@ -541,7 +536,7 @@
   >>> needed = NeededInclusions()
   >>> needed.rollup()
   >>> needed.need(b1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b1.js' in library 'foo'>]
 
 Let's look at this with a larger consolidation of 3 resources::
@@ -556,7 +551,7 @@
   >>> needed = NeededInclusions()
   >>> needed.rollup()
   >>> needed.need(c1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'c1.css' in library 'foo'>]
 
 Neither will it roll up two resources::
@@ -565,7 +560,7 @@
   >>> needed.rollup()
   >>> needed.need(c1)
   >>> needed.need(c2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'c1.css' in library 'foo'>,
    <ResourceInclusion 'c2.css' in library 'foo'>]
 
@@ -576,7 +571,7 @@
   >>> needed.need(c1)
   >>> needed.need(c2)
   >>> needed.need(c3)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantc.css' in library 'foo'>]
 
 The default behavior is to play it safe: we cannot be certain that we
@@ -606,7 +601,7 @@
   >>> needed.rollup()
   >>> needed.need(d1)
   >>> needed.need(d2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantd.js' in library 'foo'>]
 
 In fact even if we only need a single resource the eager superseder will
@@ -615,7 +610,7 @@
   >>> needed = NeededInclusions()
   >>> needed.rollup()
   >>> needed.need(d1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantd.js' in library 'foo'>]
 
 If there are two potential eager superseders, the biggest one will
@@ -628,7 +623,7 @@
   >>> needed.rollup()
   >>> needed.need(d1)
   >>> needed.need(d2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantd-bigger.js' in library 'foo'>]
 
 If there is a potential non-eager superseder and an eager one, the eager one
@@ -642,7 +637,7 @@
   >>> needed.need(d2)
   >>> needed.need(d3)
   >>> needed.need(d4)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantd-bigger.js' in library 'foo'>]
 
 A resource can be part of multiple rollups. In this case the rollup
@@ -662,7 +657,7 @@
   >>> needed.need(e1)
   >>> needed.need(e2)
   >>> needed.need(e3)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giante-three.js' in library 'foo'>]
 
 Consolidation also works with modes::
@@ -676,10 +671,10 @@
   >>> needed.rollup()
   >>> needed.need(f1)
   >>> needed.need(f2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantf.js' in library 'foo'>]
   >>> needed.mode('debug')
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantf-debug.js' in library 'foo'>]
 
 What if the rolled up resources have no mode but the superseding resource
@@ -694,10 +689,10 @@
   >>> needed.rollup()
   >>> needed.need(g1)
   >>> needed.need(g2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantg.js' in library 'foo'>]
   >>> needed.mode('debug')
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'giantg.js' in library 'foo'>]
 
 What if the rolled up resources have a mode but the superseding resource
@@ -710,14 +705,14 @@
   >>> needed.rollup()
   >>> needed.need(h1)
   >>> needed.need(h2)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'gianth.js' in library 'foo'>]
 
 Since there is no superseder for the debug mode, we will get the two
 resources, not rolled up::
 
   >>> needed.mode('debug')
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'h1-debug.js' in library 'foo'>,
    <ResourceInclusion 'h2-debug.js' in library 'foo'>]
 
@@ -728,7 +723,7 @@
 
   >>> needed = NeededInclusions()
   >>> needed.need(y1)
-  >>> needed.inclusions() #doctest: +NORMALIZE_WHITESPACE
+  >>> needed.inclusions()
   [<ResourceInclusion 'b.css' in library 'foo'>,
    <ResourceInclusion 'a.js' in library 'foo'>,
    <ResourceInclusion 'c.js' in library 'foo'>]
@@ -740,7 +735,6 @@
     ...
   AttributeError: 'NoneType' object has no attribute 'endswith'
 
-
 That didn't work. In order to render an inclusion, we need to tell
 ``hurry.resource`` the base URL for a resource inclusion. We
 already know the relative URL, so we need to specify how to get a URL
@@ -759,9 +753,9 @@
 for instance)::
 
   >>> print needed.render()
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
 
 Let's set this a currently needed inclusions::
 
@@ -772,10 +766,59 @@
 
   >>> from hurry import resource
   >>> print resource.render()
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
 
+Hashing resources
+=================
+
+As you have seen in the rendered URLs above, we insert a ``hash:`` segment
+into the URLs. Hashing of resources is inspired by z3c.hashedresource:
+
+'While we want browsers to cache static resources such as CSS-stylesheets and
+JavaScript files, we also want them *not* to use the cached version if the
+files on the server have been updated. (And we don't want to make end-users
+have to empty their browser cache to get the latest version. Nor explain how
+to do that over the phone every time.)'
+
+To make browsers update their caches of resources immediately when the
+resource changes, the absolute URLs of resources can now be made to contain a
+hash of the resource's contents, so it will look like
+/foo/hash:12345/myresource instead of /foo/myresource.
+
+  >>> before_hash = foo.hash()
+  >>> from pkg_resources import resource_filename, resource_string
+  >>> before = resource_string('mypackage', 'resources/style.css')
+  >>> mypackage_style = resource_filename('mypackage', 'resources/style.css')
+  >>> open(mypackage_style, 'w').write('body {color: #0f0;}')
+  >>> foo.hash() == before_hash
+  False
+
+  >>> # Reset the content.
+  >>> open(mypackage_style, 'w').write(before)
+  >>> foo.hash() == before_hash
+  True
+
+Any VCS directories are ignores in calculating the hash:
+
+  >>> import os
+  >>> os.mkdir(resource_filename('mypackage',
+  ...                            os.path.join('resources', 'sub')))
+  >>> os.mkdir(resource_filename('mypackage',
+  ...                            os.path.join('resources', 'sub', '.svn')))
+  >>> open(os.path.join(resource_filename('mypackage', 'resources/sub/.svn'), 'test'),
+  ...     'w').write('test')
+  >>> foo.hash() == before_hash
+  True
+
+In developer mode the hash is recomputed each time the resource is asked for
+its URL, while in production mode the hash is computed only once, so remember
+to restart the server after changing resource files (else browsers will still
+see the old URL unchanged and use their outdated cached versions of the files).
+
+  XXX Add tests for dev mode and non dev mode.
+
 Inserting resources in HTML
 ===========================
 
@@ -791,9 +834,9 @@
 
   >>> print needed.render_into_html(html)
   <html><head>
-      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   something more</head></html>
 
 The top-level convenience function does this for the currently needed
@@ -801,9 +844,9 @@
 
   >>> print resource.render_into_html(html)
   <html><head>
-      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:...b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   something more</head></html>
 
 See below for a way to insert into HTML when bottom fragments are
@@ -824,9 +867,9 @@
 
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   >>> print bottom
   <BLANKLINE>
 
@@ -841,9 +884,9 @@
 
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   >>> print bottom
   <BLANKLINE>
 
@@ -853,10 +896,10 @@
   >>> needed.bottom(force=True)
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
   >>> print bottom
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
 
 Let's now introduce a javascript resource that says it is safe to be
 included on the bottom::
@@ -873,10 +916,10 @@
 
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script>
   >>> print bottom
   <BLANKLINE>
 
@@ -888,22 +931,22 @@
 
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   >>> print bottom
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script>
 
 There's also a convenience function for the currently needed inclusion::
 
   >>> request.needed = needed
   >>> top, bottom = resource.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   >>> print bottom
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script>
 
 When we force bottom rendering of Javascript, there is no effect of
 making a resource bottom-safe: all ``.js`` resources will be rendered
@@ -912,11 +955,11 @@
   >>> needed.bottom(force=True)
   >>> top, bottom = needed.render_topbottom()
   >>> print top
-  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
+  <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
   >>> print bottom
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script>
 
 Note that if ``bottom`` is enabled, it makes no sense to have a
 resource inclusion ``b`` that depends on a resource inclusion ``a``
@@ -947,20 +990,20 @@
 
   >>> print needed.render_topbottom_into_html(html)
   <html><head>
-      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  rest of head</head><body>rest of body<script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script></body></html>
+      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  rest of head</head><body>rest of body<script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script></body></html>
 
 There's also a function available to do this for the currently needed
 resources::
 
   >>> print resource.render_topbottom_into_html(html)
   <html><head>
-      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  rest of head</head><body>rest of body<script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/y2.js"></script></body></html>
+      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  rest of head</head><body>rest of body<script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../y2.js"></script></body></html>
 
 Using WSGI middleware to insert into HTML
 =========================================
@@ -995,9 +1038,9 @@
 
   >>> print res.body
   <html><head>
-      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
-  <script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
-  <script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
+      <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/hash:.../b.css" />
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../a.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../c.js"></script>
   </head><body</body></html>
 
 When we set the response Content-Type to non-HTML, the middleware
@@ -1031,7 +1074,7 @@
   >>> c.base_url = 'http://localhost/static'
   >>> top, bottom = c.render_topbottom()
   >>> print top
-  <script type="text/javascript" src="http://localhost/static/foo/l1.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../l1.js"></script>
   >>> print bottom
   <BLANKLINE>
 
@@ -1047,7 +1090,7 @@
   >>> print top
   <BLANKLINE>
   >>> print bottom
-  <script type="text/javascript" src="http://localhost/static/foo/l1.js"></script>
+  <script type="text/javascript" src="http://localhost/static/foo/hash:.../l1.js"></script>
 
 Generating resource code
 ========================
@@ -1067,7 +1110,7 @@
   >>> print generate_code(i1=i1, i2=i2, i3=i3, i4=i4, i5=i5)
   from hurry.resource import Library, ResourceInclusion
   <BLANKLINE>
-  foo = Library('foo', 'dummy')
+  foo = Library('foo', 'resources')
   <BLANKLINE>
   i1 = ResourceInclusion(foo, 'i1.js')
   i2 = ResourceInclusion(foo, 'i2.js', depends=[i1])
@@ -1085,7 +1128,7 @@
   >>> print generate_code(j1=j1, j2=j2, giantj=giantj)
   from hurry.resource import Library, ResourceInclusion
   <BLANKLINE>
-  foo = Library('foo', 'dummy')
+  foo = Library('foo', 'resources')
   <BLANKLINE>
   j1 = ResourceInclusion(foo, 'j1.js', debug='j1-debug.js')
   j2 = ResourceInclusion(foo, 'j2.js', debug='j2-debug.js')
@@ -1097,14 +1140,14 @@
   >>> print generate_code(hoi=i1)
   from hurry.resource import Library, ResourceInclusion
   <BLANKLINE>
-  foo = Library('foo', 'dummy')
+  foo = Library('foo', 'resources')
   <BLANKLINE>
   hoi = ResourceInclusion(foo, 'i1.js')
 
   >>> print generate_code(hoi=i1, i2=i2)
   from hurry.resource import Library, ResourceInclusion
   <BLANKLINE>
-  foo = Library('foo', 'dummy')
+  foo = Library('foo', 'resources')
   <BLANKLINE>
   hoi = ResourceInclusion(foo, 'i1.js')
   i2 = ResourceInclusion(foo, 'i2.js', depends=[hoi])
@@ -1123,7 +1166,7 @@
 Let's make a list of resource inclusions not sorted by dependency::
 
   >>> i = [a5, a3, a1, a2, a4]
-  >>> sort_inclusions_topological(i) #doctest: +NORMALIZE_WHITESPACE
+  >>> sort_inclusions_topological(i)
   [<ResourceInclusion 'a1.js' in library 'foo'>,
    <ResourceInclusion 'a4.js' in library 'foo'>,
    <ResourceInclusion 'a2.js' in library 'foo'>,
@@ -1153,7 +1196,7 @@
   >>> a6 = ResourceInclusion(foo, 'nothing.unknown')
 
   >>> from hurry.resource.core import render_inclusions
-  >>> render_inclusions([a6], '/') #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+  >>> render_inclusions([a6], '/')
   Traceback (most recent call last):
   ...
   UnknownResourceExtension: Unknown resource extension .unknown for resource
@@ -1167,4 +1210,58 @@
   >>> inclusion_renderers['.unknown'] = render_unknown
 
   >>> render_inclusions([a6], 'http://localhost/static/')
-  '<link rel="unknown" href="http://localhost/static/foo/nothing.unknown" />'
+  '<link rel="unknown" href="http://localhost/static/foo/hash:.../nothing.unknown" />'
+
+
+Resource publisher
+==================
+
+The hurry.resource.publisher is a WSGI component that publishes registered
+resource libraries.
+
+  >>> from hurry.resource.publisher import Publisher
+  >>> from paste.fixture import TestApp
+  >>> app = TestApp(Publisher())
+
+We don't do anything fancy if the resource can not be found, but raise 404.
+
+  >>> res = app.get('/', expect_errors=True)
+  >>> res.status
+  404
+
+The resources are handled by paste.fileapp.DirectoryApp, which sets the
+ETag header, among other things::
+
+  >>> res = app.get('/foo/style.css')
+  >>> print res.body
+  body {
+    color: #f00;
+  }
+  >>> headers = dict(res.headers)
+  >>> 'ETag' in headers
+  True
+
+When we find the 'hash' marker in the requested URL, we send headers that let
+the user agent cache the resources for a long time.
+
+  >>> res = app.get('/foo/hash:12345/style.css')
+  >>> headers = dict(res.headers)
+  >>> 'Expires' in headers
+  True
+  >>> print headers['Cache-Control']
+  public, max-age=314496000
+
+We don't set cache-control headers on non-successful responses::
+
+  >>> res = app.get('/foo/hash:12345/notfound.css', expect_errors=True)
+  >>> headers = dict(res.headers)
+  >>> 'Expires' in headers
+  False
+  >>> 'Cache-Control' in headers
+  False
+
+Hidden files and directories are not served:
+
+  >>> res = app.get('/foo/sub/.svn/test', expect_errors=True)
+  >>> print res.status
+  404



More information about the checkins mailing list