<commit_before># Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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 monasca_api.version import version_info

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.6'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'openstackdocstheme',
    'reno.sphinxext'
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
source_encoding = 'utf-8'

# The master toctree document.
master_doc = 'index'

# General information about the project.
repository_name = u'openstack/monasca-api'
project = u'Monitoring API Release Notes'
version = version_info.canonical_version_string()
release = version_info.version_string_with_vcs()
bug_project = u'863'
bug_tag = u''
copyright = u'2014-present, OpenStack Foundation'
author = u'OpenStack Foundation'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
# html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
# html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = []

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}

# If false, no module index is generated.
# html_domain_indices = True

# If false, no index is generated.
# html_use_index = True

# If true, the index is split into individual pages for each letter.
# html_split_index = False

# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'MonitoringApiReleaseNotesDoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    # 'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [(
     master_doc, 'MonitoringApiReleaseNotes.tex',
     u'Monitoring API Release Notes', [author],
     'manual'
)]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False

# If true, show page references after internal links.
# latex_show_pagerefs = False

# If true, show URL addresses after external links.
# latex_show_urls = False

# Documents to append as an appendix to all manuals.
# latex_appendices = []

# If false, no module index is generated.
# latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'monitoringapireleasenotes',
     u'Monitoring API Release Notes', [author],
     1)
]

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']
<commit_msg>Remove setting of version/release from releasenotes<commit_after># Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.6'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'openstackdocstheme',
    'reno.sphinxext'
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
source_encoding = 'utf-8'

# The master toctree document.
master_doc = 'index'

# General information about the project.
repository_name = u'openstack/monasca-api'
project = u'Monitoring API Release Notes'

# Release notes do not need a version number in the title, they
# cover multiple releases.
version = ''
release = ''
bug_project = u'863'
bug_tag = u''
copyright = u'2014-present, OpenStack Foundation'
author = u'OpenStack Foundation'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
# html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
# html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = []

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}

# If false, no module index is generated.
# html_domain_indices = True

# If false, no index is generated.
# html_use_index = True

# If true, the index is split into individual pages for each letter.
# html_split_index = False

# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'MonitoringApiReleaseNotesDoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    # 'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [(
     master_doc, 'MonitoringApiReleaseNotes.tex',
     u'Monitoring API Release Notes', [author],
     'manual'
)]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False

# If true, show page references after internal links.
# latex_show_pagerefs = False

# If true, show URL addresses after external links.
# latex_show_urls = False

# Documents to append as an appendix to all manuals.
# latex_appendices = []

# If false, no module index is generated.
# latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'monitoringapireleasenotes',
     u'Monitoring API Release Notes', [author],
     1)
]

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
#

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'oslosphinx',
    'reno.sphinxext',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'python-tripleoclient Release Notes'
copyright = u'2016, TripleO Developers'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
from tripleoclient import __version__ as tripleo_version
# The full version, including alpha/beta/rc tags.
release = tripleo_version.version_string_with_vcs()
# The short X.Y version.
version = tripleo_version.canonical_version_string()

# The full version, including alpha/beta/rc tags.

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'python-tripleoclientReleaseNotesdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  ('index', 'python-tripleoclientReleaseNotes.tex', u'python-tripleoclient Release Notes Documentation',
   u'2016, TripleO Developers', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'python-tripleoclientreleasenotes', u'python-tripleoclient Release Notes Documentation',
     [u'2016, TripleO Developers'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'python-tripleoclientReleaseNotes', u'python-tripleoclient Release Notes Documentation',
   u'2016, TripleO Developers', 'python-tripleoclientReleaseNotes', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']
<commit_msg>Use Python to compute release notes version<commit_after># -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
#

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'oslosphinx',
    'reno.sphinxext',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'python-tripleoclient Release Notes'
copyright = u'2016, TripleO Developers'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = ''
# The short X.Y version.
version = ''

# The full version, including alpha/beta/rc tags.

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'python-tripleoclientReleaseNotesdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  ('index', 'python-tripleoclientReleaseNotes.tex', u'python-tripleoclient Release Notes Documentation',
   u'2016, TripleO Developers', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'python-tripleoclientreleasenotes', u'python-tripleoclient Release Notes Documentation',
     [u'2016, TripleO Developers'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'python-tripleoclientReleaseNotes', u'python-tripleoclient Release Notes Documentation',
   u'2016, TripleO Developers', 'python-tripleoclientReleaseNotes', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']
<|endoftext|>


<commit_before># coding: utf-8
from character import character as character
import ply.yacc as yacc

class Parser:
    # error rule
    def __init__(self, lex):
        def p_obsidianFormat (p):
            '''
            obsidianFormat : blocks
            '''

        def p_blocks (p):
            '''
            blocks : blocks block
                | block
            '''

        def p_block (p):
            '''
            block : overview
                | defense_block
                | offense_block
            '''

        def p_overview (p):
            '''
            overview : name statlist
            '''

        def p_statlist (p):
            '''statlist : statlist stat
                        | stat'''

        def p_stat (p):
            '''
            stat : stat EOL
                | stat SEMICOLON
                | challenge_rating
                | xp_value
                | core
                | bio
                | initiative
                | senses
            '''

        def p_name (p) :
            'name : words'
            self.character.name = p[1]

        def p_challenge_rating (p) :
            'challenge_rating : CR NUMBER'
            self.character.challenge = p[2]

        def p_xp_value (p) :
            'xp_value : XP NUMBER'
            self.character.xp_value = p[2]

        def p_core (p) :
            'core : race_definition class_definitions'
            self.character.classes = p[2]

        def p_race (p) :
            'race_definition : GENDER words'
            self.character.gender = p[1]
            self.character.race = p[2]

        def p_class_definitions (p) :
            '''
            class_definitions : class_definitions SOLIDUS class_definition
                | class_definition
            '''
            if len(p) > 2:
                class_list = p[1]
                class_list.append(p[3])
                p[0] = class_list
            else:
                p[0] = [p[1]];

        def p_class_definition (p) :
            'class_definition : CLASS NUMBER'
            p[0] = {'class' : p[1], 'level' : p[2]}

        def p_bio (p) :
            'bio : alignment size_definition type_definition'
            self.character.alignment = p[1]
            self.character.size = p[2]
            self.character.creature_type = p[3]

        def p_alignment (p) :
            'alignment : ALIGNMENT'
            moral = p[1][:1]
            ethical = p[1][1:]
            p[0] = {'moral' : 0, 'ethical' : 0}
            if moral == 'L':
                p[0]['moral'] = 1
            elif moral == 'C':
                p[0]['moral'] = -1

            if ethical == 'G':
                p[0]['ethical'] = 1
            elif ethical == 'E':
                p[0]['ethical'] = -1

        def p_size_definition (p) :
            '''
            size_definition : SIZE LPAREN SIZE_MOD RPAREN
                | SIZE
            '''
            if len(p) == 5:
                p[0] = p[1] + " (" + p[3] + ")"
            else:
                p[0] = p[1]

        def p_type_definition (p) :
            '''
            type_definition :  CREATURE_TYPE LPAREN wordlist RPAREN
                | CREATURE_TYPE
            '''
            if len(p) == 5:
                p[0] = {'primary' : p[1], 'subtypes' : p[3]}
            else :
                p[0] = {'primary' : p[1], 'subtypes' : []}

        def p_initiative (p):
            '''
            initiative : INIT modifier
            '''
            self.character.initiative = p[2]

        def p_senses (p):
            '''
            senses : SENSES WORD modifier
            '''
            self.character.senses = {p[2] : p[3]}

        def p_defense_block (p):
            '''
            defense_block : DEFENSE EOL defenses
                | DEFENSE defenses
            '''

        def p_defenses (p):
            '''
            defenses : defenses defense
                | defense
            '''

        def p_defense (p):
            '''
            defense : defense EOL
                | ac_definition
                | hp_definition
                | saves_definition
                | defensive_abilities
            '''

        def p_ac_definition (p):
            '''
            ac_definition : AC NUMBER COMMA alternate_acs ac_sources
                | AC NUMBER
            '''
            self.character.ac = {"alternates" : p[4], "sources" : p[5]}

        def p_alternate_acs (p):
            '''
            alternate_acs : alternate_acs COMMA alternate_ac
                | alternate_ac
                |
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            elif len(p) == 2:
                p[0] = [p[1]]
            else:
                p[0] = []

        def p_alternate_ac (p):
            '''
            alternate_ac : AC_TYPE NUMBER
            '''
            p[0] = {"type" : p[1], "value" : p[2]};

        def p_ac_sources (p):
            '''
            ac_sources : LPAREN ac_source_list RPAREN
                |
            '''
            p[0] = p[2];

        def p_ac_source_list (p):
            '''
            ac_source_list : ac_source_list COMMA ac_source
                | ac_source
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            else:
                p[0] = [p[1]]

        def p_ac_source (p):
            '''
            ac_source : modifier words
                | modifier DEX
            '''
            p[0] = { "name" : p[2], "value" : p[1] }

        def p_hp_definition (p):
            '''
            hp_definition : HP NUMBER hit_dice_definition
            '''
            self.character.hp = p[2]

        def p_hit_dice_definition (p):
            '''
            hit_dice_definition : LPAREN NUMBER HD SEMICOLON hit_dice_list modifier RPAREN
            '''
            self.character.hit_dice = p[2]
            self.character.hit_dice_list = p[5]
            self.character.hp_modifier = p[6]

        def p_hit_dice_list (p):
            '''
            hit_dice_list : hit_dice_list PLUS dice_definition
                | dice_definition
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            elif len(p) == 2:
                p[0] = [p[1]]

        def p_saves_definition (p):
            '''
            saves_definition : saves_list SEMICOLON special_saves
            '''
            self.character.saves = p[1]
            self.character.save_modifiers = p[3]

        def p_saves_list (p):
            '''
            saves_list : saves_list COMMA save
                | save
            '''
            if len(p) == 4:
                p[0] = dict(p[1].items() + p[3].items())
            else:
                p[0] = p[1]

        def p_save (p):
            '''
            save : SAVING_THROW modifier
            '''
            p[0] = {p[1]: p[2]}

        def p_special_saves (p):
            '''
            special_saves : special_saves COMMA special_save
                | special_save
            '''
            if len(p) == 4:
                p[0] = dict(p[3].items() + p[1].items())
            else :
                p[0] = p[1]

        def p_special_save (p):
            '''
            special_save : modifier VS WORD
            '''
            p[0] = {p[3] : p[1]}

        def p_defensive_abilities (p):
            '''
            defensive_abilities : DEFENSIVE ABILITIES def_abilities_list
            '''
            self.character.defensive_abilities = p[3]

        def p_def_abilities_list (p):
            '''
            def_abilities_list : def_abilities_list COMMA defensive_ability
                | defensive_ability
            '''
            if len(p) == 4:
                p[0] = dict(p[3].items() + p[1].items())
            else:
                p[0] = p[1]

        def p_defensive_ability (p):
            '''
            defensive_ability : WORD modifier
                | WORD
            '''
            if len(p) == 3:
                p[0] = { p[1]  : p[2]}
            else:
                p[0] = { p[1]  : None}

        def p_offense_block (p):
            '''
            offense_block : OFFENSE EOL offenses
                | OFFENSE offenses
            '''

        def p_offenses (p):
            '''
            offenses : offenses offense
                | offense
            '''

        def p_offense (p):
            '''
            offense : offense EOL
                | speed
                | melee_definition
            '''

        def p_speed (p):
            '''
            speed : SPEED NUMBER FEET
            '''
            self.character.speed = p[2]

        def p_melee_definition (p):
            '''
            melee_definition : MELEE melee_attacks
            '''
            self.character.melee_attacks = p[2]

        def p_melee_attacks (p):
            '''
            melee_attacks : melee_attacks OR EOL melee_attack
                | melee_attack
            '''
            if len(p) == 5:
                p[0] = p[1] + [p[4]]
            else:
                p[0] = [p[1]]

        def p_melee_attack (p):
            '''
            melee_attack : words modifier damage_specification
            '''
            full_attack_spec = p[3]
            full_attack_spec["name"] = p[1]
            full_attack_spec["attack bonus"] = p[2]
            p[0] = full_attack_spec

        def p_damage_specification (p):
            '''
            damage_specification : LPAREN dice_definition damage_type modifier crit_spec RPAREN
            '''
            p[0] = {"roll" : p[2], "type" : p[3], "modifier" : p[4], "critical" : p[5]}

        def p_damage_type (p):
            '''
            damage_type : DAMAGE_TYPE
                |
            '''
            if len(p) == 2:
                p[0] = p[1]
            else :
                p[0] = ""

        def p_crit_spec (p):
            '''
            crit_spec : SOLIDUS TIMES NUMBER
                |
            '''
            if len(p) == 4:
                p[0] = p[3]
            else:
                p[0] = 0

        # utility rules

        def p_wordlist (p) :
            '''
            wordlist : wordlist COMMA words
                |  words
            '''
            if len(p) == 4:
                p[0] = p[1]
                p[0].append(p[3])
            else:
                p[0] = [p[1]]

        def p_words (p) :
            '''
            words : words WORD
                | WORD
            '''
            if len(p) == 3:
                p[0] = p[1] + " " + p[2]
            else :
                p[0] = p[1]

        def p_dice_definition (p):
            '''
            dice_definition : NUMBER D NUMBER
            '''
            p[0] = str(p[1]) + "d" + str(p[3])

        def p_modifier (p):
            '''
            modifier : PLUS NUMBER
            '''
            p[0] = p[2]

        # Compute column.
        # token is a token instance
        def find_column(token):
            last_cr = self.input.rfind('\n',0,token.lexpos)
            if last_cr < 0:
                last_cr = 0
            column = (token.lexpos - last_cr) + 1
            return column

        # error rule
        def p_error (p):
            print "syntax error at " + str(p.type) + " token '" + str(p.value),
            print  "' at position " + str(find_column(p)) + ", line " + str(p.lineno)
            #self.lex.readTokens(self.input)

            self.error = True

        self.error = False;
        self.lex = lex

        tokens = lex.tokens

        self.parser = yacc.yacc()

        self.character = character();

    def parse(self, s):
        self.input = s;
        result = self.parser.parse(s, lexer=self.lex.lexer)
        return result
<commit_msg>added melee attack effects<commit_after># coding: utf-8
from character import character as character
import ply.yacc as yacc

class Parser:
    # error rule
    def __init__(self, lex):
        def p_obsidianFormat (p):
            '''
            obsidianFormat : blocks
            '''

        def p_blocks (p):
            '''
            blocks : blocks block
                | block
            '''

        def p_block (p):
            '''
            block : overview
                | defense_block
                | offense_block
            '''

        def p_overview (p):
            '''
            overview : name statlist
            '''

        def p_statlist (p):
            '''statlist : statlist stat
                        | stat'''

        def p_stat (p):
            '''
            stat : stat EOL
                | stat SEMICOLON
                | challenge_rating
                | xp_value
                | core
                | bio
                | initiative
                | senses
            '''

        def p_name (p) :
            'name : words'
            self.character.name = p[1]

        def p_challenge_rating (p) :
            'challenge_rating : CR NUMBER'
            self.character.challenge = p[2]

        def p_xp_value (p) :
            'xp_value : XP NUMBER'
            self.character.xp_value = p[2]

        def p_core (p) :
            'core : race_definition class_definitions'
            self.character.classes = p[2]

        def p_race (p) :
            'race_definition : GENDER words'
            self.character.gender = p[1]
            self.character.race = p[2]

        def p_class_definitions (p) :
            '''
            class_definitions : class_definitions SOLIDUS class_definition
                | class_definition
            '''
            if len(p) > 2:
                class_list = p[1]
                class_list.append(p[3])
                p[0] = class_list
            else:
                p[0] = [p[1]];

        def p_class_definition (p) :
            'class_definition : CLASS NUMBER'
            p[0] = {'class' : p[1], 'level' : p[2]}

        def p_bio (p) :
            'bio : alignment size_definition type_definition'
            self.character.alignment = p[1]
            self.character.size = p[2]
            self.character.creature_type = p[3]

        def p_alignment (p) :
            'alignment : ALIGNMENT'
            moral = p[1][:1]
            ethical = p[1][1:]
            p[0] = {'moral' : 0, 'ethical' : 0}
            if moral == 'L':
                p[0]['moral'] = 1
            elif moral == 'C':
                p[0]['moral'] = -1

            if ethical == 'G':
                p[0]['ethical'] = 1
            elif ethical == 'E':
                p[0]['ethical'] = -1

        def p_size_definition (p) :
            '''
            size_definition : SIZE LPAREN SIZE_MOD RPAREN
                | SIZE
            '''
            if len(p) == 5:
                p[0] = p[1] + " (" + p[3] + ")"
            else:
                p[0] = p[1]

        def p_type_definition (p) :
            '''
            type_definition :  CREATURE_TYPE LPAREN wordlist RPAREN
                | CREATURE_TYPE
            '''
            if len(p) == 5:
                p[0] = {'primary' : p[1], 'subtypes' : p[3]}
            else :
                p[0] = {'primary' : p[1], 'subtypes' : []}

        def p_initiative (p):
            '''
            initiative : INIT modifier
            '''
            self.character.initiative = p[2]

        def p_senses (p):
            '''
            senses : SENSES WORD modifier
            '''
            self.character.senses = {p[2] : p[3]}

        def p_defense_block (p):
            '''
            defense_block : DEFENSE EOL defenses
                | DEFENSE defenses
            '''

        def p_defenses (p):
            '''
            defenses : defenses defense
                | defense
            '''

        def p_defense (p):
            '''
            defense : defense EOL
                | ac_definition
                | hp_definition
                | saves_definition
                | defensive_abilities
            '''

        def p_ac_definition (p):
            '''
            ac_definition : AC NUMBER COMMA alternate_acs ac_sources
                | AC NUMBER
            '''
            self.character.ac = {"alternates" : p[4], "sources" : p[5]}

        def p_alternate_acs (p):
            '''
            alternate_acs : alternate_acs COMMA alternate_ac
                | alternate_ac
                |
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            elif len(p) == 2:
                p[0] = [p[1]]
            else:
                p[0] = []

        def p_alternate_ac (p):
            '''
            alternate_ac : AC_TYPE NUMBER
            '''
            p[0] = {"type" : p[1], "value" : p[2]};

        def p_ac_sources (p):
            '''
            ac_sources : LPAREN ac_source_list RPAREN
                |
            '''
            p[0] = p[2];

        def p_ac_source_list (p):
            '''
            ac_source_list : ac_source_list COMMA ac_source
                | ac_source
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            else:
                p[0] = [p[1]]

        def p_ac_source (p):
            '''
            ac_source : modifier words
                | modifier DEX
            '''
            p[0] = { "name" : p[2], "value" : p[1] }

        def p_hp_definition (p):
            '''
            hp_definition : HP NUMBER hit_dice_def
            '''
            self.character.hp = p[2]

        def p_hit_dice_def (p):
            '''
            hit_dice_def : LPAREN NUMBER HD SEMICOLON hit_dice_list modifier RPAREN
            '''
            self.character.hit_dice = p[2]
            self.character.hit_dice_list = p[5]
            self.character.hp_modifier = p[6]

        def p_hit_dice_list (p):
            '''
            hit_dice_list : hit_dice_list PLUS dice_def
                | dice_def
            '''
            if len(p) == 4:
                p[0] = p[1] + [p[3]]
            elif len(p) == 2:
                p[0] = [p[1]]

        def p_saves_definition (p):
            '''
            saves_definition : saves_list SEMICOLON special_saves
            '''
            self.character.saves = p[1]
            self.character.save_modifiers = p[3]

        def p_saves_list (p):
            '''
            saves_list : saves_list COMMA save
                | save
            '''
            if len(p) == 4:
                p[0] = dict(p[1].items() + p[3].items())
            else:
                p[0] = p[1]

        def p_save (p):
            '''
            save : SAVING_THROW modifier
            '''
            p[0] = {p[1]: p[2]}

        def p_special_saves (p):
            '''
            special_saves : special_saves COMMA special_save
                | special_save
            '''
            if len(p) == 4:
                p[0] = dict(p[3].items() + p[1].items())
            else :
                p[0] = p[1]

        def p_special_save (p):
            '''
            special_save : modifier VS WORD
            '''
            p[0] = {p[3] : p[1]}

        def p_defensive_abilities (p):
            '''
            defensive_abilities : DEFENSIVE ABILITIES def_abilities_list
            '''
            self.character.defensive_abilities = p[3]

        def p_def_abilities_list (p):
            '''
            def_abilities_list : def_abilities_list COMMA defensive_ability
                | defensive_ability
            '''
            if len(p) == 4:
                p[0] = dict(p[3].items() + p[1].items())
            else:
                p[0] = p[1]

        def p_defensive_ability (p):
            '''
            defensive_ability : WORD modifier
                | WORD
            '''
            if len(p) == 3:
                p[0] = { p[1]  : p[2]}
            else:
                p[0] = { p[1]  : None}

        def p_offense_block (p):
            '''
            offense_block : OFFENSE EOL offenses
                | OFFENSE offenses
            '''

        def p_offenses (p):
            '''
            offenses : offenses offense
                | offense
            '''

        def p_offense (p):
            '''
            offense : offense EOL
                | speed
                | melee_definition
            '''

        def p_speed (p):
            '''
            speed : SPEED NUMBER FEET
            '''
            self.character.speed = p[2]

        def p_melee_definition (p):
            '''
            melee_definition : MELEE melee_attacks
            '''
            self.character.melee_attacks = p[2]

        def p_melee_attacks (p):
            '''
            melee_attacks : melee_attacks OR EOL melee_attack
                | melee_attack
            '''
            if len(p) == 5:
                p[0] = p[1] + [p[4]]
            else:
                p[0] = [p[1]]

        def p_melee_attack (p):
            '''
            melee_attack : words modifier damage_specification
            '''
            full_attack_spec = p[3]
            full_attack_spec["name"] = p[1]
            full_attack_spec["attack bonus"] = p[2]
            p[0] = full_attack_spec

        def p_damage_specification (p):
            '''
            damage_specification : LPAREN dice_def damage_type modifier crit_spec effect_spec RPAREN
            '''
            p[0] = {
                "roll" : p[2],
                "type" : p[3],
                "modifier" : p[4],
                "critical" : p[5],
                "effect" : p[6]
            }

        def p_damage_type (p):
            '''
            damage_type : DAMAGE_TYPE
                |
            '''
            if len(p) == 2:
                p[0] = p[1]
            else :
                p[0] = ""

        def p_crit_spec (p):
            '''
            crit_spec : SOLIDUS TIMES NUMBER
                |
            '''
            if len(p) == 4:
                p[0] = p[3]
            else:
                p[0] = 0

        def p_effect_spec (p):
            '''
            effect_spec : words
                |
            '''
            effect_string = ""
            if len(p) == 2:
                effect_string = p[1]

            p[0] = effect_string;

        # utility rules

        def p_wordlist (p) :
            '''
            wordlist : wordlist COMMA words
                |  words
            '''
            if len(p) == 4:
                p[0] = p[1]
                p[0].append(p[3])
            else:
                p[0] = [p[1]]

        def p_words (p) :
            '''
            words : words WORD
                | WORD
            '''
            if len(p) == 3:
                p[0] = p[1] + " " + p[2]
            else :
                p[0] = p[1]

        def p_dice_def (p):
            '''
            dice_def : NUMBER D NUMBER
            '''
            p[0] = str(p[1]) + "d" + str(p[3])

        def p_modifier (p):
            '''
            modifier : PLUS NUMBER
            '''
            p[0] = p[2]

        # Compute column.
        # token is a token instance
        def find_column(token):
            last_cr = self.input.rfind('\n',0,token.lexpos)
            if last_cr < 0:
                last_cr = 0
            column = (token.lexpos - last_cr) + 1
            return column

        # error rule
        def p_error (p):
            print "syntax error at " + str(p.type) + " token '" + str(p.value),
            print  "' at position " + str(find_column(p)) + ", line " + str(p.lineno)
            #self.lex.readTokens(self.input)

            self.error = True

        self.error = False;
        self.lex = lex

        tokens = lex.tokens

        self.parser = yacc.yacc()

        self.character = character();

    def parse(self, s):
        self.input = s;
        result = self.parser.parse(s, lexer=self.lex.lexer)
        return result
<|endoftext|>


<commit_before>from graph import SimpleGraph
import pytest

def test_add_nodes():
    """nodes are added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert 'a' in g.nodes()
    assert 'b' in g.nodes()
    assert 'c' in g.nodes()

def test_del_nodes():
    """nodes are deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.del_node('a')
    assert 'a' not in g.nodes()
    assert g.has_node('a') is False
    assert g.nodes() == ['c', 'b']

def test_del_error():
    """error raised when non-existant value deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.del_node('p')

def test_del_edge():
    """other edges preserved when one deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.del_edge('a', 'c')
    assert g.neighbors('a') == {'b': 0}

def test_edge_error():
    """delete non-existant edge"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.del_edge('a', 'z')

def test_add_edge():
    """edge added, new node added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.add_edge('q', 'a')
    assert g.has_node('q')
    assert g.neighbors('q') == {'a': 0}

def test_has_node():
    """edge added, new node added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert g.has_node('b')

def test_adjacent():
    """adjacent correctly identify edges, new edge add node"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'd')
    g.add_edge('a', 'f')
    assert 'f' in g.nodes()
    assert g.adjacent('a', 'd')
    assert not g.adjacent('a', 'b')

def test_adjacent_error():
    """error raised when nodes non-existant"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.adjacent('l', 'm')

def test_edges():
    """edges prints correct edges as tuples"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert ('a', 'c') in g.edges()
    assert ('a', 'b') in g.edges()

def test_breadth():
    g = SimpleGraph()
    g.add_edge('a', 'b')
    g.add_edge('a', 'c')
    g.add_edge('b', 'd')
    g.add_edge('b', 'e')
    g.add_edge('e', 'f')
    g.add_edge('f', 'g')
    assert g.breadth_first_traversal('a') == ['a', 'c', 'b', 'e', 'd', 'f', 'g']

def test_depth():
    g = SimpleGraph()
    g.add_edge('a', 'b')
    g.add_edge('a', 'c')
    g.add_edge('b', 'd')
    g.add_edge('b', 'e')
    g.add_edge('e', 'f')
    g.add_edge('f', 'g')
    assert g.depth_first_traversal('a') == ['a', 'c', 'b', 'e', 'f', 'g', 'd']

def test_one_breadth():
    g = SimpleGraph()
    g.add_node('a')
    assert g.breadth_first_traversal('a') == ['a']

def test_one_depth():
    g = SimpleGraph()
    g.add_node('a')
    assert g.depth_first_traversal('a') == ['a']

def test_one_loop_breadth():
    g = SimpleGraph()
    g.add_edge('a', 'a')
    assert g.breadth_first_traversal('a') == ['a']

def test_one_loop_depth():
    g = SimpleGraph()
    g.add_edge('a', 'a')
    assert g.depth_first_traversal('a') == ['a']

def fully_connected_graph(nodes):
    g = SimpleGraph()
    for node in nodes:
        g.add_node(node)
        for other_node in nodes:
            if other_node is not node:
                g.add_edge(node, other_node)
    return g

def test_fully_connected():
    g = fully_connected_graph(['a', 'b', 'c', 'd'])
    assert g.breadth_first_traversal('a') == ['a', 'c', 'b', 'd']

def test_fully_connected_again():
    g = fully_connected_graph(['a', 'b', 'c', 'd'])
    assert g.depth_first_traversal('a') == ['a', 'c', 'b', 'd']
<commit_msg>Add tests for weighted edges<commit_after>from graph import SimpleGraph
import pytest

def test_add_nodes():
    """nodes are added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert 'a' in g.nodes()
    assert 'b' in g.nodes()
    assert 'c' in g.nodes()

def test_del_nodes():
    """nodes are deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.del_node('a')
    assert 'a' not in g.nodes()
    assert g.has_node('a') is False
    assert g.nodes() == ['c', 'b']

def test_del_error():
    """error raised when non-existant value deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.del_node('p')

def test_del_edge():
    """other edges preserved when one deleted"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.del_edge('a', 'c')
    assert g.neighbors('a') == {'b': 0}

def test_edge_error():
    """delete non-existant edge"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.del_edge('a', 'z')

def test_add_edge():
    """edge added, new node added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    g.add_edge('q', 'a')
    assert g.has_node('q')
    assert g.neighbors('q') == {'a': 0}

def test_has_node():
    """edge added, new node added"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert g.has_node('b')

def test_adjacent():
    """adjacent correctly identify edges, new edge add node"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'd')
    g.add_edge('a', 'f')
    assert 'f' in g.nodes()
    assert g.adjacent('a', 'd')
    assert not g.adjacent('a', 'b')

def test_adjacent_error():
    """error raised when nodes non-existant"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    with pytest.raises(KeyError):
        g.adjacent('l', 'm')

def test_edges():
    """edges prints correct edges as tuples"""
    g = SimpleGraph()
    g.add_node('a')
    g.add_node('b')
    g.add_node('c')
    g.add_edge('a', 'c')
    g.add_edge('a', 'b')
    assert ('a', 'c') in g.edges()
    assert ('a', 'b') in g.edges()

def test_breadth():
    g = SimpleGraph()
    g.add_edge('a', 'b')
    g.add_edge('a', 'c')
    g.add_edge('b', 'd')
    g.add_edge('b', 'e')
    g.add_edge('e', 'f')
    g.add_edge('f', 'g')
    assert g.breadth_first_traversal('a') == ['a', 'c', 'b', 'e', 'd', 'f', 'g']

def test_depth():
    g = SimpleGraph()
    g.add_edge('a', 'b')
    g.add_edge('a', 'c')
    g.add_edge('b', 'd')
    g.add_edge('b', 'e')
    g.add_edge('e', 'f')
    g.add_edge('f', 'g')
    assert g.depth_first_traversal('a') == ['a', 'c', 'b', 'e', 'f', 'g', 'd']

def test_one_breadth():
    g = SimpleGraph()
    g.add_node('a')
    assert g.breadth_first_traversal('a') == ['a']

def test_one_depth():
    g = SimpleGraph()
    g.add_node('a')
    assert g.depth_first_traversal('a') == ['a']

def test_one_loop_breadth():
    g = SimpleGraph()
    g.add_edge('a', 'a')
    assert g.breadth_first_traversal('a') == ['a']

def test_one_loop_depth():
    g = SimpleGraph()
    g.add_edge('a', 'a')
    assert g.depth_first_traversal('a') == ['a']

def fully_connected_graph(nodes):
    g = SimpleGraph()
    for node in nodes:
        g.add_node(node)
        for other_node in nodes:
            if other_node is not node:
                g.add_edge(node, other_node)
    return g

def test_fully_connected():
    g = fully_connected_graph(['a', 'b', 'c', 'd'])
    assert g.breadth_first_traversal('a') == ['a', 'c', 'b', 'd']

def test_fully_connected_again():
    g = fully_connected_graph(['a', 'b', 'c', 'd'])
    assert g.depth_first_traversal('a') == ['a', 'c', 'b', 'd']

def test_weighted_edges():
    g = SimpleGraph()
    g.add_edge('a', 'b', 5)
    g.add_edge('a', 'c', 2)
    g.add_edge('b', 'c', 1)
    assert g.dict_graph['a'] == {'b': 5, 'c': 2}

def test_weighted_edges_with_edge_delete():
    g = SimpleGraph()
    g.add_edge('a', 'b', 5)
    g.add_edge('a', 'c', 2)
    g.del_edge('a', 'c')
    assert g.dict_graph['a'] == {'b': 5}

def test_weighted_edges_with_node_delete():
    g = SimpleGraph()
    g.add_edge('a', 'b', 5)
    g.add_edge('a', 'c', 2)
    g.del_node('c')
    assert g.dict_graph['a'] == {'b': 5}
<|endoftext|>


<commit_before>"""PyDbLite.py adapted for SQLite backend

Differences with PyDbLite:
- pass the connection to the SQLite db as argument to Base()
- in create(), field definitions must specify a type
    DATE for datetime.date
    TIMESTAMP or DATETIME for datetime.date
- no index
- no drop_field (not supported by SQLite)
- the Base() instance has a cursor attribute, so that SQL requests
  can be executed :
    db.cursor.execute(an_sql_request)
    result = db.cursor.fetchall()

Syntax :
    from PyDbLite.SQLite import Base
    # connect to SQLite database "test"
    connection = sqlite.connect("test")
    # pass the connection as argument to Base creation
    db = Base('dummy',connection)
    # create new base with field names
    db.create(('name','TEXT'),('age',"INTEGER'),('size','REAL'))
    # existing base
    db.open()
    # insert new record
    db.insert(name='homer',age=23,size=1.84)
    # records are dictionaries with a unique integer key __id__
    # selection by list comprehension
    res = [ r for r in db if 30 > r['age'] >= 18 and r['size'] < 2 ]
    # or generator expression
    for r in (r for r in db if r['name'] in ('homer','marge') ):
    # simple selection (equality test)
    res = db(age=30)
    # delete a record or a list of records
    db.delete(one_record)
    db.delete(list_of_records)
    # delete a record by its id
    del db[rec_id]
    # direct access by id
    record = db[rec_id] # the record such that record['__id__'] == rec_id
    # update
    db.update(record,age=24)
    # add a field
    db.add_field('new_field')
    # save changes on disk
    db.commit()
"""

import os
import cPickle
import bisect

import datetime

# test if sqlite is installed or raise exception
try:
    from sqlite3 import dbapi2 as sqlite
except ImportError:
    try:
        from pysqlite2 import dbapi2 as sqlite
    except ImportError:
        print "SQLite is not installed"
        raise

def make_date(d):
    if d is None:
        return None
    d = str(d)
    y,m,d = int(d[:4]),int(d[4:6]),int(d[6:])
    return datetime.date(y,m,d)

def make_datetime(d):
    if d is None:
        return None
    d = str(d)
    y,m,d,h,mn,s = int(d[:4]),int(d[4:6]),int(d[6:8]),\
        int(d[8:10]),int(d[10:12]),int(d[12:14])
    return datetime.datetime(y,m,d,h,mn,s)

# compatibility with Python 2.3
try:
    set([])
except NameError:
    from sets import Set as set

class Base:

    conv_func = {"DATE":make_date,"TIMESTAMP":make_datetime,
        "DATETIME":make_datetime}

    def __init__(self,basename,connection):
        """basename = name of the PyDbLite database = a MySQL table
        connection = a connection to a MySQL database"""
        self.name = basename
        self.conn = connection
        self.cursor = connection.cursor()
        self._iterating = False

    def create(self,*fields,**kw):
        """Create a new base with specified field names
        A keyword argument mode can be specified ; it is used if a file
        with the base name already exists
        - if mode = 'open' : open the existing base, ignore the fields
        - if mode = 'override' : erase the existing base and create a
        new one with the specified fields"""
        mode = kw.get("mode",None)
        if self._table_exists():
            if mode == "override":
                self.cursor.execute("DROP TABLE %s" %self.name)
            elif mode == "open":
                import sys
                sys.stderr.write("base exists")
                return self.open()
            else:
                raise IOError,"Base %s already exists" %self.name
        self.fields = [ f[0] for f in fields ]
        self.all_fields = ["__id__","__version__"] #+self.fields
        _types = ["INTEGER PRIMARY KEY AUTOINCREMENT","INTEGER"] #+ \
        # [f[1] for f in fields]
        for field in fields:
            if len(field) !=2:
                raise SyntaxError,"Error in field definition %s" %field
            self.all_fields.append(field[0])
            _type = field[1]
            if _type.upper() == "DATETIME":
                _type = "TIMESTAMP"
            _types.append(_type)
        f_string = [ "%s %s" %(f,t) for (f,t) in zip(self.all_fields,_types)]
        self.types = dict([ (f[0],self.conv_func[f[1].upper()])
            for f in fields if f[1].upper() in self.conv_func ])
        sql = "CREATE TABLE %s (%s)" %(self.name,",".join(f_string))
        self.cursor.execute(sql)
        return self

    def open(self):
        """Open an existing database"""
        if self._table_exists():
            self.mode = "open"
            # get table info
            self._get_table_info()
            return self
        else:
            # table not found
            raise IOError,"Table %s doesn't exist" %self.name

    def _table_exists(self):
        self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
        for table_info in self.cursor.fetchall():
            if table_info[0] == self.name:
                return True
        return False

    def _get_table_info(self):
        """Inspect the base to get field names"""
        self.cursor.execute('PRAGMA table_info (%s)' %self.name)
        fields = [ (f[1],f[2].encode()) for f in self.cursor.fetchall() ]
        self.all_fields = [ f[0] for f in fields ]
        self.fields = self.all_fields[2:]
        self.types = dict([ (f[0],self.conv_func[f[1].upper()])
            for f in fields if f[1].upper() in self.conv_func ])

    def commit(self):
        """Commit changes on disk"""
        self.conn.commit()

    def insert(self,*args,**kw):
        """Insert a record in the database
        Parameters can be positional or keyword arguments. If positional
        they must be in the same order as in the create() method
        If some of the fields are missing the value is set to None
        Returns the record identifier
        """
        if args:
            kw = dict([(f,arg) for f,arg in zip(self.all_fields[2:],args)])
        kw["__version__"] = 0

        ks = kw.keys()
        vals = [ str(self._conv(kw[k])) for k in kw.keys() ]
        sql = "INSERT INTO %s (%s) VALUES (%s)" %(self.name,
            ",".join(ks),",".join(vals))
        self.cursor.execute(sql)
        # return last row id
        return self.cursor.lastrowid

    def delete(self,removed):
        """Remove a single record, or the records in an iterable
        Before starting deletion, test if all records are in the base
        and don't have twice the same __id__
        Return the number of deleted items
        """
        if isinstance(removed,dict):
            # remove a single record
            removed = [removed]
        else:
            # convert iterable into a list (to be able to sort it)
            removed = [ r for r in removed ]
        if not removed:
            return 0
        _ids = [ r['__id__'] for r in removed ]
        _ids.sort()
        sql = "DELETE FROM %s WHERE __id__ IN (%s)" %(self.name,
            ",".join([str(_id) for _id in _ids]))
        self.cursor.execute(sql)
        return len(removed)

    def update(self,record,**kw):
        """Update the record with new keys and values"""
        # increment version number
        kw["__version__"] = record["__version__"] + 1
        vals = self._make_sql_params(kw)
        sql = "UPDATE %s SET %s WHERE __id__=%s" %(self.name,
            ",".join(vals),record["__id__"])
        self.cursor.execute(sql)

    def _make_sql_params(self,kw):
        """Make a list of strings to pass to an SQL statement
        from the dictionary kw with Python types"""
        vals = []
        for k,v in kw.iteritems():
            vals.append('%s=%s' %(k,self._conv(v)))
        return vals

    def _conv(self,v):
        if isinstance(v,str):
            return '"%s"' %v.replace("'","''")
        elif isinstance(v,datetime.datetime):
            if v.tzinfo is not None:
                raise ValueError,\
                    "datetime instances with tzinfo not supported"
            return v.strftime("%Y%m%d%H%M%S%Z")
        elif isinstance(v,datetime.date):
            return v.strftime("%Y%m%d")
        else:
            return v

    def _make_record(self,row):
        """Make a record dictionary from the result of a fetch_"""
        res = dict(zip(self.all_fields,row))
        for k in self.types:
            res[k] = self.types[k](res[k])
        return res

    def add_field(self,field,default=None):
        fname,ftype = field
        if fname in self.all_fields:
            raise ValueError,'Field "%s" already defined' %fname
        self.all_fields.append(fname)
        if ftype.upper() in self.conv_func:
            self.types[fname] = self.conv_func[ftype.upper()]
        sql = "ALTER TABLE %s ADD %s %s" %(self.name,fname,ftype)
        if default is not None:
            sql += " DEFAULT %s" %self._conv(default)
        self.cursor.execute(sql)
        self.commit()

    def drop_field(self,field):
        raise SyntaxError,"Dropping fields is not supported by SQLite"

    def __call__(self,**kw):
        """Selection by field values
        db(key=value) returns the list of records where r[key] = value"""
        if kw:
            for key in kw:
                if not key in self.all_fields:
                    raise ValueError,"Field %s not in the database" %key
            vals = self._make_sql_params(kw)
            sql = "SELECT * FROM %s WHERE %s" %(self.name," AND ".join(vals))
        else:
            sql = "SELECT * FROM %s" %self.name
        self.cursor.execute(sql)
        return [self._make_record(row) for row in self.cursor.fetchall() ]

    def __getitem__(self,record_id):
        """Direct access by record id"""
        sql = "SELECT * FROM %s WHERE __id__=%s" %(self.name,record_id)
        self.cursor.execute(sql)
        res = self.cursor.fetchone()
        if res is None:
            raise IndexError,"No record at index %s" %record_id
        else:
            return self._make_record(res)

    def __len__(self):
        return len(self.records)

    def __delitem__(self,record_id):
        """Delete by record id"""
        self.delete(self[record_id])

    def __iter__(self):
        """Iteration on the records"""
        self.cursor.execute("SELECT * FROM %s" %self.name)
        results = [ self._make_record(r) for r in self.cursor.fetchall() ]
        return iter(results)

if __name__ == '__main__':

    try:
        from sqlite3 import dbapi2 as sqlite
    except ImportError:
        try:
            from pysqlite2 import dbapi2 as sqlite
        except ImportError:
            print "SQLite is not installed"
            raise

    connection = sqlite.connect("test_sqlite")
    cursor = connection.cursor()

    db = Base("pydbsqlite_test",connection).create(("name","TEXT"),("age","INTEGER"),
        ("size","REAL"),("birth","TIMESTAMP"),
        mode="override")

    try:
        db.add_field(("name","TEXT"))
    except:
        pass

    import random
    import datetime

    names = ['pierre','claire','simon','camille','jean',
                 'florence','marie-anne']
    #db = Base('PyDbLite_test')
    #db.create('name','age','size','birth',mode="override")
    for i in range(1000):
        db.insert(name=random.choice(names),
             age=random.randint(7,47),size=random.uniform(1.10,1.95),
             birth=datetime.datetime(1990,10,10,20,10,20,2345))
    db.commit()

    print 'Record #20 :',db[20]
    raw_input()
    print '\nRecords with age=30 :'
    for rec in [ r for r in db if r["age"]==30 ]:
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))

    print "\nSame with __call__"
    # same with select
    for rec in db(age=30):
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))
    print [ r for r in db if r["age"]==30 ] == db(age=30)
    raw_input()

    db.insert(name=random.choice(names)) # missing fields
    print '\nNumber of records with 30 <= age < 33 :',
    print sum([1 for r in db if 33 > r['age'] >= 30])

    print db.delete([])

    d = db.delete([r for r in db if 32> r['age'] >= 30 and r['name']==u'pierre'])
    print "\nDeleting %s records with name == 'pierre' and 30 <= age < 32" %d
    print '\nAfter deleting records '
    for rec in db(age=30):
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))
    print '\n',sum([1 for r in db]),'records in the database'
    print '\nMake pierre uppercase for age > 27'
    for record in ([r for r in db if r['name']=='pierre' and r['age'] >27]) :
        db.update(record,name="Pierre")
    print len([r for r in db if r['name']=='Pierre']),'Pierre'
    print len([r for r in db if r['name']=='pierre']),'pierre'
    print len([r for r in db if r['name'] in ['pierre','Pierre']]),'p/Pierre'
    print 'is unicode :',isinstance(db[20]['name'],unicode)
    db.commit()
    db.open()
    print '\nSame operation after commit + open'
    print len([r for r in db if r['name']=='Pierre']),'Pierre'
    print len([r for r in db if r['name']=='pierre']),'pierre'
    print len([r for r in db if r['name'] in ['pierre','Pierre']]),'p/Pierre'
    print 'is unicode :',isinstance(db[20]['name'],unicode)

    print "\nDeleting record #21"
    del db[21]
    if not 21 in db:
        print "record 21 removed"

    print db[22]
    print "add field adate"
    db.add_field(('adate',"DATE"),datetime.date.today())

    print db[22]

    <commit_msg>Bug fixes in SQLite, utf-8 related :-(<commit_after>"""PyDbLite.py adapted for SQLite backend

Differences with PyDbLite:
- pass the connection to the SQLite db as argument to Base()
- in create(), field definitions must specify a type
    DATE for datetime.date
    TIMESTAMP or DATETIME for datetime.date
- no index
- no drop_field (not supported by SQLite)
- the Base() instance has a cursor attribute, so that SQL requests
  can be executed :
    db.cursor.execute(an_sql_request)
    result = db.cursor.fetchall()

Syntax :
    from PyDbLite.SQLite import Base
    # connect to SQLite database "test"
    connection = sqlite.connect("test")
    # pass the connection as argument to Base creation
    db = Base('dummy',connection)
    # create new base with field names
    db.create(('name','TEXT'),('age',"INTEGER'),('size','REAL'))
    # existing base
    db.open()
    # insert new record
    db.insert(name='homer',age=23,size=1.84)
    # records are dictionaries with a unique integer key __id__
    # selection by list comprehension
    res = [ r for r in db if 30 > r['age'] >= 18 and r['size'] < 2 ]
    # or generator expression
    for r in (r for r in db if r['name'] in ('homer','marge') ):
    # simple selection (equality test)
    res = db(age=30)
    # delete a record or a list of records
    db.delete(one_record)
    db.delete(list_of_records)
    # delete a record by its id
    del db[rec_id]
    # direct access by id
    record = db[rec_id] # the record such that record['__id__'] == rec_id
    # update
    db.update(record,age=24)
    # add a field
    db.add_field('new_field')
    # save changes on disk
    db.commit()
"""

import os
import cPickle
import bisect

import datetime

# test if sqlite is installed or raise exception
try:
    from sqlite3 import dbapi2 as sqlite
except ImportError:
    try:
        from pysqlite2 import dbapi2 as sqlite
    except ImportError:
        print "SQLite is not installed"
        raise

def make_date(d):
    if d is None:
        return None
    d = str(d)
    y,m,d = int(d[:4]),int(d[4:6]),int(d[6:])
    return datetime.date(y,m,d)

def make_datetime(d):
    if d is None:
        return None
    d = str(d)
    y,m,d,h,mn,s = int(d[:4]),int(d[4:6]),int(d[6:8]),\
        int(d[8:10]),int(d[10:12]),int(d[12:14])
    return datetime.datetime(y,m,d,h,mn,s)

# compatibility with Python 2.3
try:
    set([])
except NameError:
    from sets import Set as set

class SQLiteError(Exception):

    pass

class Base:

    conv_func = {"DATE":make_date,"TIMESTAMP":make_datetime,
        "DATETIME":make_datetime}

    def __init__(self,basename,connection):
        """basename = name of the PyDbLite database = a MySQL table
        connection = a connection to a MySQL database"""
        self.name = basename
        self.conn = connection
        self.cursor = connection.cursor()
        self._iterating = False

    def create(self,*fields,**kw):
        """Create a new base with specified field names
        A keyword argument mode can be specified ; it is used if a file
        with the base name already exists
        - if mode = 'open' : open the existing base, ignore the fields
        - if mode = 'override' : erase the existing base and create a
        new one with the specified fields"""
        mode = kw.get("mode",None)
        if self._table_exists():
            if mode == "override":
                self.cursor.execute("DROP TABLE %s" %self.name)
            elif mode == "open":
                return self.open()
            else:
                raise IOError,"Base %s already exists" %self.name
        self.fields = [ f[0] for f in fields ]
        self.all_fields = ["__id__","__version__"] #+self.fields
        _types = ["INTEGER PRIMARY KEY AUTOINCREMENT","INTEGER"] #+ \
        # [f[1] for f in fields]
        for field in fields:
            if len(field) !=2:
                msg = "Error in field definition %s" %field
                msg += ": should be a 2-element tuple (field_name,field_type)"
                raise SQLiteError,msg
            self.all_fields.append(field[0])
            _type = field[1]
            if _type.upper() == "DATETIME":
                _type = "TIMESTAMP"
            _types.append(_type)
        f_string = [ "%s %s" %(f,t) for (f,t) in zip(self.all_fields,_types)]
        self.types = dict([ (f[0],self.conv_func[f[1].upper()])
            for f in fields if f[1].upper() in self.conv_func ])
        sql = "CREATE TABLE %s (%s)" %(self.name,",".join(f_string))
        self.cursor.execute(sql)
        return self

    def open(self):
        """Open an existing database"""
        if self._table_exists():
            self.mode = "open"
            # get table info
            self._get_table_info()
            return self
        else:
            # table not found
            raise IOError,"Table %s doesn't exist" %self.name

    def _table_exists(self):
        self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
        for table_info in self.cursor.fetchall():
            if table_info[0] == self.name:
                return True
        return False

    def _get_table_info(self):
        """Inspect the base to get field names"""
        self.cursor.execute('PRAGMA table_info (%s)' %self.name)
        fields = [ (f[1].encode('utf-8'),f[2].encode('utf-8'))
            for f in self.cursor.fetchall() ]
        self.all_fields = [ f[0] for f in fields ]
        self.fields = self.all_fields[2:]
        self.types = dict([ (f[0],self.conv_func[f[1].upper()])
            for f in fields if f[1].upper() in self.conv_func ])

    def commit(self):
        """Commit changes on disk"""
        self.conn.commit()

    def insert(self,*args,**kw):
        """Insert a record in the database
        Parameters can be positional or keyword arguments. If positional
        they must be in the same order as in the create() method
        If some of the fields are missing the value is set to None
        Returns the record identifier
        """
        if args:
            kw = dict([(f,arg) for f,arg in zip(self.all_fields[2:],args)])
        kw["__version__"] = 0

        ks = kw.keys()
        vals = [ str(self._conv(kw[k])) for k in kw.keys() ]
        s1 = ",".join(ks)
        s2 = ",".join(vals)
        sql = "INSERT INTO %s (%s) VALUES (%s)" %(self.name,s1,s2)
        self.cursor.execute(sql)
        # return last row id
        return self.cursor.lastrowid

    def delete(self,removed):
        """Remove a single record, or the records in an iterable
        Before starting deletion, test if all records are in the base
        and don't have twice the same __id__
        Return the number of deleted items
        """
        if isinstance(removed,dict):
            # remove a single record
            removed = [removed]
        else:
            # convert iterable into a list (to be able to sort it)
            removed = [ r for r in removed ]
        if not removed:
            return 0
        _ids = [ r['__id__'] for r in removed ]
        _ids.sort()
        sql = "DELETE FROM %s WHERE __id__ IN (%s)" %(self.name,
            ",".join([str(_id) for _id in _ids]))
        self.cursor.execute(sql)
        return len(removed)

    def update(self,record,**kw):
        """Update the record with new keys and values"""
        # increment version number
        kw["__version__"] = record["__version__"] + 1
        vals = self._make_sql_params(kw)
        sql = "UPDATE %s SET %s WHERE __id__=%s" %(self.name,
            ",".join(vals),record["__id__"])
        self.cursor.execute(sql)

    def _make_sql_params(self,kw):
        """Make a list of strings to pass to an SQL statement
        from the dictionary kw with Python types"""
        vals = []
        for k,v in kw.iteritems():
            vals.append('%s=%s' %(k,self._conv(v)))
        return vals

    def _conv(self,v):
        if isinstance(v,str):
            return '"%s"' %v.replace("'","''")
        elif isinstance(v,datetime.datetime):
            if v.tzinfo is not None:
                raise ValueError,\
                    "datetime instances with tzinfo not supported"
            return v.strftime("%Y%m%d%H%M%S%Z")
        elif isinstance(v,datetime.date):
            return v.strftime("%Y%m%d")
        else:
            return v

    def _make_record(self,row):
        """Make a record dictionary from the result of a fetch_"""
        res = dict(zip(self.all_fields,row))
        for k in self.types:
            res[k] = self.types[k](res[k])
        return res

    def add_field(self,field,default=None):
        fname,ftype = field
        if fname in self.all_fields:
            raise ValueError,'Field "%s" already defined' %fname
        self.all_fields.append(fname)
        if ftype.upper() in self.conv_func:
            self.types[fname] = self.conv_func[ftype.upper()]
        sql = "ALTER TABLE %s ADD %s %s" %(self.name,fname,ftype)
        if default is not None:
            sql += " DEFAULT %s" %self._conv(default)
        self.cursor.execute(sql)
        self.commit()

    def drop_field(self,field):
        raise SQLiteError,"Dropping fields is not supported by SQLite"

    def __call__(self,**kw):
        """Selection by field values
        db(key=value) returns the list of records where r[key] = value"""
        if kw:
            for key in kw:
                if not key in self.all_fields:
                    raise ValueError,"Field %s not in the database" %key
            vals = self._make_sql_params(kw)
            sql = "SELECT * FROM %s WHERE %s" %(self.name," AND ".join(vals))
        else:
            sql = "SELECT * FROM %s" %self.name
        self.cursor.execute(sql)
        return [self._make_record(row) for row in self.cursor.fetchall() ]

    def __getitem__(self,record_id):
        """Direct access by record id"""
        sql = "SELECT * FROM %s WHERE __id__=%s" %(self.name,record_id)
        self.cursor.execute(sql)
        res = self.cursor.fetchone()
        if res is None:
            raise IndexError,"No record at index %s" %record_id
        else:
            return self._make_record(res)

    def __len__(self):
        return len(self.records)

    def __delitem__(self,record_id):
        """Delete by record id"""
        self.delete(self[record_id])

    def __iter__(self):
        """Iteration on the records"""
        self.cursor.execute("SELECT * FROM %s" %self.name)
        results = [ self._make_record(r) for r in self.cursor.fetchall() ]
        return iter(results)

if __name__ == '__main__':

    try:
        from sqlite3 import dbapi2 as sqlite
    except ImportError:
        try:
            from pysqlite2 import dbapi2 as sqlite
        except ImportError:
            print "SQLite is not installed"
            raise

    connection = sqlite.connect("test_sqlite")
    cursor = connection.cursor()

    db = Base("pydbsqlite_test",connection).create(("name","TEXT"),("age","INTEGER"),
        ("size","REAL"),("birth","TIMESTAMP"),
        mode="override")

    try:
        db.add_field(("name","TEXT"))
    except:
        pass

    import random
    import datetime

    names = ['pierre','claire','simon','camille','jean',
                 'florence','marie-anne']
    #db = Base('PyDbLite_test')
    #db.create('name','age','size','birth',mode="override")
    for i in range(1000):
        db.insert(name=random.choice(names),
             age=random.randint(7,47),size=random.uniform(1.10,1.95),
             birth=datetime.datetime(1990,10,10,20,10,20,2345))
    db.commit()

    print 'Record #20 :',db[20]
    raw_input()
    print '\nRecords with age=30 :'
    for rec in [ r for r in db if r["age"]==30 ]:
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))

    print "\nSame with __call__"
    # same with select
    for rec in db(age=30):
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))
    print [ r for r in db if r["age"]==30 ] == db(age=30)
    raw_input()

    db.insert(name=random.choice(names)) # missing fields
    print '\nNumber of records with 30 <= age < 33 :',
    print sum([1 for r in db if 33 > r['age'] >= 30])

    print db.delete([])

    d = db.delete([r for r in db if 32> r['age'] >= 30 and r['name']==u'pierre'])
    print "\nDeleting %s records with name == 'pierre' and 30 <= age < 32" %d
    print '\nAfter deleting records '
    for rec in db(age=30):
        print '%-10s | %2s | %s' %(rec['name'],rec['age'],round(rec['size'],2))
    print '\n',sum([1 for r in db]),'records in the database'
    print '\nMake pierre uppercase for age > 27'
    for record in ([r for r in db if r['name']=='pierre' and r['age'] >27]) :
        db.update(record,name="Pierre")
    print len([r for r in db if r['name']=='Pierre']),'Pierre'
    print len([r for r in db if r['name']=='pierre']),'pierre'
    print len([r for r in db if r['name'] in ['pierre','Pierre']]),'p/Pierre'
    print 'is unicode :',isinstance(db[20]['name'],unicode)
    db.commit()
    db.open()
    print '\nSame operation after commit + open'
    print len([r for r in db if r['name']=='Pierre']),'Pierre'
    print len([r for r in db if r['name']=='pierre']),'pierre'
    print len([r for r in db if r['name'] in ['pierre','Pierre']]),'p/Pierre'
    print 'is unicode :',isinstance(db[20]['name'],unicode)

    print "\nDeleting record #21"
    del db[21]
    if not 21 in db:
        print "record 21 removed"

    print db[22]
    print "add field adate"
    db.add_field(('adate',"DATE"),datetime.date.today())

    print db[22]

    <|endoftext|>


<commit_before>print "Hello World"
print "adding something mroe"
<commit_msg>second update<commit_after>print "Hello World"
print "adding something mroe"
print "This is a test"
<|endoftext|>