Пример #1
0
def bind_blueprint(pale_api_module, flask_blueprint):
    """Binds an implemented pale API module to a Flask Blueprint."""

    if not isinstance(flask_blueprint, Blueprint):
        raise TypeError(("pale.flask_adapter.bind_blueprint expected the "
                         "passed in flask_blueprint to be an instance of "
                         "Blueprint, but it was an instance of %s instead.")
                        % (type(flask_blueprint),))

    if not pale.is_pale_module(pale_api_module):
        raise TypeError(("pale.flask_adapter.bind_blueprint expected the "
                         "passed in pale_api_module to be a module, and to "
                         "have a _module_type defined to equal "
                         "pale.ImplementationModule, but it was an instance of "
                         "%s instead.")
                        % (type(pale_api_module),))

    endpoints = pale.extract_endpoints(pale_api_module)

    for endpoint in endpoints:
        endpoint._set_response_class(RESPONSE_CLASS)
        method = [endpoint._http_method]
        name = endpoint._route_name
        handler = endpoint._execute

        flask_blueprint.add_url_rule(
                endpoint._uri,
                name,
                view_func=ContextualizedHandler(handler),
                methods=method)
Пример #2
0
def generate_doc_dict(module, user):
    """Compile a Pale module's documentation into a python dictionary.

    The returned dictionary is suitable to be rendered by a JSON formatter,
    or passed to a template engine, or manipulated in some other way.
    """
    from pale import extract_endpoints, extract_resources, is_pale_module
    if not is_pale_module(module):
        raise ValueError(
                """The passed in `module` (%s) is not a pale module. `paledoc`
                only works on modules with a `_module_type` set to equal
                `pale.ImplementationModule`.""")

    module_endpoints = extract_endpoints(module)
    ep_doc = { ep._route_name: document_endpoint(ep) for ep \
            in module_endpoints }

    ep_doc_filtered = {}

    for endpoint in ep_doc:
        # check if user has permission to view this endpoint
        # this is currently an on/off switch: if any endpoint has a "@requires_permission"
        # decorator, user.is_admin must be True for the user to see documentation
        # @TODO - make this permission more granular if necessary

        if ep_doc[endpoint].get("requires_permission") != None and user != None and user.is_admin or \
            ep_doc[endpoint].get("requires_permission") == None:
            ep_doc_filtered[endpoint] = ep_doc[endpoint]

    module_resources = extract_resources(module)
    res_doc = { r._value_type: document_resource(r) for r \
            in module_resources }

    return {'endpoints': ep_doc_filtered,
            'resources': res_doc}
Пример #3
0
def bind_pale_to_webapp2(pale_app_module,
                         webapp_wsgiapplication,
                         route_prefix=None):
    """Binds a Pale API implementation to a webapp2 WSGIApplication"""

    if not isinstance(webapp_wsgiapplication, webapp2.WSGIApplication):
        raise TypeError(
            "pale.adapters.webapp2.bind_pale_to_webapp2 expected "
            "the passed in webapp_wsgiapplication to be an instance of "
            "WSGIApplication, but it was an instance of %s instead." %
            (type(webapp_wsgiapplication), ))

    if not pale.is_pale_module(pale_app_module):
        raise TypeError(
            "pale.adapters.webapp2.bind_pale_to_webapp2 expected "
            "the passed in pale_app_module to be a Python module with a "
            "`_module_type` value equal to `pale.ImplementationModule`, "
            "but it found an instance of %s instead." %
            (type(pale_app_module), ))

    endpoints = pale.extract_endpoints(pale_app_module)

    for endpoint in endpoints:
        endpoint._set_response_class(RESPONSE_CLASS)
        method = endpoint._http_method
        name = endpoint._route_name

        req_handler = pale_webapp2_request_handler_generator(endpoint)

        route_uri = endpoint._uri
        if route_prefix is not None:
            route_uri = "%s%s" % (route_prefix, route_uri)

        route = webapp2.Route(route_uri,
                              handler=req_handler,
                              name=name,
                              handler_method='pale_handler',
                              methods=[method, "OPTIONS"])
        webapp_wsgiapplication.router.add(route)
Пример #4
0
def generate_doc_dict(module):
    """Compile a Pale module's documentation into a python dictionary.

    The returned dictionary is suitable to be rendered by a JSON formatter,
    or passed to a template engine, or manipulated in some other way.
    """
    from pale import extract_endpoints, extract_resources, is_pale_module
    if not is_pale_module(module):
        raise ValueError(
            """The passed in `module` (%s) is not a pale module. `paledoc`
                only works on modules with a `_module_type` set to equal
                `pale.ImplementationModule`.""")

    module_endpoints = extract_endpoints(module)
    ep_doc = { ep._route_name: document_endpoint(ep) for ep \
            in module_endpoints }

    module_resources = extract_resources(module)
    res_doc = { r._value_type: document_resource(r) for r \
            in module_resources }

    return {'endpoints': ep_doc, 'resources': res_doc}
Пример #5
0
def bind_pale_to_webapp2(pale_app_module,
        webapp_wsgiapplication,
        route_prefix=None):
    """Binds a Pale API implementation to a webapp2 WSGIApplication"""

    if not isinstance(webapp_wsgiapplication, webapp2.WSGIApplication):
        raise TypeError("pale.adapters.webapp2.bind_pale_to_webapp2 expected "
                "the passed in webapp_wsgiapplication to be an instance of "
                "WSGIApplication, but it was an instance of %s instead."
                % (type(webapp_wsgiapplication), ))

    if not pale.is_pale_module(pale_app_module):
        raise TypeError("pale.adapters.webapp2.bind_pale_to_webapp2 expected "
                "the passed in pale_app_module to be a Python module with a "
                "`_module_type` value equal to `pale.ImplementationModule`, "
                "but it found an instance of %s instead."
                % (type(pale_app_module), ))

    endpoints = pale.extract_endpoints(pale_app_module)

    for endpoint in endpoints:
        endpoint._set_response_class(RESPONSE_CLASS)
        method = endpoint._http_method
        name = endpoint._route_name

        req_handler = pale_webapp2_request_handler_generator(endpoint)

        route_uri = endpoint._uri
        if route_prefix is not None:
            route_uri = "%s%s" % (route_prefix, route_uri)

        route = webapp2.Route(
                route_uri,
                handler=req_handler,
                name=name,
                handler_method='pale_handler',
                methods=[method])
        webapp_wsgiapplication.router.add(route)
Пример #6
0
def generate_doc_dict(module):
    """Compile a Pale module's documentation into a python dictionary.

    The returned dictionary is suitable to be rendered by a JSON formatter,
    or passed to a template engine, or manipulated in some other way.
    """
    from pale import extract_endpoints, extract_resources, is_pale_module
    if not is_pale_module(module):
        raise ValueError(
                """The passed in `module` (%s) is not a pale module. `paledoc`
                only works on modules with a `_module_type` set to equal
                `pale.ImplementationModule`.""")

    module_endpoints = extract_endpoints(module)
    ep_doc = { ep._route_name: document_endpoint(ep) for ep \
            in module_endpoints }

    module_resources = extract_resources(module)
    res_doc = { r._value_type: document_resource(r) for r \
            in module_resources }

    return {'endpoints': ep_doc,
            'resources': res_doc}
Пример #7
0
def generate_raml_resources(module, api_root, user):
    """Compile a Pale module's endpoint documentation into RAML format.

    RAML calls Pale endpoints 'resources'. This function converts Pale
    endpoints into RAML resource format.

    The returned string should be appended to the RAML documentation file
    before it is returned.
    """

    from pale import extract_endpoints, extract_resources, is_pale_module
    if not is_pale_module(module):
        raise ValueError(
                """The passed in `module` (%s) is not a pale module. `paledoc`
                only works on modules with a `_module_type` set to equal
                `pale.ImplementationModule`.""")

    raml_resources = extract_endpoints(module)
    raml_resource_doc_flat = { ep._route_name: document_endpoint(ep) for ep in raml_resources }
    raml_resource_doc_tree = generate_raml_tree(raml_resource_doc_flat, api_root)

    # @TODO generate this dynamically
    pale_argument_type_map = {
        "StringArgument": "string",
        "QueryCursorArgument": "string",
        "KeyArgument": "string",
        "IntegerArgument": "integer",
        "BooleanArgument": "boolean",
        "DateArgument": "date",
        "StringChoiceArgument": "string",
        "QueryKindsArgument": "string",
        "StringChoiceArgument": "string",
        "ListArgument": "array"
    }


    def check_children_for_public_endpoints(subtree):
        """Recursively check a subtree to see if any of its children require permissions.
        Returns an object with property "public" set to True
        if there are children that do not require permissions, false if not."""

        found_children = {}
        found_children["public"] = False

        def subroutine(subtree, found_children):
            if subtree.get("path") != None and len(subtree["path"]) > 0:
                path = subtree["path"]
                for branch in path:
                    if path[branch].get("endpoint") != None and path[branch]["endpoint"].get("requires_permission") == None:
                        found_children["public"] = True
                    else:
                        subroutine(path[branch], found_children)

        subroutine(subtree, found_children)

        return found_children


    def print_resource_tree(tree, output, indent, user, level=0):
        """Walk the tree and add the endpoint documentation to the output buffer.
        The user will only see documentation for endpoints they have the appropriate
        permissions for.
        """

        # check for an endpoint at this level first
        if tree.get("endpoint") != None:

            this_endpoint = tree["endpoint"]

            # check if user has permission to view this endpoint
            # this is currently an on/off switch: if any endpoint has a "@requires_permission"
            # decorator, user.is_admin must be True for the user to see documentation
            # @TODO - make this permission more granular if necessary
            if this_endpoint.get("requires_permission") != None and user != None and user.is_admin or \
                this_endpoint.get("requires_permission") == None:

                indent += "  "
                # add the HTTP method
                if this_endpoint.get("http_method") != None:
                    output.write(indent + this_endpoint["http_method"].lower() + ":\n")
                    indent += "  "

                    # add the description
                    if this_endpoint.get("description") != None:
                        modified_description = clean_description(this_endpoint["description"])
                        output.write(indent + "description: " + modified_description + "\n")

                    # add queryParameters per RAML spec
                    if this_endpoint.get("arguments") != None and len(this_endpoint["arguments"]) > 0:
                        output.write(indent + "queryParameters:\n")
                        sorted_arguments = OrderedDict(sorted(this_endpoint["arguments"].items(), key=lambda t: t[0]))
                        indent += "  "

                        for argument in sorted_arguments:
                            output.write(indent + argument + ":\n")
                            indent += "  "
                            this_argument = sorted_arguments[argument]

                            for arg_detail in this_argument:
                                # check for special kinds of queryParameters
                                this_arg_detail = this_argument[arg_detail]

                                if this_arg_detail != None:

                                    if arg_detail == "default":
                                        default = str(this_arg_detail)
                                        if default == "True" or default == "False":
                                            default = default.lower()
                                        output.write(indent + arg_detail + ": " + default + "\n")
                                    elif arg_detail == "description":
                                        output.write(indent + "description: " + this_arg_detail + "\n")
                                    elif arg_detail == "type":
                                        if pale_argument_type_map.get(this_arg_detail) != None:
                                            if pale_argument_type_map[this_arg_detail] == "array":
                                                # @TODO set the items dynamically
                                                output.write(indent + "type: array\n")
                                                output.write(indent + "items: string\n")
                                            else:
                                                output.write(indent + "type: " + pale_argument_type_map[this_arg_detail].replace(" ", "_") + "\n")
                                        else:
                                            output.write(indent + "type: " + this_arg_detail.replace(" ", "_") + "\n")
                                    elif arg_detail == "required":
                                        output.write(indent + "required" + ": " + str(this_arg_detail).lower() + "\n")
                                    elif arg_detail == "min_length":
                                        output.write(indent + "minLength: " + str(this_arg_detail) + "\n")
                                    elif arg_detail == "max_length":
                                        output.write(indent + "maxLength: " + str(this_arg_detail) + "\n")
                                    elif arg_detail == "min_value":
                                        output.write(indent + "minimum: " + str(this_arg_detail) + "\n")
                                    elif arg_detail == "max_value":
                                        output.write(indent + "maximum: " + str(this_arg_detail) + "\n")

                            indent = indent[:-2]    # reset indent after arg_detail

                        indent = indent[:-2]    # reset indent after argument

                    # add the responses
                    if this_endpoint.get("returns") != None and len(this_endpoint["returns"]) > 0:
                        output.write(indent + "responses:\n")
                        this_response = this_endpoint["returns"]
                        indent += "  "

                        # @TODO refactor the endpoints so that they use a variable called _success
                        # to determine the HTTP code for a successful response
                        # (see changes made to history.py)

                        if this_response.get("success") != None:
                            output.write(indent + str(this_response["success"]) + ":\n")
                        else:
                            output.write(indent + "200:\n")
                        indent += "  "
                        output.write(indent + "body:\n")
                        indent += "  "

                        for res_detail in this_response:
                            if res_detail != "success":
                                if res_detail == "resource_type":
                                    output.write(indent + "type: " + this_endpoint["returns"][res_detail].replace(" ", "_") + "\n")
                                elif res_detail == "description":
                                    modified_description = clean_description(this_endpoint["returns"][res_detail])
                                    output.write(indent + "description: " + modified_description + "\n")

                        indent = indent [:-6]   # resent indent after responses

                    indent = indent [:-2]   # reset indent after endpoint


        # check for further branches and recurse on them
        if tree.get("path") != None and len(tree["path"]) > 0:
            # only increase the indent if we are not on root level
            if level > 0:
                # set the base indentation per the level we are at in the tree
                indent = level * "  "

            subtree = tree["path"]

            admin_user = user!= None and user.is_admin != None and user.is_admin

            for branch in subtree:

                has_public_children = check_children_for_public_endpoints(subtree[branch]).get("public") == True

                is_public_endpoint = subtree[branch].get("endpoint") != None \
                    and subtree[branch]["endpoint"].get("requires_permission") == None

                # @TODO - make this permission more granular if necessary
                # if the user is admin or this endpoint is pubic or has public children:
                if admin_user or has_public_children or is_public_endpoint:
                    # write branch name
                    output.write(indent + "/" + branch + ":\n")
                    # and continue printing the endpoints
                    print_resource_tree(subtree[branch], output, indent, user, level=level+1)



    output = StringIO()
    indent = ""

    print_resource_tree(raml_resource_doc_tree, output, indent, user)
    raml_resources = output.getvalue()
    output.close()
    return raml_resources
Пример #8
0
def generate_raml_resource_types(module):
    """Compile a Pale module's resource documentation into RAML format.

    RAML calls Pale resources 'resourceTypes'. This function converts Pale
    resources into the RAML resourceType format.

    The returned string should be appended to the RAML documentation string
    before it is returned.
    """

    from pale import extract_endpoints, extract_resources, is_pale_module
    if not is_pale_module(module):
        raise ValueError(
            """The passed in `module` (%s) is not a pale module. `paledoc`
            only works on modules with a `_module_type` set to equal
            `pale.ImplementationModule`.""")

    module_resource_types = extract_resources(module)
    raml_resource_types_unsorted = {}

    for resource in module_resource_types:
        resource_name = resource.__name__
        raml_resource_types_unsorted[resource_name] = document_resource(resource)
        if hasattr(resource, "_description"):
            modified_description = clean_description(resource._description)
            raml_resource_types_unsorted[resource_name]["description"] = modified_description

    raml_resource_types_doc = OrderedDict(sorted(raml_resource_types_unsorted.items(), key=lambda t: t[0]))

    output = StringIO()
    indent = "  "  # 2

    # blacklist of resources to ignore
    ignored_resources = []

    for resource_type in raml_resource_types_doc:

        this_resource_type = raml_resource_types_doc[resource_type]

        # add the name, ignoring the blacklist
        if resource_type not in ignored_resources:
            output.write(indent + resource_type + ":\n")

            indent += "  "  # 4

            # add the description
            if this_resource_type.get("description") != None:
                modified_description = clean_description(this_resource_type["description"])
                output.write(indent + "description: " + modified_description + "\n")

            # if there are no fields, set type directly:
            if len(this_resource_type["fields"]) == 0:
                this_type = "object"
                if this_resource_type.get("_underlying_model") != None:
                    if this_resource_type["_underlying_model"] != object:
                        if hasattr(this_resource_type._underlying_model, "_value_type") \
                        and this_resource_type["_underlying_model"]._value_type not in ignored_resources:
                            this_type = this_resource_type["_underlying_model"]._value_type
                output.write(indent + "type: " + this_type + "\n")

                indent = indent[:-2] # 2

            # if there are fields, use them as the properties, which implies type = object
            else:

                output.write(indent + "properties:\n")
                indent += "  "  # 6

                sorted_fields = OrderedDict(sorted(this_resource_type["fields"].items(), key=lambda t: t[0]))


                # add the field name, a.k.a. RAML type name
                for field in sorted_fields:
                    output.write(indent + field + ":\n")

                    # add the query parameters, a.k.a. RAML properties
                    properties = sorted_fields[field]

                    indent += "  "  # 8

                    # if this type is a list of other types, set it to type 'array' and note the item types
                    # if not, add the type from the Pale type
                    if "_underlying_model" in this_resource_type and this_resource_type["_underlying_model"] == object:
                        output.write(indent + "type: base\n")
                    elif "item_type" in properties:
                        output.write(indent + "type: array\n")
                        output.write(indent + "items: " + properties["item_type"] + "\n")
                    elif "type" in properties:
                        output.write(indent + "type: " + properties["type"].replace(" ", "_") + "\n")

                    # if extended description exists, strip newlines and whitespace and add as description
                    if properties.get("extended_description") != None:
                        modified_description = clean_description(properties["extended_description"])
                        output.write(indent + "description: " + modified_description + "\n")
                    # otherwise, use description
                    elif properties.get("description") != None:
                        modified_description = clean_description(properties["description"])
                        output.write(indent + "description: " + modified_description + "\n")

                    if properties.get("default_fields") != None:
                        output.write(indent + "properties:\n")
                        indent += "  " # 10
                        for field_name in sorted(properties["default_fields"]):
                            # @TODO check if every default field is actually a string type
                            output.write(indent + field_name + ": string\n")

                        indent = indent[:-2] # 8

                    indent = indent[:-2] # 6

                indent = indent[:-4] # 2


    raml_resource_types = output.getvalue()
    output.close()
    return raml_resource_types