def add_helper(self, helper, name=None, static=False, package=None, replace=False): """Add a "helper" function. ``helper`` can be a string pointing to the helper or the helper itself. If it's a string, ``helper`` and ``package`` will be passed to :func:`load_object`. Helper functions can be methods that take a ``Helpers`` instance as their first arg or they can be static methods. The latter is useful for adding third party functions as helpers. Helper functions can be accessed via ``request.helpers``. The advantage of this is that helpers added as method have access to the application and the current request. """ helper = load_object(helper, package=package) if name is None: name = helper.__name__ if static: helper = staticmethod(helper) self.register('helper', helper, name, replace=replace) if abcs.AHelpers not in self: helpers_factory = self.get_setting('helpers_factory') self.register(abcs.AHelpers, load_object(helpers_factory))
def query(self, q, cost_func='bicycle', heuristic_func=None, points=None): waypoints = self.get_waypoints(q, points) graph = get_graph(self.session) if ':' in cost_func: cost_func = load_object(cost_func) else: cost_func = load_object('.cost', cost_func) paths_info = [] for s, e in zip(waypoints[:-1], waypoints[1:]): path_info = self.find_path( graph, s, e, cost_func=cost_func, heuristic_func=heuristic_func) paths_info.append(path_info) routes = [] starts = waypoints[:-1] ends = waypoints[1:] for start, end, path_info in zip(starts, ends, paths_info): node_ids, edge_attrs, split_ways = path_info directions, linestring, distance = self.make_directions(node_ids, edge_attrs, split_ways) route = Route(start, end, directions, linestring, distance) routes.append(route) return routes[0] if len(routes) == 1 else routes
def add_subscriber(self, event_type, func, priority=None, once=False, **args): """Add a subscriber for the specified event type. ``args`` will be passed to ``func`` as keyword args. (Note: this functionality is somewhat esoteric and should perhaps be removed.) You can also use the :class:`~tangled.web.events.subscriber` decorator to register subscribers. """ event_type = load_object(event_type) func = load_object(func) subscriber = Subscriber(event_type, func, priority, once, args) self.register(event_type, subscriber, subscriber)
def register_representation_type(self, representation_type, replace=False): """Register a content type. This does a few things: - Makes the representation type accessible via its key - Makes the representation type accessible via its content type - Registers the representation's content type """ representation_type = load_object(representation_type) key = representation_type.key content_type = representation_type.content_type quality = representation_type.quality self.register(Representation, representation_type, key, replace=replace) self.register(Representation, representation_type, content_type, replace=replace) self.register('content_type', (content_type, quality), content_type, replace=replace)
def bycycle(service: arg(choices=('lookup', 'route')), q): """Run a byCycle service.""" module_name = 'bycycle.core.service.{service}'.format(service=service) service_factory = load_object(module_name, 'Service') if service == 'route': q = re.split('\s+to\s+', q, re.I) if len(q) < 2: abort(1, 'Route must be specified as "A to B"') engine = get_engine() session_factory = get_session_factory(engine) session = session_factory() start_time = time.time() try: service = service_factory(session) response = service.query(q) except Exception: session.rollback() raise else: session.close() print(response) print('{:.2f} seconds'.format(time.time() - start_time))
def _handlers(self): """Set up the handler chain.""" settings = self.get_settings(prefix="tangled.app.handler.") # System handler chain handlers = [settings["exc"]] if self.has_any("static_directory"): # Only enable static file handler if there's at least one # local static directory registered. dirs = self.get_all("static_directory") if any(isinstance(d, LocalDirectory) for d in dirs): handlers.append(settings["static_files"]) handlers.append(settings["tweaker"]) handlers.append(settings["notifier"]) handlers.append(settings["resource_finder"]) if self.get_setting("csrf.enabled"): handlers.append(settings["csrf"]) if "auth" in settings: handlers.append(settings["auth"]) # Handlers added by extensions and applications handlers += self.get_all(abcs.AHandler, []) # Main handler handlers.append(settings["main"]) # Wrap handlers wrapped_handlers = [] next_handler = None for handler in reversed(handlers): handler = load_object(handler) handler = HandlerWrapper(handler, next_handler) wrapped_handlers.append(handler) next_handler = handler wrapped_handlers.reverse() return wrapped_handlers
def _handlers(self): """Set up the handler chain.""" settings = self.get_settings(prefix='tangled.app.handler.') # System handler chain handlers = [settings['exc']] if self.has_any('static_directory'): # Only enable static file handler if there's at least one # local static directory registered. dirs = self.get_all('static_directory') if any(isinstance(d, LocalDirectory) for d in dirs): handlers.append(settings['static_files']) handlers.append(settings['tweaker']) handlers.append(settings['notifier']) handlers.append(settings['resource_finder']) if self.get_setting('csrf.enabled'): handlers.append(settings['csrf']) if 'auth' in settings: handlers.append(settings['auth']) # Handlers added by extensions and applications handlers += self.get_all(abcs.AHandler, []) # Main handler handlers.append(settings['main']) # Wrap handlers wrapped_handlers = [] next_handler = None for handler in reversed(handlers): handler = load_object(handler) handler = HandlerWrapper(handler, next_handler) wrapped_handlers.append(handler) next_handler = handler wrapped_handlers.reverse() return wrapped_handlers
def __init__(self, parser, args): app_factory = self.args.app_factory if app_factory is None: app_factory = self.settings.pop('factory', None) if app_factory is None: parser.error( 'App factory must be specified via --app-factory or ' 'in settings') self.args.app_factory = load_object(app_factory, 'make_app')
def __init__(self, parser, args): app_factory = self.args.app_factory if app_factory is None: app_factory = self.settings.get('factory', None) if app_factory is None: parser.error( '\n An app factory must be specified via --app-factory or factory setting.' '\n Did you specify a settings file via -f?' '\n If so, does it contain an [app] section with a factory setting?') self.args.app_factory = load_object(app_factory, 'make_app')
def __init__(self, parser, args): app_factory = self.args.app_factory if app_factory is None: app_factory = self.settings.get('factory', None) if app_factory is None: parser.error( '\n An app factory must be specified via --app-factory or factory setting.' '\n Did you specify a settings file via -f?' '\n If so, does it contain an [app] section with a factory setting?' ) self.args.app_factory = load_object(app_factory, 'make_app')
def get_type(name: str): """Get the type corresponding to ``name``.""" if name is None: return str if name == 'object': return load_object if hasattr(builtins, name): return getattr(builtins, name) if is_object_path(name): return load_object(name) raise TypeError('Unknown type: %s' % name)
def include(self, obj): """Include some other code. If a callable is passed, that callable will be called with this app instance as its only argument. If a module is passed, it must contain a function named ``include``, which will be called as described above. """ obj = load_object(obj, 'include') return obj(self)
def make_app(self, app_factory, settings_file, settings): settings = self.make_settings(settings_file, settings) if app_factory is None: app_factory = settings.get('factory') if app_factory is None: abort(1, ( 'An app factory must be specified via the factory setting ' 'or using the --factory option.' '\nDid you specify a settings file via --settings-file?' '\nIf so, does it contain an [app] section with a factory setting?' )) app_factory = load_object(app_factory, 'make_app') app = app_factory(settings) return app
def register_representation_type(self, representation_type, replace=False): """Register a content type. This does a few things: - Makes the representation type accessible via its key - Makes the representation type accessible via its content type - Registers the representation's content type """ representation_type = load_object(representation_type) key = representation_type.key content_type = representation_type.content_type quality = representation_type.quality self.register(Representation, representation_type, key, replace=replace) self.register(Representation, representation_type, content_type, replace=replace) self.register("content_type", (content_type, quality), content_type, replace=replace)
def shell(config, shell_: dict(choices=('bpython', 'ipython', 'python')) = 'bpython', locals_: 'Pass shell locals using name=package.module:object syntax' = {}): banner = ['Tangled Shell'] if locals_: banner.append('Locals:') for k, v in locals_.items(): v = load_object(v) locals_[k] = v v = str(v) v = v.replace('\n', '\n ' + ' ' * len(k)) banner.append(' {} = {}'.format(k, v)) banner = '\n'.join(banner) if shell_ == 'bpython': try: import bpython except ImportError: printer.warning('bpython is not installed; falling back to python') shell_ = 'python' else: from bpython import embed banner = 'bpython {}\n{}'.format(bpython.__version__, banner) embed(locals_=locals_, banner=banner) if shell_ == 'ipython': try: import IPython except ImportError: printer.warning('IPython is not installed; falling back to python') shell_ = 'python' else: from IPython.terminal.embed import InteractiveShellEmbed InteractiveShellEmbed(user_ns=locals_, banner2=banner)() if shell_ == 'python': banner = 'python {}\n{}'.format(sys.version, banner) code.interact(banner=banner, local=locals_)
def fire_actions(where, tags=(), args=(), kwargs=None, _registry=_ACTION_REGISTRY): """Fire actions previously registered via :func:`register_action`. ``where`` is typically a package or module. Only actions registered in that package or module will be fired. ``where`` can also be some other type of object, such as a class, in which case only the actions registered on the class and its methods will be fired. Currently, this is considered non-standard usage, but it's useful for testing. If no ``tags`` are specified, all registered actions under ``where`` will be fired. ``*args`` and ``**kwargs`` will be passed to each action that is fired. """ where = load_object(where) where_fq_name = fully_qualified_name(where) tags = (tags, ) if isinstance(tags, str) else tags kwargs = {} if kwargs is None else kwargs if hasattr(where, '__path__'): # Load all modules in package path = where.__path__ prefix = where.__name__ + '.' for (_, name, is_pkg) in pkgutil.walk_packages(path, prefix): if name not in sys.modules: __import__(name) tags = _registry.keys() if not tags else tags for tag in tags: tag_actions = _registry[tag] for fq_name, wrapped_actions in tag_actions.items(): if fq_name.startswith(where_fq_name): for action in wrapped_actions: action(*args, **kwargs)
def fire_actions(where, tags=(), args=(), kwargs=None, _registry=_ACTION_REGISTRY): """Fire actions previously registered via :func:`register_action`. ``where`` is typically a package or module. Only actions registered in that package or module will be fired. ``where`` can also be some other type of object, such as a class, in which case only the actions registered on the class and its methods will be fired. Currently, this is considered non-standard usage, but it's useful for testing. If no ``tags`` are specified, all registered actions under ``where`` will be fired. ``*args`` and ``**kwargs`` will be passed to each action that is fired. """ where = load_object(where) where_fq_name = fully_qualified_name(where) tags = (tags,) if isinstance(tags, str) else tags kwargs = {} if kwargs is None else kwargs if hasattr(where, '__path__'): # Load all modules in package path = where.__path__ prefix = where.__name__ + '.' for (_, name, is_pkg) in pkgutil.walk_packages(path, prefix): if name not in sys.modules: __import__(name) tags = _registry.keys() if not tags else tags for tag in tags: tag_actions = _registry[tag] for fq_name, wrapped_actions in tag_actions.items(): if fq_name.startswith(where_fq_name): for action in wrapped_actions: action(*args, **kwargs)
def test_load_object_pass_name(self): obj = util.load_object('tangled.util', 'load_object') self.assertIs(obj, util.load_object)
def as_func_args(func, converters={}, item_sep=', ', _skip_first=False): r"""Make a converter that converts lines of args based on ``func``. This inspects ``func`` and configures converters based on the types of its args as follows: - If an arg has no default, no converter will be used unless a converter is specified for it via ``converters`` - If an arg has a default that's a built-in type, the corresponding converter will be used unless a different converter is specified via ``converters`` - If an arg has a default that's *not* a built-in type, that type will be used as the converter unless a different converter is specified via ``converters`` The resulting converter would typically be used to parse lines of args from a settings file. Examples:: >>> lines = ('1, 1, 1, 1\n1, 0, 1, 1') >>> c = as_func_args((lambda a, b=True, i=1, s='s': None), converters={'a': int}) >>> args_list = c(lines) >>> args = args_list[0] >>> args['a'] 1 >>> args['b'] True >>> args['i'] 1 >>> args['s'] '1' >>> args = args_list[1] >>> args['b'] False >>> c = as_func_args(as_func_args, converters={'func': 'object'}) >>> args = c('tangled.converters:as_func_args')[0] >>> args['func'].__name__ 'as_func_args' """ func = load_object(func) signature = inspect.signature(func) parameters = iter(signature.parameters.values()) if _skip_first: next(parameters) arg_converters = OrderedDict() for i, p in enumerate(parameters): name = p.name if name in converters: converter = converters[name] elif name is None: # Positional-only arg name = (None, i) converter = 'self' elif p.default is p.empty: converter = 'self' elif p.default is None: converter = 'self' else: c = p.default.__class__ is_builtin = c is __builtins__.get(c.__name__) converter = c.__name__ if is_builtin else c arg_converters[name] = get_converter(converter) line_splitter = as_seq_of_seq(item_sep=item_sep) def converter(lines): lines = line_splitter(lines) args_list = [] for line in lines: args = OrderedDict() for v, name in zip(line, arg_converters): converter = arg_converters[name] args[name] = converter(v) args_list.append(args) return args_list return converter
def as_object(v): v = v.strip() if not v: return None return load_object(v)
def __init__(self, settings, **extra_settings): if not isinstance(settings, abcs.AAppSettings): settings = make_app_settings(settings, **extra_settings) self.settings = settings package = settings.get('package') # Register default representations (content type => repr. type). # Includes can override this. for obj in vars(representations).values(): is_representation_type = ( isinstance(obj, type) and issubclass(obj, Representation) and obj is not Representation) if is_representation_type: self.register_representation_type(obj) self.add_config_field('*/*', 'quality', None) self.add_config_field('*/*', 'type', None) self.add_config_field('*/*', 'status', None) self.add_config_field('*/*', 'location', None) self.add_config_field('*/*', 'response_attrs', dict) # Handlers added from settings have precedence over handlers # added via includes. handlers = self.get_setting('handlers') for handler in handlers: self.add_handler(handler) # Mount static directories and resources from settings before # those from includes. It's assumed that only the main # application will specify static directories and resources this # way. for static_args in self.get_setting('static_directories'): self.mount_static_directory(**static_args) resources_package = self.get_setting('tangled.app.resources.package', package) for resource_args in self.get_setting('resources'): factory = resource_args['factory'] if factory.startswith('.'): if resources_package is None: raise ValueError( 'Package-relative resource factory "{factory}" requires the package ' 'containing resources to be specified (set either the `package` or ' '`tangled.app.resources.package` setting).' .format(factory=factory)) factory = '{package}{factory}'.format(package=resources_package, factory=factory) resource_args['factory'] = factory self.mount_resource(**resource_args) # Before config is loaded via load_config() if self.get_setting('csrf.enabled'): self.include('.csrf') for include in self.get_setting('includes'): self.include(include) for where in self.get_setting('load_config'): self.load_config(where) request_factory = self.get_setting('request_factory') request_factory = load_object(request_factory) self.register(abcs.ARequest, request_factory) response_factory = self.get_setting('response_factory') response_factory = load_object(response_factory) self.register(abcs.AResponse, response_factory) # TODO: Not sure this belongs here if not self.testing: self._configure_logging() self.name = self.get_setting('name') or 'id={}'.format(id(self)) process_registry.register(abcs.AApplication, self, self.name) for subscriber in self.get_setting('on_created'): self.on_created(subscriber) if not self.get_setting('tangled.app.defer_created', False): self.created()
def mount_resource(self, name, factory, path, methods=(), method=None, add_slash=False, replace=False, _level=3): """Mount a resource at the specified path. Resources mounted later will take precedence over resources mounted earlier. Basic example:: app.mount_resource('home', 'mypackage.resources:Home', '/') URL vars are delimited with angle brackets like so:: # The user's ID will be extracted from the URL and passed to # the User resource's methods. app.mount_resource( 'user', 'mypackage.resources:User', '/user/<id>') A unique ``name`` for the mounted resource must be specified. This can be *any* string. It's used when generating resource URLs via :meth:`.request.Request.resource_url`. Pass ``replace=True`` to replace a resource mounted with a given name with a different resource (possibly mounted at a different path and/or with different methods). ``factory`` is a class or other callable that produces objects that implement the resource interface (typically a subclass of :class:`.resource.resource.Resource`). The factory may be passed as an object path like ``'package.module:factory'``. ``path`` is an application-relative path that may or may not include URL vars. It *must* begin with a slash. If ``path`` ends with a slash or ``add_slash=True``, requests to ``path`` without a trailing slash will be redirected to ``path`` with a slash appended. A list of HTTP ``methods`` can be passed to constrain which methods the resource will respond to. By default, it's assumed that a resource will respond to all methods. Note however that when subclassing :class:`.resource.resource.Resource`, unimplemented methods will return a ``405 Method Not Allowed`` response, so it's often unnecessary to specify the list of allowed methods here; this is mainly useful if you want to mount different resources at the same path for different methods. **Mounting Subresources** Subresources can be mounted like this:: parent = app.mount_resource('parent', factory, '/parent') parent.mount('child', 'child') or like this:: with app.mount_resource('parent', factory, '/parent') as parent: parent.mount('child', 'child') In either case, the subresource's ``name`` will be prepended with its parent's name plus a slash, and its ``path`` will be prepended with its parent's path plus a slash. If no ``factory`` is specified, the parent's factory will be used. ``methods`` will be propagated as well. ``method`` and ``add_slash`` are *not* propagated. In the examples above, the child's name would be ``parent/child`` and its path would be ``/parent/child``. """ factory = load_object(factory, level=_level) mounted_resource = MountedResource(self, name, factory, path, methods, method, add_slash) self.register(abcs.AMountedResource, mounted_resource, mounted_resource.name, replace) return SubResourceMounter(self, mounted_resource)
def test_load_object(self): obj = util.load_object('tangled.util:load_object') self.assertIs(obj, util.load_object)
def local_type(val): k, v = val.split('=') v = load_object(v) return k, v
def exc_log_message_factory(self): factory = self.get_setting('exc_log_message_factory') factory = load_object(factory) return factory
def load_config(self, where): """Load config registered via decorators.""" where = load_object(where, level=3) fire_actions(where, tags='tangled.web', args=(self,))
def _request_finished_handler(self): """Calls finished callbacks in exc handling context.""" exc_handler = load_object(self.get_setting('handler.exc')) handler = load_object('.handlers:request_finished_handler') handler = HandlerWrapper(exc_handler, HandlerWrapper(handler, None)) return handler
def __init__(self, settings, **extra_settings): if not isinstance(settings, abcs.AAppSettings): settings = make_app_settings(settings, **extra_settings) self.settings = settings package = settings.get('package') # Register default representations (content type => repr. type). # Includes can override this. for obj in vars(representations).values(): is_representation_type = (isinstance(obj, type) and issubclass(obj, Representation) and obj is not Representation) if is_representation_type: self.register_representation_type(obj) self.add_config_field('*/*', 'quality', None) self.add_config_field('*/*', 'type', None) self.add_config_field('*/*', 'status', None) self.add_config_field('*/*', 'location', None) self.add_config_field('*/*', 'response_attrs', dict) # Handlers added from settings have precedence over handlers # added via includes. handlers = self.get_setting('handlers') for handler in handlers: self.add_handler(handler) # Mount static directories and resources from settings before # those from includes. It's assumed that only the main # application will specify static directories and resources this # way. for static_args in self.get_setting('static_directories'): self.mount_static_directory(**static_args) resources_package = self.get_setting('tangled.app.resources.package', package) for resource_args in self.get_setting('resources'): factory = resource_args['factory'] if factory.startswith('.'): if resources_package is None: raise ValueError( 'Package-relative resource factory "{factory}" requires the package ' 'containing resources to be specified (set either the `package` or ' '`tangled.app.resources.package` setting).'.format( factory=factory)) factory = '{package}{factory}'.format( package=resources_package, factory=factory) resource_args['factory'] = factory self.mount_resource(**resource_args) # Before config is loaded via load_config() if self.get_setting('csrf.enabled'): self.include('.csrf') for include in self.get_setting('includes'): self.include(include) for where in self.get_setting('load_config'): self.load_config(where) request_factory = self.get_setting('request_factory') request_factory = load_object(request_factory) self.register(abcs.ARequest, request_factory) response_factory = self.get_setting('response_factory') response_factory = load_object(response_factory) self.register(abcs.AResponse, response_factory) # TODO: Not sure this belongs here if not self.testing: self._configure_logging() self.name = self.get_setting('name') or 'id={}'.format(id(self)) process_registry.register(abcs.AApplication, self, self.name) for subscriber in self.get_setting('on_created'): self.on_created(subscriber) if not self.get_setting('tangled.app.defer_created', False): self.created()
def __init__(self, callable_, next_handler): if isinstance(callable_, str): callable_ = load_object(callable_) self.callable_ = callable_ self.next = next_handler
def test_load_object_with_dotted_name(self): obj = util.load_object('sys:implementation.name') self.assertEqual(obj, sys.implementation.name)
def __init__(self, name, factory, path, methods=(), method_name=None, add_slash=False): if not path.startswith('/'): path = '/' + path if path == '/': add_slash = False else: if path.endswith('/'): add_slash = True elif add_slash: path = '{}/'.format(path) self.name = name self.factory = factory self.path = path # normalized path self.methods = set(as_tuple(methods, sep=',')) self.method_name = method_name self.add_slash = add_slash urlvars_info = {} path_regex = [] format_string = [] i = 0 for match in re.finditer(self.urlvar_regex, path): info = match.groupdict() identifier = info['identifier'] if identifier in urlvars_info: raise ValueError('{} already present'.format(identifier)) regex = info['regex'] or r'[\w-]+' converter = info['converter'] if converter: if ':' in converter: converter = load_object(converter) else: converter = get_converter(converter) else: converter = str urlvars_info[identifier] = {'regex': regex, 'converter': converter} # Get the non-matching part of the string after the previous # match and before the current match. s, e = match.span() if i != s: before_match = path[i:s] path_regex.append(before_match) format_string.append(before_match) i = e path_regex.append(r'(?P<{}>{})'.format(identifier, regex)) format_string.extend(('{', identifier, '}')) if i != len(path): remainder = path[i:] path_regex.append(remainder) format_string.append(remainder) path_regex = ''.join(path_regex) self.urlvars_info = urlvars_info self.segments = path_regex.strip('/').split('/') self.format_string = ''.join(format_string) if add_slash: path_regex = r'{}(?:/?)'.format(path_regex.rstrip('/')) self.path_regex = path_regex
def test_load_module(self): module = util.load_object('tangled.util') self.assertIs(module, util)
def load_config(self, where): """Load config registered via decorators.""" where = load_object(where, level=3) fire_actions(where, tags='tangled.web', args=(self, ))
def mount_resource(self, name, factory, path, methods=(), method_name=None, add_slash=False, _level=3): """Mount a resource at the specified path. Basic example:: app.mount_resource('home', 'mypackage.resources:Home', '/') Specifying URL vars:: app.mount_resource( 'user', 'mypackage.resources:User', '/user/<id>') A unique ``name`` for the mounted resource must be specified. This can be *any* string. It's used when generating resource URLs via :meth:`.request.Request.resource_url`. A ``factory`` must also be specified. This can be any class or function that produces objects that implement the resource interface (typically a subclass of :class:`.resource.resource.Resource`). The factory may be passed as a string with the following format: ``package.module:factory``. The ``path`` is an application relative path that may or may not include URL vars. A list of HTTP ``methods`` can be passed to constrain which methods the resource will respond to. By default, it's assumed that a resource will respond to all methods. Note however that when subclassing :class:`.resource.resource.Resource`, unimplemented methods will return a ``405 Method Not Allowed`` response, so it's often unnecessary to specify the list of allowed methods here; this is mainly useful if you want to mount different resources at the same path for different methods. If ``path`` ends with a slash or ``add_slash`` is True, requests to ``path`` without a trailing slash will be redirected to the ``path`` with a slash appended. About URL vars: The format of a URL var is ``<(converter)identifier:regex>``. Angle brackets delimit URL vars. Only the ``identifier`` is required; it can be any valid Python identifier. If a ``converter`` is specified, it can be a built-in name, the name of a converter in :mod:`tangled.util.converters`, or a ``package.module:callable`` path that points to a callable that accepts a single argument. URL vars found in a request path will be converted automatically. The ``regex`` can be *almost* any regular expression. The exception is that ``<`` and ``>`` can't be used. In practice, this means that named groups (``(?P<name>regex)``) can't be used (which would be pointless anyway), nor can "look behinds". **Mounting Subresources** Subresources can be mounted like this:: parent = app.mount_resource('parent', factory, '/parent') parent.mount('child', 'child') or like this:: with app.mount_resource('parent', factory, '/parent') as parent: parent.mount('child', 'child') In either case, the subresource's ``name`` will be prepended with its parent's name plus a slash, and its ``path`` will be prepended with its parent's path plus a slash. If no ``factory`` is specified, the parent's factory will be used. ``methods`` will be propagated as well. ``method_name`` and ``add_slash`` are *not* propagated. In the examples above, the child's name would be ``parent/child`` and its path would be ``/parent/child``. """ mounted_resource = MountedResource( name, load_object(factory, level=_level), path, methods=methods, method_name=method_name, add_slash=add_slash) self.register( abcs.AMountedResource, mounted_resource, mounted_resource.name) tree = self.get_required(abcs.AMountedResourceTree) tree.add(mounted_resource) return SubResourceMounter(self, mounted_resource)