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)
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}
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)
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}
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)
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
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