def testAssign(self): a = AttributeDictionary() a['ciao'] = 5 self.assertEqual(a.ciao, 5) self.assertEqual(a['ciao'], 5) self.assertEqual(list(a.values()), [5]) self.assertEqual(list(a.items()), [('ciao', 5)])
def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self.route = rule self._name = parameters.pop('name', rule.rule) self.routes = [] for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) if rparameters.pop('async', False): # asynchronous method handler = async ()(handler) handler.rule_method = rule_method router = self.add_child(Router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, name, value)
def testAssign(self): a = AttributeDictionary() a['ciao'] = 5 self.assertEqual(a.ciao, 5) self.assertEqual(a['ciao'], 5) self.assertEqual(list(a.values()), [5]) self.assertEqual(list(a.items()), [('ciao',5)])
def get_session(self, request, key): session = request.app.cache_server.get_json(self._key(key)) if session: session = AttributeDictionary(session) if session.user_id: session.user = self.get_user(request, user_id=session.user_id) return session
def items(self, request): cms = request.app.cms for item in cms.all(request, self.name): html_url = request.absolute_uri(item['path']) if html_url.endswith('/index'): html_url = html_url[:-6] page = AttributeDictionary(loc=html_url, lastmod=item.get('modified')) if cms.set_priority: page.priority = item.get('priority', 1) yield page
def session_create(self, request, id=None, user=None, expiry=None): '''Create a new session ''' if not id: id = uuid.uuid4().hex session = AttributeDictionary(id=id) if expiry: session.expiry = expiry.isoformat() if user: session.user_id = user.id session.user = user return session
def __init__(self, app, content, metadata, path, src=None, **params): self._app = app self._content = content self._path = path self._src = src self._meta = AttributeDictionary(params) self._update_meta(metadata) if not self._meta.modified: if src: self._meta.modified = modified_datetime(src) else: self._meta.modified = datetime.now() self._meta.name = slugify(self._path, separator='_')
def process_request(self, request): from django.http import HttpResponse data = AttributeDictionary(request.__dict__) environ = data.pop('environ') environ['django.cache'] = data response = self._web_socket(environ, None) if response is not None: # Convert to django response if is_failure(response): response.throw() resp = HttpResponse(status=response.status_code, content_type=response.content_type) for header, value in response.headers: resp[header] = value return resp
def __test_pickle(self): # TODO: this fails at times a = AttributeDictionary() a['ciao'] = 5 b = pickle.dumps(a) c = pickle.loads(b) self.assertEqual(a, c)
def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self.route = rule self._name = parameters.pop('name', rule.rule) self.routes = [] for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) if rparameters.pop('async', False): # asynchronous method handler = async()(handler) handler.rule_method = rule_method router = self.add_child(Router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, name, value)
def __init__(self, environ, name=None): self.environ = environ if 'pulsar.cache' not in environ: environ['pulsar.cache'] = AttributeDictionary() self.cache.mixins = {} if name: self.cache.mixins[name] = self
def items(self, request): for index, map in enumerate(self.cms.sitemaps): if not index: continue url = request.absolute_uri(str(map.route)) _, last_modified = map.sitemap(request) yield AttributeDictionary(loc=url, lastmod=last_modified)
def items(self, request): middleware = request.app._handler.middleware for map in middleware: if isinstance(map, RouterMap): url = request.absolute_uri(str(map.route)) _, last_modified = map.sitemap(request) yield AttributeDictionary(loc=url, lastmod=last_modified)
def __init__(self, url, version=None, data=None, full_response=False, **kw): self.__url = url self.__version = version or self.__class__.default_version self._full_response = full_response self.__data = data if data is not None else {} self.local = AttributeDictionary() self.setup(**kw)
def before_test_function_run(self, test): '''Called before the test run, in the test process domain.''' test.plugins = plugins = {} for p in self.plugins: local = AttributeDictionary() plugins[p.name] = local test = p.before_test_function_run(test, local) or test return test
def __init__(self, environ, name=None): self.environ = environ if pulsar_cache not in environ: environ[pulsar_cache] = AttributeDictionary() self.cache.mixins = {} self.cache.logger = LOGGER if name: self.cache.mixins[name] = self
def init_parameters(self, tag=None, **parameters): '''Called at the and of initialisation. It fills the :attr:`parameters` attribute. It can be overwritten to customise behaviour. ''' self.tag = tag or self.tag self.parameters = AttributeDictionary(parameters)
def before_test_function_run(self, test): '''Called just before the test is run''' test.plugins = plugins = {} for p in self.plugins: local = AttributeDictionary() plugins[p.name] = local test = p.before_test_function_run(test, local) or test return test
def __init__(self, url, name=None, version=None, id=None, data=None, **kwargs): self.__url = url self.__name = name self.__version = version or self.__class__.default_version self.__id = id self.__data = data if data is not None else {} self.local = AttributeDictionary() self.setup(**kwargs)
def inner_html(self, request, page, self_comp=''): '''Build page html content using json layout. :param layout: json (with rows and components) e.g. layout = { 'rows': [ {}, {cols: ['col-md-6', 'col-md-6']}, {cols: ['col-md-6', 'col-md-6']} ], 'components': [ {'type': 'text', 'id': 1, 'row': 0, 'col': 0, 'pos': 0}, {'type': 'gallery', 'id': 2, 'row': 1, 'col': 1, 'pos': 0} ] } :return: raw html <div class="row"> <div class="col-md-6"> <render-component id="1" text></render-component> </div> <div class="col-md-6"></div> </div> <div class="row"> <div class="col-md-6"></div> <div class="col-md-6"> <render-component id="2" gallery></render-component> </div> </div> ''' layout = page.layout if layout: try: layout = json.loads(layout) except Exception: request.app.logger.exception('Could not parse layout') layout = None if not layout: layout = dict(rows=[{}]) components = layout.get('components') or [] if not components: components.append(dict(type='self')) inner = Html(None) # Loop over rows for row_idx, row in enumerate(layout.get('rows', ())): row = AttributeDictionary(row) if row.cols: html = self._row(row, components, self_comp) else: html = self._component(components[0], self_comp) html = super().inner_html(request, page, html) inner.append(html) return inner.render(request)
def __init__(self, app, content, metadata, src, path=None, context=None, **params): self._app = app self._content = content self._context_for = context self._additional_context = {} self._src = src self._path = path or src self._meta = AttributeDictionary(params) if src: self._meta.modified = modified_datetime(src) else: self._meta.modified = datetime.now() # Get the site meta data dictionary. # Used to render Content metadata self._update_meta(metadata) meta = self._meta if self.is_html: dir, slug = os.path.split(self._path) if not slug: slug = self._path dir = None if not meta.slug: meta.slug = slugify(slug, separator='_') if dir: meta.slug = '%s/%s' % (dir, meta.slug) else: if self.suffix: # Any other file suffix = '.%s' % self.suffix if not self._path.endswith(suffix): self._path = self._path + suffix if not meta.slug: meta.slug = self._path meta.name = slugify(meta.slug, separator='_') for name in self.mandatory_properties: if not meta.get(name): raise BuildError("Property '%s' not available in %s" % (name, self))
def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self._route = rule self._name = parameters.pop('name', rule.name) self.routes = [] # add routes specified via the initialiser first for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) router = self.add_child(self.make_router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, slugify(name, separator='_'), value)
def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self._route = rule self._name = parameters.pop('name', rule.rule) self.routes = [] # add routes specified via the initialiser for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) router = self.add_child(Router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, name, value)
class Router(RouterType('RouterBase', (object,), {})): '''A :ref:`WSGI middleware <wsgi-middleware>` to handle client requests on multiple :ref:`routes <apps-wsgi-route>`. The user must implement the HTTP methods required by the application. For example if the route needs to serve a ``GET`` request, the ``get(self, request)`` method must be implemented. :param rule: String used for creating the :attr:`route` of this :class:`Router`. :param routes: Optional :class:`Router` instances which are added to the children :attr:`routes` of this router. :param parameters: Optional parameters for this router. They are stored in the :attr:`parameters` attribute with the exception of :attr:`response_content_types` and :attr:`response_wrapper` .. attribute:: rule_methods A class attribute built during class creation. It is an ordered dictionary mapping method names with a five-elements tuple containing information about a child route (See the :class:`.route` decorator). .. attribute:: routes List of children :class:`Router` of this :class:`Router`. .. attribute:: parent The parent :class:`Router` of this :class:`Router`. .. attribute:: response_content_types A list/tuple of possible content types of a response to a client request. The client request must accept at least one of the response content types, otherwise an HTTP ``415`` exception occurs. .. attribute:: response_wrapper Optional function which wraps all handlers of this :class:`.Router`. The function must accept two parameters, the original handler and the :class:`.WsgiRequest`:: def response_wrapper(handler, request): ... return handler(request) .. attribute:: parameters A :class:`.AttributeDictionary` of parameters for this :class:`Router`. Parameters are created at initialisation from the ``parameters`` class attribute and the key-valued parameters passed to the ``__init__`` method which are available in the class ``parameters`` attribute. ''' _creation_count = 0 _parent = None _name = None response_content_types = RouterParam(None) response_wrapper = RouterParam(None) def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self._route = rule self._name = parameters.pop('name', rule.name) self.routes = [] # add routes specified via the initialiser first for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) router = self.add_child(self.make_router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, slugify(name, separator='_'), value) @property def route(self): '''The relative :class:`.Route` served by this :class:`Router`. ''' parent = self._parent if parent and parent._route.is_leaf: return parent.route + self._route else: return self._route @property def full_route(self): '''The full :attr:`route` for this :class:`.Router`. It includes the :attr:`parent` portion of the route if a parent router is available. ''' if self._parent: return self._parent.full_route + self._route else: return self._route @property def name(self): '''The name of this :class:`Router`. This attribute can be specified during initialisation. If available, it can be used to retrieve a child router by name via the :meth:`get_route` method. ''' return self._name @property def root(self): '''The root :class:`Router` for this :class:`Router`.''' if self.parent: return self.parent.root else: return self @property def parent(self): return self._parent @property def creation_count(self): '''Integer for sorting :class:`Router` by creation. Auto-generated during initialisation.''' return self._creation_count @property def rule(self): '''The full ``rule`` string for this :class:`Router`. It includes the :attr:`parent` portion of the rule if a :attr:`parent` router is available. ''' return self.full_route.rule def path(self, **urlargs): '''The full path of this :class:`Router`. It includes the :attr:`parent` portion of url if a parent router is available. ''' return self.full_route.url(**urlargs) def getparam(self, name, default=None): '''A parameter in this :class:`.Router` ''' try: return getattr(self, name) except AttributeError: return default def __getattr__(self, name): '''Check the value of a :attr:`parameters` ``name``. If the parameter is not available, retrieve the parameter from the :attr:`parent` :class:`Router` if it exists. ''' if not name.startswith('_'): return self._get_router_parameter(name, False) self._no_param(name) def content_type(self, request): '''Evaluate the content type for the response to a client ``request``. The method uses the :attr:`response_content_types` parameter of accepted content types and the content types accepted by the client ``request`` and figures out the best match. ''' response_content_types = self.response_content_types request_content_types = request.content_types if request_content_types: ct = request_content_types.best_match(response_content_types) if ct and '*' in ct: ct = None if not ct and response_content_types: raise HttpException(status=415, msg=request_content_types) return ct def __repr__(self): return self.route.__repr__() def __call__(self, environ, start_response=None): path = environ.get('PATH_INFO') or '/' path = path[1:] router_args = self.resolve(path) if router_args: router, args = router_args return router.response(environ, args) def resolve(self, path, urlargs=None): '''Resolve a path and return a ``(handler, urlargs)`` tuple or ``None`` if the path could not be resolved. ''' match = self.route.match(path) if match is None: if not self.route.is_leaf: # no match return elif '__remaining__' in match: path = match.pop('__remaining__') urlargs = update_args(urlargs, match) else: return self, update_args(urlargs, match) # for handler in self.routes: view_args = handler.resolve(path, urlargs) if view_args is None: continue return view_args def response(self, environ, args): '''Once the :meth:`resolve` method has matched the correct :class:`Router` for serving the request, this matched router invokes this method to produce the WSGI response. ''' request = wsgi_request(environ, self, args) request.response.content_type = self.content_type(request) method = request.method.lower() callable = getattr(self, method, None) if callable is None: raise HttpException(status=405) response_wrapper = self.response_wrapper if response_wrapper: return response_wrapper(callable, request) return callable(request) def add_child(self, router): '''Add a new :class:`Router` to the :attr:`routes` list. ''' assert isinstance(router, Router), 'Not a valid Router' assert router is not self, 'cannot add self to children' # # Remove from previous parent if router.parent: router.parent.remove_child(router) router._parent = self # Loop over available routers to check it the router # is already available for r in self.routes: if r.route == router.route: r.parameters.update(router.parameters) return r self.routes.append(router) return router def remove_child(self, router): '''remove a :class:`Router` from the :attr:`routes` list.''' if router in self.routes: self.routes.remove(router) router._parent = None def get_route(self, name): '''Get a child :class:`Router` by its :attr:`name`. This method search child routes recursively. ''' for route in self.routes: if route.name == name: return route for child in self.routes: route = child.get_route(name) if route: return route def link(self, *args, **urlargs): '''Return an anchor :class:`Html` element with the `href` attribute set to the url of this :class:`Router`.''' if len(args) > 1: raise ValueError url = self.route.url(**urlargs) if len(args) == 1: text = args[0] else: text = url return Html('a', text, href=url) def has_parent(self, router): '''Check if ``router`` is ``self`` or a parent or ``self`` ''' parent = self while parent and parent is not router: parent = parent._parent return parent is not None def make_router(self, rule, cls=None, **params): '''Create a new :class:`.Router` from a ``rule`` and parameters. This method is used during initialisation when building child Routers from the :attr:`rule_methods`. ''' cls = cls or Router return cls(rule, **params) def _no_param(self, name): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def _get_router_parameter(self, name, safe=True): value = self.parameters.get(name) if value is None: if self._parent: return self._parent._get_router_parameter(name, safe) elif name in self.parameters: return value elif not safe: self._no_param(name) else: return value
class Router(RouterType('RouterBase', (object, ), {})): '''A :ref:`WSGI middleware <wsgi-middleware>` to handle client requests on multiple :ref:`routes <apps-wsgi-route>`. The user must implement the HTTP methods required by the application. For example if the route needs to serve a ``GET`` request, the ``get(self, request)`` method must be implemented. :param rule: String used for creating the :attr:`route` of this :class:`Router`. :param routes: Optional :class:`Router` instances which are added to the children :attr:`routes` of this router. :param parameters: Optional parameters for this router. They are stored in the :attr:`parameters` attribute. If a ``response_content_types`` value is passed, it overrides the :attr:`response_content_types` attribute. .. attribute:: route The :ref:`Route <apps-wsgi-route>` served by this :class:`Router`. .. attribute:: routes List of children :class:`Router` of this :class:`Router`. .. attribute:: parent The parent :class:`Router` of this :class:`Router`. .. attribute:: response_content_types a list/tuple of possible content types of a response to a client request. The client request must accept at least one of the response content types, otherwise an HTTP ``415`` exception occurs. .. attribute:: parameters A :class:`.AttributeDictionary` of parameters for this :class:`Router`. Parameters are created at initialisation from the ``parameters`` class attribute and the key-valued parameters passed to the ``__init__`` method for which the value is not callable. ''' _creation_count = 0 _parent = None _name = None response_content_types = RouterParam(None) def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self.route = rule self._name = parameters.pop('name', rule.rule) self.routes = [] for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) if rparameters.pop('async', False): # asynchronous method handler = async ()(handler) handler.rule_method = rule_method router = self.add_child(Router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, name, value) @property def name(self): '''The name of this :class:`Router`. This attribute can be specified during initialisation. If available, it can be used to retrieve a child router by name via the :meth:`get_route` method. ''' return self._name @property def root(self): '''The root :class:`Router` for this :class:`Router`.''' if self.parent: return self.parent.root else: return self @property def parent(self): return self._parent @property def default_content_type(self): '''The default content type for responses. This is the first element in the :attr:`response_content_types` list.''' ct = self.response_content_types return ct[0] if ct else None @property def creation_count(self): '''Integer for sorting :class:`Router` by creation. Auto-generated during initialisation.''' return self._creation_count @property def full_route(self): '''The full :attr:`route` for this :class:`Router`. It includes the :attr:`parent` portion of the route if a parent router is available.''' route = self.route if self._parent: route = self._parent.route + route return route @property def rule(self): '''The full ``rule`` string for this :class:`Router`. It includes the :attr:`parent` portion of rule if a parent router is available.''' return self.full_route.rule def path(self, **urlargs): '''The full path of this :class:`Router`. It includes the :attr:`parent` portion of url if a parent router is available.''' route = self.route if self._parent: route = self._parent.route + route return route.url(**urlargs) def __getattr__(self, name): '''Check the value of a :attr:`parameters` ``name``. If the parameter is not available, retrieve the parameter from the :attr:`parent` :class:`Router` if it exists. ''' if not name.startswith('_'): return self.get_parameter(name, False) self.no_param(name) def get_parameter(self, name, safe=True): value = self.parameters.get(name) if value is None: if self._parent: return self._parent.get_parameter(name, safe) elif name in self.parameters: return value elif not safe: self.no_param(name) else: return value def no_param(self, name): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def content_type(self, request): '''Evaluate the content type for the response to a client ``request``. The method uses the :attr:`response_content_types` parameter of accepted content types and the content types accepted by the client and figure out the best match. ''' response_content_types = self.response_content_types if response_content_types: return request.content_types.best_match(response_content_types) def accept_content_type(self, content_type): '''Check if ``content_type`` is accepted by this :class:`Router`. Return the best mach or ``None`` if not accepted.''' response_content_types = self.response_content_types if response_content_types: return ContentAccept([(content_type, 1) ]).best_match(response_content_types) def __repr__(self): return self.route.__repr__() def __call__(self, environ, start_response=None): path = environ.get('PATH_INFO') or '/' path = path[1:] router_args = self.resolve(path) if router_args: router, args = router_args return router.response(environ, args) else: if self.route.is_leaf: if path.endswith('/'): router_args = self.resolve(path[:-1]) if router_args is not None: return self.redirect(environ, '/%s' % path[:-1]) else: if not path.endswith('/'): router_args = self.resolve('%s/' % path) if router_args is not None: return self.redirect(environ, '/%s/' % path) def resolve(self, path, urlargs=None): '''Resolve a path and return a ``(handler, urlargs)`` tuple or ``None`` if the path could not be resolved.''' urlargs = urlargs if urlargs is not None else {} match = self.route.match(path) if match is None: return if '__remaining__' in match: remaining_path = match['__remaining__'] for handler in self.routes: view_args = handler.resolve(remaining_path, urlargs) if view_args is None: continue #remaining_path = match.pop('__remaining__','') #urlargs.update(match) return view_args else: return self, match @async (get_result=True) def response(self, environ, args): '''Once the :meth:`resolve` method has matched the correct :class:`Router` for serving the request, this matched router invokes this method to produce the WSGI response.''' request = wsgi_request(environ, self, args) # Set the response content type request.response.content_type = self.content_type(request) method = request.method.lower() callable = getattr(self, method, None) if callable is None: raise HttpException(status=405, msg='Method "%s" not allowed' % method) # make sure cache does not contain asynchronous data async_cache = multi_async(request.cache, raise_on_error=False) cache = yield async_cache if async_cache.num_failures: for key, value in list(cache.items()): if isinstance(value, Failure): cache.pop(key) environ['pulsar.cache'] = cache yield async_cache.failures else: environ['pulsar.cache'] = cache yield callable(request) @async (get_result=True) def redirect(self, environ, path): request = wsgi_request(environ, self) environ['pulsar.cache'] = yield multi_async(request.cache) raise HttpRedirect(path) def add_child(self, router): '''Add a new :class:`Router` to the :attr:`routes` list. If this :class:`Router` is a leaf route, add a slash to the url.''' assert isinstance(router, Router), 'Not a valid Router' assert router is not self, 'cannot add self to children' if self.route.is_leaf: self.route = Route('%s/' % self.route.rule) for r in self.routes: if r.route == router.route: r.parameters.update(router.parameters) return r if router.parent: router.parent.remove_child(router) router._parent = self self.routes.append(router) return router def remove_child(self, router): '''remove a :class:`Router` from the :attr:`routes` list.''' if router in self.routes: self.routes.remove(router) router._parent = None def get_route(self, name): '''Get a child :class:`Router` by its :attr:`name`.''' for route in self.routes: if route.name == name: return route def link(self, *args, **urlargs): '''Return an anchor :class:`Html` element with the `href` attribute set to the url of this :class:`Router`.''' if len(args) > 1: raise ValueError url = self.route.url(**urlargs) if len(args) == 1: text = args[0] else: text = url return Html('a', text, href=url) def sitemap(self, root=None): '''This utility method returns a sitemap starting at root. If *root* is ``None`` it starts from this :class:`Router`. :param request: a :ref:`wsgi request wrapper <app-wsgi-request>` :param root: Optional url path where to start the sitemap. By default it starts from this :class:`Router`. Pass `"/"` to start from the root :class:`Router`. :param levels: Number of nested levels to include. :return: A list of children ''' if not root: root = self else: handler_urlargs = self.root.resolve(root[1:]) if handler_urlargs: root, urlargs = handler_urlargs else: return [] return list(self.routes) def encoding(self, request): '''The encoding to use for the response. By default it returns ``utf-8``.''' return 'utf-8'
def __init__(self, *children, **params): self.as_list = params.pop('as_list', False) self.parameters = AttributeDictionary(params) for child in children: self.append(child)
class Router(RouterType('RouterBase', (object, ), {})): '''A :ref:`WSGI middleware <wsgi-middleware>` to handle client requests on multiple :ref:`routes <apps-wsgi-route>`. The user must implement the HTTP methods required by the application. For example if the route needs to serve a ``GET`` request, the ``get(self, request)`` method must be implemented. :param rule: String used for creating the :attr:`route` of this :class:`Router`. :param routes: Optional :class:`Router` instances which are added to the children :attr:`routes` of this router. :param parameters: Optional parameters for this router. They are stored in the :attr:`parameters` attribute with the exception of :attr:`response_content_types` and :attr:`response_wrapper` .. attribute:: rule_methods A class attribute built during class creation. It is an ordered dictionary mapping method names with a five-elements tuple containing information about a child route (See the :class:`.route` decorator). .. attribute:: routes List of children :class:`Router` of this :class:`Router`. .. attribute:: parent The parent :class:`Router` of this :class:`Router`. .. attribute:: response_content_types A list/tuple of possible content types of a response to a client request. The client request must accept at least one of the response content types, otherwise an HTTP ``415`` exception occurs. .. attribute:: response_wrapper Optional function which wraps all handlers of this :class:`.Router`. The function must accept two parameters, the original handler and the :class:`.WsgiRequest`:: def response_wrapper(handler, request): ... return handler(request) .. attribute:: parameters A :class:`.AttributeDictionary` of parameters for this :class:`Router`. Parameters are created at initialisation from the ``parameters`` class attribute and the key-valued parameters passed to the ``__init__`` method which are available in the class ``parameters`` attribute. ''' _creation_count = 0 _parent = None _name = None response_content_types = RouterParam(None) response_wrapper = RouterParam(None) def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self._route = rule self._name = parameters.pop('name', rule.name) self.routes = [] # add routes specified via the initialiser first for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) router = self.add_child(self.make_router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, slugify(name, separator='_'), value) @property def route(self): '''The relative :class:`.Route` served by this :class:`Router`. ''' parent = self._parent if parent and parent._route.is_leaf: return parent.route + self._route else: return self._route @property def full_route(self): '''The full :attr:`route` for this :class:`.Router`. It includes the :attr:`parent` portion of the route if a parent router is available. ''' if self._parent: return self._parent.full_route + self._route else: return self._route @property def name(self): '''The name of this :class:`Router`. This attribute can be specified during initialisation. If available, it can be used to retrieve a child router by name via the :meth:`get_route` method. ''' return self._name @property def root(self): '''The root :class:`Router` for this :class:`Router`.''' if self.parent: return self.parent.root else: return self @property def parent(self): return self._parent @property def creation_count(self): '''Integer for sorting :class:`Router` by creation. Auto-generated during initialisation.''' return self._creation_count @property def rule(self): '''The full ``rule`` string for this :class:`Router`. It includes the :attr:`parent` portion of the rule if a :attr:`parent` router is available. ''' return self.full_route.rule def path(self, **urlargs): '''The full path of this :class:`Router`. It includes the :attr:`parent` portion of url if a parent router is available. ''' return self.full_route.url(**urlargs) def getparam(self, name, default=None): '''A parameter in this :class:`.Router` ''' try: return getattr(self, name) except AttributeError: return default def __getattr__(self, name): '''Check the value of a :attr:`parameters` ``name``. If the parameter is not available, retrieve the parameter from the :attr:`parent` :class:`Router` if it exists. ''' if not name.startswith('_'): return self._get_router_parameter(name, False) self._no_param(name) def content_type(self, request): '''Evaluate the content type for the response to a client ``request``. The method uses the :attr:`response_content_types` parameter of accepted content types and the content types accepted by the client ``request`` and figures out the best match. ''' content_types = self.response_content_types ct = request.content_types.best_match(content_types) if ct and '*' in ct: ct = None if not ct and content_types: raise HttpException(status=415, msg=request.content_types) return ct def __repr__(self): return self.route.__repr__() def __call__(self, environ, start_response=None): path = environ.get('PATH_INFO') or '/' path = path[1:] router_args = self.resolve(path) if router_args: router, args = router_args return router.response(environ, args) def resolve(self, path, urlargs=None): '''Resolve a path and return a ``(handler, urlargs)`` tuple or ``None`` if the path could not be resolved. ''' match = self.route.match(path) if match is None: if not self.route.is_leaf: # no match return elif '__remaining__' in match: path = match.pop('__remaining__') urlargs = update_args(urlargs, match) else: return self, update_args(urlargs, match) # for handler in self.routes: view_args = handler.resolve(path, urlargs) if view_args is None: continue return view_args def response(self, environ, args): '''Once the :meth:`resolve` method has matched the correct :class:`Router` for serving the request, this matched router invokes this method to produce the WSGI response. ''' request = wsgi_request(environ, self, args) request.response.content_type = self.content_type(request) method = request.method.lower() callable = getattr(self, method, None) if callable is None: raise HttpException(status=405) response_wrapper = self.response_wrapper if response_wrapper: return response_wrapper(callable, request) return callable(request) def add_child(self, router): '''Add a new :class:`Router` to the :attr:`routes` list. ''' assert isinstance(router, Router), 'Not a valid Router' assert router is not self, 'cannot add self to children' # # Remove from previous parent if router.parent: router.parent.remove_child(router) router._parent = self # Loop over available routers to check it the router # is already available for r in self.routes: if r.route == router.route: r.parameters.update(router.parameters) return r self.routes.append(router) return router def remove_child(self, router): '''remove a :class:`Router` from the :attr:`routes` list.''' if router in self.routes: self.routes.remove(router) router._parent = None def get_route(self, name): '''Get a child :class:`Router` by its :attr:`name`. This method search child routes recursively. ''' for route in self.routes: if route.name == name: return route for child in self.routes: route = child.get_route(name) if route: return route def link(self, *args, **urlargs): '''Return an anchor :class:`Html` element with the `href` attribute set to the url of this :class:`Router`.''' if len(args) > 1: raise ValueError url = self.route.url(**urlargs) if len(args) == 1: text = args[0] else: text = url return Html('a', text, href=url) def has_parent(self, router): '''Check if ``router`` is ``self`` or a parent or ``self`` ''' parent = self while parent and parent is not router: parent = parent._parent return parent is not None def make_router(self, rule, cls=None, **params): '''Create a new :class:`.Router` from a ``rule`` and parameters. This method is used during initialisation when building child Routers from the :attr:`rule_methods`. ''' cls = cls or Router return cls(rule, **params) def _no_param(self, name): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def _get_router_parameter(self, name, safe=True): value = self.parameters.get(name) if value is None: if self._parent: return self._parent._get_router_parameter(name, safe) elif name in self.parameters: return value elif not safe: self._no_param(name) else: return value
def testInit(self): self.assertRaises(TypeError, AttributeDictionary, {}, {}) a = AttributeDictionary({'bla': 1}, foo='pippo') self.assertEqual(dict(a), {'bla': 1, 'foo': 'pippo'}) self.assertEqual(len(a), 2)
''' Constants used throughout pulsar. ''' from pulsar.utils.structures import AttributeDictionary # LOW LEVEL CONSTANTS - NO NEED TO CHANGE THOSE ########################### ACTOR_STATES = AttributeDictionary(INITIAL=0X0, INACTIVE=0X1, STARTING=0x2, RUN=0x3, STOPPING=0x4, CLOSE=0x5, TERMINATE=0x6) ''' .. _actor-states: Actor state constants are access via:: from pulsar import ACTOR_STATES They are: * ``ACTOR_STATES.INITIAL = 0`` when an actor is just created, before the :class:`pulsar.Actor.start` method is called. * ``ACTOR_STATES.STARTING = 2`` when :class:`pulsar.Actor.start` method is called. * ``ACTOR_STATES.RUN = 3`` when :class:`pulsar.Actor._loop` is up and running. * ``ACTOR_STATES.STOPPING = 4`` when :class:`pulsar.Actor.stop` has been called for the first time and the actor is running. '''
class Content(Cacheable): '''A class for managing a file-based content ''' template = None template_engine = None def __init__(self, app, content, metadata, path, src=None, **params): self._app = app self._content = content self._path = path self._src = src self._meta = AttributeDictionary(params) self._update_meta(metadata) if not self._meta.modified: if src: self._meta.modified = modified_datetime(src) else: self._meta.modified = datetime.now() self._meta.name = slugify(self._path, separator='_') @property def app(self): return self._app @property def content_type(self): return self._meta.content_type @property def is_text(self): return self._meta.content_type in CONTENT_EXTENSIONS @property def is_html(self): return is_html(self._meta.content_type) @property def suffix(self): return CONTENT_EXTENSIONS.get(self._meta.content_type) @property def path(self): return self._path @property def reldate(self): return self._meta.date or self._meta.modified @property def year(self): return self.reldate.year @property def month(self): return self.reldate.month @property def month2(self): return self.reldate.strftime('%m') @property def month3(self): return self.reldate.strftime('%b').lower() @property def id(self): if self.is_html: return '%s.json' % self._path def cache_key(self, app): return self._meta.name def __repr__(self): return self._path __str__ = __repr__ def key(self, name=None): '''The key for a context dictionary ''' name = name or self.name suffix = self.suffix return '%s_%s' % (suffix, name) if suffix else name def context(self, context=None): '''Extract the context dictionary for server side template rendering ''' ctx = dict(self._flatten(self._meta)) if context: ctx.update(context) return ctx def urlparams(self, names=None): urlparams = {} if names: for name in names: value = self._meta.get(name) or getattr(self, name, None) if value in (None, ''): if name == 'id': raise SkipBuild elif names: raise KeyError("%s could not obtain url variable '%s'" % (self, name)) urlparams[name] = value return urlparams def render(self, context=None): '''Render the content ''' if self.is_html: context = self.context(context) content = self._engine(self._content, context) if self.template: template = self._app.template_full_path(self.template) if template: context[self.key('main')] = content with open(template, 'r') as file: template_str = file.read() content = self._engine(template_str, context) return content else: return self._content def raw(self, request): return self._content @cached def json(self, request): '''Convert the content into a Json dictionary for the API ''' if self.is_html: context = self._app.context(request) context = self.context(context) # data = self._to_json(request, self._meta) text = data.get(self.suffix) or {} data[self.suffix] = text text['main'] = self.render(context) # head = {} for key in HEAD_META: value = data.get(key) if value: head[key] = value # if 'head' in data: head.update(data['head']) data['url'] = request.absolute_uri(self._path) data['head'] = head return data def html(self, request): '''Build the ``html_main`` key for this content and set content specific values to the ``head`` tag of the HTML5 document. ''' if not self.is_html: raise Unsupported # The JSON data for this page data = self.json(request) doc = request.html_document doc.jscontext['page'] = dict(page_info(data)) # image = absolute_uri(request, data.get('image')) doc.meta.update({'og:image': image, 'og:published_time': data.get('date'), 'og:modified_time': data.get('modified')}) doc.meta.update(data['head']) # if not request.config.get('HTML5_NAVIGATION'): for css in data.get('require_css') or (): doc.head.links.append(css) doc.head.scripts.require.extend(data.get('require_js') or ()) # if request.cache.uirouter is False: doc.head.scripts.require.extend(data.get('require_js') or ()) self.on_html(doc) return data[self.suffix]['main'] def on_html(self, doc): pass @classmethod def as_draft(cls): mp = tuple((a for a in cls.mandatory_properties if a not in no_draft_field)) return cls.__class__('Draft', (cls,), {'mandatory_properties': mp}) # INTERNALS def _update_meta(self, metadata): meta = self._meta meta.site = {} for name in ('template_engine', 'template'): default = getattr(self, name) value = metadata.pop(name, default) meta.site[name] = value setattr(self, name, value) context = self.context(self.app.config) self._engine = self._app.template_engine(self.template_engine) meta.update(((key, self._render_meta(value, context)) for key, value in metadata.items())) def _flatten(self, meta): for key, value in mapping_iterator(meta): if isinstance(value, Mapping): for child, value in self._flatten(value): yield '%s_%s' % (key, child), value else: yield key, self._to_string(value) def _to_string(self, value): if isinstance(value, Mapping): raise BuildError('A dictionary found when coverting to string') elif isinstance(value, (list, tuple)): return ', '.join(self._to_string(v) for v in value) elif isinstance(value, date): return iso8601(value) else: return to_string(value) def _render_meta(self, value, context): if isinstance(value, Mapping): return dict(((k, self._render_meta(v, context)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._render_meta(v, context) for v in value] elif isinstance(value, str): return self._engine(to_string(value), context) else: return value def _to_json(self, request, value): if isinstance(value, Mapping): return dict(((k, self._to_json(request, v)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._to_json(request, v) for v in value] elif isinstance(value, date): return iso8601(value) elif isinstance(value, URLWrapper): return value.to_json(request) else: return value
''' Constants used throughout pulsar. ''' from pulsar.utils.structures import AttributeDictionary # LOW LEVEL CONSTANTS - NO NEED TO CHANGE THOSE ########################### ACTOR_STATES = AttributeDictionary(INITIAL=0X0, INACTIVE=0X1, STARTING=0x2, RUN=0x3, STOPPING=0x4, CLOSE=0x5, TERMINATE=0x6) ''' .. _actor-states: Actor state constants are access via:: from pulsar.api import ACTOR_STATES They are: * ``ACTOR_STATES.INITIAL = 0`` when an actor is just created, before the :class:`pulsar.Actor.start` method is called. * ``ACTOR_STATES.STARTING = 2`` when :class:`pulsar.Actor.start` method is called. * ``ACTOR_STATES.RUN = 3`` when :class:`pulsar.Actor._loop` is up and running. * ``ACTOR_STATES.STOPPING = 4`` when :class:`pulsar.Actor.stop` has been called for the first time and the actor is running. '''
def testCopy(self): a = AttributeDictionary(foo=5, bla='ciao') self.assertEqual(len(a), 2) b = a.copy() self.assertEqual(a, b) self.assertNotEqual(id(a), id(b))
class Content(object): template = None template_engine = None _json_dict = None _loc = None '''Template engine to render this content. Overwritten my metadata. If not available, the application default engine is used''' mandatory_properties = () def __init__(self, app, content, metadata, src, path=None, context=None, **params): self._app = app self._content = content self._context_for = context self._additional_context = {} self._src = src self._path = path or src self._meta = AttributeDictionary(params) if src: self._meta.modified = modified_datetime(src) else: self._meta.modified = datetime.now() # Get the site meta data dictionary. # Used to render Content metadata self._update_meta(metadata) meta = self._meta if self.is_html: dir, slug = os.path.split(self._path) if not slug: slug = self._path dir = None if not meta.slug: meta.slug = slugify(slug, separator='_') if dir: meta.slug = '%s/%s' % (dir, meta.slug) else: if self.suffix: # Any other file suffix = '.%s' % self.suffix if not self._path.endswith(suffix): self._path = self._path + suffix if not meta.slug: meta.slug = self._path meta.name = slugify(meta.slug, separator='_') for name in self.mandatory_properties: if not meta.get(name): raise BuildError("Property '%s' not available in %s" % (name, self)) @property def name(self): return self._meta.name @property def src(self): return self._src @property def loc(self): return self._loc or self._src @loc.setter def loc(self, value): self._loc = value @property def content_type(self): return self._meta.content_type @property def is_text(self): return self._meta.content_type in CONTENT_EXTENSIONS @property def is_html(self): return is_html(self._meta.content_type) @property def suffix(self): return CONTENT_EXTENSIONS.get(self._meta.content_type) @property def path(self): return self._path @property def reldate(self): return self._meta.date or self._meta.modified @property def year(self): return self.reldate.year @property def month(self): return self.reldate.month @property def month2(self): return self.reldate.strftime('%m') @property def month3(self): return self.reldate.strftime('%b').lower() @property def id(self): if self.is_html: return '%s.json' % self._meta.slug @property def context_for(self): '''A list of contents names for which this snippet is required in the context dictionary ''' return self._context_for @property def additional_context(self): '''Dictionary of key and :class:`.Snippet` providing additional keys for this content ''' return self._additional_context def __repr__(self): return self._src __str__ = __repr__ def key(self, name=None): '''The key for a context dictionary ''' name = name or self.name return 'html_%s' % name if self.is_html else name def context(self, context=None): '''Extract the context dictionary for server side template rendering ''' ctx = dict(self._flatten(self._meta)) if context: ctx.update(context) return ctx def urlparams(self, names=None): urlparams = {} if names: for name in names: value = self._meta.get(name) or getattr(self, name, None) if value in (None, ''): if name == 'id': raise SkipBuild elif names: raise KeyError("%s could not obtain url variable '%s'" % (self, name)) urlparams[name] = value return urlparams def render(self, context=None): '''Render the content ''' if self.is_html: context = self.context(context) content = self._engine(self._content, context) if self.template: template = self._app.template_full_path(self.template) if template: context[self.key('main')] = content with open(template, 'r') as file: template_str = file.read() raw = self._engine(template_str, context) reader = get_reader(self._app, template) ct = reader.process(raw, template) content = ct._content return content else: return self._content def json(self, request): '''Convert the content into a Json dictionary for the API ''' if not self._json_dict and self.is_html: context = self._app.context(request) context = self.context(context) # Add additional context keys if self.additional_context: for key, ct in self.additional_context.items(): if isinstance(ct, Content): key = ct.key(key) ct = ct.render(context) context[key] = ct # assert self.suffix data = self._to_json(self._meta) text = data.get(self.suffix) or {} data[self.suffix] = text text['main'] = self.render(context) # head = {} for key in HEAD_META: value = data.get(key) if value: head[key] = value # require_css = data.get('require_css') if require_css: data['require_css'] = [] cfg = request.config links = Links(cfg['MEDIA_URL'], minified=cfg['MINIFIED_MEDIA']) for css in require_css: css = CssLibraries.get(css, css) links.append(css) for link in links.children: link = link.split("href=") if len(link) == 2: href = link[1] c = href[0] href = href[1:] link = href[:href.find(c)] data['require_css'].append(link) # if 'head' in data: head.update(data['head']) data['head'] = head self._json_dict = data return self._json_dict def html(self, request): '''Build the ``html_main`` key for this content and set content specific values to the ``head`` tag of the HTML5 document. ''' if not self.is_html: raise Unsupported # The JSON data for this page data = self.json(request) doc = request.html_document doc.jscontext['page'] = dict(page_info(data)) # doc.meta.update({'og:image': data.get('image'), 'og:published_time': data.get('date'), 'og:modified_time': data.get('modified')}) doc.meta.update(data['head']) # if not request.config['ANGULAR_UI_ROUTER']: for css in data.get('require_css') or (): doc.head.links.append(css) doc.head.scripts.require.extend(data.get('require_js') or ()) # if request.cache.uirouter is False: doc.head.scripts.require.extend(data.get('require_js') or ()) self.on_html(doc) return data[self.suffix]['main'] def on_html(self, doc): pass @classmethod def as_draft(cls): mp = tuple((a for a in cls.mandatory_properties if a not in no_draft_field)) return cls.__class__('Draft', (cls,), {'mandatory_properties': mp}) # INTERNALS def _update_meta(self, metadata): meta = self._meta meta.site = self._app.extensions['static'].build_info(self._app) context = self.context() for name in ('template_engine', 'template'): default = getattr(self, name) value = metadata.pop(name, default) meta.site[name] = value setattr(self, name, value) self._engine = engine = self._app.template_engine(self.template_engine) meta.update(((key, self._render_meta(value, context)) for key, value in metadata.items())) def _flatten(self, meta): for key, value in mapping_iterator(meta): if isinstance(value, Mapping): for child, value in self._flatten(value): yield '%s_%s' % (key, child), value else: yield key, self._to_string(value) def _to_string(self, value): if isinstance(value, Mapping): raise BuildError('A dictionary found when coverting to string') elif isinstance(value, (list, tuple)): return ', '.join(self._to_string(v) for v in value) elif isinstance(value, date): return iso8601(value) else: return to_string(value) def _render_meta(self, value, context): if isinstance(value, Mapping): return dict(((k, self._render_meta(v, context)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._render_meta(v, context) for v in value] elif isinstance(value, date): return value elif value is not None: return self._engine(to_string(value), context) def _to_json(self, value): if isinstance(value, Mapping): return dict(((k, self._to_json(v)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._to_json(v) for v in value] elif isinstance(value, date): return iso8601(value) else: return value
class Content(Cacheable): '''A class for managing a file-based content ''' template = None template_engine = None def __init__(self, app, content, metadata, path, src=None, **params): self._app = app self._content = content self._path = path self._src = src self._meta = AttributeDictionary(params) self._update_meta(metadata) if not self._meta.modified: if src: self._meta.modified = modified_datetime(src) else: self._meta.modified = datetime.now() self._meta.name = slugify(self._path, separator='_') @property def app(self): return self._app @property def content_type(self): return self._meta.content_type @property def is_text(self): return self._meta.content_type in CONTENT_EXTENSIONS @property def is_html(self): return is_html(self._meta.content_type) @property def suffix(self): return CONTENT_EXTENSIONS.get(self._meta.content_type) @property def path(self): return self._path @property def reldate(self): return self._meta.date or self._meta.modified @property def year(self): return self.reldate.year @property def month(self): return self.reldate.month @property def month2(self): return self.reldate.strftime('%m') @property def month3(self): return self.reldate.strftime('%b').lower() @property def id(self): if self.is_html: return '%s.json' % self._path def cache_key(self, app): return self._meta.name def __repr__(self): return self._path __str__ = __repr__ def key(self, name=None): '''The key for a context dictionary ''' name = name or self.name suffix = self.suffix return '%s_%s' % (suffix, name) if suffix else name def context(self, context=None): '''Extract the context dictionary for server side template rendering ''' ctx = dict(self._flatten(self._meta)) if context: ctx.update(context) return ctx def urlparams(self, names=None): urlparams = {} if names: for name in names: value = self._meta.get(name) or getattr(self, name, None) if value in (None, ''): if name == 'id': raise SkipBuild elif names: raise KeyError( "%s could not obtain url variable '%s'" % (self, name)) urlparams[name] = value return urlparams def render(self, context=None): '''Render the content ''' if self.is_html: context = self.context(context) content = self._engine(self._content, context) if self.template: template = self._app.template_full_path(self.template) if template: context[self.key('main')] = content with open(template, 'r') as file: template_str = file.read() content = self._engine(template_str, context) return content else: return self._content def raw(self, request): return self._content @cached def json(self, request): '''Convert the content into a Json dictionary for the API ''' if self.is_html: context = self._app.context(request) context = self.context(context) # data = self._to_json(request, self._meta) text = data.get(self.suffix) or {} data[self.suffix] = text text['main'] = self.render(context) # head = {} for key in HEAD_META: value = data.get(key) if value: head[key] = value # if 'head' in data: head.update(data['head']) data['url'] = request.absolute_uri(self._path) data['head'] = head return data def html(self, request): '''Build the ``html_main`` key for this content and set content specific values to the ``head`` tag of the HTML5 document. ''' if not self.is_html: raise Unsupported # The JSON data for this page data = self.json(request) doc = request.html_document doc.jscontext['page'] = dict(page_info(data)) # image = absolute_uri(request, data.get('image')) doc.meta.update({ 'og:image': image, 'og:published_time': data.get('date'), 'og:modified_time': data.get('modified') }) doc.meta.update(data['head']) # if not request.config.get('HTML5_NAVIGATION'): for css in data.get('require_css') or (): doc.head.links.append(css) doc.head.scripts.require.extend(data.get('require_js') or ()) # if request.cache.uirouter is False: doc.head.scripts.require.extend(data.get('require_js') or ()) self.on_html(doc) return data[self.suffix]['main'] def on_html(self, doc): pass @classmethod def as_draft(cls): mp = tuple( (a for a in cls.mandatory_properties if a not in no_draft_field)) return cls.__class__('Draft', (cls, ), {'mandatory_properties': mp}) # INTERNALS def _update_meta(self, metadata): meta = self._meta meta.site = {} for name in ('template_engine', 'template'): default = getattr(self, name) value = metadata.pop(name, default) meta.site[name] = value setattr(self, name, value) context = self.context(self.app.config) self._engine = self._app.template_engine(self.template_engine) meta.update(((key, self._render_meta(value, context)) for key, value in metadata.items())) def _flatten(self, meta): for key, value in mapping_iterator(meta): if isinstance(value, Mapping): for child, value in self._flatten(value): yield '%s_%s' % (key, child), value else: yield key, self._to_string(value) def _to_string(self, value): if isinstance(value, Mapping): raise BuildError('A dictionary found when coverting to string') elif isinstance(value, (list, tuple)): return ', '.join(self._to_string(v) for v in value) elif isinstance(value, date): return iso8601(value) else: return to_string(value) def _render_meta(self, value, context): if isinstance(value, Mapping): return dict( ((k, self._render_meta(v, context)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._render_meta(v, context) for v in value] elif isinstance(value, str): return self._engine(to_string(value), context) else: return value def _to_json(self, request, value): if isinstance(value, Mapping): return dict( ((k, self._to_json(request, v)) for k, v in value.items())) elif isinstance(value, (list, tuple)): return [self._to_json(request, v) for v in value] elif isinstance(value, date): return iso8601(value) elif isinstance(value, URLWrapper): return value.to_json(request) else: return value
class Router(RouterType('RouterBase', (object,), {})): '''A :ref:`WSGI middleware <wsgi-middleware>` to handle client requests on multiple :ref:`routes <apps-wsgi-route>`. The user must implement the HTTP methods required by the application. For example if the route needs to serve a ``GET`` request, the ``get(self, request)`` method must be implemented. :param rule: String used for creating the :attr:`route` of this :class:`Router`. :param routes: Optional :class:`Router` instances which are added to the children :attr:`routes` of this router. :param parameters: Optional parameters for this router. They are stored in the :attr:`parameters` attribute. If a ``response_content_types`` value is passed, it overrides the :attr:`response_content_types` attribute. .. attribute:: routes List of children :class:`Router` of this :class:`Router`. .. attribute:: parent The parent :class:`Router` of this :class:`Router`. .. attribute:: response_content_types a list/tuple of possible content types of a response to a client request. The client request must accept at least one of the response content types, otherwise an HTTP ``415`` exception occurs. .. attribute:: allows_redirects boolean indicating if this router can redirect requests to valid urls within this router and its children. For example, if a router serves the '/echo' url but not the ``/echo/`` one, a request on ``/echo/`` will be redirected to ``/echo``. Default: ``False`` .. attribute:: parameters A :class:`.AttributeDictionary` of parameters for this :class:`Router`. Parameters are created at initialisation from the ``parameters`` class attribute and the key-valued parameters passed to the ``__init__`` method for which the value is not callable. ''' _creation_count = 0 _parent = None _name = None response_content_types = RouterParam(None) allows_redirects = RouterParam(False) def __init__(self, rule, *routes, **parameters): Router._creation_count += 1 self._creation_count = Router._creation_count if not isinstance(rule, Route): rule = Route(rule) self._route = rule self._name = parameters.pop('name', rule.rule) self.routes = [] # add routes specified via the initialiser for router in routes: self.add_child(router) # copy parameters self.parameters = AttributeDictionary(self.parameters) for name, rule_method in self.rule_methods.items(): rule, method, params, _, _ = rule_method rparameters = params.copy() handler = getattr(self, name) router = self.add_child(Router(rule, **rparameters)) setattr(router, method, handler) for name, value in parameters.items(): if name in self.parameters: self.parameters[name] = value else: setattr(self, name, value) @property def route(self): '''The relative :class:`.Route` served by this :class:`Router`. ''' parent = self._parent if parent and parent._route.is_leaf: return parent.route + self._route else: return self._route @property def full_route(self): '''The full :attr:`route` for this :class:`.Router`. It includes the :attr:`parent` portion of the route if a parent router is available. ''' if self._parent: return self._parent.full_route + self._route else: return self._route @property def name(self): '''The name of this :class:`Router`. This attribute can be specified during initialisation. If available, it can be used to retrieve a child router by name via the :meth:`get_route` method. ''' return self._name @property def root(self): '''The root :class:`Router` for this :class:`Router`.''' if self.parent: return self.parent.root else: return self @property def parent(self): return self._parent @property def default_content_type(self): '''The default content type for responses. This is the first element in the :attr:`response_content_types` list. ''' ct = self.response_content_types return ct[0] if ct else None @property def creation_count(self): '''Integer for sorting :class:`Router` by creation. Auto-generated during initialisation.''' return self._creation_count @property def rule(self): '''The full ``rule`` string for this :class:`Router`. It includes the :attr:`parent` portion of the rule if a :attr:`parent` router is available. ''' return self.full_route.rule def path(self, **urlargs): '''The full path of this :class:`Router`. It includes the :attr:`parent` portion of url if a parent router is available. ''' return self.full_route.url(**urlargs) def __getattr__(self, name): '''Check the value of a :attr:`parameters` ``name``. If the parameter is not available, retrieve the parameter from the :attr:`parent` :class:`Router` if it exists. ''' if not name.startswith('_'): return self.get_parameter(name, False) self.no_param(name) def get_parameter(self, name, safe=True): value = self.parameters.get(name) if value is None: if self._parent: return self._parent.get_parameter(name, safe) elif name in self.parameters: return value elif not safe: self.no_param(name) else: return value def no_param(self, name): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def content_type(self, request): '''Evaluate the content type for the response to a client ``request``. The method uses the :attr:`response_content_types` parameter of accepted content types and the content types accepted by the client and figure out the best match. ''' return request.content_types.best_match(self.response_content_types) def accept_content_type(self, content_type): '''Check if ``content_type`` is accepted by this :class:`Router`. Return the best mach or ``None`` if not accepted.''' response_content_types = self.response_content_types if response_content_types: return ContentAccept( [(content_type, 1)]).best_match(response_content_types) def __repr__(self): return self.route.__repr__() def __call__(self, environ, start_response=None): path = environ.get('PATH_INFO') or '/' path = path[1:] router_args = self.resolve(path) if router_args: router, args = router_args return router.response(environ, args) elif self.allows_redirects: if self.route.is_leaf: if path.endswith('/'): router_args = self.resolve(path[:-1]) if router_args is not None: return self.redirect(environ, '/%s' % path[:-1]) else: if not path.endswith('/'): router_args = self.resolve('%s/' % path) if router_args is not None: return self.redirect(environ, '/%s/' % path) def resolve(self, path, urlargs=None): '''Resolve a path and return a ``(handler, urlargs)`` tuple or ``None`` if the path could not be resolved. ''' match = self.route.match(path) if match is None: if not self.route.is_leaf: # no match return elif '__remaining__' in match: path = match.pop('__remaining__') urlargs = update_args(urlargs, match) else: return self, update_args(urlargs, match) # for handler in self.routes: view_args = handler.resolve(path, urlargs) if view_args is None: continue return view_args def response(self, environ, args): '''Once the :meth:`resolve` method has matched the correct :class:`Router` for serving the request, this matched router invokes this method to produce the WSGI response. ''' request = wsgi_request(environ, self, args) # Set the response content type request.response.content_type = self.content_type(request) method = request.method.lower() callable = getattr(self, method, None) if callable is None: raise HttpException(status=405, msg='Method "%s" not allowed' % method) return callable(request) def redirect(self, environ, path): raise HttpRedirect(path) def add_child(self, router): '''Add a new :class:`Router` to the :attr:`routes` list. ''' assert isinstance(router, Router), 'Not a valid Router' assert router is not self, 'cannot add self to children' # Loop over available routers to check it the router # is already available for r in self.routes: if r.route == router.route: r.parameters.update(router.parameters) return r if router.parent: router.parent.remove_child(router) router._parent = self self.routes.append(router) return router def remove_child(self, router): '''remove a :class:`Router` from the :attr:`routes` list.''' if router in self.routes: self.routes.remove(router) router._parent = None def get_route(self, name): '''Get a child :class:`Router` by its :attr:`name`.''' for route in self.routes: if route.name == name: return route def link(self, *args, **urlargs): '''Return an anchor :class:`Html` element with the `href` attribute set to the url of this :class:`Router`.''' if len(args) > 1: raise ValueError url = self.route.url(**urlargs) if len(args) == 1: text = args[0] else: text = url return Html('a', text, href=url) def sitemap(self, root=None): '''This utility method returns a sitemap starting at root. If *root* is ``None`` it starts from this :class:`Router`. :param request: a :ref:`wsgi request wrapper <app-wsgi-request>` :param root: Optional url path where to start the sitemap. By default it starts from this :class:`Router`. Pass `"/"` to start from the root :class:`Router`. :param levels: Number of nested levels to include. :return: A list of children ''' if not root: root = self else: handler_urlargs = self.root.resolve(root[1:]) if handler_urlargs: root, urlargs = handler_urlargs else: return [] return list(self.routes) def encoding(self, request): '''The encoding to use for the response. By default it returns ``utf-8``.''' return 'utf-8'
def items(self, request): model = self.content_router.model(request) for item in model.all(request): yield AttributeDictionary(loc=item['url'], lastmod=item['modified'], priority=item['priority'])
class JsonProxy(object): '''A python Proxy class for :class:`JSONRPC` Servers. :param url: server location :param version: JSONRPC server version. Default ``2.0`` :param id: optional request id, generated if not provided. Default ``None``. :param data: Extra data to include in all requests. Default ``None``. :param http: optional http opener. If provided it must have the ``request`` method available which must be of the form:: http.request(url, body=..., method=...) Default ``None``. Lets say your RPC server is running at ``http://domain.name.com/``:: >>> a = JsonProxy('http://domain.name.com/') >>> a.add(3,4) 7 >>> a.ping() 'pong' ''' separator = '.' rawprefix = 'raw' default_version = '2.0' default_timeout = 3 _json = JsonToolkit def __init__(self, url, name=None, version=None, id=None, data=None, **kwargs): self.__url = url self.__name = name self.__version = version or self.__class__.default_version self.__id = id self.__data = data if data is not None else {} self.local = AttributeDictionary() self.setup(**kwargs) def setup(self, http=None, timeout=None, **kwargs): if not http: timeout = timeout if timeout is not None else self.default_timeout http = HttpClient(timeout=timeout, **kwargs) self.local.http = http @property def url(self): return self.__url @property def http(self): return self.local.http @property def path(self): return self.__name def makeid(self): '''Can be re-implemented by your own Proxy''' return gen_unique_id() def __str__(self): return self.__repr__() def __repr__(self): if self.__id: d = '%s - %s' % (self.__url,self.__id) else: d = self.__url return 'JSONRPCProxy(%s)' % d def __getattr__(self, name): if self.__name != None: name = "%s%s%s" % (self.__name, self.separator, name) id = self.makeid() return self.__class__(self.__url, name=name, version=self.__version, id=id, data=self.__data, **self.local.all()) def timeit(self, func, times, *args, **kwargs): '''Usefull little utility for timing responses from server. The usage is simple:: >>> from pulsar.apps import rpc >>> p = rpc.JsonProxy('http://127.0.0.1:8060') >>> p.timeit('ping',10) 0.56... >>> _ ''' r = range(times) func = getattr(self,func) start = default_timer() for t in r: func(*args, **kwargs) return default_timer() - start def _get_data(self, *args, **kwargs): func_name = self.__name fs = func_name.split('_') raw = False if len(fs) > 1 and fs[0] == self.rawprefix: raw = True fs.pop(0) func_name = '_'.join(fs) params = self.get_params(*args, **kwargs) data = {'method': func_name, 'params': params, 'id': self.__id, 'jsonrpc': self.__version} return data, raw def __call__(self, *args, **kwargs): data, raw = self._get_data(*args, **kwargs) body = self._json.dumps(data).encode('latin-1') # Always make sure the content-type is application/json self.http.headers['content-type'] = 'application/json' resp = self.http.post(self.__url, data=body) if is_async(resp): return resp.add_callback(lambda r: self._end_call(r, raw)) else: return self._end_call(resp, raw) def _end_call(self, resp, raw): content = resp.content.decode('utf-8') if resp.is_error: if 'error' in content: return self.loads(content) else: resp.raise_for_status() else: if raw: return content else: return self.loads(content) def get_params(self, *args, **kwargs): ''' Create an array or positional or named parameters Mixing positional and named parameters in one call is not possible. ''' kwargs.update(self.__data) if args and kwargs: raise ValueError('Cannot mix positional and named parameters') if args: return list(args) else: return kwargs def loads(self, obj): res = self._json.loads(obj) if isinstance(res, dict): if 'error' in res: error = res['error'] code = error['code'] message = error['message'] raise exception(code, message) else: return res.get('result',None) return res