Source code for flask_weasyprint

# coding: utf8
"""
    flask_weasyprint
    ~~~~~~~~~~~~~~~~

    Flask-WeasyPrint: Make PDF in your Flask app with WeasyPrint.

    :copyright: (c) 2012 by Simon Sapin.
    :license: BSD, see LICENSE for more details.

"""

import weasyprint
from flask import request, current_app
from werkzeug.test import Client, ClientRedirectError
from werkzeug.wrappers import Response

try:
    import urlparse
except ImportError:  # Python 3
    from urllib import parse as urlparse

try:
    unicode
except NameError:  # Python 3
    unicode = str


VERSION = '0.5'
__all__ = ['VERSION', 'make_flask_url_dispatcher', 'make_url_fetcher',
           'HTML', 'CSS', 'render_pdf']


DEFAULT_PORTS = frozenset([('http', 80), ('https', 443)])


[docs]def make_flask_url_dispatcher(): """Return an URL dispatcher based on the current :ref:`request context <flask:request-context>`. You generally don’t need to call this directly. The context is used when the dispatcher is first created but not afterwards. It is not required after this function has returned. Dispatch to the context’s app URLs below the context’s root URL. If the app has a ``SERVER_NAME`` :ref:`config <flask:config>`, also accept URLs that have that domain name or a subdomain thereof. """ def parse_netloc(netloc): """Return (hostname, port).""" parsed = urlparse.urlsplit('http://' + netloc) return parsed.hostname, parsed.port app = current_app._get_current_object() root_path = request.script_root server_name = app.config.get('SERVER_NAME') if server_name: hostname, port = parse_netloc(server_name) def accept(url): """Accept any URL scheme; also accept subdomains.""" return url.hostname is not None and ( url.hostname == hostname or url.hostname.endswith('.' + hostname)) else: scheme = request.scheme hostname, port = parse_netloc(request.host) if (scheme, port) in DEFAULT_PORTS: port = None def accept(url): """Do not accept subdomains.""" return (url.scheme, url.hostname) == (scheme, hostname) def dispatch(url_string): if isinstance(url_string, bytes): url_string = url_string.decode('utf8') url = urlparse.urlsplit(url_string) url_port = url.port if (url.scheme, url_port) in DEFAULT_PORTS: url_port = None if accept(url) and url_port == port and url.path.startswith(root_path): netloc = url.netloc if url.port and not url_port: netloc = netloc.rsplit(':', 1)[0] # remove default port base_url = '%s://%s%s' % (url.scheme, netloc, root_path) path = url.path[len(root_path):] if url.query: path += '?' + url.query # Ignore url.fragment return app, base_url, path return dispatch
[docs]def make_url_fetcher(dispatcher=None, next_fetcher=weasyprint.default_url_fetcher): """Return an function suitable as a ``url_fetcher`` in WeasyPrint. You generally don’t need to call this directly. If ``dispatcher`` is not provided, :func:`make_flask_url_dispatcher` is called to get one. This requires a request context. Otherwise, it must be a callable that take an URL and return either ``None`` or a ``(wsgi_callable, base_url, path)`` tuple. For None ``next_fetcher`` is used. (By default, fetch normally over the network.) For a tuple the request is made at the WSGI level. ``wsgi_callable`` must be a Flask application or another WSGI callable. ``base_url`` is the root URL for the application while ``path`` is the path within the application. Typically ``base_url + path`` is equal or equivalent to the passed URL. """ if dispatcher is None: dispatcher = make_flask_url_dispatcher() def flask_url_fetcher(url): redirect_chain = set() while 1: result = dispatcher(url) if result is None: return next_fetcher(url) app, base_url, path = result client = Client(app, response_wrapper=Response) if isinstance(path, unicode): # TODO: double-check this. Apparently Werzeug %-unquotes bytes # but not Unicode URLs. (IRI vs. URI or something.) path = path.encode('utf8') response = client.get(path, base_url=base_url) if response.status_code == 200: return dict( string=response.data, mime_type=response.mimetype, encoding=response.charset, redirected_url=url) # The test client can follow redirects, but do it ourselves # to get access to the redirected URL. elif response.status_code in (301, 302, 303, 305, 307): redirect_chain.add(url) url = response.location if url in redirect_chain: raise ClientRedirectError('loop detected') else: raise ValueError('Flask-WeasyPrint got HTTP status %s for %s%s' % (response.status, base_url, path)) return flask_url_fetcher
def _wrapper(class_, *args, **kwargs): if args: guess = args[0] args = args[1:] else: guess = kwargs.pop('guess', None) if guess is not None and not hasattr(guess, 'read'): # Assume a (possibly relative) URL guess = urlparse.urljoin(request.url, guess) if 'string' in kwargs and 'base_url' not in kwargs: # Strings do not have an "intrinsic" base URL, use the request context. kwargs['base_url'] = request.url kwargs['url_fetcher'] = make_url_fetcher() return class_(guess, *args, **kwargs)
[docs]def HTML(*args, **kwargs): """Like `weasyprint.HTML() <http://weasyprint.org/using/#the-weasyprint-html-class>`_ but: * :func:`make_url_fetcher` is used to create an ``url_fetcher`` * If ``guess`` is not a file object, it is an URL relative to the current request context. This means that you can just pass a result from :func:`flask.url_for`. * If ``string`` is passed, ``base_url`` defaults to the current request’s URL. This requires a Flask request context. """ return _wrapper(weasyprint.HTML, *args, **kwargs)
[docs]def CSS(*args, **kwargs): return _wrapper(weasyprint.CSS, *args, **kwargs)
CSS.__doc__ = HTML.__doc__.replace('HTML', 'CSS').replace('html', 'css')
[docs]def render_pdf(html, stylesheets=None, download_filename=None): """Render a PDF to a response with the correct ``Content-Type`` header. :param html: Either a :class:`weasyprint.HTML` object or an URL to be passed to :func:`flask_weasyprint.HTML`. The latter case requires a request context. :param stylesheets: A list of user stylesheets, passed to :meth:`~weasyprint.HTML.write_pdf` :param download_filename: If provided, the ``Content-Disposition`` header is set so that most web browser will show the "Save as…" dialog with the value as the default filename. :returns: a :class:`flask.Response` object. """ if not hasattr(html, 'write_pdf'): html = HTML(html) pdf = html.write_pdf(stylesheets=stylesheets) response = current_app.response_class(pdf, mimetype='application/pdf') if download_filename: response.headers.add('Content-Disposition', 'attachment', filename=download_filename) return response