1.. index:: 2 single: assets 3 single: static asssets 4 5.. _assets_chapter: 6 7Static Assets 8============= 9 10An :term:`asset` is any file contained within a Python :term:`package` which is 11*not* a Python source code file. For example, each of the following is an 12asset: 13 14- a GIF image file contained within a Python package or contained within any 15 subdirectory of a Python package. 16 17- a CSS file contained within a Python package or contained within any 18 subdirectory of a Python package. 19 20- a JavaScript source file contained within a Python package or contained 21 within any subdirectory of a Python package. 22 23- A directory within a package that does not have an ``__init__.py`` in it (if 24 it possessed an ``__init__.py`` it would *be* a package). 25 26- a :term:`Chameleon` or :term:`Mako` template file contained within a Python 27 package. 28 29The use of assets is quite common in most web development projects. For 30example, when you create a :app:`Pyramid` application using one of the 31available scaffolds, as described in :ref:`creating_a_project`, the directory 32representing the application contains a Python :term:`package`. Within that 33Python package, there are directories full of files which are static assets. 34For example, there's a ``static`` directory which contains ``.css``, ``.js``, 35and ``.gif`` files. These asset files are delivered when a user visits an 36application URL. 37 38.. index:: 39 single: asset specifications 40 41.. _asset_specifications: 42 43Understanding Asset Specifications 44---------------------------------- 45 46Let's imagine you've created a :app:`Pyramid` application that uses a 47:term:`Chameleon` ZPT template via the 48:func:`pyramid.renderers.render_to_response` API. For example, the application 49might address the asset using the :term:`asset specification` 50``myapp:templates/some_template.pt`` using that API within a ``views.py`` file 51inside a ``myapp`` package: 52 53.. code-block:: python 54 :linenos: 55 56 from pyramid.renderers import render_to_response 57 render_to_response('myapp:templates/some_template.pt', {}, request) 58 59"Under the hood", when this API is called, :app:`Pyramid` attempts to make 60sense out of the string ``myapp:templates/some_template.pt`` provided by the 61developer. This string is an :term:`asset specification`. It is composed of 62two parts: 63 64- The *package name* (``myapp``) 65 66- The *asset name* (``templates/some_template.pt``), relative to the package 67 directory. 68 69The two parts are separated by a colon ``:`` character. 70 71:app:`Pyramid` uses the Python :term:`pkg_resources` API to resolve the package 72name and asset name to an absolute (operating system-specific) file name. It 73eventually passes this resolved absolute filesystem path to the Chameleon 74templating engine, which then uses it to load, parse, and execute the template 75file. 76 77There is a second form of asset specification: a *relative* asset 78specification. Instead of using an "absolute" asset specification which 79includes the package name, in certain circumstances you can omit the package 80name from the specification. For example, you might be able to use 81``templates/mytemplate.pt`` instead of ``myapp:templates/some_template.pt``. 82Such asset specifications are usually relative to a "current package". The 83"current package" is usually the package which contains the code that *uses* 84the asset specification. :app:`Pyramid` APIs which accept relative asset 85specifications typically describe to what the asset is relative in their 86individual documentation. 87 88.. index:: 89 single: add_static_view 90 pair: assets; serving 91 92.. _static_assets_section: 93 94Serving Static Assets 95--------------------- 96 97:app:`Pyramid` makes it possible to serve up static asset files from a 98directory on a filesystem to an application user's browser. Use the 99:meth:`pyramid.config.Configurator.add_static_view` to instruct :app:`Pyramid` 100to serve static assets, such as JavaScript and CSS files. This mechanism makes 101a directory of static files available at a name relative to the application 102root URL, e.g., ``/static``, or as an external URL. 103 104.. note:: 105 106 :meth:`~pyramid.config.Configurator.add_static_view` cannot serve a single 107 file, nor can it serve a directory of static files directly relative to the 108 root URL of a :app:`Pyramid` application. For these features, see 109 :ref:`advanced_static`. 110 111Here's an example of a use of 112:meth:`~pyramid.config.Configurator.add_static_view` that will serve files up 113from the ``/var/www/static`` directory of the computer which runs the 114:app:`Pyramid` application as URLs beneath the ``/static`` URL prefix. 115 116.. code-block:: python 117 :linenos: 118 119 # config is an instance of pyramid.config.Configurator 120 config.add_static_view(name='static', path='/var/www/static') 121 122The ``name`` represents a URL *prefix*. In order for files that live in the 123``path`` directory to be served, a URL that requests one of them must begin 124with that prefix. In the example above, ``name`` is ``static`` and ``path`` is 125``/var/www/static``. In English this means that you wish to serve the files 126that live in ``/var/www/static`` as sub-URLs of the ``/static`` URL prefix. 127Therefore, the file ``/var/www/static/foo.css`` will be returned when the user 128visits your application's URL ``/static/foo.css``. 129 130A static directory named at ``path`` may contain subdirectories recursively, 131and any subdirectories may hold files; these will be resolved by the static 132view as you would expect. The ``Content-Type`` header returned by the static 133view for each particular type of file is dependent upon its file extension. 134 135By default, all files made available via 136:meth:`~pyramid.config.Configurator.add_static_view` are accessible by 137completely anonymous users. Simple authorization can be required, however. To 138protect a set of static files using a permission, in addition to passing the 139required ``name`` and ``path`` arguments, also pass the ``permission`` keyword 140argument to :meth:`~pyramid.config.Configurator.add_static_view`. The value of 141the ``permission`` argument represents the :term:`permission` that the user 142must have relative to the current :term:`context` when the static view is 143invoked. A user will be required to possess this permission to view any of the 144files represented by ``path`` of the static view. If your static assets must 145be protected by a more complex authorization scheme, see 146:ref:`advanced_static`. 147 148Here's another example that uses an :term:`asset specification` instead of an 149absolute path as the ``path`` argument. To convince 150:meth:`~pyramid.config.Configurator.add_static_view` to serve files up under 151the ``/static`` URL from the ``a/b/c/static`` directory of the Python package 152named ``some_package``, we can use a fully qualified :term:`asset 153specification` as the ``path``: 154 155.. code-block:: python 156 :linenos: 157 158 # config is an instance of pyramid.config.Configurator 159 config.add_static_view(name='static', path='some_package:a/b/c/static') 160 161The ``path`` provided to :meth:`~pyramid.config.Configurator.add_static_view` 162may be a fully qualified :term:`asset specification` or an *absolute path*. 163 164Instead of representing a URL prefix, the ``name`` argument of a call to 165:meth:`~pyramid.config.Configurator.add_static_view` can alternately be a 166*URL*. Each of the examples we've seen so far have shown usage of the ``name`` 167argument as a URL prefix. However, when ``name`` is a *URL*, static assets can 168be served from an external webserver. In this mode, the ``name`` is used as 169the URL prefix when generating a URL using 170:meth:`pyramid.request.Request.static_url`. 171 172For example, :meth:`~pyramid.config.Configurator.add_static_view` may be fed a 173``name`` argument which is ``http://example.com/images``: 174 175.. code-block:: python 176 :linenos: 177 178 # config is an instance of pyramid.config.Configurator 179 config.add_static_view(name='http://example.com/images', 180 path='mypackage:images') 181 182Because :meth:`~pyramid.config.Configurator.add_static_view` is provided with a 183``name`` argument that is the URL ``http://example.com/images``, subsequent 184calls to :meth:`~pyramid.request.Request.static_url` with paths that start with 185the ``path`` argument passed to 186:meth:`~pyramid.config.Configurator.add_static_view` will generate a URL 187something like ``http://example.com/images/logo.png``. The external webserver 188listening on ``example.com`` must be itself configured to respond properly to 189such a request. The :meth:`~pyramid.request.Request.static_url` API is 190discussed in more detail later in this chapter. 191 192.. index:: 193 single: generating static asset urls 194 single: static asset urls 195 pair: assets; generating urls 196 197.. _generating_static_asset_urls: 198 199Generating Static Asset URLs 200~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 201 202When an :meth:`~pyramid.config.Configurator.add_static_view` method is used to 203register a static asset directory, a special helper API named 204:meth:`pyramid.request.Request.static_url` can be used to generate the 205appropriate URL for an asset that lives in one of the directories named by the 206static registration ``path`` attribute. 207 208For example, let's assume you create a set of static declarations like so: 209 210.. code-block:: python 211 :linenos: 212 213 config.add_static_view(name='static1', path='mypackage:assets/1') 214 config.add_static_view(name='static2', path='mypackage:assets/2') 215 216These declarations create URL-accessible directories which have URLs that begin 217with ``/static1`` and ``/static2``, respectively. The assets in the 218``assets/1`` directory of the ``mypackage`` package are consulted when a user 219visits a URL which begins with ``/static1``, and the assets in the ``assets/2`` 220directory of the ``mypackage`` package are consulted when a user visits a URL 221which begins with ``/static2``. 222 223You needn't generate the URLs to static assets "by hand" in such a 224configuration. Instead, use the :meth:`~pyramid.request.Request.static_url` 225API to generate them for you. For example: 226 227.. code-block:: python 228 :linenos: 229 230 from pyramid.renderers import render_to_response 231 232 def my_view(request): 233 css_url = request.static_url('mypackage:assets/1/foo.css') 234 js_url = request.static_url('mypackage:assets/2/foo.js') 235 return render_to_response('templates/my_template.pt', 236 dict(css_url=css_url, js_url=js_url), 237 request=request) 238 239If the request "application URL" of the running system is 240``http://example.com``, the ``css_url`` generated above would be: 241``http://example.com/static1/foo.css``. The ``js_url`` generated above would 242be ``http://example.com/static2/foo.js``. 243 244One benefit of using the :meth:`~pyramid.request.Request.static_url` function 245rather than constructing static URLs "by hand" is that if you need to change 246the ``name`` of a static URL declaration, the generated URLs will continue to 247resolve properly after the rename. 248 249URLs may also be generated by :meth:`~pyramid.request.Request.static_url` to 250static assets that live *outside* the :app:`Pyramid` application. This will 251happen when the :meth:`~pyramid.config.Configurator.add_static_view` API 252associated with the path fed to :meth:`~pyramid.request.Request.static_url` is 253a *URL* instead of a view name. For example, the ``name`` argument may be 254``http://example.com`` while the ``path`` given may be ``mypackage:images``: 255 256.. code-block:: python 257 :linenos: 258 259 config.add_static_view(name='http://example.com/images', 260 path='mypackage:images') 261 262Under such a configuration, the URL generated by ``static_url`` for assets 263which begin with ``mypackage:images`` will be prefixed with 264``http://example.com/images``: 265 266.. code-block:: python 267 :linenos: 268 269 request.static_url('mypackage:images/logo.png') 270 # -> http://example.com/images/logo.png 271 272Using :meth:`~pyramid.request.Request.static_url` in conjunction with a 273:meth:`~pyramid.config.Configurator.add_static_view` makes it possible to put 274static media on a separate webserver during production (if the ``name`` 275argument to :meth:`~pyramid.config.Configurator.add_static_view` is a URL), 276while keeping static media package-internal and served by the development 277webserver during development (if the ``name`` argument to 278:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix). 279 280For example, we may define a :ref:`custom setting <adding_a_custom_setting>` 281named ``media_location`` which we can set to an external URL in production when 282our assets are hosted on a CDN. 283 284.. code-block:: python 285 :linenos: 286 287 media_location = settings.get('media_location', 'static') 288 289 config = Configurator(settings=settings) 290 config.add_static_view(path='myapp:static', name=media_location) 291 292Now we can optionally define the setting in our ini file: 293 294.. code-block:: ini 295 :linenos: 296 297 # production.ini 298 [app:main] 299 use = egg:myapp#main 300 301 media_location = http://static.example.com/ 302 303It is also possible to serve assets that live outside of the source by 304referring to an absolute path on the filesystem. There are two ways to 305accomplish this. 306 307First, :meth:`~pyramid.config.Configurator.add_static_view` supports taking an 308absolute path directly instead of an asset spec. This works as expected, 309looking in the file or folder of files and serving them up at some URL within 310your application or externally. Unfortunately, this technique has a drawback in 311that it is not possible to use the :meth:`~pyramid.request.Request.static_url` 312method to generate URLs, since it works based on an asset specification. 313 314.. versionadded:: 1.6 315 316The second approach, available in Pyramid 1.6+, uses the asset overriding APIs 317described in the :ref:`overriding_assets_section` section. It is then possible 318to configure a "dummy" package which then serves its file or folder from an 319absolute path. 320 321.. code-block:: python 322 323 config.add_static_view(path='myapp:static_images', name='static') 324 config.override_asset(to_override='myapp:static_images/', 325 override_with='/abs/path/to/images/') 326 327From this configuration it is now possible to use 328:meth:`~pyramid.request.Request.static_url` to generate URLs to the data in the 329folder by doing something like 330``request.static_url('myapp:static_images/foo.png')``. While it is not 331necessary that the ``static_images`` file or folder actually exist in the 332``myapp`` package, it is important that the ``myapp`` portion points to a valid 333package. If the folder does exist, then the overriden folder is given priority, 334if the file's name exists in both locations. 335 336.. index:: 337 single: Cache Busting 338 339.. _cache_busting: 340 341Cache Busting 342------------- 343 344.. versionadded:: 1.6 345 346In order to maximize performance of a web application, you generally want to 347limit the number of times a particular client requests the same static asset. 348Ideally a client would cache a particular static asset "forever", requiring it 349to be sent to the client a single time. The HTTP protocol allows you to send 350headers with an HTTP response that can instruct a client to cache a particular 351asset for an amount of time. As long as the client has a copy of the asset in 352its cache and that cache hasn't expired, the client will use the cached copy 353rather than request a new copy from the server. The drawback to sending cache 354headers to the client for a static asset is that at some point the static asset 355may change, and then you'll want the client to load a new copy of the asset. 356Under normal circumstances you'd just need to wait for the client's cached copy 357to expire before they get the new version of the static resource. 358 359A commonly used workaround to this problem is a technique known as 360:term:`cache busting`. Cache busting schemes generally involve generating a 361URL for a static asset that changes when the static asset changes. This way 362headers can be sent along with the static asset instructing the client to cache 363the asset for a very long time. When a static asset is changed, the URL used 364to refer to it in a web page also changes, so the client sees it as a new 365resource and requests the asset, regardless of any caching policy set for the 366resource's old URL. 367 368:app:`Pyramid` can be configured to produce cache busting URLs for static 369assets using :meth:`~pyramid.config.Configurator.add_cache_buster`: 370 371.. code-block:: python 372 :linenos: 373 374 import time 375 from pyramid.static import QueryStringConstantCacheBuster 376 377 # config is an instance of pyramid.config.Configurator 378 config.add_static_view(name='static', path='mypackage:folder/static/') 379 config.add_cache_buster( 380 'mypackage:folder/static/', 381 QueryStringConstantCacheBuster(str(int(time.time())))) 382 383Adding the cachebuster instructs :app:`Pyramid` to add the current time for 384a static asset to the query string in the asset's URL: 385 386.. code-block:: python 387 :linenos: 388 389 js_url = request.static_url('mypackage:folder/static/js/myapp.js') 390 # Returns: 'http://www.example.com/static/js/myapp.js?x=1445318121' 391 392When the web server restarts, the time constant will change and therefore so 393will its URL. 394 395.. note:: 396 397 Cache busting is an inherently complex topic as it integrates the asset 398 pipeline and the web application. It is expected and desired that 399 application authors will write their own cache buster implementations 400 conforming to the properties of their own asset pipelines. See 401 :ref:`custom_cache_busters` for information on writing your own. 402 403Disabling the Cache Buster 404~~~~~~~~~~~~~~~~~~~~~~~~~~ 405 406It can be useful in some situations (e.g., development) to globally disable all 407configured cache busters without changing calls to 408:meth:`~pyramid.config.Configurator.add_cache_buster`. To do this set the 409``PYRAMID_PREVENT_CACHEBUST`` environment variable or the 410``pyramid.prevent_cachebust`` configuration value to a true value. 411 412.. _custom_cache_busters: 413 414Customizing the Cache Buster 415~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 416 417Calls to :meth:`~pyramid.config.Configurator.add_cache_buster` may use 418any object that implements the interface 419:class:`~pyramid.interfaces.ICacheBuster`. 420 421:app:`Pyramid` ships with a very simplistic 422:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an 423arbitrary token you provide to the query string of the asset's URL. This 424is almost never what you want in production as it does not allow fine-grained 425busting of individual assets. 426 427In order to implement your own cache buster, you can write your own class from 428scratch which implements the :class:`~pyramid.interfaces.ICacheBuster` 429interface. Alternatively you may choose to subclass one of the existing 430implementations. One of the most likely scenarios is you'd want to change the 431way the asset token is generated. To do this just subclass 432:class:`~pyramid.static.QueryStringCacheBuster` and define a 433``tokenize(pathspec)`` method. Here is an example which uses Git to get 434the hash of the current commit: 435 436.. code-block:: python 437 :linenos: 438 439 import os 440 import subprocess 441 from pyramid.static import QueryStringCacheBuster 442 443 class GitCacheBuster(QueryStringCacheBuster): 444 """ 445 Assuming your code is installed as a Git checkout, as opposed to an egg 446 from an egg repository like PYPI, you can use this cachebuster to get 447 the current commit's SHA1 to use as the cache bust token. 448 """ 449 def __init__(self, param='x', repo_path=None): 450 super(GitCacheBuster, self).__init__(param=param) 451 if repo_path is None: 452 repo_path = os.path.dirname(os.path.abspath(__file__)) 453 self.sha1 = subprocess.check_output( 454 ['git', 'rev-parse', 'HEAD'], 455 cwd=repo_path).strip() 456 457 def tokenize(self, pathspec): 458 return self.sha1 459 460A simple cache buster that modifies the path segment can be constructed as 461well: 462 463.. code-block:: python 464 :linenos: 465 466 import posixpath 467 468 class PathConstantCacheBuster(object): 469 def __init__(self, token): 470 self.token = token 471 472 def __call__(self, request, subpath, kw): 473 base_subpath, ext = posixpath.splitext(subpath) 474 new_subpath = base_subpath + self.token + ext 475 return new_subpath, kw 476 477The caveat with this approach is that modifying the path segment 478changes the file name, and thus must match what is actually on the 479filesystem in order for :meth:`~pyramid.config.Configurator.add_static_view` 480to find the file. It's better to use the 481:class:`~pyramid.static.ManifestCacheBuster` for these situations, as 482described in the next section. 483 484.. _path_segment_cache_busters: 485 486Path Segments and Choosing a Cache Buster 487~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 488 489Many caching HTTP proxies will fail to cache a resource if the URL contains 490a query string. Therefore, in general, you should prefer a cache busting 491strategy which modifies the path segment rather than methods which add a 492token to the query string. 493 494You will need to consider whether the :app:`Pyramid` application will be 495serving your static assets, whether you are using an external asset pipeline 496to handle rewriting urls internal to the css/javascript, and how fine-grained 497do you want the cache busting tokens to be. 498 499In many cases you will want to host the static assets on another web server 500or externally on a CDN. In these cases your :app:`Pyramid` application may not 501even have access to a copy of the static assets. In order to cache bust these 502assets you will need some information about them. 503 504If you are using an external asset pipeline to generate your static files you 505should consider using the :class:`~pyramid.static.ManifestCacheBuster`. 506This cache buster can load a standard JSON formatted file generated by your 507pipeline and use it to cache bust the assets. This has many performance 508advantages as :app:`Pyramid` does not need to look at the files to generate 509any cache busting tokens, but still supports fine-grained per-file tokens. 510 511Assuming an example ``manifest.json`` like: 512 513.. code-block:: json 514 515 { 516 "css/main.css": "css/main-678b7c80.css", 517 "images/background.png": "images/background-a8169106.png" 518 } 519 520The following code would set up a cachebuster: 521 522.. code-block:: python 523 :linenos: 524 525 from pyramid.static import ManifestCacheBuster 526 527 config.add_static_view( 528 name='http://mycdn.example.com/', 529 path='mypackage:static') 530 531 config.add_cache_buster( 532 'mypackage:static/', 533 ManifestCacheBuster('myapp:static/manifest.json')) 534 535It's important to note that the cache buster only handles generating 536cache-busted URLs for static assets. It does **NOT** provide any solutions for 537serving those assets. For example, if you generated a URL for 538``css/main-678b7c80.css`` then that URL needs to be valid either by 539configuring ``add_static_view`` properly to point to the location of the files 540or some other mechanism such as the files existing on your CDN or rewriting 541the incoming URL to remove the cache bust tokens. 542 543.. index:: 544 single: static assets view 545 546CSS and JavaScript source and cache busting 547~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 548 549Often one needs to refer to images and other static assets inside CSS and 550JavaScript files. If cache busting is active, the final static asset URL is not 551available until the static assets have been assembled. These URLs cannot be 552handwritten. Below is an example of how to integrate the cache buster into 553the entire stack. Remember, it is just an example and should be modified to 554fit your specific tools. 555 556* First, process the files by using a precompiler which rewrites URLs to their 557 final cache-busted form. Then, you can use the 558 :class:`~pyramid.static.ManifestCacheBuster` to synchronize your asset 559 pipeline with :app:`Pyramid`, allowing the pipeline to have full control 560 over the final URLs of your assets. 561 562Now that you are able to generate static URLs within :app:`Pyramid`, 563you'll need to handle URLs that are out of our control. To do this you may 564use some of the following options to get started: 565 566* Configure your asset pipeline to rewrite URL references inline in 567 CSS and JavaScript. This is the best approach because then the files 568 may be hosted by :app:`Pyramid` or an external CDN without having to 569 change anything. They really are static. 570 571* Templatize JS and CSS, and call ``request.static_url()`` inside their 572 template code. While this approach may work in certain scenarios, it is not 573 recommended because your static assets will not really be static and are now 574 dependent on :app:`Pyramid` to be served correctly. See 575 :ref:`advanced_static` for more information on this approach. 576 577If your CSS and JavaScript assets use URLs to reference other assets it is 578recommended that you implement an external asset pipeline that can rewrite the 579generated static files with new URLs containing cache busting tokens. The 580machinery inside :app:`Pyramid` will not help with this step as it has very 581little knowledge of the asset types your application may use. The integration 582into :app:`Pyramid` is simply for linking those assets into your HTML and 583other dynamic content. 584 585.. _advanced_static: 586 587Advanced: Serving Static Assets Using a View Callable 588----------------------------------------------------- 589 590For more flexibility, static assets can be served by a :term:`view callable` 591which you register manually. For example, if you're using :term:`URL 592dispatch`, you may want static assets to only be available as a fallback if no 593previous route matches. Alternatively, you might like to serve a particular 594static asset manually, because its download requires authentication. 595 596Note that you cannot use the :meth:`~pyramid.request.Request.static_url` API to 597generate URLs against assets made accessible by registering a custom static 598view. 599 600Root-Relative Custom Static View (URL Dispatch Only) 601~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 602 603The :class:`pyramid.static.static_view` helper class generates a Pyramid view 604callable. This view callable can serve static assets from a directory. An 605instance of this class is actually used by the 606:meth:`~pyramid.config.Configurator.add_static_view` configuration method, so 607its behavior is almost exactly the same once it's configured. 608 609.. warning:: 610 611 The following example *will not work* for applications that use 612 :term:`traversal`; it will only work if you use :term:`URL dispatch` 613 exclusively. The root-relative route we'll be registering will always be 614 matched before traversal takes place, subverting any views registered via 615 ``add_view`` (at least those without a ``route_name``). A 616 :class:`~pyramid.static.static_view` static view cannot be made 617 root-relative when you use traversal unless it's registered as a :term:`Not 618 Found View`. 619 620To serve files within a directory located on your filesystem at 621``/path/to/static/dir`` as the result of a "catchall" route hanging from the 622root that exists at the end of your routing table, create an instance of the 623:class:`~pyramid.static.static_view` class inside a ``static.py`` file in your 624application root as below. 625 626.. code-block:: python 627 :linenos: 628 629 from pyramid.static import static_view 630 static_view = static_view('/path/to/static/dir', use_subpath=True) 631 632.. note:: 633 634 For better cross-system flexibility, use an :term:`asset specification` as 635 the argument to :class:`~pyramid.static.static_view` instead of a physical 636 absolute filesystem path, e.g., ``mypackage:static``, instead of 637 ``/path/to/mypackage/static``. 638 639Subsequently, you may wire the files that are served by this view up to be 640accessible as ``/<filename>`` using a configuration method in your 641application's startup code. 642 643.. code-block:: python 644 :linenos: 645 646 # .. every other add_route declaration should come 647 # before this one, as it will, by default, catch all requests 648 649 config.add_route('catchall_static', '/*subpath') 650 config.add_view('myapp.static.static_view', route_name='catchall_static') 651 652The special name ``*subpath`` above is used by the 653:class:`~pyramid.static.static_view` view callable to signify the path of the 654file relative to the directory you're serving. 655 656Registering a View Callable to Serve a "Static" Asset 657~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 658 659You can register a simple view callable to serve a single static asset. To do 660so, do things "by hand". First define the view callable. 661 662.. code-block:: python 663 :linenos: 664 665 import os 666 from pyramid.response import FileResponse 667 668 def favicon_view(request): 669 here = os.path.dirname(__file__) 670 icon = os.path.join(here, 'static', 'favicon.ico') 671 return FileResponse(icon, request=request) 672 673The above bit of code within ``favicon_view`` computes "here", which is a path 674relative to the Python file in which the function is defined. It then creates 675a :class:`pyramid.response.FileResponse` using the file path as the response's 676``path`` argument and the request as the response's ``request`` argument. 677:class:`pyramid.response.FileResponse` will serve the file as quickly as 678possible when it's used this way. It makes sure to set the right content 679length and content_type, too, based on the file extension of the file you pass. 680 681You might register such a view via configuration as a view callable that should 682be called as the result of a traversal: 683 684.. code-block:: python 685 :linenos: 686 687 config.add_view('myapp.views.favicon_view', name='favicon.ico') 688 689Or you might register it to be the view callable for a particular route: 690 691.. code-block:: python 692 :linenos: 693 694 config.add_route('favicon', '/favicon.ico') 695 config.add_view('myapp.views.favicon_view', route_name='favicon') 696 697Because this is a simple view callable, it can be protected with a 698:term:`permission` or can be configured to respond under different 699circumstances using :term:`view predicate` arguments. 700 701 702.. index:: 703 pair: overriding; assets 704 705.. _overriding_assets_section: 706 707Overriding Assets 708----------------- 709 710It can often be useful to override specific assets from "outside" a given 711:app:`Pyramid` application. For example, you may wish to reuse an existing 712:app:`Pyramid` application more or less unchanged. However, some specific 713template file owned by the application might have inappropriate HTML, or some 714static asset (such as a logo file or some CSS file) might not be appropriate. 715You *could* just fork the application entirely, but it's often more convenient 716to just override the assets that are inappropriate and reuse the application 717"as is". This is particularly true when you reuse some "core" application over 718and over again for some set of customers (such as a CMS application, or some 719bug tracking application), and you want to make arbitrary visual modifications 720to a particular application deployment without forking the underlying code. 721 722To this end, :app:`Pyramid` contains a feature that makes it possible to 723"override" one asset with one or more other assets. In support of this 724feature, a :term:`Configurator` API exists named 725:meth:`pyramid.config.Configurator.override_asset`. This API allows you to 726*override* the following kinds of assets defined in any Python package: 727 728- Individual template files. 729 730- A directory containing multiple template files. 731 732- Individual static files served up by an instance of the 733 ``pyramid.static.static_view`` helper class. 734 735- A directory of static files served up by an instance of the 736 ``pyramid.static.static_view`` helper class. 737 738- Any other asset (or set of assets) addressed by code that uses the setuptools 739 :term:`pkg_resources` API. 740 741.. index:: 742 single: override_asset 743 744.. _override_asset: 745 746The ``override_asset`` API 747~~~~~~~~~~~~~~~~~~~~~~~~~~ 748 749An individual call to :meth:`~pyramid.config.Configurator.override_asset` can 750override a single asset. For example: 751 752.. code-block:: python 753 :linenos: 754 755 config.override_asset( 756 to_override='some.package:templates/mytemplate.pt', 757 override_with='another.package:othertemplates/anothertemplate.pt') 758 759The string value passed to both ``to_override`` and ``override_with`` sent to 760the ``override_asset`` API is called an :term:`asset specification`. The colon 761separator in a specification separates the *package name* from the *asset 762name*. The colon and the following asset name are optional. If they are not 763specified, the override attempts to resolve every lookup into a package from 764the directory of another package. For example: 765 766.. code-block:: python 767 :linenos: 768 769 config.override_asset(to_override='some.package', 770 override_with='another.package') 771 772Individual subdirectories within a package can also be overridden: 773 774.. code-block:: python 775 :linenos: 776 777 config.override_asset(to_override='some.package:templates/', 778 override_with='another.package:othertemplates/') 779 780If you wish to override a directory with another directory, you *must* make 781sure to attach the slash to the end of both the ``to_override`` specification 782and the ``override_with`` specification. If you fail to attach a slash to the 783end of a specification that points to a directory, you will get unexpected 784results. 785 786You cannot override a directory specification with a file specification, and 787vice versa; a startup error will occur if you try. You cannot override an 788asset with itself; a startup error will occur if you try. 789 790Only individual *package* assets may be overridden. Overrides will not 791traverse through subpackages within an overridden package. This means that if 792you want to override assets for both ``some.package:templates``, and 793``some.package.views:templates``, you will need to register two overrides. 794 795The package name in a specification may start with a dot, meaning that the 796package is relative to the package in which the configuration construction file 797resides (or the ``package`` argument to the 798:class:`~pyramid.config.Configurator` class construction). For example: 799 800.. code-block:: python 801 :linenos: 802 803 config.override_asset(to_override='.subpackage:templates/', 804 override_with='another.package:templates/') 805 806Multiple calls to ``override_asset`` which name a shared ``to_override`` but a 807different ``override_with`` specification can be "stacked" to form a search 808path. The first asset that exists in the search path will be used; if no asset 809exists in the override path, the original asset is used. 810 811Asset overrides can actually override assets other than templates and static 812files. Any software which uses the 813:func:`pkg_resources.get_resource_filename`, 814:func:`pkg_resources.get_resource_stream`, or 815:func:`pkg_resources.get_resource_string` APIs will obtain an overridden file 816when an override is used. 817 818.. versionadded:: 1.6 819 As of Pyramid 1.6, it is also possible to override an asset by supplying an 820 absolute path to a file or directory. This may be useful if the assets are 821 not distributed as part of a Python package. 822 823Cache Busting and Asset Overrides 824~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 825 826Overriding static assets that are being hosted using 827:meth:`pyramid.config.Configurator.add_static_view` can affect your cache 828busting strategy when using any cache busters that are asset-aware such as 829:class:`pyramid.static.ManifestCacheBuster`. What sets asset-aware cache 830busters apart is that they have logic tied to specific assets. For example, 831a manifest is only generated for a specific set of pre-defined assets. Now, 832imagine you have overridden an asset defined in this manifest with a new, 833unknown version. By default, the cache buster will be invoked for an asset 834it has never seen before and will likely end up returning a cache busting 835token for the original asset rather than the asset that will actually end up 836being served! In order to get around this issue, it's possible to attach a 837different :class:`pyramid.interfaces.ICacheBuster` implementation to the 838new assets. This would cause the original assets to be served by their 839manifest, and the new assets served by their own cache buster. To do this, 840:meth:`pyramid.config.Configurator.add_cache_buster` supports an ``explicit`` 841option. For example: 842 843.. code-block:: python 844 :linenos: 845 846 from pyramid.static import ManifestCacheBuster 847 848 # define a static view for myapp:static assets 849 config.add_static_view('static', 'myapp:static') 850 851 # setup a cache buster for your app based on the myapp:static assets 852 my_cb = ManifestCacheBuster('myapp:static/manifest.json') 853 config.add_cache_buster('myapp:static', my_cb) 854 855 # override an asset 856 config.override_asset( 857 to_override='myapp:static/background.png', 858 override_with='theme:static/background.png') 859 860 # override the cache buster for theme:static assets 861 theme_cb = ManifestCacheBuster('theme:static/manifest.json') 862 config.add_cache_buster('theme:static', theme_cb, explicit=True) 863 864In the above example there is a default cache buster, ``my_cb``, for all 865assets served from the ``myapp:static`` folder. This would also affect 866``theme:static/background.png`` when generating URLs via 867``request.static_url('myapp:static/background.png')``. 868 869The ``theme_cb`` is defined explicitly for any assets loaded from the 870``theme:static`` folder. Explicit cache busters have priority and thus 871``theme_cb`` would be invoked for 872``request.static_url('myapp:static/background.png')``, but ``my_cb`` would 873be used for any other assets like 874``request.static_url('myapp:static/favicon.ico')``. 875