diff --git a/examples/example.py b/examples/example.py index 4506572..95c2f9b 100644 --- a/examples/example.py +++ b/examples/example.py @@ -49,9 +49,32 @@ def post(self, team_id): def put(self, user_id): """ - Update a user + Update a user, this summary stays swagger_from_file: user_put.yml + + --- + tags: + - user_update + parameters: + - name: filename + in: formData + description: filename. this overrides what's in the .yml file + required: false + type: string + - name: username + in: formData + description: username + required: false + type: file + - name: password + in: formData + description: password + required: false + type: file + responses: + 200: + description: this description stays """ return {} diff --git a/examples/user_put.yml b/examples/user_put.yml index a11d5f6..b508cf9 100644 --- a/examples/user_put.yml +++ b/examples/user_put.yml @@ -1,4 +1,4 @@ -Update a user +Update a user from yml. This is not used. --- tags: - users @@ -17,6 +17,11 @@ parameters: name: type: string description: name for user + - name: filename + in: formData + description: filename of the yaml or python script used for training. This is not used as there's a filename already. + required: true + type: string responses: 204: description: User updated diff --git a/flask_swagger.py b/flask_swagger.py index 352e201..5f7c950 100644 --- a/flask_swagger.py +++ b/flask_swagger.py @@ -17,24 +17,27 @@ def _sanitize(comment): return comment.replace('\n', '
') if comment else comment -def _find_from_file(full_doc, from_file_keyword): +def _find_and_remove_from_file(full_doc, from_file_keyword): """ Finds a line in like - and return path + and return path. Also remove the line from the docstring. """ path = None - for line in full_doc.splitlines(): + full_doc_lines = full_doc.splitlines() + for line_count in xrange(len(full_doc_lines) - 1, -1, -1): + line = full_doc_lines[line_count] if from_file_keyword in line: parts = line.strip().split(':') if len(parts) == 2 and parts[0].strip() == from_file_keyword: path = parts[1].strip() + del full_doc_lines[line_count] break - return path + return path, '\n'.join(full_doc_lines) def _doc_from_file(path): @@ -44,27 +47,87 @@ def _doc_from_file(path): return doc +def _merge_swag(swag, doc_swag): + # recursively update any dicts that are the same + if isinstance(swag, dict) and isinstance(doc_swag, dict): + for key, val in doc_swag.items(): + if key not in swag: + swag[key] = val + else: + swag[key] = _merge_swag(swag[key], val) + # if there are multiple params for each type, then + # we'll check the `name` to see what to override, if anything + elif isinstance(swag, list) and isinstance(doc_swag, list): + # AFAIK both lists should contain the same datatype + if not swag: + swag = doc_swag + # for strings and lists, we'll go with the default swag + # for dicts, we'll add doc_swag to our swag if there's a `name` val + # that's not in any of the swag dicts + elif swag and isinstance(swag[0], dict): + swag_names = [] + for item in swag: + name = [val for key, val in item.items() if key == 'name'] + if name: + swag_names.append(name[0]) + for doc_item in doc_swag: + name = doc_item.get('name') + if name is not None: + if name not in swag_names: + swag.append(doc_item) + else: + # if there's no name, then there's no way to check for + # uniqueness. Assume unique. + swag.append(doc_item) + return swag + + +def _merge_template(doc_template, process_doc, summary, desc, swag): + doc_summary, doc_desc, doc_swag = _get_docstring_parts( + doc_template, process_doc) + # favor everything from the original docstring + if not summary: + summary = doc_summary + if not desc: + desc = doc_desc + if doc_swag: + if not swag: + swag = doc_swag + else: + _merge_swag(swag, doc_swag) + return summary, desc, swag + + +def _get_docstring_parts(docstring, process_doc): + other_lines, swag = None, None + line_feed = docstring.find('\n') + if line_feed != -1: + first_line = process_doc(docstring[:line_feed]) + yaml_sep = docstring[line_feed+1:].find('---') + if yaml_sep != -1: + other_lines = process_doc(docstring[line_feed+1:line_feed+yaml_sep]) + swag = yaml.load(docstring[line_feed+yaml_sep:]) + else: + other_lines = process_doc(docstring[line_feed+1:]) + else: + first_line = docstring + return first_line, other_lines, swag + + def _parse_docstring(obj, process_doc, from_file_keyword): - first_line, other_lines, swag = None, None, None + first_line, other_lines, swag, doc_template = None, None, None, None full_doc = inspect.getdoc(obj) if full_doc: if from_file_keyword is not None: - from_file = _find_from_file(full_doc, from_file_keyword) + from_file, full_doc = _find_and_remove_from_file( + full_doc, from_file_keyword) if from_file: - full_doc_from_file = _doc_from_file(from_file) - if full_doc_from_file: - full_doc = full_doc_from_file - line_feed = full_doc.find('\n') - if line_feed != -1: - first_line = process_doc(full_doc[:line_feed]) - yaml_sep = full_doc[line_feed+1:].find('---') - if yaml_sep != -1: - other_lines = process_doc(full_doc[line_feed+1:line_feed+yaml_sep]) - swag = yaml.load(full_doc[line_feed+yaml_sep:]) - else: - other_lines = process_doc(full_doc[line_feed+1:]) - else: - first_line = full_doc + doc_template = _doc_from_file(from_file) + first_line, other_lines, swag = _get_docstring_parts( + full_doc, process_doc) + if doc_template: + first_line, other_lines, swag = _merge_template( + doc_template, process_doc, first_line, other_lines, swag) return first_line, other_lines, swag