import pkg_resources
from tw.core.view import EngineManager
from tw.core.util import RequestLocalDescriptor, disable_runtime_checks
from tw.core.registry import StackedObjectProxy, Registry
__all__ = ["HostFramework"]
class RequestLocal(object):
def __init__(self, environ):
self.environ = environ
self.resources = {}
class HostFramework(object):
"""
This class is the interface between ToscaWidgets and the framework or
web application that's using them.
The an instance of this class should be passed as second argument to
:class:`tw.core.middleware.ToscaWidgetsMiddleware` which will call its
:meth:`start_request` method at the beginning of every
request and :meth:`end_request` when the request is over so I have a chance
to register our per-request context.
A request-local proxy to a configured instance is placed at the beginning
of the request at :attr:`tw.framework`
**Constructor's arguments:**
engines
An instance of :class:`tw.core.view.EngineManager`.
default_view
The name of the template engine used by default in the container app's
templates. It's used to determine what conversion is neccesary when
displaying root widgets on a template.
translator
Function used to translate strings.
enable_runtime_checks
Enables runtime checks for possible programming errors regarding
modifying widget attributes once a widget has been initialized.
Disabling this option can significantly reduce Widget initializatio
time.
.. note::
This operation modifies the Widget class and will affect any
application using ToscaWidgets in the same process.
aggregation_config
This option can either be None, or must be a dictionary. The dictionary can
have two keys, **js** and **css**. The value of each key is a list of dicts
with the following keys:
- **modname** the module to create the resource link from.
- **filename** the filename relative to the **modname** of the aggregated resources.
- **map** the mapping-file for the aggregated resources. If not given, defaults to
``.map``.
"""
request_local = StackedObjectProxy(name="ToscaWidgets per-request storage")
request_local_class = RequestLocal
default_view = RequestLocalDescriptor('default_view', 'toscawidgets')
def __init__(self, engines=None, default_view='toscawidgets',
translator=lambda s: s, enable_runtime_checks=True, default_engine=None,
aggregation_config=None):
if engines is None:
engines = EngineManager()
self.engines = engines
self._default_view = default_view
self._default_engine = default_engine
if default_engine is None:
self._default_engine = default_view
self.translator = translator
if not enable_runtime_checks:
disable_runtime_checks()
# these are for now not set up,
# this is done lazy, see belowe
self.aggregation_config = aggregation_config
self.aggregated_js_mapping = {}
self.aggregated_css_mapping = {}
self.middleware = None
def start_request(self, environ):
"""
Called by the middleware when a request has just begun so I have a
chance to register the request context Widgets will use for various
things.
"""
registry = environ['paste.registry']
registry.register(self.request_local, self.request_local_class(environ))
self.request_local.default_view = self._default_view
def end_request(self, environ):
"""
Called by the middleware when a request has just finished so I can
clean up.
"""
pass
def url(self, url):
"""
Returns the absolute path for the given url.
"""
prefix = self.request_local.environ['toscawidgets.prefix']
script_name = self.request_local.environ['SCRIPT_NAME']
if hasattr(url, 'url_mapping'):
url = url.url_mapping['normal']
return ''.join([script_name, prefix, url])
def register_resources(self, resources):
"""
Registers resources for injection in the current request.
"""
from tw.api import merge_resources
merge_resources(self.request_local.resources, resources)
def pop_resources(self):
"""
Returns returns the resources that have been registered for this
request and removes them from request-local storage area.
"""
resources = self.request_local.resources
self.request_local.resources = {}
# deal with aggregated resources
if resources and "head" in resources:
# This is lazy, because we otherwise run
# into circular import issues
if self.aggregation_config is not None:
self._setup_aggregation_mapping()
if self.aggregated_js_mapping:
self._replace_resources_with_aggregates(resources,
self.aggregated_js_mapping,
JSLink,
)
if self.aggregated_css_mapping:
self._replace_resources_with_aggregates(resources,
self.aggregated_css_mapping,
CSSLink,
)
return resources
def _setup_aggregation_mapping(self):
from tw.core.resources import JSLink, CSSLink, AggregatedJSLink, AggregatedCSSLink
# insert into the globals. This is nasty...
globals()["JSLink"] = JSLink
globals()["CSSLink"] = CSSLink
if self.aggregation_config is not None:
config = self.aggregation_config
if "js" in config:
self._compute_mapping(config["js"], self.aggregated_js_mapping, AggregatedJSLink)
if "css" in config:
self._compute_mapping(config["css"], self.aggregated_css_mapping, AggregatedCSSLink)
self.aggregation_config = None
def _compute_mapping(self, configs, mapping, kind):
aggregate_mappings = set()
for config in configs:
modname = config["modname"]
filename = config["filename"]
if "map" in config:
map_name = config["map"]
else:
map_name = "%s.map" % filename
# we don't allow double-registration
# of an aggregate file
if (modname, filename) in aggregate_mappings:
raise Exception("Double aggregation file mapping: %s/%s" % (modname, filename))
aggregate_mappings.add((modname, filename))
resource = kind(modname=modname, filename=filename)
for line in pkg_resources.resource_stream(modname, map_name):
line = line.strip()
if not line:
continue
jslink_desc = tuple(line.split("|", 1))
if jslink_desc in mapping and mapping[jslink_desc] is not resource:
raise Exception("Double js file mapping: %r" % (jslink_desc,))
mapping[jslink_desc] = resource
def _replace_resources_with_aggregates(self, resources, mapping, kind):
replaced_resources = set()
new_resources = []
for resource in resources["head"]:
if not isinstance(resource, kind):
new_resources.append(resource)
continue
desc = resource.modname, resource.active_filename()
if desc in mapping:
aggregated_resource = mapping[desc]
if aggregated_resource in replaced_resources:
continue
replaced_resources.add(aggregated_resource)
new_resources.append(aggregated_resource)
else:
new_resources.append(resource)
resources["head"] = new_resources