Пример #1
0
def get_module_routes(module_name,
                      custom_routes=None,
                      exclusions=None,
                      arg_pattern=r'(?P<{}>[a-zA-Z0-9_\-]+)'):
    """Create and return routes for module_name

    Routes are (url, RequestHandler) tuples

    :returns: list of routes for ``module_name`` with respect to ``exclusions``
        and ``custom_routes``. Returned routes are with URLs formatted such
        that they are forward-slash-separated by module/class level
        and end with the lowercase name of the RequestHandler (it will also
        remove 'handler' from the end of the name of the handler).
        For example, a requesthandler with the name
        ``helloworld.api.HelloWorldHandler`` would be assigned the url
        ``/api/helloworld``.
        Additionally, if a method has extra arguments aside from ``self`` in
        its signature, routes with URL patterns will be generated to
        match ``r"(?P<{}>[a-zA-Z0-9_\-]+)".format(argname)`` for each
        argument. The aforementioned regex will match ONLY values
        with alphanumeric, hyphen and underscore characters. You can provide
        your own pattern by setting a ``arg_pattern`` param.
    :rtype: [(url, RequestHandler), ... ]
    :type  module_name: str
    :param module_name: Name of the module to get routes for
    :type  custom_routes: [(str, RequestHandler), ... ]
    :param custom_routes: List of routes that have custom URLs and therefore
        should be automagically generated
    :type  exclusions: [str, str, ...]
    :param exclusions: List of RequestHandler names that routes should not be
        generated for
    :type  arg_pattern: str
    :param arg_pattern: Default pattern for extra arguments of any method
    """
    def has_method(module, cls_name, method_name):
        return all([
            method_name in vars(getattr(module, cls_name)),
            is_method(reduce(getattr, [module, cls_name, method_name]))
        ])

    def yield_args(module, cls_name, method_name):
        """Get signature of ``module.cls_name.method_name``

        Confession: This function doesn't actually ``yield`` the arguments,
            just returns a list. Trust me, it's better that way.

        :returns: List of arg names from method_name except ``self``
        :rtype: list
        """
        wrapped_method = reduce(getattr, [module, cls_name, method_name])
        method = extract_method(wrapped_method)

        # If using tornado_json.gen.coroutine, original args are annotated...
        argspec_args = getattr(
            method,
            "__argspec_args",
            # otherwise just grab them from the method
            inspect.getargspec(method).args)

        return [a for a in argspec_args if a not in ["self"]]

    def generate_auto_route(module, module_name, cls_name, method_name,
                            url_name):
        """Generate URL for auto_route

        :rtype: str
        :returns: Constructed URL based on given arguments
        """
        def get_handler_name():
            """Get handler identifier for URL

            For the special case where ``url_name`` is
            ``__self__``, the handler is named a lowercase
            value of its own name with 'handler' removed
            from the ending if give; otherwise, we
            simply use the provided ``url_name``
            """
            if url_name == "__self__":
                if cls_name.lower().endswith('handler'):
                    return cls_name.lower().replace('handler', '', 1)
                return cls_name.lower()
            else:
                return url_name

        def get_arg_route():
            """Get remainder of URL determined by method argspec

            :returns: Remainder of URL which matches `\w+` regex
                with groups named by the method's argument spec.
                If there are no arguments given, returns ``""``.
            :rtype: str
            """
            if yield_args(module, cls_name, method_name):
                return "/{}/?$".format("/".join([
                    arg_pattern.format(argname)
                    for argname in yield_args(module, cls_name, method_name)
                ]))
            return r"/?"

        return "/{}/{}{}".format("/".join(module_name.split(".")[1:]),
                                 get_handler_name(), get_arg_route())

    if not custom_routes:
        custom_routes = []
    if not exclusions:
        exclusions = []

    # Import module so we can get its request handlers
    module = importlib.import_module(module_name)

    # Generate list of RequestHandler names in custom_routes
    custom_routes_s = [c.__name__ for r, c in custom_routes]

    # rhs is a dict of {classname: pyclbr.Class} key, value pairs
    rhs = pyclbr.readmodule(module_name)

    # You better believe this is a list comprehension
    auto_routes = list(
        chain(*[
            list(
                set(
                    chain(*[
                        # Generate a route for each "name" specified in the
                        #   __url_names__ attribute of the handler
                        [
                            # URL, requesthandler tuple
                            (generate_auto_route(module, module_name, cls_name,
                                                 method_name, url_name),
                             getattr(module, cls_name)) for url_name in
                            getattr(module, cls_name).__url_names__
                            # Add routes for each custom URL specified in the
                            #   __urls__ attribute of the handler
                        ] + [(url, getattr(module, cls_name))
                             for url in getattr(module, cls_name).__urls__]
                        # We create a route for each HTTP method in the handler
                        #   so that we catch all possible routes if different
                        #   HTTP methods have different argspecs and are expecting
                        #   to catch different routes. Any duplicate routes
                        #   are removed from the set() comparison.
                        for method_name in HTTP_METHODS
                        if has_method(module, cls_name, method_name)
                    ])))
            # foreach classname, pyclbr.Class in rhs
            for cls_name, cls in rhs.items()
            # Only add the pair to auto_routes if:
            #    * the superclass is in the list of supers we want
            #    * the requesthandler isn't already paired in custom_routes
            #    * the requesthandler isn't manually excluded
            if is_handler_subclass(cls) and cls_name not in (custom_routes_s +
                                                             exclusions)
        ]))

    routes = auto_routes + custom_routes
    return routes
Пример #2
0
def get_module_routes(module_name, custom_routes=None, exclusions=None):
    """Create and return routes for module_name

    Routes are (url, RequestHandler) tuples

    :returns: list of routes for ``module_name`` with respect to ``exclusions``
        and ``custom_routes``. Returned routes are with URLs formatted such
        that they are forward-slash-separated by module/class level
        and end with the lowercase name of the RequestHandler (it will also
        remove 'handler' from the end of the name of the handler).
        For example, a requesthandler with the name
        ``helloworld.api.HelloWorldHandler`` would be assigned the url
        ``/api/helloworld``.
        Additionally, if a method has extra arguments aside from ``self`` in
        its signature, routes with URL patterns will be generated to
        match ``r"(?P<{}>[a-zA-Z0-9_]+)".format(argname)`` for each
        argument. The aforementioned regex will match ONLY values
        with alphanumeric+underscore characters.
    :rtype: [(url, RequestHandler), ... ]
    :type  module_name: str
    :param module_name: Name of the module to get routes for
    :type  custom_routes: [(str, RequestHandler), ... ]
    :param custom_routes: List of routes that have custom URLs and therefore
        should be automagically generated
    :type  exclusions: [str, str, ...]
    :param exclusions: List of RequestHandler names that routes should not be
        generated for
    """
    def has_method(module, cls_name, method_name):
        return all([
            method_name in vars(getattr(module, cls_name)),
            is_method(reduce(getattr, [module, cls_name, method_name]))
        ])

    def yield_args(module, cls_name, method_name):
        """Get signature of ``module.cls_name.method_name``

        Confession: This function doesn't actually ``yield`` the arguments,
            just returns a list. Trust me, it's better that way.

        :returns: List of arg names from method_name except ``self``
        :rtype: list
        """
        wrapped_method = reduce(getattr, [module, cls_name, method_name])
        method = extract_method(wrapped_method)

        # If using tornado_json.gen.coroutine, original args are annotated...
        argspec_args = getattr(method, "__argspec_args",
                               # otherwise just grab them from the method
                               inspect.getargspec(method).args)

        return [a for a in argspec_args if a not in ["self"]]

    def generate_auto_route(module, module_name, cls_name, method_name, url_name):
        """Generate URL for auto_route

        :rtype: str
        :returns: Constructed URL based on given arguments
        """
        def get_handler_name():
            """Get handler identifier for URL

            For the special case where ``url_name`` is
            ``__self__``, the handler is named a lowercase
            value of its own name with 'handler' removed
            from the ending if give; otherwise, we
            simply use the provided ``url_name``
            """
            if url_name == "__self__":
                if cls_name.lower().endswith('handler'):
                    return cls_name.lower().replace('handler', '', 1)
                return cls_name.lower()
            else:
                return url_name

        def get_arg_route():
            """Get remainder of URL determined by method argspec

            :returns: Remainder of URL which matches `\w+` regex
                with groups named by the method's argument spec.
                If there are no arguments given, returns ``""``.
            :rtype: str
            """
            if yield_args(module, cls_name, method_name):
                return "/{}/?$".format("/".join(
                    ["(?P<{}>[a-zA-Z0-9_]+)".format(argname) for argname
                     in yield_args(module, cls_name, method_name)]
                ))
            return r"/?"

        return "/{}/{}{}".format(
            "/".join(module_name.split(".")[1:]),
            get_handler_name(),
            get_arg_route()
        )

    if not custom_routes:
        custom_routes = []
    if not exclusions:
        exclusions = []

    # Import module so we can get its request handlers
    module = importlib.import_module(module_name)

    # Generate list of RequestHandler names in custom_routes
    custom_routes_s = [c.__name__ for r, c in custom_routes]

    # rhs is a dict of {classname: pyclbr.Class} key, value pairs
    rhs = pyclbr.readmodule(module_name)

    # You better believe this is a list comprehension
    auto_routes = list(chain(*[
        list(set(chain(*[
            # Generate a route for each "name" specified in the
            #   __url_names__ attribute of the handler
            [
                # URL, requesthandler tuple
                (
                    generate_auto_route(
                        module, module_name, cls_name, method_name, url_name
                    ),
                    getattr(module, cls_name)
                ) for url_name in getattr(module, cls_name).__url_names__
                # Add routes for each custom URL specified in the
                #   __urls__ attribute of the handler
            ] + [
                (
                    url,
                    getattr(module, cls_name)
                ) for url in getattr(module, cls_name).__urls__
            ]
            # We create a route for each HTTP method in the handler
            #   so that we catch all possible routes if different
            #   HTTP methods have different argspecs and are expecting
            #   to catch different routes. Any duplicate routes
            #   are removed from the set() comparison.
            for method_name in HTTP_METHODS if has_method(
                module, cls_name, method_name)
        ])))
        # foreach classname, pyclbr.Class in rhs
        for cls_name, cls in rhs.items()
        # Only add the pair to auto_routes if:
        #    * the superclass is in the list of supers we want
        #    * the requesthandler isn't already paired in custom_routes
        #    * the requesthandler isn't manually excluded
        if is_handler_subclass(cls)
        and cls_name not in (custom_routes_s + exclusions)
    ]))

    routes = auto_routes + custom_routes
    return routes