Initial commit

This commit is contained in:
Senad Uka
2019-11-17 12:44:16 +01:00
parent e41eae7301
commit a3ef27c7a0
4894 changed files with 1771218 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import os
from botocore.docs.service import ServiceDocumenter
def generate_docs(root_dir, session):
"""Generates the reference documentation for botocore
This will go through every available AWS service and output ReSTructured
text files documenting each service.
:param root_dir: The directory to write the reference files to. Each
service's reference documentation is loacated at
root_dir/reference/services/service-name.rst
"""
services_doc_path = os.path.join(root_dir, 'reference', 'services')
if not os.path.exists(services_doc_path):
os.makedirs(services_doc_path)
# Generate reference docs and write them out.
for service_name in session.get_available_services():
docs = ServiceDocumenter(service_name, session).document_service()
service_doc_path = os.path.join(
services_doc_path, service_name + '.rst')
with open(service_doc_path, 'wb') as f:
f.write(docs)

View File

@@ -0,0 +1,13 @@
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
__version__ = '0.16.0'

View File

@@ -0,0 +1,103 @@
# Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
DOC_EVENTS = {
'doc-breadcrumbs': '.%s',
'doc-title': '.%s',
'doc-description': '.%s',
'doc-synopsis-start': '.%s',
'doc-synopsis-option': '.%s.%s',
'doc-synopsis-end': '.%s',
'doc-options-start': '.%s',
'doc-option': '.%s.%s',
'doc-option-example': '.%s.%s',
'doc-options-end': '.%s',
'doc-examples': '.%s',
'doc-output': '.%s',
'doc-subitems-start': '.%s',
'doc-subitem': '.%s.%s',
'doc-subitems-end': '.%s',
'doc-relateditems-start': '.%s',
'doc-relateditem': '.%s.%s',
'doc-relateditems-end': '.%s'
}
def generate_events(session, help_command):
# Now generate the documentation events
session.emit('doc-breadcrumbs.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-title.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-description.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-synopsis-start.%s' % help_command.event_class,
help_command=help_command)
if help_command.arg_table:
for arg_name in help_command.arg_table:
# An argument can set an '_UNDOCUMENTED' attribute
# to True to indicate a parameter that exists
# but shouldn't be documented. This can be used
# for backwards compatibility of deprecated arguments.
if getattr(help_command.arg_table[arg_name],
'_UNDOCUMENTED', False):
continue
session.emit(
'doc-synopsis-option.%s.%s' % (help_command.event_class,
arg_name),
arg_name=arg_name, help_command=help_command)
session.emit('doc-synopsis-end.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-options-start.%s' % help_command.event_class,
help_command=help_command)
if help_command.arg_table:
for arg_name in help_command.arg_table:
if getattr(help_command.arg_table[arg_name],
'_UNDOCUMENTED', False):
continue
session.emit('doc-option.%s.%s' % (help_command.event_class,
arg_name),
arg_name=arg_name, help_command=help_command)
session.emit('doc-option-example.%s.%s' %
(help_command.event_class, arg_name),
arg_name=arg_name, help_command=help_command)
session.emit('doc-options-end.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-subitems-start.%s' % help_command.event_class,
help_command=help_command)
if help_command.command_table:
for command_name in sorted(help_command.command_table.keys()):
if hasattr(help_command.command_table[command_name],
'_UNDOCUMENTED'):
continue
session.emit('doc-subitem.%s.%s'
% (help_command.event_class, command_name),
command_name=command_name,
help_command=help_command)
session.emit('doc-subitems-end.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-examples.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-output.%s' % help_command.event_class,
help_command=help_command)
session.emit('doc-relateditems-start.%s' % help_command.event_class,
help_command=help_command)
if help_command.related_items:
for related_item in sorted(help_command.related_items):
session.emit('doc-relateditem.%s.%s'
% (help_command.event_class, related_item),
help_command=help_command,
related_item=related_item)
session.emit('doc-relateditems-end.%s' % help_command.event_class,
help_command=help_command)

View File

@@ -0,0 +1,200 @@
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore.compat import six
class DocStringParser(six.moves.html_parser.HTMLParser):
"""
A simple HTML parser. Focused on converting the subset of HTML
that appears in the documentation strings of the JSON models into
simple ReST format.
"""
def __init__(self, doc):
self.tree = None
self.doc = doc
six.moves.html_parser.HTMLParser.__init__(self)
def reset(self):
six.moves.html_parser.HTMLParser.reset(self)
self.tree = HTMLTree(self.doc)
def feed(self, data):
# HTMLParser is an old style class, so the super() method will not work.
six.moves.html_parser.HTMLParser.feed(self, data)
self.tree.write()
self.tree = HTMLTree(self.doc)
def close(self):
six.moves.html_parser.HTMLParser.close(self)
# Write if there is anything remaining.
self.tree.write()
self.tree = HTMLTree(self.doc)
def handle_starttag(self, tag, attrs):
self.tree.add_tag(tag, attrs=attrs)
def handle_endtag(self, tag):
self.tree.add_tag(tag, is_start=False)
def handle_data(self, data):
self.tree.add_data(data)
class HTMLTree(object):
"""
A tree which handles HTML nodes. Designed to work with a python HTML parser,
meaning that the current_node will be the most recently opened tag. When
a tag is closed, the current_node moves up to the parent node.
"""
def __init__(self, doc):
self.doc = doc
self.head = StemNode()
self.current_node = self.head
self.unhandled_tags = []
def add_tag(self, tag, attrs=None, is_start=True):
if not self._doc_has_handler(tag, is_start):
self.unhandled_tags.append(tag)
return
if is_start:
if tag == 'li':
node = LineItemNode(attrs)
else:
node = TagNode(tag, attrs)
self.current_node.add_child(node)
self.current_node = node
else:
self.current_node = self.current_node.parent
def _doc_has_handler(self, tag, is_start):
if is_start:
handler_name = 'start_%s' % tag
else:
handler_name = 'end_%s' % tag
return hasattr(self.doc.style, handler_name)
def add_data(self, data):
self.current_node.add_child(DataNode(data))
def write(self):
self.head.write(self.doc)
class Node(object):
def __init__(self, parent=None):
self.parent = parent
def write(self, doc):
raise NotImplementedError
class StemNode(Node):
def __init__(self, parent=None):
super(StemNode, self).__init__(parent)
self.children = []
def add_child(self, child):
child.parent = self
self.children.append(child)
def write(self, doc):
self._write_children(doc)
def _write_children(self, doc):
for child in self.children:
child.write(doc)
class TagNode(StemNode):
"""
A generic Tag node. It will verify that handlers exist before writing.
"""
def __init__(self, tag, attrs=None, parent=None):
super(TagNode, self).__init__(parent)
self.attrs = attrs
self.tag = tag
def write(self, doc):
self._write_start(doc)
self._write_children(doc)
self._write_end(doc)
def _write_start(self, doc):
handler_name = 'start_%s' % self.tag
if hasattr(doc.style, handler_name):
getattr(doc.style, handler_name)(self.attrs)
def _write_end(self, doc):
handler_name = 'end_%s' % self.tag
if hasattr(doc.style, handler_name):
getattr(doc.style, handler_name)()
class LineItemNode(TagNode):
def __init__(self, attrs=None, parent=None):
super(LineItemNode, self).__init__('li', attrs, parent)
def write(self, doc):
self._lstrip(self)
super(LineItemNode, self).write(doc)
def _lstrip(self, node):
"""
Traverses the tree, stripping out whitespace until text data is found
:param node: The node to strip
:return: True if non-whitespace data was found, False otherwise
"""
for child in node.children:
if isinstance(child, DataNode):
child.lstrip()
if child.data:
return True
else:
found = self._lstrip(child)
if found:
return True
return False
class DataNode(Node):
"""
A Node that contains only string data.
"""
def __init__(self, data, parent=None):
super(DataNode, self).__init__(parent)
if not isinstance(data, six.string_types):
raise ValueError("Expecting string type, %s given." % type(data))
self.data = data
def lstrip(self):
self.data = self.data.lstrip()
def write(self, doc):
if not self.data:
return
if self.data.isspace():
str_data = ' '
else:
end_space = self.data[-1].isspace()
words = self.data.split()
words = doc.translate_words(words)
str_data = ' '.join(words)
if end_space:
str_data += ' '
doc.handle_data(str_data)

View File

@@ -0,0 +1,218 @@
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import logging
from botocore.compat import OrderedDict
from botocore.docs.bcdoc.docstringparser import DocStringParser
from botocore.docs.bcdoc.style import ReSTStyle
LOG = logging.getLogger('bcdocs')
class ReSTDocument(object):
def __init__(self, target='man'):
self.style = ReSTStyle(self)
self.target = target
self.parser = DocStringParser(self)
self.keep_data = True
self.do_translation = False
self.translation_map = {}
self.hrefs = {}
self._writes = []
self._last_doc_string = None
def _write(self, s):
if self.keep_data and s is not None:
self._writes.append(s)
def write(self, content):
"""
Write content into the document.
"""
self._write(content)
def writeln(self, content):
"""
Write content on a newline.
"""
self._write('%s%s\n' % (self.style.spaces(), content))
def peek_write(self):
"""
Returns the last content written to the document without
removing it from the stack.
"""
return self._writes[-1]
def pop_write(self):
"""
Removes and returns the last content written to the stack.
"""
return self._writes.pop()
def push_write(self, s):
"""
Places new content on the stack.
"""
self._writes.append(s)
def getvalue(self):
"""
Returns the current content of the document as a string.
"""
if self.hrefs:
self.style.new_paragraph()
for refname, link in self.hrefs.items():
self.style.link_target_definition(refname, link)
return ''.join(self._writes).encode('utf-8')
def translate_words(self, words):
return [self.translation_map.get(w, w) for w in words]
def handle_data(self, data):
if data and self.keep_data:
self._write(data)
def include_doc_string(self, doc_string):
if doc_string:
try:
start = len(self._writes)
self.parser.feed(doc_string)
self.parser.close()
end = len(self._writes)
self._last_doc_string = (start, end)
except Exception:
LOG.debug('Error parsing doc string', exc_info=True)
LOG.debug(doc_string)
def remove_last_doc_string(self):
# Removes all writes inserted by last doc string
if self._last_doc_string is not None:
start, end = self._last_doc_string
del self._writes[start:end]
class DocumentStructure(ReSTDocument):
def __init__(self, name, section_names=None, target='man', context=None):
"""Provides a Hierarichial structure to a ReSTDocument
You can write to it similiar to as you can to a ReSTDocument but
has an innate structure for more orginaztion and abstraction.
:param name: The name of the document
:param section_names: A list of sections to be included
in the document.
:param target: The target documentation of the Document structure
:param context: A dictionary of data to store with the strucuture. These
are only stored per section not the entire structure.
"""
super(DocumentStructure, self).__init__(target=target)
self._name = name
self._structure = OrderedDict()
self._path = [self._name]
self._context = {}
if context is not None:
self._context = context
if section_names is not None:
self._generate_structure(section_names)
@property
def name(self):
"""The name of the document structure"""
return self._name
@property
def path(self):
"""
A list of where to find a particular document structure in the
overlying document structure.
"""
return self._path
@path.setter
def path(self, value):
self._path = value
@property
def available_sections(self):
return list(self._structure)
@property
def context(self):
return self._context
def _generate_structure(self, section_names):
for section_name in section_names:
self.add_new_section(section_name)
def add_new_section(self, name, context=None):
"""Adds a new section to the current document structure
This document structure will be considered a section to the
current document structure but will in itself be an entirely
new document structure that can be written to and have sections
as well
:param name: The name of the section.
:param context: A dictionary of data to store with the strucuture. These
are only stored per section not the entire structure.
:rtype: DocumentStructure
:returns: A new document structure to add to but lives as a section
to the document structure it was instantiated from.
"""
# Add a new section
section = self.__class__(name=name, target=self.target,
context=context)
section.path = self.path + [name]
# Indent the section apporpriately as well
section.style.indentation = self.style.indentation
section.translation_map = self.translation_map
section.hrefs = self.hrefs
self._structure[name] = section
return section
def get_section(self, name):
"""Retrieve a section"""
return self._structure[name]
def delete_section(self, name):
"""Delete a section"""
del self._structure[name]
def flush_structure(self):
"""Flushes a doc structure to a ReSTructed string
The document is flushed out in a DFS style where sections and their
subsections' values are added to the string as they are visited.
"""
# We are at the root flush the links at the beginning of the
# document
if len(self.path) == 1:
if self.hrefs:
self.style.new_paragraph()
for refname, link in self.hrefs.items():
self.style.link_target_definition(refname, link)
value = self.getvalue()
for name, section in self._structure.items():
value += section.flush_structure()
return value
def getvalue(self):
return ''.join(self._writes).encode('utf-8')
def remove_all_sections(self):
self._structure = OrderedDict()
def clear_text(self):
self._writes = []

View File

@@ -0,0 +1,418 @@
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import logging
logger = logging.getLogger('bcdocs')
class BaseStyle(object):
def __init__(self, doc, indent_width=2):
self.doc = doc
self.indent_width = indent_width
self._indent = 0
self.keep_data = True
@property
def indentation(self):
return self._indent
@indentation.setter
def indentation(self, value):
self._indent = value
def new_paragraph(self):
return '\n%s' % self.spaces()
def indent(self):
self._indent += 1
def dedent(self):
if self._indent > 0:
self._indent -= 1
def spaces(self):
return ' ' * (self._indent * self.indent_width)
def bold(self, s):
return s
def ref(self, link, title=None):
return link
def h2(self, s):
return s
def h3(self, s):
return s
def underline(self, s):
return s
def italics(self, s):
return s
class ReSTStyle(BaseStyle):
def __init__(self, doc, indent_width=2):
BaseStyle.__init__(self, doc, indent_width)
self.do_p = True
self.a_href = None
self.list_depth = 0
def new_paragraph(self):
self.doc.write('\n\n%s' % self.spaces())
def new_line(self):
self.doc.write('\n%s' % self.spaces())
def _start_inline(self, markup):
self.doc.write(markup)
def _end_inline(self, markup):
# Sometimes the HTML markup has whitespace between the end
# of the text inside the inline markup and the closing element
# (e.g. <b>foobar </b>). This trailing space will cause
# problems in the ReST inline markup so we remove it here
# by popping the last item written off the stack, striping
# the whitespace and then pushing it back on the stack.
last_write = self.doc.pop_write().rstrip(' ')
# Sometimes, for whatever reason, a tag like <b/> is present. This
# is problematic because if we simply translate that directly then
# we end up with something like ****, which rst will assume is a
# heading instead of an empty bold.
if last_write == markup:
return
self.doc.push_write(last_write)
self.doc.write(markup + ' ')
def start_bold(self, attrs=None):
self._start_inline('**')
def end_bold(self):
self._end_inline('**')
def start_b(self, attrs=None):
self.doc.do_translation = True
self.start_bold(attrs)
def end_b(self):
self.doc.do_translation = False
self.end_bold()
def bold(self, s):
if s:
self.start_bold()
self.doc.write(s)
self.end_bold()
def ref(self, title, link=None):
if link is None:
link = title
self.doc.write(':doc:`%s <%s>`' % (title, link))
def _heading(self, s, border_char):
border = border_char * len(s)
self.new_paragraph()
self.doc.write('%s\n%s\n%s' % (border, s, border))
self.new_paragraph()
def h1(self, s):
self._heading(s, '*')
def h2(self, s):
self._heading(s, '=')
def h3(self, s):
self._heading(s, '-')
def start_italics(self, attrs=None):
self._start_inline('*')
def end_italics(self):
self._end_inline('*')
def italics(self, s):
if s:
self.start_italics()
self.doc.write(s)
self.end_italics()
def start_p(self, attrs=None):
if self.do_p:
self.doc.write('\n\n%s' % self.spaces())
def end_p(self):
if self.do_p:
self.doc.write('\n\n%s' % self.spaces())
def start_code(self, attrs=None):
self.doc.do_translation = True
self._start_inline('``')
def end_code(self):
self.doc.do_translation = False
self._end_inline('``')
def code(self, s):
if s:
self.start_code()
self.doc.write(s)
self.end_code()
def start_note(self, attrs=None):
self.new_paragraph()
self.doc.write('.. note::')
self.indent()
self.new_paragraph()
def end_note(self):
self.dedent()
self.new_paragraph()
def start_important(self, attrs=None):
self.new_paragraph()
self.doc.write('.. warning::')
self.indent()
self.new_paragraph()
def end_important(self):
self.dedent()
self.new_paragraph()
def start_danger(self, attrs=None):
self.new_paragraph()
self.doc.write('.. danger::')
self.indent()
self.new_paragraph()
def end_danger(self):
self.dedent()
self.new_paragraph()
def start_a(self, attrs=None):
if attrs:
for attr_key, attr_value in attrs:
if attr_key == 'href':
self.a_href = attr_value
self.doc.write('`')
else:
# There are some model documentation that
# looks like this: <a>DescribeInstances</a>.
# In this case we just write out an empty
# string.
self.doc.write(' ')
self.doc.do_translation = True
def link_target_definition(self, refname, link):
self.doc.writeln('.. _%s: %s' % (refname, link))
def sphinx_reference_label(self, label, text=None):
if text is None:
text = label
if self.doc.target == 'html':
self.doc.write(':ref:`%s <%s>`' % (text, label))
else:
self.doc.write(text)
def end_a(self):
self.doc.do_translation = False
if self.a_href:
last_write = self.doc.pop_write()
last_write = last_write.rstrip(' ')
if last_write and last_write != '`':
if ':' in last_write:
last_write = last_write.replace(':', r'\:')
self.doc.push_write(last_write)
self.doc.push_write(' <%s>`__' % self.a_href)
elif last_write == '`':
# Look at start_a(). It will do a self.doc.write('`')
# which is the start of the link title. If that is the
# case then there was no link text. We should just
# use an inline link. The syntax of this is
# `<http://url>`_
self.doc.push_write('`<%s>`__' % self.a_href)
else:
self.doc.push_write(self.a_href)
self.doc.hrefs[self.a_href] = self.a_href
self.doc.write('`__')
self.a_href = None
self.doc.write(' ')
def start_i(self, attrs=None):
self.doc.do_translation = True
self.start_italics()
def end_i(self):
self.doc.do_translation = False
self.end_italics()
def start_li(self, attrs=None):
self.new_line()
self.do_p = False
self.doc.write('* ')
def end_li(self):
self.do_p = True
self.new_line()
def li(self, s):
if s:
self.start_li()
self.doc.writeln(s)
self.end_li()
def start_ul(self, attrs=None):
if self.list_depth != 0:
self.indent()
self.list_depth += 1
self.new_paragraph()
def end_ul(self):
self.list_depth -= 1
if self.list_depth != 0:
self.dedent()
self.new_paragraph()
def start_ol(self, attrs=None):
# TODO: Need to control the bullets used for LI items
if self.list_depth != 0:
self.indent()
self.list_depth += 1
self.new_paragraph()
def end_ol(self):
self.list_depth -= 1
if self.list_depth != 0:
self.dedent()
self.new_paragraph()
def start_examples(self, attrs=None):
self.doc.keep_data = False
def end_examples(self):
self.doc.keep_data = True
def start_fullname(self, attrs=None):
self.doc.keep_data = False
def end_fullname(self):
self.doc.keep_data = True
def start_codeblock(self, attrs=None):
self.doc.write('::')
self.indent()
self.new_paragraph()
def end_codeblock(self):
self.dedent()
self.new_paragraph()
def codeblock(self, code):
"""
Literal code blocks are introduced by ending a paragraph with
the special marker ::. The literal block must be indented
(and, like all paragraphs, separated from the surrounding
ones by blank lines).
"""
self.start_codeblock()
self.doc.writeln(code)
self.end_codeblock()
def toctree(self):
if self.doc.target == 'html':
self.doc.write('\n.. toctree::\n')
self.doc.write(' :maxdepth: 1\n')
self.doc.write(' :titlesonly:\n\n')
else:
self.start_ul()
def tocitem(self, item, file_name=None):
if self.doc.target == 'man':
self.li(item)
else:
if file_name:
self.doc.writeln(' %s' % file_name)
else:
self.doc.writeln(' %s' % item)
def hidden_toctree(self):
if self.doc.target == 'html':
self.doc.write('\n.. toctree::\n')
self.doc.write(' :maxdepth: 1\n')
self.doc.write(' :hidden:\n\n')
def hidden_tocitem(self, item):
if self.doc.target == 'html':
self.tocitem(item)
def table_of_contents(self, title=None, depth=None):
self.doc.write('.. contents:: ')
if title is not None:
self.doc.writeln(title)
if depth is not None:
self.doc.writeln(' :depth: %s' % depth)
def start_sphinx_py_class(self, class_name):
self.new_paragraph()
self.doc.write('.. py:class:: %s' % class_name)
self.indent()
self.new_paragraph()
def end_sphinx_py_class(self):
self.dedent()
self.new_paragraph()
def start_sphinx_py_method(self, method_name, parameters=None):
self.new_paragraph()
content = '.. py:method:: %s' % method_name
if parameters is not None:
content += '(%s)' % parameters
self.doc.write(content)
self.indent()
self.new_paragraph()
def end_sphinx_py_method(self):
self.dedent()
self.new_paragraph()
def start_sphinx_py_attr(self, attr_name):
self.new_paragraph()
self.doc.write('.. py:attribute:: %s' % attr_name)
self.indent()
self.new_paragraph()
def end_sphinx_py_attr(self):
self.dedent()
self.new_paragraph()
def write_py_doc_string(self, docstring):
docstring_lines = docstring.splitlines()
for docstring_line in docstring_lines:
self.doc.writeln(docstring_line)
def external_link(self, title, link):
if self.doc.target == 'html':
self.doc.write('`%s <%s>`_' % (title, link))
else:
self.doc.write(title)
def internal_link(self, title, page):
if self.doc.target == 'html':
self.doc.write(':doc:`%s <%s>`' % (title, page))
else:
self.doc.write(title)

View File

@@ -0,0 +1,799 @@
# -*- coding: utf-8 -*-
"""
Custom docutils writer for plain text.
Based heavily on the Sphinx text writer. See copyright below.
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import re
import textwrap
from docutils import nodes, writers
class TextWrapper(textwrap.TextWrapper):
"""Custom subclass that uses a different word separator regex."""
wordsep_re = re.compile(
r'(\s+|' # any whitespace
r'(?<=\s)(?::[a-z-]+:)?`\S+|' # interpreted text start
r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|' # hyphenated words
r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
MAXWIDTH = 70
STDINDENT = 3
def my_wrap(text, width=MAXWIDTH, **kwargs):
w = TextWrapper(width=width, **kwargs)
return w.wrap(text)
class TextWriter(writers.Writer):
supported = ('text',)
settings_spec = ('No options here.', '', ())
settings_defaults = {}
output = None
def __init__(self):
writers.Writer.__init__(self)
def translate(self):
visitor = TextTranslator(self.document)
self.document.walkabout(visitor)
self.output = visitor.body
class TextTranslator(nodes.NodeVisitor):
sectionchars = '*=-~"+`'
def __init__(self, document):
nodes.NodeVisitor.__init__(self, document)
self.nl = os.linesep
self.states = [[]]
self.stateindent = [0]
self.list_counter = []
self.sectionlevel = 0
self.table = None
def add_text(self, text):
self.states[-1].append((-1, text))
def new_state(self, indent=STDINDENT):
self.states.append([])
self.stateindent.append(indent)
def end_state(self, wrap=True, end=[''], first=None):
content = self.states.pop()
maxindent = sum(self.stateindent)
indent = self.stateindent.pop()
result = []
toformat = []
def do_format():
if not toformat:
return
if wrap:
res = my_wrap(''.join(toformat), width=MAXWIDTH-maxindent)
else:
res = ''.join(toformat).splitlines()
if end:
res += end
result.append((indent, res))
for itemindent, item in content:
if itemindent == -1:
toformat.append(item)
else:
do_format()
result.append((indent + itemindent, item))
toformat = []
do_format()
if first is not None and result:
itemindent, item = result[0]
if item:
result.insert(0, (itemindent - indent, [first + item[0]]))
result[1] = (itemindent, item[1:])
self.states[-1].extend(result)
def visit_document(self, node):
self.new_state(0)
def depart_document(self, node):
self.end_state()
self.body = self.nl.join(line and (' '*indent + line)
for indent, lines in self.states[0]
for line in lines)
# XXX header/footer?
def visit_highlightlang(self, node):
raise nodes.SkipNode
def visit_section(self, node):
self._title_char = self.sectionchars[self.sectionlevel]
self.sectionlevel += 1
def depart_section(self, node):
self.sectionlevel -= 1
def visit_topic(self, node):
self.new_state(0)
def depart_topic(self, node):
self.end_state()
visit_sidebar = visit_topic
depart_sidebar = depart_topic
def visit_rubric(self, node):
self.new_state(0)
self.add_text('-[ ')
def depart_rubric(self, node):
self.add_text(' ]-')
self.end_state()
def visit_compound(self, node):
pass
def depart_compound(self, node):
pass
def visit_glossary(self, node):
pass
def depart_glossary(self, node):
pass
def visit_title(self, node):
if isinstance(node.parent, nodes.Admonition):
self.add_text(node.astext()+': ')
raise nodes.SkipNode
self.new_state(0)
def depart_title(self, node):
if isinstance(node.parent, nodes.section):
char = self._title_char
else:
char = '^'
text = ''.join(x[1] for x in self.states.pop() if x[0] == -1)
self.stateindent.pop()
self.states[-1].append((0, ['', text, '%s' % (char * len(text)), '']))
def visit_subtitle(self, node):
pass
def depart_subtitle(self, node):
pass
def visit_attribution(self, node):
self.add_text('-- ')
def depart_attribution(self, node):
pass
def visit_desc(self, node):
pass
def depart_desc(self, node):
pass
def visit_desc_signature(self, node):
self.new_state(0)
if node.parent['objtype'] in ('class', 'exception'):
self.add_text('%s ' % node.parent['objtype'])
def depart_desc_signature(self, node):
# XXX: wrap signatures in a way that makes sense
self.end_state(wrap=False, end=None)
def visit_desc_name(self, node):
pass
def depart_desc_name(self, node):
pass
def visit_desc_addname(self, node):
pass
def depart_desc_addname(self, node):
pass
def visit_desc_type(self, node):
pass
def depart_desc_type(self, node):
pass
def visit_desc_returns(self, node):
self.add_text(' -> ')
def depart_desc_returns(self, node):
pass
def visit_desc_parameterlist(self, node):
self.add_text('(')
self.first_param = 1
def depart_desc_parameterlist(self, node):
self.add_text(')')
def visit_desc_parameter(self, node):
if not self.first_param:
self.add_text(', ')
else:
self.first_param = 0
self.add_text(node.astext())
raise nodes.SkipNode
def visit_desc_optional(self, node):
self.add_text('[')
def depart_desc_optional(self, node):
self.add_text(']')
def visit_desc_annotation(self, node):
pass
def depart_desc_annotation(self, node):
pass
def visit_refcount(self, node):
pass
def depart_refcount(self, node):
pass
def visit_desc_content(self, node):
self.new_state()
self.add_text(self.nl)
def depart_desc_content(self, node):
self.end_state()
def visit_figure(self, node):
self.new_state()
def depart_figure(self, node):
self.end_state()
def visit_caption(self, node):
pass
def depart_caption(self, node):
pass
def visit_productionlist(self, node):
self.new_state()
names = []
for production in node:
names.append(production['tokenname'])
maxlen = max(len(name) for name in names)
for production in node:
if production['tokenname']:
self.add_text(production['tokenname'].ljust(maxlen) + ' ::=')
lastname = production['tokenname']
else:
self.add_text('%s ' % (' '*len(lastname)))
self.add_text(production.astext() + self.nl)
self.end_state(wrap=False)
raise nodes.SkipNode
def visit_seealso(self, node):
self.new_state()
def depart_seealso(self, node):
self.end_state(first='')
def visit_footnote(self, node):
self._footnote = node.children[0].astext().strip()
self.new_state(len(self._footnote) + 3)
def depart_footnote(self, node):
self.end_state(first='[%s] ' % self._footnote)
def visit_citation(self, node):
if len(node) and isinstance(node[0], nodes.label):
self._citlabel = node[0].astext()
else:
self._citlabel = ''
self.new_state(len(self._citlabel) + 3)
def depart_citation(self, node):
self.end_state(first='[%s] ' % self._citlabel)
def visit_label(self, node):
raise nodes.SkipNode
# XXX: option list could use some better styling
def visit_option_list(self, node):
pass
def depart_option_list(self, node):
pass
def visit_option_list_item(self, node):
self.new_state(0)
def depart_option_list_item(self, node):
self.end_state()
def visit_option_group(self, node):
self._firstoption = True
def depart_option_group(self, node):
self.add_text(' ')
def visit_option(self, node):
if self._firstoption:
self._firstoption = False
else:
self.add_text(', ')
def depart_option(self, node):
pass
def visit_option_string(self, node):
pass
def depart_option_string(self, node):
pass
def visit_option_argument(self, node):
self.add_text(node['delimiter'])
def depart_option_argument(self, node):
pass
def visit_description(self, node):
pass
def depart_description(self, node):
pass
def visit_tabular_col_spec(self, node):
raise nodes.SkipNode
def visit_colspec(self, node):
self.table[0].append(node['colwidth'])
raise nodes.SkipNode
def visit_tgroup(self, node):
pass
def depart_tgroup(self, node):
pass
def visit_thead(self, node):
pass
def depart_thead(self, node):
pass
def visit_tbody(self, node):
self.table.append('sep')
def depart_tbody(self, node):
pass
def visit_row(self, node):
self.table.append([])
def depart_row(self, node):
pass
def visit_entry(self, node):
if 'morerows' in node or 'morecols' in node:
raise NotImplementedError('Column or row spanning cells are '
'not implemented.')
self.new_state(0)
def depart_entry(self, node):
text = self.nl.join(self.nl.join(x[1]) for x in self.states.pop())
self.stateindent.pop()
self.table[-1].append(text)
def visit_table(self, node):
if self.table:
raise NotImplementedError('Nested tables are not supported.')
self.new_state(0)
self.table = [[]]
def depart_table(self, node):
lines = self.table[1:]
fmted_rows = []
colwidths = self.table[0]
realwidths = colwidths[:]
separator = 0
# don't allow paragraphs in table cells for now
for line in lines:
if line == 'sep':
separator = len(fmted_rows)
else:
cells = []
for i, cell in enumerate(line):
par = my_wrap(cell, width=colwidths[i])
if par:
maxwidth = max(map(len, par))
else:
maxwidth = 0
realwidths[i] = max(realwidths[i], maxwidth)
cells.append(par)
fmted_rows.append(cells)
def writesep(char='-'):
out = ['+']
for width in realwidths:
out.append(char * (width+2))
out.append('+')
self.add_text(''.join(out) + self.nl)
def writerow(row):
lines = zip(*row)
for line in lines:
out = ['|']
for i, cell in enumerate(line):
if cell:
out.append(' ' + cell.ljust(realwidths[i]+1))
else:
out.append(' ' * (realwidths[i] + 2))
out.append('|')
self.add_text(''.join(out) + self.nl)
for i, row in enumerate(fmted_rows):
if separator and i == separator:
writesep('=')
else:
writesep('-')
writerow(row)
writesep('-')
self.table = None
self.end_state(wrap=False)
def visit_acks(self, node):
self.new_state(0)
self.add_text(
', '.join(n.astext() for n in node.children[0].children) + '.')
self.end_state()
raise nodes.SkipNode
def visit_image(self, node):
if 'alt' in node.attributes:
self.add_text(_('[image: %s]') % node['alt'])
self.add_text(_('[image]'))
raise nodes.SkipNode
def visit_transition(self, node):
indent = sum(self.stateindent)
self.new_state(0)
self.add_text('=' * (MAXWIDTH - indent))
self.end_state()
raise nodes.SkipNode
def visit_bullet_list(self, node):
self.list_counter.append(-1)
def depart_bullet_list(self, node):
self.list_counter.pop()
def visit_enumerated_list(self, node):
self.list_counter.append(0)
def depart_enumerated_list(self, node):
self.list_counter.pop()
def visit_definition_list(self, node):
self.list_counter.append(-2)
def depart_definition_list(self, node):
self.list_counter.pop()
def visit_list_item(self, node):
if self.list_counter[-1] == -1:
# bullet list
self.new_state(2)
elif self.list_counter[-1] == -2:
# definition list
pass
else:
# enumerated list
self.list_counter[-1] += 1
self.new_state(len(str(self.list_counter[-1])) + 2)
def depart_list_item(self, node):
if self.list_counter[-1] == -1:
self.end_state(first='* ', end=None)
elif self.list_counter[-1] == -2:
pass
else:
self.end_state(first='%s. ' % self.list_counter[-1], end=None)
def visit_definition_list_item(self, node):
self._li_has_classifier = len(node) >= 2 and \
isinstance(node[1], nodes.classifier)
def depart_definition_list_item(self, node):
pass
def visit_term(self, node):
self.new_state(0)
def depart_term(self, node):
if not self._li_has_classifier:
self.end_state(end=None)
def visit_termsep(self, node):
self.add_text(', ')
raise nodes.SkipNode
def visit_classifier(self, node):
self.add_text(' : ')
def depart_classifier(self, node):
self.end_state(end=None)
def visit_definition(self, node):
self.new_state()
def depart_definition(self, node):
self.end_state()
def visit_field_list(self, node):
pass
def depart_field_list(self, node):
pass
def visit_field(self, node):
pass
def depart_field(self, node):
pass
def visit_field_name(self, node):
self.new_state(0)
def depart_field_name(self, node):
self.add_text(':')
self.end_state(end=None)
def visit_field_body(self, node):
self.new_state()
def depart_field_body(self, node):
self.end_state()
def visit_centered(self, node):
pass
def depart_centered(self, node):
pass
def visit_hlist(self, node):
pass
def depart_hlist(self, node):
pass
def visit_hlistcol(self, node):
pass
def depart_hlistcol(self, node):
pass
def visit_admonition(self, node):
self.new_state(0)
def depart_admonition(self, node):
self.end_state()
def visit_versionmodified(self, node):
self.new_state(0)
def depart_versionmodified(self, node):
self.end_state()
def visit_literal_block(self, node):
self.new_state()
def depart_literal_block(self, node):
self.end_state(wrap=False)
def visit_doctest_block(self, node):
self.new_state(0)
def depart_doctest_block(self, node):
self.end_state(wrap=False)
def visit_line_block(self, node):
self.new_state(0)
def depart_line_block(self, node):
self.end_state(wrap=False)
def visit_line(self, node):
pass
def depart_line(self, node):
pass
def visit_block_quote(self, node):
self.new_state()
def depart_block_quote(self, node):
self.end_state()
def visit_compact_paragraph(self, node):
pass
def depart_compact_paragraph(self, node):
pass
def visit_paragraph(self, node):
self.new_state(0)
def depart_paragraph(self, node):
self.end_state()
def visit_target(self, node):
raise nodes.SkipNode
def visit_index(self, node):
raise nodes.SkipNode
def visit_substitution_definition(self, node):
raise nodes.SkipNode
def visit_pending_xref(self, node):
pass
def depart_pending_xref(self, node):
pass
def visit_reference(self, node):
pass
def depart_reference(self, node):
pass
def visit_download_reference(self, node):
pass
def depart_download_reference(self, node):
pass
def visit_emphasis(self, node):
self.add_text('*')
def depart_emphasis(self, node):
self.add_text('*')
def visit_literal_emphasis(self, node):
self.add_text('*')
def depart_literal_emphasis(self, node):
self.add_text('*')
def visit_strong(self, node):
self.add_text('**')
def depart_strong(self, node):
self.add_text('**')
def visit_abbreviation(self, node):
self.add_text('')
def depart_abbreviation(self, node):
if node.hasattr('explanation'):
self.add_text(' (%s)' % node['explanation'])
def visit_title_reference(self, node):
self.add_text('*')
def depart_title_reference(self, node):
self.add_text('*')
def visit_literal(self, node):
self.add_text('"')
def depart_literal(self, node):
self.add_text('"')
def visit_subscript(self, node):
self.add_text('_')
def depart_subscript(self, node):
pass
def visit_superscript(self, node):
self.add_text('^')
def depart_superscript(self, node):
pass
def visit_footnote_reference(self, node):
self.add_text('[%s]' % node.astext())
raise nodes.SkipNode
def visit_citation_reference(self, node):
self.add_text('[%s]' % node.astext())
raise nodes.SkipNode
def visit_Text(self, node):
self.add_text(node.astext())
def depart_Text(self, node):
pass
def visit_generated(self, node):
pass
def depart_generated(self, node):
pass
def visit_inline(self, node):
pass
def depart_inline(self, node):
pass
def visit_problematic(self, node):
self.add_text('>>')
def depart_problematic(self, node):
self.add_text('<<')
def visit_system_message(self, node):
self.new_state(0)
self.add_text('<SYSTEM MESSAGE: %s>' % node.astext())
self.end_state()
raise nodes.SkipNode
def visit_comment(self, node):
raise nodes.SkipNode
def visit_meta(self, node):
# only valid for HTML
raise nodes.SkipNode
def visit_raw(self, node):
if 'text' in node.get('format', '').split():
self.body.append(node.astext())
raise nodes.SkipNode
def _visit_admonition(self, node):
self.new_state(2)
def _make_depart_admonition(name):
def depart_admonition(self, node):
self.end_state(first=name.capitalize() + ': ')
return depart_admonition
visit_attention = _visit_admonition
depart_attention = _make_depart_admonition('attention')
visit_caution = _visit_admonition
depart_caution = _make_depart_admonition('caution')
visit_danger = _visit_admonition
depart_danger = _make_depart_admonition('danger')
visit_error = _visit_admonition
depart_error = _make_depart_admonition('error')
visit_hint = _visit_admonition
depart_hint = _make_depart_admonition('hint')
visit_important = _visit_admonition
depart_important = _make_depart_admonition('important')
visit_note = _visit_admonition
depart_note = _make_depart_admonition('note')
visit_tip = _visit_admonition
depart_tip = _make_depart_admonition('tip')
visit_warning = _visit_admonition
depart_warning = _make_depart_admonition('warning')
def unknown_visit(self, node):
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)

View File

@@ -0,0 +1,113 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import inspect
from botocore.docs.utils import get_official_service_name
from botocore.docs.method import document_custom_method
from botocore.docs.method import document_model_driven_method
from botocore.docs.method import get_instance_public_methods
from botocore.docs.sharedexample import document_shared_examples
class ClientDocumenter(object):
def __init__(self, client, shared_examples=None):
self._client = client
self._shared_examples = shared_examples
if self._shared_examples is None:
self._shared_examples = {}
self._service_name = self._client.meta.service_model.service_name
def document_client(self, section):
"""Documents a client and its methods
:param section: The section to write to.
"""
self._add_title(section)
self._add_class_signature(section)
client_methods = get_instance_public_methods(self._client)
self._add_client_intro(section, client_methods)
self._add_client_methods(section, client_methods)
def _add_title(self, section):
section.style.h2('Client')
def _add_client_intro(self, section, client_methods):
section = section.add_new_section('intro')
# Write out the top level description for the client.
official_service_name = get_official_service_name(
self._client.meta.service_model)
section.write(
'A low-level client representing %s' % official_service_name)
# Write out the client example instantiation.
self._add_client_creation_example(section)
# List out all of the possible client methods.
section.style.new_line()
section.write('These are the available methods:')
section.style.new_line()
class_name = self._client.__class__.__name__
for method_name in sorted(client_methods):
section.style.li(':py:meth:`~%s.Client.%s`' % (
class_name, method_name))
def _add_class_signature(self, section):
section.style.start_sphinx_py_class(
class_name='%s.Client' % self._client.__class__.__name__)
def _add_client_creation_example(self, section):
section.style.start_codeblock()
section.style.new_line()
section.write(
'client = session.create_client(\'{service}\')'.format(
service=self._service_name)
)
section.style.end_codeblock()
def _add_client_methods(self, section, client_methods):
section = section.add_new_section('methods')
for method_name in sorted(client_methods):
self._add_client_method(
section, method_name, client_methods[method_name])
def _add_client_method(self, section, method_name, method):
section = section.add_new_section(method_name)
if self._is_custom_method(method_name):
self._add_custom_method(section, method_name, method)
else:
self._add_model_driven_method(section, method_name)
def _is_custom_method(self, method_name):
return method_name not in self._client.meta.method_to_api_mapping
def _add_custom_method(self, section, method_name, method):
document_custom_method(section, method_name, method)
def _add_model_driven_method(self, section, method_name):
service_model = self._client.meta.service_model
operation_name = self._client.meta.method_to_api_mapping[method_name]
operation_model = service_model.operation_model(operation_name)
example_prefix = 'response = client.%s' % method_name
document_model_driven_method(
section, method_name, operation_model,
event_emitter=self._client.meta.events,
method_description=operation_model.documentation,
example_prefix=example_prefix,
)
# Add the shared examples
shared_examples = self._shared_examples.get(operation_name)
if shared_examples:
document_shared_examples(
section, operation_model, example_prefix, shared_examples)

View File

@@ -0,0 +1,96 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore.docs.method import document_model_driven_method
from botocore.docs.waiter import document_wait_method
from botocore.docs.paginator import document_paginate_method
from botocore.docs.bcdoc.restdoc import DocumentStructure
class LazyLoadedDocstring(str):
"""Used for lazily loading docstrings
You can instantiate this class and assign it to a __doc__ value.
The docstring will not be generated till accessed via __doc__ or
help(). Note that all docstring classes **must** subclass from
this class. It cannot be used directly as a docstring.
"""
def __init__(self, *args, **kwargs):
"""
The args and kwargs are the same as the underlying document
generation function. These just get proxied to the underlying
function.
"""
super(LazyLoadedDocstring, self).__init__()
self._gen_args = args
self._gen_kwargs = kwargs
self._docstring = None
def __new__(cls, *args, **kwargs):
# Needed in order to sub class from str with args and kwargs
return super(LazyLoadedDocstring, cls).__new__(cls)
def _write_docstring(self, *args, **kwargs):
raise NotImplementedError(
'_write_docstring is not implemented. Please subclass from '
'this class and provide your own _write_docstring method'
)
def expandtabs(self, tabsize=8):
"""Expands tabs to spaces
So this is a big hack in order to get lazy loaded docstring work
for the ``help()``. In the ``help()`` function, ``pydoc`` and
``inspect`` are used. At some point the ``inspect.cleandoc``
method is called. To clean the docs ``expandtabs`` is called
and that is where we override the method to generate and return the
docstrings.
"""
if self._docstring is None:
self._generate()
return self._docstring.expandtabs(tabsize)
def __str__(self):
return self._generate()
# __doc__ of target will use either __repr__ or __str__ of this class.
__repr__ = __str__
def _generate(self):
# Generate the docstring if it is not already cached.
if self._docstring is None:
self._docstring = self._create_docstring()
return self._docstring
def _create_docstring(self):
docstring_structure = DocumentStructure('docstring', target='html')
# Call the document method function with the args and kwargs
# passed to the class.
self._write_docstring(
docstring_structure, *self._gen_args,
**self._gen_kwargs)
return docstring_structure.flush_structure().decode('utf-8')
class ClientMethodDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_model_driven_method(*args, **kwargs)
class WaiterDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_wait_method(*args, **kwargs)
class PaginatorDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_paginate_method(*args, **kwargs)

View File

@@ -0,0 +1,208 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore.docs.shape import ShapeDocumenter
from botocore.docs.utils import py_default
class BaseExampleDocumenter(ShapeDocumenter):
def document_example(self, section, shape, prefix=None, include=None,
exclude=None):
"""Generates an example based on a shape
:param section: The section to write the documentation to.
:param shape: The shape of the operation.
:param prefix: Anything to be included before the example
:type include: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include: The parameter shapes to include in the documentation.
:type exclude: List of the names of the parameters to exclude.
:param exclude: The names of the parameters to exclude from
documentation.
"""
history = []
section.style.new_line()
section.style.start_codeblock()
if prefix is not None:
section.write(prefix)
self.traverse_and_document_shape(
section=section, shape=shape, history=history,
include=include, exclude=exclude)
def document_recursive_shape(self, section, shape, **kwargs):
section.write('{\'... recursive ...\'}')
def document_shape_default(self, section, shape, history, include=None,
exclude=None, **kwargs):
py_type = self._get_special_py_default(shape)
if py_type is None:
py_type = py_default(shape.type_name)
if self._context.get('streaming_shape') == shape:
py_type = 'StreamingBody()'
section.write(py_type)
def document_shape_type_string(self, section, shape, history,
include=None, exclude=None, **kwargs):
if 'enum' in shape.metadata:
for i, enum in enumerate(shape.metadata['enum']):
section.write('\'%s\'' % enum)
if i < len(shape.metadata['enum']) - 1:
section.write('|')
else:
self.document_shape_default(section, shape, history)
def document_shape_type_list(self, section, shape, history, include=None,
exclude=None, **kwargs):
param_shape = shape.member
list_section = section.add_new_section('list-value')
self._start_nested_param(list_section, '[')
param_section = list_section.add_new_section(
'member', context={'shape': param_shape.name})
self.traverse_and_document_shape(
section=param_section, shape=param_shape, history=history)
ending_comma_section = list_section.add_new_section('ending-comma')
ending_comma_section.write(',')
ending_bracket_section = list_section.add_new_section(
'ending-bracket')
self._end_nested_param(ending_bracket_section, ']')
def document_shape_type_structure(self, section, shape, history,
include=None, exclude=None, **kwargs):
if not shape.members:
section.write('{}')
return
section = section.add_new_section('structure-value')
self._start_nested_param(section, '{')
input_members = self._add_members_to_shape(shape.members, include)
for i, param in enumerate(input_members):
if exclude and param in exclude:
continue
param_section = section.add_new_section(param)
param_section.write('\'%s\': ' % param)
param_shape = input_members[param]
param_value_section = param_section.add_new_section(
'member-value', context={'shape': param_shape.name})
self.traverse_and_document_shape(
section=param_value_section, shape=param_shape,
history=history, name=param)
if i < len(input_members) - 1:
ending_comma_section = param_section.add_new_section(
'ending-comma')
ending_comma_section.write(',')
ending_comma_section.style.new_line()
self._end_structure(section, '{', '}')
def document_shape_type_map(self, section, shape, history,
include=None, exclude=None, **kwargs):
map_section = section.add_new_section('map-value')
self._start_nested_param(map_section, '{')
value_shape = shape.value
key_section = map_section.add_new_section(
'key', context={'shape': shape.key.name})
key_section.write('\'string\': ')
value_section = map_section.add_new_section(
'value', context={'shape': value_shape.name})
self.traverse_and_document_shape(
section=value_section, shape=value_shape, history=history)
end_bracket_section = map_section.add_new_section('ending-bracket')
self._end_nested_param(end_bracket_section, '}')
def _add_members_to_shape(self, members, include):
if include:
members = members.copy()
for param in include:
members[param.name] = param
return members
def _start_nested_param(self, section, start=None):
if start is not None:
section.write(start)
section.style.indent()
section.style.indent()
section.style.new_line()
def _end_nested_param(self, section, end=None):
section.style.dedent()
section.style.dedent()
section.style.new_line()
if end is not None:
section.write(end)
def _end_structure(self, section, start, end):
# If there are no members in the strucuture, then make sure the
# start and the end bracket are on the same line, by removing all
# previous text and writing the start and end.
if not section.available_sections:
section.clear_text()
section.write(start + end)
self._end_nested_param(section)
else:
end_bracket_section = section.add_new_section('ending-bracket')
self._end_nested_param(end_bracket_section, end)
class ResponseExampleDocumenter(BaseExampleDocumenter):
EVENT_NAME = 'response-example'
def document_shape_type_event_stream(self, section, shape, history,
**kwargs):
section.write('EventStream(')
self.document_shape_type_structure(section, shape, history, **kwargs)
end_section = section.add_new_section('event-stream-end')
end_section.write(')')
class RequestExampleDocumenter(BaseExampleDocumenter):
EVENT_NAME = 'request-example'
def document_shape_type_structure(self, section, shape, history,
include=None, exclude=None, **kwargs):
param_format = '\'%s\''
operator = ': '
start = '{'
end = '}'
if len(history) <= 1:
operator = '='
start = '('
end = ')'
param_format = '%s'
section = section.add_new_section('structure-value')
self._start_nested_param(section, start)
input_members = self._add_members_to_shape(shape.members, include)
for i, param in enumerate(input_members):
if exclude and param in exclude:
continue
param_section = section.add_new_section(param)
param_section.write(param_format % param)
param_section.write(operator)
param_shape = input_members[param]
param_value_section = param_section.add_new_section(
'member-value', context={'shape': param_shape.name})
self.traverse_and_document_shape(
section=param_value_section, shape=param_shape,
history=history, name=param)
if i < len(input_members) - 1:
ending_comma_section = param_section.add_new_section(
'ending-comma')
ending_comma_section.write(',')
ending_comma_section.style.new_line()
self._end_structure(section, start, end)

View File

@@ -0,0 +1,282 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import inspect
from botocore.docs.params import RequestParamsDocumenter
from botocore.docs.params import ResponseParamsDocumenter
from botocore.docs.example import ResponseExampleDocumenter
from botocore.docs.example import RequestExampleDocumenter
AWS_DOC_BASE = 'https://docs.aws.amazon.com/goto/WebAPI'
def get_instance_public_methods(instance):
"""Retrieves an objects public methods
:param instance: The instance of the class to inspect
:rtype: dict
:returns: A dictionary that represents an instance's methods where
the keys are the name of the methods and the
values are the handler to the method.
"""
instance_members = inspect.getmembers(instance)
instance_methods = {}
for name, member in instance_members:
if not name.startswith('_'):
if inspect.ismethod(member):
instance_methods[name] = member
return instance_methods
def document_model_driven_signature(section, name, operation_model,
include=None, exclude=None):
"""Documents the signature of a model-driven method
:param section: The section to write the documentation to.
:param name: The name of the method
:param operation_model: The operation model for the method
:type include: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include: The parameter shapes to include in the documentation.
:type exclude: List of the names of the parameters to exclude.
:param exclude: The names of the parameters to exclude from
documentation.
"""
params = {}
if operation_model.input_shape:
params = operation_model.input_shape.members
parameter_names = list(params.keys())
if include is not None:
for member in include:
parameter_names.append(member.name)
if exclude is not None:
for member in exclude:
if member in parameter_names:
parameter_names.remove(member)
signature_params = ''
if parameter_names:
signature_params = '**kwargs'
section.style.start_sphinx_py_method(name, signature_params)
def document_custom_signature(section, name, method,
include=None, exclude=None):
"""Documents the signature of a custom method
:param section: The section to write the documentation to.
:param name: The name of the method
:param method: The handle to the method being documented
:type include: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include: The parameter shapes to include in the documentation.
:type exclude: List of the names of the parameters to exclude.
:param exclude: The names of the parameters to exclude from
documentation.
"""
args, varargs, keywords, defaults = inspect.getargspec(method)
args = args[1:]
signature_params = inspect.formatargspec(
args, varargs, keywords, defaults)
signature_params = signature_params.lstrip('(')
signature_params = signature_params.rstrip(')')
section.style.start_sphinx_py_method(name, signature_params)
def document_custom_method(section, method_name, method):
"""Documents a non-data driven method
:param section: The section to write the documentation to.
:param method_name: The name of the method
:param method: The handle to the method being documented
"""
document_custom_signature(
section, method_name, method)
method_intro_section = section.add_new_section('method-intro')
method_intro_section.writeln('')
doc_string = inspect.getdoc(method)
if doc_string is not None:
method_intro_section.style.write_py_doc_string(doc_string)
def document_model_driven_method(section, method_name, operation_model,
event_emitter, method_description=None,
example_prefix=None, include_input=None,
include_output=None, exclude_input=None,
exclude_output=None, document_output=True,
include_signature=True):
"""Documents an individual method
:param section: The section to write to
:param method_name: The name of the method
:param operation_model: The model of the operation
:param event_emitter: The event emitter to use to emit events
:param example_prefix: The prefix to use in the method example.
:type include_input: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include_input: The parameter shapes to include in the
input documentation.
:type include_output: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include_input: The parameter shapes to include in the
output documentation.
:type exclude_input: List of the names of the parameters to exclude.
:param exclude_input: The names of the parameters to exclude from
input documentation.
:type exclude_output: List of the names of the parameters to exclude.
:param exclude_input: The names of the parameters to exclude from
output documentation.
:param document_output: A boolean flag to indicate whether to
document the output.
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
# Add the signature if specified.
if include_signature:
document_model_driven_signature(
section, method_name, operation_model, include=include_input,
exclude=exclude_input)
# Add the description for the method.
method_intro_section = section.add_new_section('method-intro')
method_intro_section.include_doc_string(method_description)
if operation_model.deprecated:
method_intro_section.style.start_danger()
method_intro_section.writeln(
'This operation is deprecated and may not function as '
'expected. This operation should not be used going forward '
'and is only kept for the purpose of backwards compatiblity.')
method_intro_section.style.end_danger()
service_uid = operation_model.service_model.metadata.get('uid')
if service_uid is not None:
method_intro_section.style.new_paragraph()
method_intro_section.write("See also: ")
link = '%s/%s/%s' % (AWS_DOC_BASE, service_uid,
operation_model.name)
method_intro_section.style.external_link(title="AWS API Documentation",
link=link)
method_intro_section.writeln('')
# Add the example section.
example_section = section.add_new_section('example')
example_section.style.new_paragraph()
example_section.style.bold('Request Syntax')
context = {
'special_shape_types': {
'streaming_input_shape': operation_model.get_streaming_input(),
'streaming_output_shape': operation_model.get_streaming_output(),
'eventstream_output_shape': operation_model.get_event_stream_output(),
},
}
if operation_model.input_shape:
RequestExampleDocumenter(
service_name=operation_model.service_model.service_name,
operation_name=operation_model.name,
event_emitter=event_emitter, context=context).document_example(
example_section, operation_model.input_shape,
prefix=example_prefix, include=include_input,
exclude=exclude_input)
else:
example_section.style.new_paragraph()
example_section.style.start_codeblock()
example_section.write(example_prefix + '()')
# Add the request parameter documentation.
request_params_section = section.add_new_section('request-params')
if operation_model.input_shape:
RequestParamsDocumenter(
service_name=operation_model.service_model.service_name,
operation_name=operation_model.name,
event_emitter=event_emitter, context=context).document_params(
request_params_section, operation_model.input_shape,
include=include_input, exclude=exclude_input)
# Add the return value documentation
return_section = section.add_new_section('return')
return_section.style.new_line()
if operation_model.output_shape is not None and document_output:
return_section.write(':rtype: dict')
return_section.style.new_line()
return_section.write(':returns: ')
return_section.style.indent()
return_section.style.new_line()
# If the operation is an event stream, describe the tagged union
event_stream_output = operation_model.get_event_stream_output()
if event_stream_output:
event_section = return_section.add_new_section('event-stream')
event_section.style.new_paragraph()
event_section.write(
'The response of this operation contains an '
':class:`.EventStream` member. When iterated the '
':class:`.EventStream` will yield events based on the '
'structure below, where only one of the top level keys '
'will be present for any given event.'
)
event_section.style.new_line()
# Add an example return value
return_example_section = return_section.add_new_section('example')
return_example_section.style.new_line()
return_example_section.style.bold('Response Syntax')
return_example_section.style.new_paragraph()
ResponseExampleDocumenter(
service_name=operation_model.service_model.service_name,
operation_name=operation_model.name,
event_emitter=event_emitter,
context=context).document_example(
return_example_section, operation_model.output_shape,
include=include_output, exclude=exclude_output)
# Add a description for the return value
return_description_section = return_section.add_new_section(
'description')
return_description_section.style.new_line()
return_description_section.style.bold('Response Structure')
return_description_section.style.new_paragraph()
ResponseParamsDocumenter(
service_name=operation_model.service_model.service_name,
operation_name=operation_model.name,
event_emitter=event_emitter,
context=context).document_params(
return_description_section, operation_model.output_shape,
include=include_output, exclude=exclude_output)
else:
return_section.write(':returns: None')

View File

@@ -0,0 +1,177 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore import xform_name
from botocore.compat import OrderedDict
from botocore.docs.utils import DocumentedShape
from botocore.utils import get_service_module_name
from botocore.docs.method import document_model_driven_method
class PaginatorDocumenter(object):
def __init__(self, client, service_paginator_model):
self._client = client
self._service_name = self._client.meta.service_model.service_name
self._service_paginator_model = service_paginator_model
def document_paginators(self, section):
"""Documents the various paginators for a service
param section: The section to write to.
"""
section.style.h2('Paginators')
section.style.new_line()
section.writeln('The available paginators are:')
paginator_names = sorted(
self._service_paginator_model._paginator_config)
# List the available paginators and then document each paginator.
for paginator_name in paginator_names:
section.style.li(
':py:class:`%s.Paginator.%s`' % (
self._client.__class__.__name__, paginator_name))
self._add_paginator(section, paginator_name)
def _add_paginator(self, section, paginator_name):
section = section.add_new_section(paginator_name)
# Docment the paginator class
section.style.start_sphinx_py_class(
class_name='%s.Paginator.%s' % (
self._client.__class__.__name__, paginator_name))
section.style.start_codeblock()
section.style.new_line()
# Document how to instantiate the paginator.
section.write(
'paginator = client.get_paginator(\'%s\')' % xform_name(
paginator_name)
)
section.style.end_codeblock()
section.style.new_line()
# Get the pagination model for the particular paginator.
paginator_config = self._service_paginator_model.get_paginator(
paginator_name)
document_paginate_method(
section=section,
paginator_name=paginator_name,
event_emitter=self._client.meta.events,
service_model=self._client.meta.service_model,
paginator_config=paginator_config
)
def document_paginate_method(section, paginator_name, event_emitter,
service_model, paginator_config,
include_signature=True):
"""Documents the paginate method of a paginator
:param section: The section to write to
:param paginator_name: The name of the paginator. It is snake cased.
:param event_emitter: The event emitter to use to emit events
:param service_model: The service model
:param paginator_config: The paginator config associated to a particular
paginator.
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
# Retrieve the operation model of the underlying operation.
operation_model = service_model.operation_model(
paginator_name)
# Add representations of the request and response parameters
# we want to include in the description of the paginate method.
# These are parameters we expose via the botocore interface.
pagination_config_members = OrderedDict()
pagination_config_members['MaxItems'] = DocumentedShape(
name='MaxItems', type_name='integer',
documentation=(
'<p>The total number of items to return. If the total '
'number of items available is more than the value '
'specified in max-items then a <code>NextToken</code> '
'will be provided in the output that you can use to '
'resume pagination.</p>'))
if paginator_config.get('limit_key', None):
pagination_config_members['PageSize'] = DocumentedShape(
name='PageSize', type_name='integer',
documentation='<p>The size of each page.<p>')
pagination_config_members['StartingToken'] = DocumentedShape(
name='StartingToken', type_name='string',
documentation=(
'<p>A token to specify where to start paginating. '
'This is the <code>NextToken</code> from a previous '
'response.</p>'))
botocore_pagination_params = [
DocumentedShape(
name='PaginationConfig', type_name='structure',
documentation=(
'<p>A dictionary that provides parameters to control '
'pagination.</p>'),
members=pagination_config_members)
]
botocore_pagination_response_params = [
DocumentedShape(
name='NextToken', type_name='string',
documentation=(
'<p>A token to resume pagination.</p>'))
]
service_pagination_params = []
# Add the normal input token of the method to a list
# of input paramters that we wish to hide since we expose our own.
if isinstance(paginator_config['input_token'], list):
service_pagination_params += paginator_config['input_token']
else:
service_pagination_params.append(paginator_config['input_token'])
# Hide the limit key in the documentation.
if paginator_config.get('limit_key', None):
service_pagination_params.append(paginator_config['limit_key'])
# Hide the output tokens in the documentation.
service_pagination_response_params = []
if isinstance(paginator_config['output_token'], list):
service_pagination_response_params += paginator_config[
'output_token']
else:
service_pagination_response_params.append(paginator_config[
'output_token'])
paginate_description = (
'Creates an iterator that will paginate through responses '
'from :py:meth:`{0}.Client.{1}`.'.format(
get_service_module_name(service_model), xform_name(paginator_name))
)
document_model_driven_method(
section, 'paginate', operation_model,
event_emitter=event_emitter,
method_description=paginate_description,
example_prefix='response_iterator = paginator.paginate',
include_input=botocore_pagination_params,
include_output=botocore_pagination_response_params,
exclude_input=service_pagination_params,
exclude_output=service_pagination_response_params,
include_signature=include_signature
)

View File

@@ -0,0 +1,220 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore.docs.shape import ShapeDocumenter
from botocore.docs.utils import py_type_name
class BaseParamsDocumenter(ShapeDocumenter):
def document_params(self, section, shape, include=None, exclude=None):
"""Fills out the documentation for a section given a model shape.
:param section: The section to write the documentation to.
:param shape: The shape of the operation.
:type include: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include: The parameter shapes to include in the documentation.
:type exclude: List of the names of the parameters to exclude.
:param exclude: The names of the parameters to exclude from
documentation.
"""
history = []
self.traverse_and_document_shape(
section=section, shape=shape, history=history,
name=None, include=include, exclude=exclude)
def document_recursive_shape(self, section, shape, **kwargs):
self._add_member_documentation(section, shape, **kwargs)
def document_shape_default(self, section, shape, history, include=None,
exclude=None, **kwargs):
self._add_member_documentation(section, shape, **kwargs)
def document_shape_type_list(self, section, shape, history, include=None,
exclude=None, **kwargs):
self._add_member_documentation(section, shape, **kwargs)
param_shape = shape.member
param_section = section.add_new_section(
param_shape.name, context={'shape': shape.member.name})
self._start_nested_param(param_section)
self.traverse_and_document_shape(
section=param_section, shape=param_shape,
history=history, name=None)
section = section.add_new_section('end-list')
self._end_nested_param(section)
def document_shape_type_map(self, section, shape, history, include=None,
exclude=None, **kwargs):
self._add_member_documentation(section, shape, **kwargs)
key_section = section.add_new_section(
'key', context={'shape': shape.key.name})
self._start_nested_param(key_section)
self._add_member_documentation(key_section, shape.key)
param_section = section.add_new_section(
shape.value.name, context={'shape': shape.value.name})
param_section.style.indent()
self._start_nested_param(param_section)
self.traverse_and_document_shape(
section=param_section, shape=shape.value,
history=history, name=None)
end_section = section.add_new_section('end-map')
self._end_nested_param(end_section)
self._end_nested_param(end_section)
def document_shape_type_structure(self, section, shape, history,
include=None, exclude=None,
name=None, **kwargs):
members = self._add_members_to_shape(shape.members, include)
self._add_member_documentation(section, shape, name=name)
for param in members:
if exclude and param in exclude:
continue
param_shape = members[param]
param_section = section.add_new_section(
param, context={'shape': param_shape.name})
self._start_nested_param(param_section)
self.traverse_and_document_shape(
section=param_section, shape=param_shape,
history=history, name=param)
section = section.add_new_section('end-structure')
self._end_nested_param(section)
def _add_member_documentation(self, section, shape, **kwargs):
pass
def _add_members_to_shape(self, members, include):
if include:
members = members.copy()
for param in include:
members[param.name] = param
return members
def _document_non_top_level_param_type(self, type_section, shape):
special_py_type = self._get_special_py_type_name(shape)
py_type = py_type_name(shape.type_name)
type_format = '(%s) -- '
if special_py_type is not None:
# Special type can reference a linked class.
# Italicizing it blows away the link.
type_section.write(type_format % special_py_type)
else:
type_section.style.italics(type_format % py_type)
def _start_nested_param(self, section):
section.style.indent()
section.style.new_line()
def _end_nested_param(self, section):
section.style.dedent()
section.style.new_line()
class ResponseParamsDocumenter(BaseParamsDocumenter):
"""Generates the description for the response parameters"""
EVENT_NAME = 'response-params'
def _add_member_documentation(self, section, shape, name=None, **kwargs):
name_section = section.add_new_section('param-name')
name_section.write('- ')
if name is not None:
name_section.style.bold('%s ' % name)
type_section = section.add_new_section('param-type')
self._document_non_top_level_param_type(type_section, shape)
documentation_section = section.add_new_section('param-documentation')
if shape.documentation:
documentation_section.style.indent()
documentation_section.include_doc_string(shape.documentation)
section.style.new_paragraph()
def document_shape_type_event_stream(self, section, shape, history,
**kwargs):
self.document_shape_type_structure(section, shape, history, **kwargs)
class RequestParamsDocumenter(BaseParamsDocumenter):
"""Generates the description for the request parameters"""
EVENT_NAME = 'request-params'
def document_shape_type_structure(self, section, shape, history,
include=None, exclude=None, **kwargs):
if len(history) > 1:
self._add_member_documentation(section, shape, **kwargs)
section.style.indent()
members = self._add_members_to_shape(shape.members, include)
for i, param in enumerate(members):
if exclude and param in exclude:
continue
param_shape = members[param]
param_section = section.add_new_section(
param, context={'shape': param_shape.name})
param_section.style.new_line()
is_required = param in shape.required_members
self.traverse_and_document_shape(
section=param_section, shape=param_shape,
history=history, name=param, is_required=is_required)
section = section.add_new_section('end-structure')
if len(history) > 1:
section.style.dedent()
section.style.new_line()
def _add_member_documentation(self, section, shape, name=None,
is_top_level_param=False, is_required=False,
**kwargs):
py_type = self._get_special_py_type_name(shape)
if py_type is None:
py_type = py_type_name(shape.type_name)
if is_top_level_param:
type_section = section.add_new_section('param-type')
type_section.write(':type %s: %s' % (name, py_type))
end_type_section = type_section.add_new_section('end-param-type')
end_type_section.style.new_line()
name_section = section.add_new_section('param-name')
name_section.write(':param %s: ' % name)
else:
name_section = section.add_new_section('param-name')
name_section.write('- ')
if name is not None:
name_section.style.bold('%s ' % name)
type_section = section.add_new_section('param-type')
self._document_non_top_level_param_type(type_section, shape)
if is_required:
is_required_section = section.add_new_section('is-required')
is_required_section.style.indent()
is_required_section.style.bold('[REQUIRED] ')
if shape.documentation:
documentation_section = section.add_new_section(
'param-documentation')
documentation_section.style.indent()
documentation_section.include_doc_string(shape.documentation)
self._add_special_trait_documentation(documentation_section, shape)
end_param_section = section.add_new_section('end-param')
end_param_section.style.new_paragraph()
def _add_special_trait_documentation(self, section, shape):
if 'idempotencyToken' in shape.metadata:
self._append_idempotency_documentation(section)
def _append_idempotency_documentation(self, section):
docstring = 'This field is autopopulated if not provided.'
section.write(docstring)

View File

@@ -0,0 +1,96 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore.exceptions import DataNotFoundError
from botocore.docs.utils import get_official_service_name
from botocore.docs.client import ClientDocumenter
from botocore.docs.waiter import WaiterDocumenter
from botocore.docs.paginator import PaginatorDocumenter
from botocore.docs.bcdoc.restdoc import DocumentStructure
class ServiceDocumenter(object):
def __init__(self, service_name, session):
self._session = session
self._service_name = service_name
self._client = self._session.create_client(
service_name, region_name='us-east-1', aws_access_key_id='foo',
aws_secret_access_key='bar')
self._event_emitter = self._client.meta.events
self.sections = [
'title',
'table-of-contents',
'client-api',
'paginator-api',
'waiter-api'
]
def document_service(self):
"""Documents an entire service.
:returns: The reStructured text of the documented service.
"""
doc_structure = DocumentStructure(
self._service_name, section_names=self.sections,
target='html')
self.title(doc_structure.get_section('title'))
self.table_of_contents(doc_structure.get_section('table-of-contents'))
self.client_api(doc_structure.get_section('client-api'))
self.paginator_api(doc_structure.get_section('paginator-api'))
self.waiter_api(doc_structure.get_section('waiter-api'))
return doc_structure.flush_structure()
def title(self, section):
section.style.h1(self._client.__class__.__name__)
self._event_emitter.emit(
'docs.%s.%s' % ('title',
self._service_name),
section=section
)
def table_of_contents(self, section):
section.style.table_of_contents(title='Table of Contents', depth=2)
def client_api(self, section):
examples = None
try:
examples = self.get_examples(self._service_name)
except DataNotFoundError:
pass
ClientDocumenter(self._client, examples).document_client(section)
def paginator_api(self, section):
try:
service_paginator_model = self._session.get_paginator_model(
self._service_name)
except DataNotFoundError:
return
paginator_documenter = PaginatorDocumenter(
self._client, service_paginator_model)
paginator_documenter.document_paginators(section)
def waiter_api(self, section):
if self._client.waiter_names:
service_waiter_model = self._session.get_waiter_model(
self._service_name)
waiter_documenter = WaiterDocumenter(
self._client, service_waiter_model)
waiter_documenter.document_waiters(section)
def get_examples(self, service_name, api_version=None):
loader = self._session.get_component('data_loader')
examples = loader.load_service_model(
service_name, 'examples-1', api_version)
return examples['examples']

View File

@@ -0,0 +1,117 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
# NOTE: This class should not be instantiated and its
# ``traverse_and_document_shape`` method called directly. It should be
# inherited from a Documenter class with the appropriate methods
# and attributes.
from botocore.utils import is_json_value_header
class ShapeDocumenter(object):
EVENT_NAME = ''
def __init__(self, service_name, operation_name, event_emitter,
context=None):
self._service_name = service_name
self._operation_name = operation_name
self._event_emitter = event_emitter
self._context = context
if context is None:
self._context = {
'special_shape_types': {}
}
def traverse_and_document_shape(self, section, shape, history,
include=None, exclude=None, name=None,
is_required=False):
"""Traverses and documents a shape
Will take a self class and call its appropriate methods as a shape
is traversed.
:param section: The section to document.
:param history: A list of the names of the shapes that have been
traversed.
:type include: Dictionary where keys are parameter names and
values are the shapes of the parameter names.
:param include: The parameter shapes to include in the documentation.
:type exclude: List of the names of the parameters to exclude.
:param exclude: The names of the parameters to exclude from
documentation.
:param name: The name of the shape.
:param is_required: If the shape is a required member.
"""
param_type = shape.type_name
if getattr(shape, 'serialization', {}).get('eventstream'):
param_type = 'event_stream'
if shape.name in history:
self.document_recursive_shape(section, shape, name=name)
else:
history.append(shape.name)
is_top_level_param = (len(history) == 2)
getattr(self, 'document_shape_type_%s' % param_type,
self.document_shape_default)(
section, shape, history=history, name=name,
include=include, exclude=exclude,
is_top_level_param=is_top_level_param,
is_required=is_required)
if is_top_level_param:
self._event_emitter.emit(
'docs.%s.%s.%s.%s' % (self.EVENT_NAME,
self._service_name,
self._operation_name,
name),
section=section)
at_overlying_method_section = (len(history) == 1)
if at_overlying_method_section:
self._event_emitter.emit(
'docs.%s.%s.%s.complete-section' % (self.EVENT_NAME,
self._service_name,
self._operation_name),
section=section)
history.pop()
def _get_special_py_default(self, shape):
special_defaults = {
'jsonvalue_header': '{...}|[...]|123|123.4|\'string\'|True|None',
'streaming_input_shape': 'b\'bytes\'|file',
'streaming_output_shape': 'StreamingBody()',
'eventstream_output_shape': 'EventStream()',
}
return self._get_value_for_special_type(shape, special_defaults)
def _get_special_py_type_name(self, shape):
special_type_names = {
'jsonvalue_header': 'JSON serializable',
'streaming_input_shape': 'bytes or seekable file-like object',
'streaming_output_shape': ':class:`.StreamingBody`',
'eventstream_output_shape': ':class:`.EventStream`',
}
return self._get_value_for_special_type(shape, special_type_names)
def _get_value_for_special_type(self, shape, special_type_map):
if is_json_value_header(shape):
return special_type_map['jsonvalue_header']
for special_type, marked_shape in self._context[
'special_shape_types'].items():
if special_type in special_type_map:
if shape == marked_shape:
return special_type_map[special_type]
return None

View File

@@ -0,0 +1,223 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import re
import numbers
from botocore.utils import parse_timestamp
from botocore.docs.utils import escape_controls
from botocore.compat import six
class SharedExampleDocumenter(object):
def document_shared_example(self, example, prefix, section,
operation_model):
"""Documents a single shared example based on its definition.
:param example: The model of the example
:param prefix: The prefix to use in the method example.
:param section: The section to write to.
:param operation_model: The model of the operation used in the example
"""
section.style.new_paragraph()
section.write(example.get('description'))
section.style.new_line()
self.document_input(section, example, prefix,
operation_model.input_shape)
self.document_output(section, example, operation_model.output_shape)
def document_input(self, section, example, prefix, shape):
input_section = section.add_new_section('input')
input_section.style.start_codeblock()
if prefix is not None:
input_section.write(prefix)
params = example.get('input', {})
comments = example.get('comments')
if comments:
comments = comments.get('input')
param_section = input_section.add_new_section('parameters')
self._document_params(param_section, params, comments, [], shape)
closing_section = input_section.add_new_section('input-close')
closing_section.style.new_line()
closing_section.style.new_line()
closing_section.write('print(response)')
closing_section.style.end_codeblock()
def document_output(self, section, example, shape):
output_section = section.add_new_section('output')
output_section.style.new_line()
output_section.write('Expected Output:')
output_section.style.new_line()
output_section.style.start_codeblock()
params = example.get('output', {})
# There might not be an output, but we will return metadata anyway
params['ResponseMetadata'] = {"...": "..."}
comments = example.get('comments')
if comments:
comments = comments.get('output')
self._document_dict(output_section, params, comments, [], shape, True)
closing_section = output_section.add_new_section('output-close')
closing_section.style.end_codeblock()
def _document(self, section, value, comments, path, shape):
"""
:param section: The section to add the docs to.
:param value: The input / output values representing the parameters that
are included in the example.
:param comments: The dictionary containing all the comments to be
applied to the example.
:param path: A list describing where the documenter is in traversing the
parameters. This is used to find the equivalent location
in the comments dictionary.
"""
if isinstance(value, dict):
self._document_dict(section, value, comments, path, shape)
elif isinstance(value, list):
self._document_list(section, value, comments, path, shape)
elif isinstance(value, numbers.Number):
self._document_number(section, value, path)
elif shape and shape.type_name == 'timestamp':
self._document_datetime(section, value, path)
else:
self._document_str(section, value, path)
def _document_dict(self, section, value, comments, path, shape,
top_level=False):
dict_section = section.add_new_section('dict-value')
self._start_nested_value(dict_section, '{')
for key, val in value.items():
path.append('.%s' % key)
item_section = dict_section.add_new_section(key)
item_section.style.new_line()
item_comment = self._get_comment(path, comments)
if item_comment:
item_section.write(item_comment)
item_section.style.new_line()
item_section.write("'%s': " % key)
# Shape could be none if there is no output besides ResponseMetadata
item_shape = None
if shape:
if shape.type_name == 'structure':
item_shape = shape.members.get(key)
elif shape.type_name == 'map':
item_shape = shape.value
self._document(item_section, val, comments, path, item_shape)
path.pop()
dict_section_end = dict_section.add_new_section('ending-brace')
self._end_nested_value(dict_section_end, '}')
if not top_level:
dict_section_end.write(',')
def _document_params(self, section, value, comments, path, shape):
param_section = section.add_new_section('param-values')
self._start_nested_value(param_section, '(')
for key, val in value.items():
path.append('.%s' % key)
item_section = param_section.add_new_section(key)
item_section.style.new_line()
item_comment = self._get_comment(path, comments)
if item_comment:
item_section.write(item_comment)
item_section.style.new_line()
item_section.write(key + '=')
# Shape could be none if there are no input parameters
item_shape = None
if shape:
item_shape = shape.members.get(key)
self._document(item_section, val, comments, path, item_shape)
path.pop()
param_section_end = param_section.add_new_section('ending-parenthesis')
self._end_nested_value(param_section_end, ')')
def _document_list(self, section, value, comments, path, shape):
list_section = section.add_new_section('list-section')
self._start_nested_value(list_section, '[')
item_shape = shape.member
for index, val in enumerate(value):
item_section = list_section.add_new_section(index)
item_section.style.new_line()
path.append('[%s]' % index)
item_comment = self._get_comment(path, comments)
if item_comment:
item_section.write(item_comment)
item_section.style.new_line()
self._document(item_section, val, comments, path, item_shape)
path.pop()
list_section_end = list_section.add_new_section('ending-bracket')
self._end_nested_value(list_section_end, '],')
def _document_str(self, section, value, path):
# We do the string conversion because this might accept a type that
# we don't specifically address.
safe_value = escape_controls(value)
section.write(u"'%s'," % six.text_type(safe_value))
def _document_number(self, section, value, path):
section.write("%s," % str(value))
def _document_datetime(self, section, value, path):
datetime_tuple = parse_timestamp(value).timetuple()
datetime_str = str(datetime_tuple[0])
for i in range(1, len(datetime_tuple)):
datetime_str += ", " + str(datetime_tuple[i])
section.write("datetime(%s)," % datetime_str)
def _get_comment(self, path, comments):
key = re.sub(r'^\.', '', ''.join(path))
if comments and key in comments:
return '# ' + comments[key]
else:
return ''
def _start_nested_value(self, section, start):
section.write(start)
section.style.indent()
section.style.indent()
def _end_nested_value(self, section, end):
section.style.dedent()
section.style.dedent()
section.style.new_line()
section.write(end)
def document_shared_examples(section, operation_model, example_prefix,
shared_examples):
"""Documents the shared examples
:param section: The section to write to.
:param operation_model: The model of the operation.
:param example_prefix: The prefix to use in the method example.
:param shared_examples: The shared JSON examples from the model.
"""
container_section = section.add_new_section('shared-examples')
container_section.style.new_paragraph()
container_section.style.bold('Examples')
documenter = SharedExampleDocumenter()
for example in shared_examples:
documenter.document_shared_example(
example=example,
section=container_section.add_new_section(example['id']),
prefix=example_prefix,
operation_model=operation_model
)

View File

@@ -0,0 +1,197 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import re
from collections import namedtuple
def py_type_name(type_name):
"""Get the Python type name for a given model type.
>>> py_type_name('list')
'list'
>>> py_type_name('structure')
'dict'
:rtype: string
"""
return {
'blob': 'bytes',
'character': 'string',
'double': 'float',
'long': 'integer',
'map': 'dict',
'structure': 'dict',
'timestamp': 'datetime',
}.get(type_name, type_name)
def py_default(type_name):
"""Get the Python default value for a given model type.
>>> py_default('string')
'\'string\''
>>> py_default('list')
'[...]'
>>> py_default('unknown')
'...'
:rtype: string
"""
return {
'double': '123.0',
'long': '123',
'integer': '123',
'string': "'string'",
'blob': "b'bytes'",
'boolean': 'True|False',
'list': '[...]',
'map': '{...}',
'structure': '{...}',
'timestamp': 'datetime(2015, 1, 1)',
}.get(type_name, '...')
def get_official_service_name(service_model):
"""Generate the official name of an AWS Service
:param service_model: The service model representing the service
"""
official_name = service_model.metadata.get('serviceFullName')
short_name = service_model.metadata.get('serviceAbbreviation', '')
if short_name.startswith('Amazon'):
short_name = short_name[7:]
if short_name.startswith('AWS'):
short_name = short_name[4:]
if short_name and short_name.lower() not in official_name.lower():
official_name += ' ({0})'.format(short_name)
return official_name
_DocumentedShape = namedtuple(
'DocumentedShape', ['name', 'type_name', 'documentation', 'metadata',
'members', 'required_members'])
class DocumentedShape (_DocumentedShape):
"""Use this class to inject new shapes into a model for documentation"""
def __new__(cls, name, type_name, documentation, metadata=None,
members=None, required_members=None):
if metadata is None:
metadata = []
if members is None:
members = []
if required_members is None:
required_members = []
return super(DocumentedShape, cls).__new__(
cls, name, type_name, documentation, metadata, members,
required_members)
class AutoPopulatedParam(object):
def __init__(self, name, param_description=None):
self.name = name
self.param_description = param_description
if param_description is None:
self.param_description = (
'Please note that this parameter is automatically populated '
'if it is not provided. Including this parameter is not '
'required\n')
def document_auto_populated_param(self, event_name, section, **kwargs):
"""Documents auto populated parameters
It will remove any required marks for the parameter, remove the
parameter from the example, and add a snippet about the parameter
being autopopulated in the description.
"""
if event_name.startswith('docs.request-params'):
if self.name in section.available_sections:
section = section.get_section(self.name)
if 'is-required' in section.available_sections:
section.delete_section('is-required')
description_section = section.get_section(
'param-documentation')
description_section.writeln(self.param_description)
elif event_name.startswith('docs.request-example'):
section = section.get_section('structure-value')
if self.name in section.available_sections:
section.delete_section(self.name)
class HideParamFromOperations(object):
"""Hides a single parameter from multiple operations.
This method will remove a parameter from documentation and from
examples. This method is typically used for things that are
automatically populated because a user would be unable to provide
a value (e.g., a checksum of a serialized XML request body)."""
def __init__(self, service_name, parameter_name, operation_names):
"""
:type service_name: str
:param service_name: Name of the service to modify.
:type parameter_name: str
:param parameter_name: Name of the parameter to modify.
:type operation_names: list
:param operation_names: Operation names to modify.
"""
self._parameter_name = parameter_name
self._params_events = set()
self._example_events = set()
# Build up the sets of relevant event names.
param_template = 'docs.request-params.%s.%s.complete-section'
example_template = 'docs.request-example.%s.%s.complete-section'
for name in operation_names:
self._params_events.add(param_template % (service_name, name))
self._example_events.add(example_template % (service_name, name))
def hide_param(self, event_name, section, **kwargs):
if event_name in self._example_events:
# Modify the structure value for example events.
section = section.get_section('structure-value')
elif event_name not in self._params_events:
return
if self._parameter_name in section.available_sections:
section.delete_section(self._parameter_name)
class AppendParamDocumentation(object):
"""Appends documentation to a specific parameter"""
def __init__(self, parameter_name, doc_string):
self._parameter_name = parameter_name
self._doc_string = doc_string
def append_documentation(self, event_name, section, **kwargs):
if self._parameter_name in section.available_sections:
section = section.get_section(self._parameter_name)
description_section = section.get_section(
'param-documentation')
description_section.writeln(self._doc_string)
_CONTROLS = {
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\b': '\\b',
'\f': '\\f',
}
# Combines all CONTROLS keys into a big or regular expression
_ESCAPE_CONTROLS_RE = re.compile('|'.join(map(re.escape, _CONTROLS)))
# Based on the match get the appropriate replacement from CONTROLS
_CONTROLS_MATCH_HANDLER = lambda match: _CONTROLS[match.group(0)]
def escape_controls(value):
return _ESCAPE_CONTROLS_RE.sub(_CONTROLS_MATCH_HANDLER, value)

View File

@@ -0,0 +1,127 @@
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from botocore import xform_name
from botocore.compat import OrderedDict
from botocore.docs.utils import DocumentedShape
from botocore.utils import get_service_module_name
from botocore.docs.method import document_model_driven_method
class WaiterDocumenter(object):
def __init__(self, client, service_waiter_model):
self._client = client
self._service_name = self._client.meta.service_model.service_name
self._service_waiter_model = service_waiter_model
def document_waiters(self, section):
"""Documents the various waiters for a service.
:param section: The section to write to.
"""
section.style.h2('Waiters')
section.style.new_line()
section.writeln('The available waiters are:')
for waiter_name in self._service_waiter_model.waiter_names:
section.style.li(
':py:class:`%s.Waiter.%s`' % (
self._client.__class__.__name__, waiter_name))
self._add_single_waiter(section, waiter_name)
def _add_single_waiter(self, section, waiter_name):
section = section.add_new_section(waiter_name)
section.style.start_sphinx_py_class(
class_name='%s.Waiter.%s' % (
self._client.__class__.__name__, waiter_name))
# Add example on how to instantiate waiter.
section.style.start_codeblock()
section.style.new_line()
section.write(
'waiter = client.get_waiter(\'%s\')' % xform_name(waiter_name)
)
section.style.end_codeblock()
# Add information on the wait() method
section.style.new_line()
document_wait_method(
section=section,
waiter_name=waiter_name,
event_emitter=self._client.meta.events,
service_model=self._client.meta.service_model,
service_waiter_model=self._service_waiter_model
)
def document_wait_method(section, waiter_name, event_emitter,
service_model, service_waiter_model,
include_signature=True):
"""Documents a the wait method of a waiter
:param section: The section to write to
:param waiter_name: The name of the waiter
:param event_emitter: The event emitter to use to emit events
:param service_model: The service model
:param service_waiter_model: The waiter model associated to the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
waiter_model = service_waiter_model.get_waiter(waiter_name)
operation_model = service_model.operation_model(
waiter_model.operation)
waiter_config_members = OrderedDict()
waiter_config_members['Delay'] = DocumentedShape(
name='Delay', type_name='integer',
documentation=(
'<p>The amount of time in seconds to wait between '
'attempts. Default: {0}</p>'.format(waiter_model.delay)))
waiter_config_members['MaxAttempts'] = DocumentedShape(
name='MaxAttempts', type_name='integer',
documentation=(
'<p>The maximum number of attempts to be made. '
'Default: {0}</p>'.format(waiter_model.max_attempts)))
botocore_waiter_params = [
DocumentedShape(
name='WaiterConfig', type_name='structure',
documentation=(
'<p>A dictionary that provides parameters to control '
'waiting behavior.</p>'),
members=waiter_config_members)
]
wait_description = (
'Polls :py:meth:`{0}.Client.{1}` every {2} '
'seconds until a successful state is reached. An error is '
'returned after {3} failed checks.'.format(
get_service_module_name(service_model),
xform_name(waiter_model.operation),
waiter_model.delay, waiter_model.max_attempts)
)
document_model_driven_method(
section, 'wait', operation_model,
event_emitter=event_emitter,
method_description=wait_description,
example_prefix='waiter.wait',
include_input=botocore_waiter_params,
document_output=False,
include_signature=include_signature
)