pax_global_header00006660000000000000000000000064146735400730014523gustar00rootroot0000000000000052 comment=242dfd09510e937574758da76ddc644b71f8cc4f configobj-5.0.9/000077500000000000000000000000001467354007300134765ustar00rootroot00000000000000configobj-5.0.9/.coveragerc000066400000000000000000000000241467354007300156130ustar00rootroot00000000000000[run] branch = True configobj-5.0.9/.github/000077500000000000000000000000001467354007300150365ustar00rootroot00000000000000configobj-5.0.9/.github/workflows/000077500000000000000000000000001467354007300170735ustar00rootroot00000000000000configobj-5.0.9/.github/workflows/python-test.yml000066400000000000000000000016131467354007300221150ustar00rootroot00000000000000name: Python package on: [push] jobs: build-on-latest: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest coverage pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi pip install -e . - name: Test with pytest run: | python src/tests/configobj_doctests.py python -m configobj.validate py.test -c setup.cfg --color=yes --cov=configobj --cov-report=term --cov-report=html --cov-report=xml configobj-5.0.9/.gitignore000066400000000000000000000006061467354007300154700ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages docs/_build *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea # virtualenv env # autogenerated by distutils MANIFEST configobj-5.0.9/CHANGES.rst000066400000000000000000000024461467354007300153060ustar00rootroot00000000000000Changelog --------- Release 5.0.9 """"""""""""" * drop support for Python 2 and <3.7 * fix CVE-2023-26112, ReDoS attack Release 5.0.8 """"""""""""" * fixing/test for a regression introduced in 5.0.7 that prevented ``import validate`` from working Release 5.0.7 """"""""""""" * update testing to validate against python version 2.7 and 3.5-3.11 * update broken links / non-existent services and references Older Releases """""""""""""" * Release 5.0.6 improves error messages in certain edge cases * Release 5.0.5 corrects a unicode-bug that still existed in writing files * Release 5.0.4 corrects a unicode-bug that still existed in reading files after fixing lists of string in 5.0.3 * Release 5.0.3 corrects errors related to the incorrectly handling unicode encoding and writing out files * Release 5.0.2 adds a specific error message when trying to install on Python versions older than 2.5 * Release 5.0.1 fixes a regression with unicode conversion not happening in certain cases PY2 * Release 5.0.0 updates the supported Python versions to 2.6, 2.7, 3.2, 3.3 and is otherwise unchanged * Release 4.7.2 fixes several bugs in 4.7.1 * Release 4.7.1 fixes a bug with the deprecated options keyword in 4.7.0. * Release 4.7.0 improves performance adds features for validation and fixes some bugs. configobj-5.0.9/LICENSE000066400000000000000000000033031467354007300145020ustar00rootroot00000000000000Copyright (c): 2003-2010, Michael Foord, Nicola Larosa 2014-2023, Eli Courtwright, Rob Dennis All rights reserved. E-mails : michael AT python DOT org nico AT tekNico DOT net eli AT courtwright DOT org rdennis AT gmail DOT com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names of Michael Foord, Eli Courtwright or Rob Dennis, nor the name of Voidspace, may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. configobj-5.0.9/README.md000066400000000000000000000027061467354007300147620ustar00rootroot00000000000000# configobj [![Coverage Status](https://img.shields.io/coveralls/DiffSK/configobj.svg)](https://coveralls.io/r/DiffSK/configobj?branch=master) [![PyPI version](http://img.shields.io/pypi/v/configobj.svg)](https://pypi.python.org/pypi/configobj) [![License](https://img.shields.io/badge/license-BSD_3--clause-red.svg)](https://github.com/DiffSK/configobj/blob/master/LICENSE) Python 3+ compatible port of the [configobj](https://pypi.python.org/pypi/configobj/) library. The Github CI/CD Pipeline runs tests on python versions: - 3.7 - 3.8 - 3.9 - 3.10 - 3.11 - 3.12 ## Documentation You can find a full manual on how to use ConfigObj at [readthedocs](http://configobj.readthedocs.io/). ## Status This is a mature project that is not actively maintained at this time. ## Past Contributors: - [Michael Foord](https://agileabstractions.com/) - original creator of ``configobj`` and ``validate`` and maintainer through version 4 - [Rob Dennis](https://github.com/robdennis) - released version 5 (first python 3-compatible release) in 2014, bringing the project to github - released the last maintenance release (until new maintainership is established) in 2023 - [Eli Courtwright](https://github.com/EliAndrewC) - released version 5 (first python 3-compatible release) in 2014 - [Nicola Larosa](https://pypi.org/user/tekNico/) - Contributions to the pre-version 5 codebase - [Jürgen Hermann](https://github.com/jhermann) - day-to-day maintenance of the repo configobj-5.0.9/docs/000077500000000000000000000000001467354007300144265ustar00rootroot00000000000000configobj-5.0.9/docs/Makefile000066400000000000000000000151661467354007300160770ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/configobj.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/configobj.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/configobj" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/configobj" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." configobj-5.0.9/docs/conf.py000066400000000000000000000202661467354007300157330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # configobj documentation build configuration file, created by # sphinx-quickstart on Sat Feb 8 01:26:54 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # 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 = [ 'sphinx.ext.coverage', ] # 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'configobj' copyright = u'2014, Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright' # 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 = '5.0.6' # The short X.Y version. version = '.'.join(release.split('.')[:2]) # 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 = ['_build'] # 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 # " v 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 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 = 'configobjdoc' # -- 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', 'configobj.tex', u'configobj Documentation', u'Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright', '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', 'configobj', u'configobj Documentation', [u'Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright'], 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', 'configobj', u'configobj Documentation', u'Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright', 'configobj', '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 configobj-5.0.9/docs/configobj.rst000066400000000000000000003053431467354007300171300ustar00rootroot00000000000000.. _config_doc: ================================== Reading and Writing Config Files ================================== ---------------------------------------- ConfigObj 5 Introduction and Reference ---------------------------------------- :Authors: Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright :Version: ConfigObj 5.0.7 :Date: 2023/01/17 :PyPI Entry: `ConfigObj on PyPI `_ :Homepage: `Github Page`_ :License: `BSD License`_ :Support: `Mailing List`_ .. _Mailing List: http://lists.sourceforge.net/lists/listinfo/configobj-develop .. _Github Page: https://github.com/DiffSK/configobj .. meta:: :description: ConfigObj - a Python module for easy reading and writing of config files. :keywords: python, script, module, config, configuration, data, persistence, developer, configparser .. contents:: ConfigObj Manual .. note:: The best introduction to working with ConfigObj, including the powerful configuration validation system, is the article: * `An Introduction to ConfigObj `_ Introduction ============ **ConfigObj** is a simple but powerful config file reader and writer: an *ini file round tripper*. Its main feature is that it is very easy to use, with a straightforward programmer's interface and a simple syntax for config files. It has lots of other features though : * Nested sections (subsections), to any level * List values * Multiple line values * String interpolation (substitution) * Integrated with a powerful validation system - including automatic type checking/conversion - repeated sections - and allowing default values * When writing out config files, ConfigObj preserves all comments and the order of members and sections * Many useful methods and options for working with configuration files (like the 'reload' method) * Full Unicode support For support and bug reports please use the ConfigObj `Github Page`_. Downloading =========== The current version is **5.0.7**, dated January 2023. ConfigObj 5 is stable and mature. We still expect to pick up a few bugs along the way though, particularly with respect to Python 3 compatibility [#]_. We recommend downloading and installing using pip: pip install configobj Development Version ------------------- It's possible to get the latest *development version* of ConfigObj from the Git Repository maintained on the `Github Page`_. ConfigObj in the Real World =========================== **ConfigObj** is widely used. Projects using it include: * `Bazaar `_. Bazaar is a Python distributed {acro;VCS;Version Control System}. ConfigObj is used to read ``bazaar.conf`` and ``branches.conf``. * `Chandler `_ A Python and `wxPython `_ Personal Information Manager, being developed by the `OSAFoundation `_. * `matplotlib `_ A 2D plotting library. * `IPython `_ IPython is an enhanced interactive Python shell. IPython uses ConfigObj in a module called 'TConfig' that combines it with enthought `Traits `_: `tconfig `_. * `Elisa - the Fluendo Mediacenter `_ Elisa is an open source cross-platform media center solution designed to be simple for people not particularly familiar with computers. Getting Started =============== The outstanding feature of using ConfigObj is simplicity. Most functions can be performed with single line commands. Reading a Config File --------------------- The normal way to read a config file, is to give ConfigObj the filename : .. code-block:: python from configobj import ConfigObj config = ConfigObj(filename) You can also pass the config file in as a list of lines, or a ``StringIO`` instance, so it doesn't matter where your config data comes from. You can then access members of your config file as a dictionary. Subsections will also be dictionaries. .. code-block:: python from configobj import ConfigObj config = ConfigObj(filename) # value1 = config['keyword1'] value2 = config['keyword2'] # section1 = config['section1'] value3 = section1['keyword3'] value4 = section1['keyword4'] # # you could also write value3 = config['section1']['keyword3'] value4 = config['section1']['keyword4'] Writing a Config File --------------------- Creating a new config file is just as easy as reading one. You can specify a filename when you create the ConfigObj, or do it later [#]_. If you *don't* set a filename, then the ``write`` method will return a list of lines instead of writing to file. See the write_ method for more details. Here we show creating an empty ConfigObj, setting a filename and some values, and then writing to file : .. code-block:: python from configobj import ConfigObj config = ConfigObj() config.filename = filename # config['keyword1'] = value1 config['keyword2'] = value2 # config['section1'] = {} config['section1']['keyword3'] = value3 config['section1']['keyword4'] = value4 # section2 = { 'keyword5': value5, 'keyword6': value6, 'sub-section': { 'keyword7': value7 } } config['section2'] = section2 # config['section3'] = {} config['section3']['keyword 8'] = [value8, value9, value10] config['section3']['keyword 9'] = [value11, value12, value13] # config.write() .. caution:: Keywords and section names can only be strings [#]_. Attempting to set anything else will raise a ``ValueError``. See `String Interpolation and List Values`_ for an important note on using lists in combination with `String Interpolation`_. Config Files ------------ The config files that ConfigObj will read and write are based on the 'INI' format. This means it will read and write files created for ``ConfigParser`` [#]_. Keywords and values are separated by an ``'='``, and section markers are between square brackets. Keywords, values, and section names can be surrounded by single or double quotes. Indentation is not significant, but can be preserved. Subsections are indicated by repeating the square brackets in the section marker. You nest levels by using more brackets. You can have list values by separating items with a comma, and values spanning multiple lines by using triple quotes (single or double). For full details on all these see `the config file format`_. Here's an example to illustrate:: # This is the 'initial_comment' # Which may be several lines keyword1 = value1 'keyword 2' = 'value 2' [ "section 1" ] # This comment goes with keyword 3 keyword 3 = value 3 'keyword 4' = value4, value 5, 'value 6' [[ sub-section ]] # an inline comment # sub-section is inside "section 1" 'keyword 5' = 'value 7' 'keyword 6' = '''A multiline value, that spans more than one line :-) The line breaks are included in the value.''' [[[ sub-sub-section ]]] # sub-sub-section is *in* 'sub-section' # which is in 'section 1' 'keyword 7' = 'value 8' [section 2] # an inline comment keyword8 = "value 9" keyword9 = value10 # an inline comment # The 'final_comment' # Which also may be several lines ConfigObj specifications ======================== .. code-block:: python config = ConfigObj(infile=None, options=None, configspec=None, encoding=None, interpolation=True, raise_errors=False, list_values=True, create_empty=False, file_error=False, stringify=True, indent_type=None, default_encoding=None, unrepr=False, write_empty_values=False, _inspec=False) Many of the keyword arguments are available as attributes after the config file has been parsed. .. note:: New in ConfigObj 4.7.0: Instantiating ConfigObj with an ``options`` dictionary is now deprecated. To modify code that used to do this simply unpack the dictionary in the constructor call: .. code-block:: python config = ConfigObj(filename, **options) ConfigObj takes the following arguments (with the default values shown) : * infile: ``None`` You don't need to specify an infile. If you omit it, an empty ConfigObj will be created. ``infile`` *can* be : * Nothing. In which case the ``filename`` attribute of your ConfigObj will be ``None``. You can set a filename at any time. * A filename. What happens if the file doesn't already exist is determined by the options ``file_error`` and ``create_empty``. The filename will be preserved as the ``filename`` attribute. This can be changed at any time. * A list of lines. Any trailing newlines will be removed from the lines. The ``filename`` attribute of your ConfigObj will be ``None``. * A ``StringIO`` instance or file object, or any object with a ``read`` method. The ``filename`` attribute of your ConfigObj will be ``None`` [#]_. * A dictionary. You can initialise a ConfigObj from a dictionary [#]_. The ``filename`` attribute of your ConfigObj will be ``None``. All keys must be strings. In this case, the order of values and sections is arbitrary. * 'raise_errors': ``False`` When parsing, it is possible that the config file will be badly formed. The default is to parse the whole file and raise a single error at the end. You can set ``raise_errors = True`` to have errors raised immediately. See the exceptions_ section for more details. Altering this value after initial parsing has no effect. * 'list_values': ``True`` If ``True`` (the default) then list values are possible. If ``False``, the values are not parsed for lists. If ``list_values = False`` then single line values are not quoted or unquoted when reading and writing. Changing this value affects whether single line values will be quoted or not when writing. * 'create_empty': ``False`` If this value is ``True`` and the file specified by ``infile`` doesn't exist, ConfigObj will create an empty file. This can be a useful test that the filename makes sense: an impossible filename will cause an error. Altering this value after initial parsing has no effect. * 'file_error': ``False`` If this value is ``True`` and the file specified by ``infile`` doesn't exist, ConfigObj will raise an ``IOError``. This error will be raised whenever an attempt to load the ``infile`` occurs, either in the constructor or using the reload method. * 'interpolation': ``True`` Whether string interpolation is switched on or not. It is on (``True``) by default. You can set this attribute to change whether string interpolation is done when values are fetched. See the `String Interpolation`_ section for more details. New in ConfigObj 4.7.0: Interpolation will also be done in list values. * 'configspec': ``None`` If you want to use the validation system, you supply a configspec. This is effectively a type of config file that specifies a check for each member. This check can be used to do type conversion as well as check that the value is within your required parameters. You provide a configspec in the same way as you do the initial file: a filename, or list of lines, etc. See the validation_ section for full details on how to use the system. When parsed, every section has a ``configspec`` with a dictionary of configspec checks for *that section*. * 'stringify': ``True`` If you use the validation scheme, it can do type checking *and* conversion for you. This means you may want to set members to integers, or other non-string values. If 'stringify' is set to ``True`` (default) then non-string values will be converted to strings when you write the config file. The validation_ process converts values from strings to the required type. If 'stringify' is set to ``False``, attempting to set a member to a non-string value [#]_ will raise a ``TypeError`` (no type conversion is done by validation). * 'indent_type': ``' '`` Indentation is not significant; it can however be present in the input and output config. Any combination of tabs and spaces may be used: the string will be repeated for each level of indentation. Typical values are: ``''`` (no indentation), ``' '`` (indentation with four spaces, the default), ``'\t'`` (indentation with one tab). If this option is not specified, and the ConfigObj is initialised with a dictionary, the indentation used in the output is the default one, that is, four spaces. If this option is not specified, and the ConfigObj is initialised with a list of lines or a file, the indentation used in the first indented line is selected and used in all output lines. If no input line is indented, no output line will be either. If this option *is* specified, the option value is used in the output config, overriding the type of indentation in the input config (if any). * 'encoding': ``None`` By default **ConfigObj** does not decode the file/strings you pass it into Unicode [#]_. If you want your config file as Unicode (keys and members) you need to provide an encoding to decode the file with. This encoding will also be used to encode the config file when writing. You can change the encoding attribute at any time. Any characters in your strings that can't be encoded with the specified encoding will raise a ``UnicodeEncodeError``. .. note:: ``UTF16`` encoded files will automatically be detected and decoded, even if ``encoding`` is ``None``. This is because it is a 16-bit encoding, and ConfigObj will mangle it (split characters on byte boundaries) if it parses it without decoding. * 'default_encoding': ``None`` When using the ``write`` method, **ConfigObj** uses the ``encoding`` attribute to encode the Unicode strings. If any members (or keys) have been set as byte strings instead of Unicode, these must first be decoded to Unicode before outputting in the specified encoding. ``default_encoding``, if specified, is the encoding used to decode byte strings in the **ConfigObj** before writing. If this is ``None``, then the Python default encoding (``sys.defaultencoding`` - usually ASCII) is used. For most Western European users, a value of ``latin-1`` is sensible. ``default_encoding`` is *only* used if an ``encoding`` is specified. Any characters in byte-strings that can't be decoded using the ``default_encoding`` will raise a ``UnicodeDecodeError``. * 'unrepr': ``False`` The ``unrepr`` option reads and writes files in a different mode. This allows you to store and retrieve the basic Python data-types using config files. This uses Python syntax for lists and quoting. See `unrepr mode`_ for the full details. * 'write_empty_values': ``False`` If ``write_empty_values`` is ``True``, empty strings are written as empty values. See `Empty Values`_ for more details. * '_inspec': ``False`` Used internally by ConfigObj when parsing configspec files. If you are creating a ConfigObj instance from a configspec file you must pass True for this argument as well as ``list_values=False``. Methods ------- The ConfigObj is a subclass of an object called ``Section``, which is itself a subclass of ``dict``, the builtin dictionary type. This means it also has **all** the normal dictionary methods. In addition, the following `Section Methods`_ may be useful : * 'restore_default' * 'restore_defaults' * 'walk' * 'merge' * 'dict' * 'as_bool' * 'as_float' * 'as_int' * 'as_list' Read about Sections_ for details of all the methods. .. hint:: The *merge* method of sections is a recursive update. You can use this to merge sections, or even whole ConfigObjs, into each other. You would typically use this to create a default ConfigObj and then merge in user settings. This way users only need to specify values that are different from the default. You can use configspecs and validation to achieve the same thing of course. The public methods available on ConfigObj are : * 'write' * 'validate' * 'reset' * 'reload' write ~~~~~ .. code-block:: python write(file_object=None) This method writes the current ConfigObj and takes a single, optional argument [#]_. If you pass in a file like object to the ``write`` method, the config file will be written to this. (The only method of this object that is used is its ``write`` method, so a ``StringIO`` instance, or any other file like object will work.) Otherwise, the behaviour of this method depends on the ``filename`` attribute of the ConfigObj. ``filename`` ConfigObj will write the configuration to the file specified. ``None`` ``write`` returns a list of lines. (Not ``'\n'`` terminated) First the 'initial_comment' is written, then the config file, followed by the 'final_comment'. Comment lines and inline comments are written with each key/value. validate ~~~~~~~~ .. code-block:: python validate(validator, preserve_errors=False, copy=False) .. code-block:: python # filename is the config file # filename2 is the configspec # (which could also be hardcoded into your program) config = ConfigObj(filename, configspec=filename2) # from validate import Validator val = Validator() test = config.validate(val) if test == True: print 'Succeeded.' The validate method uses the :validate: module to do the validation. This method validates the ConfigObj against the configspec. By doing type conversion as well it can abstract away the config file altogether and present the config *data* to your application (in the types it expects it to be). If the ``configspec`` attribute of the ConfigObj is ``None``, it raises a ``ValueError``. If the stringify_ attribute is set, this process will convert values to the type defined in the configspec. The validate method uses checks specified in the configspec and defined in the ``Validator`` object. It is very easy to extend. The configspec looks like the config file, but instead of the value, you specify the check (and any default value). See the validation_ section for details. .. hint:: The system of configspecs can seem confusing at first, but is actually quite simple and powerful. The best guide to them is this article on ConfigObj: * `An Introduction to ConfigObj`_ The ``copy`` parameter fills in missing values from the configspec (default values), *without* marking the values as defaults. It also causes comments to be copied from the configspec into the config file. This allows you to use a configspec to create default config files. (Normally default values aren't written out by the ``write`` method.) As of ConfigObj 4.3.0 you can also pass in a ConfigObj instance as your configspec. This is especially useful if you need to specify the encoding of your configspec file. When you read your configspec file, you *must* specify ``list_values=False``. If you need to support hashes inside the configspec values then you must also pass in ``_inspec=True``. This is because configspec files actually use a different syntax to config files and inline comment support must be switched off to correctly read configspec files with hashes in the values. .. code-block:: python from configobj import ConfigObj configspec = ConfigObj(configspecfilename, encoding='UTF8', list_values=False, _inspec=True) config = ConfigObj(filename, configspec=configspec) Return Value ############ By default, the validate method either returns ``True`` (everything passed) or a dictionary of ``True`` / ``False`` representing pass/fail. The dictionary follows the structure of the ConfigObj. If a whole section passes then it is replaced with the value ``True``. If a whole section fails, then it is replaced with the value ``False``. If a value is missing, and there is no default in the check, then the check automatically fails. The ``validate`` method takes an optional keyword argument ``preserve_errors``. If you set this to ``True``, instead of getting ``False`` for failed checks you get the actual error object from the **validate** module. This usually contains useful information about why the check failed. See the `flatten_errors`_ function for how to turn your results dictionary into a useful list of error messages. Even if ``preserve_errors`` is ``True``, missing keys or sections will still be represented by a ``False`` in the results dictionary. Mentioning Default Values ######################### In the check in your configspec, you can specify a default to be used - by using the ``default`` keyword. E.g. :: key1 = integer(0, 30, default=15) key2 = integer(default=15) key3 = boolean(default=True) key4 = option('Hello', 'Goodbye', 'Not Today', default='Not Today') If the configspec check supplies a default and the value is missing in the config, then the default will be set in your ConfigObj. (It is still passed to the ``Validator`` so that type conversion can be done: this means the default value must still pass the check.) ConfigObj keeps a record of which values come from defaults, using the ``defaults`` attribute of sections_. Any key in this list isn't written out by the ``write`` method. If a key is set from outside (even to the same value) then it is removed from the ``defaults`` list. .. note: Even if all the keys in a section are in the defaults list, the section marker is still written out. There is additionally a special case default value of ``None``. If you set the default value to ``None`` and the value is missing, the value will always be set to ``None``. As the other checks don't return ``None`` (unless you implement your own that do), you can tell that this value came from a default value (and was missing from the config file). It allows an easy way of implementing optional values. Simply check (and ignore) members that are set to ``None``. .. note:: If stringify_ is ``False`` then ``default=None`` returns ``''`` instead of ``None``. This is because setting a value to a non-string raises an error if stringify is unset. The default value can be a list. See `List Values`_ for the way to do this. Writing invalid default values is a *guaranteed* way of confusing your users. Default values **must** pass the check. Mentioning Repeated Sections and Values ####################################### In the configspec it is possible to cause *every* sub-section in a section to be validated using the same configspec. You do this with a section in the configspec called ``__many__``. Every sub-section in that section has the ``__many__`` configspec applied to it (without you having to explicitly name them in advance). Your ``__many__`` section can have nested subsections, which can also include ``__many__`` type sections. You can also specify that all values should be validated using the same configspec, by having a member with the name ``__many__``. If you want to use repeated values along with repeated sections then you can call one of them ``___many___`` (triple underscores). Sections with repeated sections or values can also have specifically named sub-sections or values. The ``__many__`` configspec will only be used to validate entries that don't have an explicit configspec. See `Repeated Sections`_ for examples. Mentioning SimpleVal #################### If you just want to check if all members are present, then you can use the ``SimpleVal`` object that comes with ConfigObj. It only fails members if they are missing. Write a configspec that has all the members you want to check for, but set every section to ``''``. .. code-block:: python val = SimpleVal() test = config.validate(val) if test is True: print 'Succeeded.' Mentioning copy Mode #################### As discussed in `Mentioning Default Values`_, you can use a configspec to supply default values. These are marked in the ConfigObj instance as defaults, and *not* written out by the ``write`` mode. This means that your users only need to supply values that are different from the defaults. This can be inconvenient if you *do* want to write out the default values, for example to write out a default config file. If you set ``copy=True`` when you call validate, then no values are marked as defaults. In addition, all comments from the configspec are copied into your ConfigObj instance. You can then call ``write`` to create your config file. There is a limitation with this. In order to allow `String Interpolation`_ to work within configspecs, ``DEFAULT`` sections are not processed by validation; even in copy mode. reload ~~~~~~ If a ConfigObj instance was loaded from the filesystem, then this method will reload it. It will also reuse any configspec you supplied at instantiation (including reloading it from the filesystem if you passed it in as a filename). If the ConfigObj does not have a filename attribute pointing to a file, then a ``ReloadError`` will be raised. reset ~~~~~ This method takes no arguments and doesn't return anything. It restores a ConfigObj instance to a freshly created state. Attributes ---------- A ConfigObj has the following attributes : * indent_type * interpolation * stringify * BOM * initial_comment * final_comment * list_values * encoding * default_encoding * unrepr * write_empty_values * newlines .. note:: This doesn't include *comments*, *inline_comments*, *defaults*, or *configspec*. These are actually attributes of Sections_. It also has the following attributes as a result of parsing. They correspond to options when the ConfigObj was created, but changing them has no effect. * raise_errors * create_empty * file_error interpolation ~~~~~~~~~~~~~ ConfigObj can perform string interpolation in a *similar* way to ``ConfigParser``. See the `String Interpolation`_ section for full details. If ``interpolation`` is set to ``False``, then interpolation is *not* done when you fetch values. stringify ~~~~~~~~~ If this attribute is set (``True``) then the validate_ method changes the values in the ConfigObj. These are turned back into strings when write_ is called. If stringify is unset (``False``) then attempting to set a value to a non string (or a list of strings) will raise a ``TypeError``. BOM ~~~ If the initial config file *started* with the UTF8 Unicode signature (known slightly incorrectly as the BOM - Byte Order Mark), or the UTF16 BOM, then this attribute is set to ``True``. Otherwise it is ``False``. If it is set to ``True`` when ``write`` is called then, if ``encoding`` is set to ``None`` *or* to ``utf_8`` (and variants) a UTF BOM will be written. For UTF16 encodings, a BOM is *always* written. initial_comment ~~~~~~~~~~~~~~~ This is a list of lines. If the ConfigObj is created from an existing file, it will contain any lines of comments before the start of the members. If you create a new ConfigObj, this will be an empty list. The write method puts these lines before it starts writing out the members. final_comment ~~~~~~~~~~~~~ This is a list of lines. If the ConfigObj is created from an existing file, it will contain any lines of comments after the last member. If you create a new ConfigObj, this will be an empty list. The ``write`` method puts these lines after it finishes writing out the members. list_values ~~~~~~~~~~~ This attribute is ``True`` or ``False``. If set to ``False`` then values are not parsed for list values. In addition single line values are not unquoted. This allows you to do your own parsing of values. It exists primarily to support the reading of the configspec_ - but has other use cases. For example you could use the ``LineParser`` from the `listquote module `_ to read values for nested lists. Single line values aren't quoted when writing - but multiline values are handled as normal. .. caution:: Because values aren't quoted, leading or trailing whitespace can be lost. This behaviour was changed in version 4.0.1. Prior to this, single line values might have been quoted; even with ``list_values=False``. This means that files written by earlier versions of ConfigObj *could* now be incompatible and need the quotes removing by hand. encoding ~~~~~~~~ This is the encoding used to encode the output, when you call ``write``. It must be a valid encoding `recognised by Python `_. If this value is ``None`` then no encoding is done when ``write`` is called. default_encoding ~~~~~~~~~~~~~~~~ If encoding is set, any byte-strings in your ConfigObj instance (keys or members) will first be decoded to Unicode using the encoding specified by the ``default_encoding`` attribute. This ensures that the output is in the encoding specified. If this value is ``None`` then ``sys.defaultencoding`` is used instead. unrepr ~~~~~~ Another boolean value. If this is set, then ``repr(value)`` is used to write values. This writes values in a slightly different way to the normal ConfigObj file syntax. This preserves basic Python data-types when read back in. See `unrepr mode`_ for more details. write_empty_values ~~~~~~~~~~~~~~~~~~ Also boolean. If set, values that are an empty string (``''``) are written as empty values. See `Empty Values`_ for more details. newlines ~~~~~~~~ When a config file is read, ConfigObj records the type of newline separators in the file and uses this separator when writing. It defaults to ``None``, and ConfigObj uses the system default (``os.linesep``) if write is called without newlines having been set. The Config File Format ====================== You saw an example config file in the `Config Files`_ section. Here is a fuller specification of the config files used and created by ConfigObj. The basic pattern for keywords is:: # comment line # comment line keyword = value # inline comment Both keyword and value can optionally be surrounded in quotes. The equals sign is the only valid divider. Values can have comments on the lines above them, and an inline comment after them. This, of course, is optional. See the comments_ section for details. If a keyword or value starts or ends with whitespace, or contains a quote mark or comma, then it should be surrounded by quotes. Quotes are not necessary if whitespace is surrounded by non-whitespace. Values can also be lists. Lists are comma separated. You indicate a single member list by a trailing comma. An empty list is shown by a single comma:: keyword1 = value1, value2, value3 keyword2 = value1, # a single member list keyword3 = , # an empty list Values that contain line breaks (multi-line values) can be surrounded by triple quotes. These can also be used if a value contains both types of quotes. List members cannot be surrounded by triple quotes:: keyword1 = ''' A multi line value on several lines''' # with a comment keyword2 = '''I won't be "afraid".''' # keyword3 = """ A multi line value on several lines""" # with a comment keyword4 = """I won't be "afraid".""" .. warning:: There is no way of safely quoting values that contain both types of triple quotes. A line that starts with a '#', possibly preceded by whitespace, is a comment. New sections are indicated by a section marker line. That is the section name in square brackets. Whitespace around the section name is ignored. The name can be quoted with single or double quotes. The marker can have comments before it and an inline comment after it:: # The First Section [ section name 1 ] # first section keyword1 = value1 # The Second Section [ "section name 2" ] # second section keyword2 = value2 Any subsections (sections that are *inside* the current section) are designated by repeating the square brackets before and after the section name. The number of square brackets represents the nesting level of the sub-section. Square brackets may be separated by whitespace; such whitespace, however, will not be present in the output config written by the ``write`` method. Indentation is not significant, but can be preserved. See the description of the ``indent_type`` option, in the `ConfigObj specifications`_ chapter, for the details. A *NestingError* will be raised if the number of the opening and the closing brackets in a section marker is not the same, or if a sub-section's nesting level is greater than the nesting level of it parent plus one. In the outer section, single values can only appear before any sub-section. Otherwise they will belong to the sub-section immediately before them:: # initial comment keyword1 = value1 keyword2 = value2 [section 1] keyword1 = value1 keyword2 = value2 [[sub-section]] # this is in section 1 keyword1 = value1 keyword2 = value2 [[[nested section]]] # this is in sub section keyword1 = value1 keyword2 = value2 [[sub-section2]] # this is in section 1 again keyword1 = value1 keyword2 = value2 [[sub-section3]] # this is also in section 1, indentation is misleading here keyword1 = value1 keyword2 = value2 # final comment When parsed, the above config file produces the following data structure: .. code-block:: python ConfigObj({ 'keyword1': 'value1', 'keyword2': 'value2', 'section 1': { 'keyword1': 'value1', 'keyword2': 'value2', 'sub-section': { 'keyword1': 'value1', 'keyword2': 'value2', 'nested section': { 'keyword1': 'value1', 'keyword2': 'value2', }, }, 'sub-section2': { 'keyword1': 'value1', 'keyword2': 'value2', }, 'sub-section3': { 'keyword1': 'value1', 'keyword2': 'value2', }, }, }) Sections are ordered: note how the structure of the resulting ConfigObj is in the same order as the original file. .. note:: In ConfigObj 4.3.0 *empty values* became valid syntax. They are read as the empty string. There is also an option/attribute (``write_empty_values``) to allow the writing of these. This is mainly to support 'legacy' config files, written from other applications. This is documented under `Empty Values`_. `unrepr mode`_ introduces *another* syntax variation, used for storing basic Python datatypes in config files. Sections ======== Every section in a ConfigObj has certain properties. The ConfigObj itself also has these properties, because it too is a section (sometimes called the *root section*). ``Section`` is a subclass of the standard new-class dictionary, therefore it has **all** the methods of a normal dictionary. This means you can ``update`` and ``clear`` sections. .. note:: You create a new section by assigning a member to be a dictionary. The new ``Section`` is created *from* the dictionary, but isn't the same thing as the dictionary. (So references to the dictionary you use to create the section *aren't* references to the new section). Note the following. .. code-block:: python config = ConfigObj() vals = {'key1': 'value 1', 'key2': 'value 2' } config['vals'] = vals config['vals'] == vals True config['vals'] is vals False If you now change ``vals``, the changes won't be reflected in ``config['vals']``. A section is ordered, following its ``scalars`` and ``sections`` attributes documented below. This means that the following dictionary attributes return their results in order. * '__iter__' More commonly known as ``for member in section:``. * '__repr__' and '__str__' Any time you print or display the ConfigObj. * 'items' * 'iteritems' * 'iterkeys' * 'itervalues' * 'keys' * 'popitem' * 'values' Section Attributes ------------------ * main A reference to the main ConfigObj. * parent A reference to the 'parent' section, the section that this section is a member of. On the ConfigObj this attribute is a reference to itself. You can use this to walk up the sections, stopping when ``section.parent is section``. * depth The nesting level of the current section. If you create a new ConfigObj and add sections, 1 will be added to the depth level between sections. * defaults This attribute is a list of scalars that came from default values. Values that came from defaults aren't written out by the ``write`` method. Setting any of these values in the section removes them from the defaults list. * default_values This attribute is a dictionary mapping keys to the default values for the keys. By default it is an empty dictionary and is populated when you validate the ConfigObj. * scalars, sections These attributes are normal lists, representing the order that members, single values and subsections appear in the section. The order will either be the order of the original config file, *or* the order that you added members. The order of members in this lists is the order that ``write`` creates in the config file. The ``scalars`` list is output before the ``sections`` list. Adding or removing members also alters these lists. You can manipulate the lists directly to alter the order of members. .. warning:: If you alter the ``scalars``, ``sections``, or ``defaults`` attributes so that they no longer reflect the contents of the section, you will break your ConfigObj. See also the ``rename`` method. * comments This is a dictionary of comments associated with each member. Each entry is a list of lines. These lines are written out before the member. * inline_comments This is *another* dictionary of comments associated with each member. Each entry is a string that is put inline with the member. * configspec The configspec attribute is a dictionary mapping scalars to *checks*. A check defines the expected type and possibly the allowed values for a member. The configspec has the same format as a config file, but instead of values it has a specification for the value (which may include a default value). The validate_ method uses it to check the config file makes sense. If a configspec is passed in when the ConfigObj is created, then it is parsed and broken up to become the ``configspec`` attribute of each section. If you didn't pass in a configspec, this attribute will be ``None`` on the root section (the main ConfigObj). You can set the configspec attribute directly on a section. See the validation_ section for full details of how to write configspecs. * extra_values By default an empty list. After validation_ this is populated with any members of the section that don't appear in the configspec (i.e. they are additional values). Rather than accessing this directly it may be more convenient to get all the extra values in a config file using the get_extra_values_ function. New in ConfigObj 4.7.0. Section Methods --------------- * **dict** This method takes no arguments. It returns a deep copy of the section as a dictionary. All subsections will also be dictionaries, and list values will be copies, rather than references to the original [#]_. * **rename** ``rename(oldkey, newkey)`` This method renames a key, without affecting its position in the sequence. * **merge** ``merge(indict)`` This method is a *recursive update* method. It allows you to merge two config files together. You would typically use this to create a default ConfigObj and then merge in user settings. This way users only need to specify values that are different from the default. For example : .. code-block:: python # def_cfg contains your default config settings # user_cfg contains the user settings cfg = ConfigObj(def_cfg) usr = ConfigObj(user_cfg) # cfg.merge(usr) """ cfg now contains a combination of the default settings and the user settings. The user settings will have overwritten any of the default ones. """ * **walk** This method can be used to transform values and names. See `walking a section`_ for examples and explanation. * **as_bool** ``as_bool(key)`` Returns ``True`` if the key contains a string that represents ``True``, or is the ``True`` object. Returns ``False`` if the key contains a string that represents ``False``, or is the ``False`` object. Raises a ``ValueError`` if the key contains anything else. Strings that represent ``True`` are (not case sensitive):: true, yes, on, 1 Strings that represent ``False`` are:: false, no, off, 0 * **as_int** ``as_int(key)`` This returns the value contained in the specified key as an integer. It raises a ``ValueError`` if the conversion can't be done. * **as_float** ``as_float(key)`` This returns the value contained in the specified key as a float. It raises a ``ValueError`` if the conversion can't be done. * **as_list** ``as_list(key)`` This returns the value contained in the specified key as a list. If it isn't a list it will be wrapped as a list so that you can guarantee the returned value will be a list. * **restore_default** ``restore_default(key)`` Restore (and return) the default value for the specified key. This method will only work for a ConfigObj that was created with a configspec and has been validated. If there is no default value for this key, ``KeyError`` is raised. * **restore_defaults** ``restore_defaults()`` Recursively restore default values to all members that have them. This method will only work for a ConfigObj that was created with a configspec and has been validated. It doesn't delete or modify entries without default values. Walking a Section ----------------- .. note:: The walk method allows you to call a function on every member/name. .. code-block:: python walk(function, raise_errors=True, call_on_sections=False, **keywargs) ``walk`` is a method of the ``Section`` object. This means it is also a method of ConfigObj. It walks through every member and calls a function on the keyword and value. It walks recursively through subsections. It returns a dictionary of all the computed values. If the function raises an exception, the default is to propagate the error, and stop. If ``raise_errors=False`` then it sets the return value for that keyword to ``False`` instead, and continues. This is similar to the way validation_ works. Your function receives the arguments ``(section, key)``. The current value is then ``section[key]`` [#]_. Any unrecognised keyword arguments you pass to walk, are passed on to the function. Normally ``walk`` just recurses into subsections. If you are transforming (or checking) names as well as values, then you want to be able to change the names of sections. In this case set ``call_on_sections`` to ``True``. Now, on encountering a sub-section, *first* the function is called for the *whole* sub-section, and *then* it recurses into it's members. This means your function must be able to handle receiving dictionaries as well as strings and lists. If you are using the return value from ``walk`` *and* ``call_on_sections``, note that walk discards the return value when it calls your function. .. caution:: You can use ``walk`` to transform the names of members of a section but you mustn't add or delete members. Examples -------- You can use this for transforming all values in your ConfigObj. For example you might like the nested lists from ConfigObj 3. This was provided by the listquote_ module. You could switch off the parsing for list values (``list_values=False``) and use listquote to parse every value. Another thing you might want to do is use the Python escape codes in your values. You might be *used* to using ``\n`` for line feed and ``\t`` for tab. Obviously we'd need to decode strings that come from the config file (using the escape codes). Before writing out we'll need to put the escape codes back in encode. As an example we'll write a function to use with walk, that encodes or decodes values using the ``string-escape`` codec. The function has to take each value and set the new value. As a bonus we'll create one function that will do decode *or* encode depending on a keyword argument. We don't want to work with section names, we're only transforming values, so we can leave ``call_on_sections`` as ``False``. This means the two datatypes we have to handle are strings and lists, we can ignore everything else. (We'll treat tuples as lists as well). We're not using the return values, so it doesn't need to return anything, just change the values if appropriate. .. code-block:: python def string_escape(section, key, encode=False): """ A function to encode or decode using the 'string-escape' codec. To be passed to the walk method of a ConfigObj. By default it decodes. To encode, pass in the keyword argument ``encode=True``. """ val = section[key] # is it a type we can work with # NOTE: for platforms where Python > 2.2 # you can use basestring instead of (str, unicode) if not isinstance(val, (str, unicode, list, tuple)): # no ! return elif isinstance(val, (str, unicode)): # it's a string ! if not encode: section[key] = val.decode('string-escape') else: section[key] = val.encode('string-escape') else: # it must be a list or tuple! # we'll be lazy and create a new list newval = [] # we'll check every member of the list for entry in val: if isinstance(entry, (str, unicode)): if not encode: newval.append(entry.decode('string-escape')) else: newval.append(entry.encode('string-escape')) else: newval.append(entry) # done ! section[key] = newval # assume we have a ConfigObj called ``config`` # # To decode config.walk(string_escape) # # To encode. # Because ``walk`` doesn't recognise the ``encode`` argument # it passes it to our function. config.walk(string_escape, encode=True) Here's a simple example of using ``walk`` to transform names and values. One usecase of this would be to create a *standard* config file with placeholders for section and keynames. You can then use walk to create new config files and change values and member names : .. code-block:: python # We use 'XXXX' as a placeholder config = ''' XXXXkey1 = XXXXvalue1 XXXXkey2 = XXXXvalue2 XXXXkey3 = XXXXvalue3 [XXXXsection1] XXXXkey1 = XXXXvalue1 XXXXkey2 = XXXXvalue2 XXXXkey3 = XXXXvalue3 [XXXXsection2] XXXXkey1 = XXXXvalue1 XXXXkey2 = XXXXvalue2 XXXXkey3 = XXXXvalue3 [[XXXXsection1]] XXXXkey1 = XXXXvalue1 XXXXkey2 = XXXXvalue2 XXXXkey3 = XXXXvalue3 '''.splitlines() cfg = ConfigObj(config) # def transform(section, key): val = section[key] newkey = key.replace('XXXX', 'CLIENT1') section.rename(key, newkey) if isinstance(val, (tuple, list, dict)): pass else: val = val.replace('XXXX', 'CLIENT1') section[newkey] = val # cfg.walk(transform, call_on_sections=True) print cfg ConfigObj({'CLIENT1key1': 'CLIENT1value1', 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3', 'CLIENT1section1': {'CLIENT1key1': 'CLIENT1value1', 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3'}, 'CLIENT1section2': {'CLIENT1key1': 'CLIENT1value1', 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3', 'CLIENT1section1': {'CLIENT1key1': 'CLIENT1value1', 'CLIENT1key2': 'CLIENT1value2', 'CLIENT1key3': 'CLIENT1value3'}}}) Exceptions ========== There are several places where ConfigObj may raise exceptions (other than because of bugs). 1) If a configspec filename you pass in doesn't exist, or a config file filename doesn't exist *and* ``file_error=True``, an ``IOError`` will be raised. 2) If you try to set a non-string key, or a non string value when ``stringify=False``, a ``TypeError`` will be raised. 3) A badly built config file will cause parsing errors. 4) A parsing error can also occur when reading a configspec. 5) In string interpolation you can specify a value that doesn't exist, or create circular references (recursion). Number 5 (which is actually two different types of exceptions) is documented in `String Interpolation`_. *This* section is about errors raised during parsing. The base error class is ``ConfigObjError``. This is a subclass of ``SyntaxError``, so you can trap for ``SyntaxError`` without needing to directly import any of the ConfigObj exceptions. The following other exceptions are defined (all deriving from ``ConfigObjError``) : * ``NestingError`` This error indicates either a mismatch in the brackets in a section marker, or an excessive level of nesting. * ``ParseError`` This error indicates that a line is badly written. It is neither a valid ``key = value`` line, nor a valid section marker line, nor a comment line. * ``DuplicateError`` The keyword or section specified already exists. * ``ConfigspecError`` An error occurred whilst parsing a configspec. * ``UnreprError`` An error occurred when parsing a value in `unrepr mode`_. * ``ReloadError`` ``reload`` was called on a ConfigObj instance that doesn't have a valid filename attribute. When parsing a configspec, ConfigObj will stop on the first error it encounters. It will raise a ``ConfigspecError``. This will have an ``error`` attribute, which is the actual error that was raised. Behaviour when parsing a config file depends on the option ``raise_errors``. If ConfigObj encounters an error while parsing a config file: If ``raise_errors=True`` then ConfigObj will raise the appropriate error and parsing will stop. If ``raise_errors=False`` (the default) then parsing will continue to the end and *all* errors will be collected. If ``raise_errors`` is False and multiple errors are found a ``ConfigObjError`` is raised. The error raised has a ``config`` attribute, which is the parts of the ConfigObj that parsed successfully. It also has an attribute ``errors``, which is a list of *all* the errors raised. Each entry in the list is an instance of the appropriate error type. Each one has the following attributes (useful for delivering a sensible error message to your user) : * ``line``: the original line that caused the error. * ``line_number``: its number in the config file. * ``message``: the error message that accompanied the error. If only one error is found, then that error is re-raised. The error still has the ``config`` and ``errors`` attributes. This means that your error handling code can be the same whether one error is raised in parsing , or several. It also means that in the most common case (a single error) a useful error message will be raised. .. note:: One wrongly written line could break the basic structure of your config file. This could cause every line after it to flag an error, so having a list of all the lines that caused errors may not be as useful as it sounds. Validation ========== .. hint:: The system of configspecs can seem confusing at first, but is actually quite simple and powerful. The best reference is my article on ConfigObj: * `An Introduction to ConfigObj`_ Validation is done through a combination of the configspec_ and a ``Validator`` object. For this you need *validate.py* [#]_. See downloading_ if you don't have a copy. Validation can perform two different operations : 1) Check that a value meets a specification. For example, check that a value is an integer between one and six, or is a choice from a specific set of options. 2) It can convert the value into the type required. For example, if one of your values is a port number, validation will turn it into an integer for you. So validation can act as a transparent layer between the datatypes of your application configuration (boolean, integers, floats, etc) and the text format of your config file. configspec ---------- The ``validate`` method checks members against an entry in the configspec. Your configspec therefore resembles your config file, with a check for every member. In order to perform validation you need a ``Validator`` object. This has several useful built-in check functions. You can also create your own custom functions and register them with your Validator object. Each check is the name of one of these functions, including any parameters and keyword arguments. The configspecs look like function calls, and they map to function calls. The basic datatypes that an un-extended Validator can test for are : * boolean values (True and False) * integers (including minimum and maximum values) * floats (including min and max) * strings (including min and max length) * IP addresses (v4 only) It can also handle lists of these types and restrict a value to being one from a set of options. An example configspec is going to look something like:: port = integer(0, 100) user = string(max=25) mode = option('quiet', 'loud', 'silent') You can specify default values, and also have the same configspec applied to several sections. This is called `repeated sections`_. For full details on writing configspecs, please refer to the `validate.py documentation`_. .. important:: Your configspec is read by ConfigObj in the same way as a config file. That means you can do interpolation *within* your configspec. In order to allow this, checks in the 'DEFAULT' section (of the root level of your configspec) are *not* used. If you want to use a configspec *without* interpolation being done in it you can create your configspec manually and switch off interpolation: .. code-block:: python from configobj import ConfigObj configspec = ConfigObj(spec_filename, interpolation=False, list_values=False, _inspec=True) conf = ConfigObj(config_filename, configspec=configspec) If you need to specify the encoding of your configspec, then you can pass in a ConfigObj instance as your configspec. When you read your configspec file, you *must* specify ``list_values=False``. If you need to support hashes in configspec values then you must also pass in ``_inspec=True``. .. code-block:: python from configobj import ConfigObj configspec = ConfigObj(configspecfilename, encoding='UTF8', list_values=False, _inspec=True) config = ConfigObj(filename, configspec=configspec) .. _validate.py documentation: :ref:`validate_doc` Type Conversion --------------- By default, validation does type conversion. This means that if you specify ``integer`` as the check, then calling validate_ will actually change the value to an integer (so long as the check succeeds). It also means that when you call the write_ method, the value will be converted back into a string using the ``str`` function. To switch this off, and leave values as strings after validation, you need to set the stringify_ attribute to ``False``. If this is the case, attempting to set a value to a non-string will raise an error. Default Values -------------- You can set a default value in your check. If the value is missing from the config file then this value will be used instead. This means that your user only has to supply values that differ from the defaults. If you *don't* supply a default then for a value to be missing is an error, and this will show in the `return value`_ from validate. Additionally you can set the default to be ``None``. This means the value will be set to ``None`` (the object) *whichever check is used*. (It will be set to ``''`` rather than ``None`` if stringify_ is ``False``). You can use this to easily implement optional values in your config files. :: port = integer(0, 100, default=80) user = string(max=25, default=0) mode = option('quiet', 'loud', 'silent', default='loud') nick = string(default=None) .. note:: Because the default goes through type conversion, it also has to pass the check. Note that ``default=None`` is case sensitive. List Values ~~~~~~~~~~~ It's possible that you will want to specify a list as a default value. To avoid confusing syntax with commas and quotes you use a list constructor to specify that keyword arguments are lists. This includes the ``default`` value. This makes checks look something like:: checkname(default=list('val1', 'val2', 'val3')) This works with all keyword arguments, but is most useful for default values. Repeated Sections ----------------- Repeated sections are a way of specifying a configspec for a section that should be applied to all unspecified subsections in the same section. The easiest way of explaining this is to give an example. Suppose you have a config file that describes a dog. That dog has various attributes, but it can also have many fleas. You don't know in advance how many fleas there will be, or what they will be called, but you want each flea validated against the same configspec. We can define a section called *fleas*. We want every flea in that section (every sub-section) to have the same configspec applied to it. We do this by defining a single section called ``__many__``. :: [dog] name = string(default=Rover) age = float(0, 99, default=0) [[fleas]] [[[__many__]]] bloodsucker = boolean(default=True) children = integer(default=10000) size = option(small, tiny, micro, default=tiny) Every flea on our dog will now be validated using the ``__many__`` configspec. ``__many__`` sections can have sub-sections, including their own ``__many__`` sub-sections. Defaults work in the normal way in repeated sections. Repeated Values --------------- As well as using ``__many__`` to validate unspecified sections you can use it to validate values. For example, to specify that all values in a section should be integers:: [section] __many__ = integer If you want to use repeated values alongside repeated sections you can call one ``__many__`` and the other ``___many___`` (with three underscores). Copy Mode --------- Because you can specify default values in your configspec, you can use ConfigObj to write out default config files for your application. However, normally values supplied from a default in a configspec are *not* written out by the ``write`` method. To do this, you need to specify ``copy=True`` when you call validate. As well as not marking values as default, all the comments in the configspec file will be copied into your ConfigObj instance. .. code-block:: python from configobj import ConfigObj from validate import Validator vdt = Validator() config = ConfigObj(configspec='default.ini') config.filename = 'new_default.ini' config.validate(vdt, copy=True) config.write() If you need to support hashes in the configspec values then you must create it with ``_inspec=True``. This has the side effect of switching off the parsing of inline comments, meaning that they won't be copied into the new config file. (ConfigObj syntax is slightly different from configspec syntax and the parser can't support both inline comments and hashes in configspec values.) Validation and Interpolation ---------------------------- String interpolation and validation don't play well together. When validation changes type it sets the value. If the value uses interpolation, then the interpolation reference would normally be overwritten. Calling ``write`` would then use the absolute value and the interpolation reference would be lost. As a compromise - if the value is unchanged by validation then it is not reset. This means strings that pass through validation unmodified will not be overwritten. If validation changes type - the value has to be overwritten, and any interpolation references are lost. Extra Values ------------ After validation the ``extra_values`` member of every section that is listed in the configspec will be populated with the names of members that are in the config file but not in the configspec. If you are reporting configuration errors to your user this information can be useful, for example some missing entries may be due to misspelt entries that appear as extra values. See the get_extra_values_ function New in ConfigObj 4.7.0. SimpleVal --------- You may not need a full validation process, but still want to check if all the expected values are present. Provided as part of the ConfigObj module is the ``SimpleVal`` object. This has a dummy ``test`` method that always passes. The only reason a test will fail is if the value is missing. The return value from ``validate`` will either be ``True``, meaning all present, or a dictionary with ``False`` for all missing values/sections. To use it, you still need to pass in a valid configspec when you create the ConfigObj, but just set all the values to ``''``. Then create an instance of ``SimpleVal`` and pass it to the ``validate`` method. As a trivial example if you had the following config file:: # config file for an application port = 80 protocol = http domain = voidspace top_level_domain = org.uk You would write the following configspec:: port = '' protocol = '' domain = '' top_level_domain = '' .. code-block:: python config = Configobj(filename, configspec=configspec) val = SimpleVal() test = config.validate(val) if test == True: print 'All values present.' elif test == False: print 'No values present!' else: for entry in test: if test[entry] == False: print '"%s" missing.' % entry Empty values ============ Many config files from other applications allow empty values. As of version 4.3.0, ConfigObj will read these as an empty string. A new option/attribute has been added (``write_empty_values``) to allow ConfigObj to write empty strings as empty values. .. code-block:: python from configobj import ConfigObj cfg = ''' key = key2 = # a comment '''.splitlines() config = ConfigObj(cfg) print config ConfigObj({'key': '', 'key2': ''}) config.write_empty_values = True for line in config.write(): print line key = key2 = # a comment unrepr mode =========== The ``unrepr`` option allows you to store and retrieve the basic Python data-types using config files. It has to use a slightly different syntax to normal ConfigObj files. Unsurprisingly it uses Python syntax. This means that lists are different (they are surrounded by square brackets), and strings *must* be quoted. The types that ``unrepr`` can work with are : | strings, lists tuples | None, True, False | dictionaries, integers, floats | longs and complex numbers You can't store classes, types or instances. ``unrepr`` uses ``repr(object)`` to write out values, so it currently *doesn't* check that you are writing valid objects. If you attempt to read an unsupported value, ConfigObj will raise a ``configobj.UnknownType`` exception. Values that are triple quoted cased. The triple quotes are removed *before* converting. This means that you can use triple quotes to write dictionaries over several lines in your config files. They won't be written like this though. If you are writing config files by hand, for use with ``unrepr``, you should be aware of the following differences from normal ConfigObj syntax : | List : ``['A List', 'With', 'Strings']`` | Strings : ``"Must be quoted."`` | Backslash : ``"The backslash must be escaped \\"`` These all follow normal Python syntax. In unrepr mode *inline comments* are not saved. This is because lines are parsed using the `compiler package `_ which discards comments. String Interpolation ==================== .. note:: String interpolation can slow down (slightly) the fetching of values from your config object. If you aren't using interpolation and it is performance critical then create your instance with ``interpolation=False``. ConfigObj allows string interpolation *similar* to the way ``ConfigParser`` or ``string.Template`` work. The value of the ``interpolation`` attribute determines which style of interpolation you want to use. Valid values are "ConfigParser" or "Template" (case-insensitive, so "configparser" and "template" will also work). For backwards compatibility reasons, the value ``True`` is also a valid value for the ``interpolation`` attribute, and will select ``ConfigParser``-style interpolation. At some undetermined point in the future, that default *may* change to ``Template``-style interpolation. For ``ConfigParser``-style interpolation, you specify a value to be substituted by including ``%(name)s`` in the value. For ``Template``-style interpolation, you specify a value to be substituted by including ``${cl}name{cr}`` in the value. Alternately, if 'name' is a valid Python identifier (i.e., is composed of nothing but alphanumeric characters, plus the underscore character), then the braces are optional and the value can be written as ``$name``. Note that ``ConfigParser``-style interpolation and ``Template``-style interpolation are mutually exclusive; you cannot have a configuration file that's a mix of one or the other. Pick one and stick to it. ``Template``-style interpolation is simpler to read and write by hand, and is recommended if you don't have a particular reason to use ``ConfigParser``-style. Interpolation checks first the current section to see if ``name`` is the key to a value. ('name' is case sensitive). If it doesn't find it, next it checks the 'DEFAULT' sub-section of the current section. If it still doesn't find it, it moves on to check the parent section and the parent section's 'DEFAULT' subsection, and so on all the way up to the main section. If the value specified isn't found in any of these locations, then a ``MissingInterpolationOption`` error is raised (a subclass of ``ConfigObjError``). If it is found then the returned value is also checked for substitutions. This allows you to make up compound values (for example directory paths) that use more than one default value. It also means it's possible to create circular references. If there are any circular references which would cause an infinite interpolation loop, an ``InterpolationLoopError`` is raised. Both of these errors are subclasses of ``InterpolationError``, which is a subclass of ``ConfigObjError``. String interpolation and validation don't play well together. This is because validation overwrites values - and so may erase the interpolation references. See `Validation and Interpolation`_. (This can only happen if validation has to *change* the value). New in ConfigObj 4.7.0: String interpolation is now done in members of list values. String Interpolation and List Values ------------------------------------ Since version 4.7 string interpolation is done on string members of list values. If interpolation changes any members of the list then what you get back is a *copy* of the list rather than the original list. This makes fetching list values slightly slower when interpolation is on, it also means that if you mutate the list changes won't be reflected in the original list: .. code-block:: python >>> c = ConfigObj() >>> c['foo'] = 'boo' >>> c['bar'] = ['%(foo)s'] >>> c['bar'] ['boo'] >>> c['bar'].append('fish') >>> c['bar'] ['boo'] Instead of mutating the list you must create a new list and reassign it. Comments ======== Any line that starts with a '#', possibly preceded by whitespace, is a comment. If a config file starts with comments then these are preserved as the initial_comment_. If a config file ends with comments then these are preserved as the final_comment_. Every key or section marker may have lines of comments immediately above it. These are saved as the ``comments`` attribute of the section. Each member is a list of lines. You can also have a comment inline with a value. These are saved as the ``inline_comments`` attribute of the section, with one entry per member of the section. Subsections (section markers in the config file) can also have comments. See `Section Attributes`_ for more on these attributes. These comments are all written back out by the ``write`` method. flatten_errors ============== .. code-block:: python flatten_errors(cfg, res) Validation_ is a powerful way of checking that the values supplied by the user make sense. The validate_ method returns a results dictionary that represents pass or fail for each value. This doesn't give you any information about *why* the check failed. ``flatten_errors`` is an example function that turns a results dictionary into a flat list, that only contains values that *failed*. ``cfg`` is the ConfigObj instance being checked, ``res`` is the results dictionary returned by ``validate``. It returns a list of keys that failed. Each member of the list is a tuple:: ([list of sections...], key, result) If ``validate`` was called with ``preserve_errors=False`` (the default) then ``result`` will always be ``False``. *list of sections* is a flattened list of sections that the key was found in. If the section was missing then key will be ``None``. If the value (or section) was missing then ``result`` will be ``False``. If ``validate`` was called with ``preserve_errors=True`` and a value was present, but failed the check, then ``result`` will be the exception object returned. You can use this as a string that describes the failure. For example : *The value "3" is of the wrong type*. Example Usage ------------- The output from ``flatten_errors`` is a list of tuples. Here is an example of how you could present this information to the user. .. code-block:: python vtor = validate.Validator() # ini is your config file - cs is the configspec cfg = ConfigObj(ini, configspec=cs) res = cfg.validate(vtor, preserve_errors=True) for entry in flatten_errors(cfg, res): # each entry is a tuple section_list, key, error = entry if key is not None: section_list.append(key) else: section_list.append('[missing section]') section_string = ', '.join(section_list) if error == False: error = 'Missing value or section.' print section_string, ' = ', error get_extra_values ================ .. code-block:: python get_extra_values(conf) New in ConfigObj 4.7.0. Find all the values and sections not in the configspec from a validated ConfigObj. ``get_extra_values`` returns a list of tuples where each tuple represents either an extra section, or an extra value. The tuples contain two values, a tuple representing the section the value is in and the name of the extra values. For extra values in the top level section the first member will be an empty tuple. For values in the 'foo' section the first member will be ``('foo',)``. For members in the 'bar' subsection of the 'foo' section the first member will be ``('foo', 'bar')``. Extra sections will only have one entry. Values and subsections inside an extra section aren't listed separately. NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't been validated it will return an empty list. Example Usage ------------- The output from ``get_extra_values`` is a list of tuples. Here is an example of how you could present this information to the user. .. code-block:: python vtor = validate.Validator() # ini is your config file - cs is the configspec cfg = ConfigObj(ini, configspec=cs) cfg.validate(vtor, preserve_errors=True) for sections, name in get_extra_values(cfg): # this code gets the extra values themselves the_section = cfg for section in sections: the_section = cfg[section] # the_value may be a section or a value the_value = the_section[name] section_or_value = 'value if isinstance(the_value, dict): # Sections are subclasses of dict section_or_value = 'section' section_string = ', '.join(sections) or "top level" print 'Extra entry in section: %s. Entry %r is a %s' % (section_string, name, section_or_value) CREDITS ======= ConfigObj version 4 and forward is written by (and copyright) Michael Foord, Nicola Larosa, Rob Dennis and Eli Courtwright. Rob Dennis and Eli Courtwright added Python 2 and 3 compatibility in a single source starting with version 5, and have taken stewardship of ConfigObj moving forward. Particularly thanks to Nicola Larosa for help on the config file spec, the validation system and the doctests. *validate.py* was originally written by Michael Foord and Mark Andrews. Thanks to many others for input, patches and bugfixes. LICENSE ======= ConfigObj, and related files, are licensed under the BSD license. This is a very unrestrictive license, but it comes with the usual disclaimer. This is free software: test it, break it, just don't blame us if it eats your data ! Of course if it does, let us know and we'll fix the problem so it doesn't happen to anyone else:: Copyright (C) 2003-2023: (name) : (email) Michael Foord: fuzzyman AT voidspace DOT org DOT uk Nicola Larosa: nico AT tekNico DOT net Rob Dennis: rdennis AT gmail DOT com Eli Courtwright: eli AT courtwright DOT org * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * None of the authors names may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You should also be able to find a copy of this license at : `BSD License`_ .. _BSD License: http://opensource.org/licenses/BSD-3-Clause TODO ==== Better support for configuration from multiple files, including tracking *where* the original file came from and writing changes to the correct file. Make ``newline`` a keyword argument (as well as an attribute) ? ``UTF16`` encoded files, when returned as a list of lines, will have the BOM at the start of every line. Should this be removed from all but the first line ? Option to set warning type for unicode decode ? (Defaults to strict). A method to optionally remove uniform indentation from multiline values. (do as an example of using ``walk`` - along with string-escape) Should the results dictionary from validate be an ordered dictionary if `odict `_ is available ? Implement some of the sequence methods (which include slicing) from the newer ``odict`` ? Preserve line numbers of values (and possibly the original text of each value). ISSUES ====== .. note:: Please file any bug reports at the `Github Page`_ There is currently no way to specify the encoding of a configspec file. As a consequence of the changes to configspec handling in version 4.6.0, when you create a ConfigObj instance and provide a configspec, the configspec attribute is only set on the ConfigObj instance - it isn't set on the sections until you validate. You also can't set the configspec attribute to be a dictionary. This wasn't documented but did work previously. In order to fix the problem with hashes in configspecs I had to turn off the parsing of inline comments in configspecs. This will only affect you if you are using ``copy=True`` when validating and expecting inline comments to be copied from the configspec into the ConfigObj instance (all other comments will be copied as usual). If you *create* the configspec by passing in a ConfigObj instance (usual way is to pass in a filename or list of lines) then you should pass in ``_inspec=True`` to the constructor to allow hashes in values. This is the magic that switches off inline comment parsing. When using ``copy`` mode for validation, it won't copy ``DEFAULT`` sections. This is so that you *can* use interpolation in configspec files. This is probably true even if interpolation is off in the configspec. You can't have a keyword with the same name as a section (in the same section). They are both dictionary keys - so they would overlap. ConfigObj doesn't quote and unquote values if ``list_values=False``. This means that leading or trailing whitespace in values will be lost when writing. (Unless you manually quote). Interpolation checks first the current section, then the 'DEFAULT' subsection of the current section, before moving on to the current section's parent and so on up the tree. Does it matter that we don't support the ':' divider, which is supported by ``ConfigParser`` ? String interpolation and validation don't play well together. When validation changes type it sets the value. This will correctly fetch the value using interpolation - but then overwrite the interpolation reference. If the value is unchanged by validation (it's a string) - but other types will be. CHANGELOG ========= This is an abbreviated changelog showing the major releases up to version 4. From version 4 it lists all releases and changes. 2024/09/21 - Version 5.0.9 -------------------------- * drop support for Python 2 and <3.7 * fix CVE-2023-26112, ReDoS attack 2023/01/18 - Version 5.0.8 -------------------------- * fixing/test for a regression introduced in 5.0.7 that prevented ``import validate`` from working 2023/01/17 - Version 5.0.7 -------------------------- * Update documentation to remove some dead links * Update unit tests to go against updated versions of Python 2014/08/25 - Version 5.0.6 -------------------------- * BUGFIX: Did not correctly handle %-chars in invalid lines * BUGFIX: unhelpful error message when nesting invalid 2014/04/28 - Version 5.0.5 -------------------------- * BUGFIX: error in writing out config files to disk with non-ascii characters 2014/04/11 - Version 5.0.4 -------------------------- * BUGFIX: correcting that the code path fixed in 5.0.3 didn't cover reading in config files 2014/04/04 - Version 5.0.3 -------------------------- * BUGFIX: not handling unicode encoding well, especially with respect to writing out files 2014/02/27 - Version 5.0.2 -------------------------- * Specific error message for installing version this version on Python versions older than 2.5 * Documentation corrections 2014/02/19 - Version 5.0.1 -------------------------- * BUGFIX: Fixed regression on python 2.x where passing an ``encoding`` parameter did not convert a bytestring config file (which is the most common) to unicode. Added unit tests for this and related cases * BUGFIX: A particular error message would fail to display with a type error on python 2.6 only 2014/02/08 - Version 5.0.0 -------------------------- * Python 3 single-source compatibility at the cost of a more restrictive set of versions: 2.6, 2.7, 3.2, 3.3 (otherwise unchanged) * New maintainers: Rob Dennis and Eli Courtwright * New home on github 2010/02/27 - Version 4.7.2 -------------------------- * BUGFIX: Restore Python 2.3 compatibility * BUGFIX: Members that were lists were being returned as copies due to interpolation introduced in 4.7. Lists are now only copies if interpolation changes a list member. * BUGFIX: ``pop`` now does interpolation in list values as well. * BUGFIX: where interpolation matches a section name rather than a value it is ignored instead of raising an exception on fetching the item. * BUGFIX: values that use interpolation to reference members that don't exist can now be repr'd. * BUGFIX: Fix to avoid writing '\\r\\r\\n' on Windows when given a file opened in text write mode ('w'). See `String Interpolation and List Values`_ for information about the problem with lists and interpolation. 2010/02/06 - Version 4.7.1 -------------------------- * Fix bug in options deprecation warning added in 4.7.0 2010/01/09 - Version 4.7.0 -------------------------- * Minimum supported version of Python is now 2.3 * ~25% performance improvement thanks to Christian Heimes * String interpolation now works in list value members * After validation any additional entries not in the configspec are listed in the ``extra_values`` section member * Addition of the ``get_extra_values`` function for finding all extra values in a validated ConfigObj instance * Deprecated the use of the ``options`` dictionary in the ConfigObj constructor and added explicit keyword arguments instead. Use \*\*options if you want to initialise a ConfigObj instance from a dictionary * Constructing a ConfigObj from an existing ConfigObj instance now preserves the order of values and sections from the original instance in the new one * BUGFIX: Checks that failed validation would not populate ``default_values`` and ``restore_default_value()`` wouldn't work for those entries * BUGFIX: clear() now clears 'defaults' * BUGFIX: empty values in list values were accidentally valid syntax. They now raise a ``ParseError``. e.g. "value = 1, , 2" * BUGFIX: Change to the result of a call to ``validate`` when ``preserve_errors`` is True. Previously sections where *all* values failed validation would return False for the section rather than preserving the errors. False will now only be returned for a section if it is missing * Distribution includes version 1.0.1 of validate.py * Removed __revision__ and __docformat__ 2009/04/13 - Version 4.6.0 -------------------------- * Pickling of ConfigObj instances now supported (thanks to Christian Heimes) * Hashes in confgspecs are now allowed (see note below) * Replaced use of hasattr (which can swallow exceptions) with getattr * __many__ in configspecs can refer to scalars (ordinary values) as well as sections * You can use ___many___ (three underscores!) where you want to use __many__ as well * You can now have normal sections inside configspec sections that use __many__ * You can now create an empty ConfigObj with a configspec, programmatically set values and then validate * A section that was supplied as a value (or vice-versa) in the actual config file would cause an exception during validation (the config file is still broken of course, but it is now handled gracefully) * Added ``as_list`` method * Removed the deprecated ``istrue``, ``encode`` and ``decode`` methods * Running test_configobj.py now also runs the doctests in the configobj module As a consequence of the changes to configspec handling, when you create a ConfigObj instance and provide a configspec, the configspec attribute is only set on the ConfigObj instance - it isn't set on the sections until you validate. You also can't set the configspec attribute to be a dictionary. This wasn't documented but did work previously. In order to fix the problem with hashes in configspecs I had to turn off the parsing of inline comments in configspecs. This will only affect you if you are using ``copy=True`` when validating and expecting inline comments to be copied from the configspec into the ConfigObj instance (all other comments will be copied as usual). If you *create* the configspec by passing in a ConfigObj instance (usual way is to pass in a filename or list of lines) then you should pass in ``_inspec=True`` to the constructor to allow hashes in values. This is the magic that switches off inline comment parsing. 2008/06/27 - Version 4.5.3 -------------------------- BUGFIX: fixed a problem with ``copy=True`` when validating with configspecs that use ``__many__`` sections. 2008/02/05 - Version 4.5.2 -------------------------- Distribution updated to include version 0.3.2 of validate_. This means that ``None`` as a default value in configspecs works. 2008/02/05 - Version 4.5.1 -------------------------- Distribution updated to include version 0.3.1 of validate_. This means that Unicode configspecs now work. 2008/02/05 - Version 4.5.0 -------------------------- ConfigObj will now guarantee that files will be written terminated with a newline. ConfigObj will no longer attempt to import the ``validate`` module, until/unless you call ``ConfigObj.validate`` with ``preserve_errors=True``. This makes it faster to import. New methods ``restore_default`` and ``restore_defaults``. ``restore_default`` resets an entry to its default value (and returns that value). ``restore_defaults`` resets all entries to their default value. It doesn't modify entries without a default value. You must have validated a ConfigObj (which populates the ``default_values`` dictionary) before calling these methods. BUGFIX: Proper quoting of keys, values and list values that contain hashes (when writing). When ``list_values=False``, values containing hashes are triple quoted. Added the ``reload`` method. This reloads a ConfigObj from file. If the filename attribute is not set then a ``ReloadError`` (a new exception inheriting from ``IOError``) is raised. BUGFIX: Files are read in with 'rb' mode, so that native/non-native line endings work! Minor efficiency improvement in ``unrepr`` mode. Added missing docstrings for some overidden dictionary methods. Added the ``reset`` method. This restores a ConfigObj to a freshly created state. Removed old CHANGELOG file. 2007/02/04 - Version 4.4.0 -------------------------- Official release of 4.4.0 2006/12/17 - Version 4.3.3-alpha4 --------------------------------- By Nicola Larosa Allowed arbitrary indentation in the ``indent_type`` parameter, removed the ``NUM_INDENT_SPACES`` and ``MAX_INTERPOL_DEPTH`` (a leftover) constants, added indentation tests (including another docutils workaround, sigh), updated the documentation. By Michael Foord Made the import of ``compiler`` conditional so that ``ConfigObj`` can be used with `IronPython `_. 2006/12/17 - Version 4.3.3-alpha3 --------------------------------- By Nicola Larosa Added a missing ``self.`` in the _handle_comment method and a related test, per Sourceforge bug #1523975. 2006/12/09 - Version 4.3.3-alpha2 --------------------------------- By Nicola Larosa Changed interpolation search strategy, based on this patch by Robin Munn: http://sourceforge.net/mailarchive/message.php?msg_id=17125993 2006/12/09 - Version 4.3.3-alpha1 --------------------------------- By Nicola Larosa Added Template-style interpolation, with tests, based on this patch by Robin Munn: http://sourceforge.net/mailarchive/message.php?msg_id=17125991 (awful archives, bad Sourceforge, bad). 2006/06/04 - Version 4.3.2 -------------------------- Changed error handling, if parsing finds a single error then that error will be re-raised. That error will still have an ``errors`` and a ``config`` attribute. Fixed bug where '\\n' terminated files could be truncated. Bugfix in ``unrepr`` mode, it couldn't handle '#' in values. (Thanks to Philippe Normand for the report.) As a consequence of this fix, ConfigObj doesn't now keep inline comments in ``unrepr`` mode. This is because the parser in the `compiler package`_ doesn't keep comments. Error messages are now more useful. They tell you the number of parsing errors and the line number of the first error. (In the case of multiple errors.) Line numbers in exceptions now start at 1, not 0. Errors in ``unrepr`` mode are now handled the same way as in the normal mode. The errors stored will be an ``UnreprError``. 2006/04/29 - Version 4.3.1 -------------------------- Added ``validate.py`` back into ``configobj.zip``. (Thanks to Stewart Midwinter) Updated to ``validate.py`` 0.2.2. Preserve tuples when calling the ``dict`` method. (Thanks to Gustavo Niemeyer.) Changed ``__repr__`` to return a string that contains ``ConfigObj({ ... })``. Change so that an options dictionary isn't modified by passing it to ConfigObj. (Thanks to Artarious.) Added ability to handle negative integers in ``unrepr``. (Thanks to Kevin Dangoor.) 2006/03/24 - Version 4.3.0 -------------------------- Moved the tests and the CHANGELOG (etc) into a separate file. This has reduced the size of ``configobj.py`` by about 40%. Added the ``unrepr`` mode to reading and writing config files. Thanks to Kevin Dangoor for this suggestion. Empty values are now valid syntax. They are read as an empty string ``''``. (``key =``, or ``key = # comment``.) ``validate`` now honours the order of the configspec. Added the ``copy`` mode to validate. Thanks to Louis Cordier for this suggestion. Fixed bug where files written on windows could be given ``'\r\r\n'`` line terminators. Fixed bug where last occurring comment line could be interpreted as the final comment if the last line isn't terminated. Fixed bug where nested list values would be flattened when ``write`` is called. Now sub-lists have a string representation written instead. Deprecated ``encode`` and ``decode`` methods instead. You can now pass in a ConfigObj instance as a configspec (remember to read the configspec file using ``list_values=False``). Sorted footnotes in the docs. 2006/02/16 - Version 4.2.0 -------------------------- Removed ``BOM_UTF8`` from ``__all__``. The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is *only* ``True`` for the ``UTF16/UTF8`` encodings. File like objects no longer need a ``seek`` attribute. Full unicode support added. New options/attributes ``encoding``, ``default_encoding``. ConfigObj no longer keeps a reference to file like objects. Instead the ``write`` method takes a file like object as an optional argument. (Which will be used in preference of the ``filename`` attribute if that exists as well.) utf16 files decoded to unicode. If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is written out at the start of the file. (It will normally only be ``True`` if the utf8 BOM was found when the file was read.) Thanks to Aaron Bentley for help and testing on the unicode issues. File paths are *not* converted to absolute paths, relative paths will remain relative as the ``filename`` attribute. Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning a list of lines. Deprecated ``istrue``, replaced it with ``as_bool``. Added ``as_int`` and ``as_float``. 2005/12/14 - Version 4.1.0 -------------------------- Added ``merge``, a recursive update. Added ``preserve_errors`` to ``validate`` and the ``flatten_errors`` example function. Thanks to Matthew Brett for suggestions and helping me iron out bugs. Fixed bug where a config file is *all* comment, the comment will now be ``initial_comment`` rather than ``final_comment``. Validation no longer done on the 'DEFAULT' section (only in the root level). This allows interpolation in configspecs. Also use the new list syntax in validate_ 0.2.1. (For configspecs). 2005/12/02 - Version 4.0.2 -------------------------- Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report. 2005/11/05 - Version 4.0.1 -------------------------- Fixed bug in ``Section.walk`` when transforming names as well as values. Added the ``istrue`` method. (Fetches the boolean equivalent of a string value). Fixed ``list_values=False`` - they are now only quoted/unquoted if they are multiline values. List values are written as ``item, item`` rather than ``item,item``. 2005/10/17 - Version 4.0.0 -------------------------- **ConfigObj 4.0.0 Final** Fixed bug in ``setdefault``. When creating a new section with setdefault the reference returned would be to the dictionary passed in *not* to the new section. Bug fixed and behaviour documented. Obscure typo/bug fixed in ``write``. Wouldn't have affected anyone though. 2005/09/09 - Version 4.0.0 beta 5 --------------------------------- Removed ``PositionError``. Allowed quotes around keys as documented. Fixed bug with commas in comments. (matched as a list value) 2005/09/07 - Version 4.0.0 beta 4 --------------------------------- Fixed bug in ``__delitem__``. Deleting an item no longer deletes the ``inline_comments`` attribute. Fixed bug in initialising ConfigObj from a ConfigObj. Changed the mailing list address. 2005/08/28 - Version 4.0.0 beta 3 --------------------------------- Interpolation is switched off before writing out files. Fixed bug in handling ``StringIO`` instances. (Thanks to report from Gustavo Niemeyer.) Moved the doctests from the ``__init__`` method to a separate function. (For the sake of IDE calltips). 2005/08/25 - Version 4.0.0 beta 2 --------------------------------- Amendments to *validate.py*. First public release. 2005/08/21 - Version 4.0.0 beta 1 --------------------------------- Reads nested subsections to any depth. Multiline values. Simplified options and methods. New list syntax. Faster, smaller, and better parser. Validation greatly improved. Includes: * type conversion * default values * repeated sections Improved error handling. Plus lots of other improvements. 2004/05/24 - Version 3.0.0 -------------------------- Several incompatible changes: another major overhaul and change. (Lots of improvements though). Added support for standard config files with sections. This has an entirely new interface: each section is a dictionary of values. Changed the update method to be called writein: update clashes with a dict method. Made various attributes keyword arguments, added several. Configspecs and orderlists have changed a great deal. Removed support for adding dictionaries: use update instead. Now subclasses a new class called caselessDict. This should add various dictionary methods that could have caused errors before. It also preserves the original casing of keywords when writing them back out. Comments are also saved using a ``caselessDict``. Using a non-string key will now raise a ``TypeError`` rather than converting the key. Added an exceptions keyword for *much* better handling of errors. Made ``creatempty=False`` the default. Now checks indict *and* any keyword args. Keyword args take precedence over indict. ``' ', ':', '=', ','`` and ``'\t'`` are now all valid dividers where the keyword is unquoted. ConfigObj now does no type checking against configspec when you set items. delete and add methods removed (they were unnecessary). Docs rewritten to include all this gumph and more; actually ConfigObj is *really* easy to use. Support for stdout was removed. A few new methods added. Charmap is now incorporated into ConfigObj. 2004/03/14 - Version 2.0.0 beta ------------------------------- Re-written it to subclass dict. My first forays into inheritance and operator overloading. The config object now behaves like a dictionary. I've completely broken the interface, but I don't think anyone was really using it anyway. This new version is much more 'classy'. It will also read straight from/to a filename and completely parse a config file without you *having* to supply a config spec. Uses listparse, so can handle nested list items as values. No longer has getval and setval methods: use normal dictionary methods, or add and delete. 2004/01/29 - Version 1.0.5 -------------------------- Version 1.0.5 has a couple of bugfixes as well as a couple of useful additions over previous versions. Since 1.0.0 the buildconfig function has been moved into this distribution, and the methods reset, verify, getval and setval have been added. A couple of bugs have been fixed. Origins ------- ConfigObj originated in a set of functions for reading config files in the `atlantibots `_ project. The original functions were written by Rob McNeur. ---------- Footnotes ========= .. [#] And if you discover any bugs, let us know. We'll fix them quickly. .. [#] If you specify a filename that doesn't exist, ConfigObj will assume you are creating a new one. See the *create_empty* and *file_error* options. .. [#] They can be byte strings (*ordinary* strings) or Unicode. .. [#] Except we don't support the RFC822 style line continuations, nor ':' as a divider. .. [#] This is a change in ConfigObj 4.2.0. Note that ConfigObj doesn't call the seek method of any file like object you pass in. You may want to call ``file_object.seek(0)`` yourself, first. .. [#] A side effect of this is that it enables you to copy a ConfigObj : .. code-block:: python # only copies members # not attributes/comments config2 = ConfigObj(config1) Since ConfigObj 4.7.0 the order of members and sections will be preserved when copying a ConfigObj instance. .. [#] Other than lists of strings. .. [#] The exception is if it detects a ``UTF16`` encoded file which it must decode before parsing. .. [#] The method signature shows that this method takes two arguments. The second is the section to be written. This is because the ``write`` method is called recursively. .. [#] The dict method doesn't actually use the deepcopy mechanism. This means if you add nested lists (etc) to your ConfigObj, then the dictionary returned by dict may contain some references. For all *normal* ConfigObjs it will return a deepcopy. .. [#] Passing ``(section, key)`` rather than ``(value, key)`` allows you to change the value by setting ``section[key] = newval``. It also gives you access to the *rename* method of the section. .. [#] Minimum required version of *validate.py* 0.2.0 . configobj-5.0.9/docs/default.css000066400000000000000000000132301467354007300165630ustar00rootroot00000000000000/* :Author: David Goodger (goodger@python.org) :Id: $Id: html4css1.css 5951 2009-05-18 18:03:10Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to customize this style sheet. */ /* used to remove borders from tables and images */ .borderless, table.borderless td, table.borderless th { border: 0 } table.borderless td, table.borderless th { /* Override padding for "table.docutils td" with "! important". The right padding separates the table cells. */ padding: 0 0.5em 0 0 ! important } .first { /* Override more specific margin styles with "! important". */ margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } /* Uncomment (and remove this text!) to get bold-faced definition list terms dl.docutils dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em ; margin-right: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin: 0 0 0.5em 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center } h2.subtitle { text-align: center } hr.docutils { width: 75% } img.align-left, .figure.align-left{ clear: left ; float: left ; margin-right: 1em } img.align-right, .figure.align-right { clear: right ; float: right ; margin-left: 1em } .align-left { text-align: left } .align-center { clear: both ; text-align: center } .align-right { text-align: right } /* reset inner alignment in figures */ div.align-right { text-align: left } /* div.align-center * { */ /* text-align: left } */ ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font: inherit } pre.literal-block, pre.doctest-block { margin-left: 2em ; margin-right: 2em } span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } span.pre { white-space: pre } span.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid 1px gray; margin-left: 1px } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } table.footnote { border-left: solid 1px black; margin-left: 1px } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } ul.auto-toc { list-style-type: none } configobj-5.0.9/docs/docutils.conf000066400000000000000000000001621467354007300171220ustar00rootroot00000000000000[html4css1 writer] embed-stylesheet: no cloak_email_addresses: yes stylesheet: docutils.css template: template.tmpconfigobj-5.0.9/docs/docutils.css000066400000000000000000000047621467354007300167770ustar00rootroot00000000000000/* :Authors: Ian Bicking, Michael Foord :Contact: fuzzyman@voidspace.org.uk :Date: 2005/08/26 :Version: 0.1.0 :Copyright: This stylesheet has been placed in the public domain. Stylesheet for Docutils. Based on ``blue_box.css`` by Ian Bicking and ``default.css`` revision 3442 */ @import url(default.css); @import url(pygments.css); body { font-family: Arial, sans-serif; } em, i { /* Typically serif fonts have much nicer italics */ font-family: Times New Roman, Times, serif; } a.target { color: blue; } a.toc-backref { text-decoration: none; color: black; } a.toc-backref:hover { background-color: inherit; } a:hover { background-color: #cccccc; } div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { background-color: #cccccc; padding: 3px; width: 80%; } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { text-align: center; background-color: #999999; display: block; margin: 0; } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: #cc0000; font-family: sans-serif; text-align: center; background-color: #999999; display: block; margin: 0; } h1, h2, h3, h4, h5, h6 { font-family: Helvetica, Arial, sans-serif; border: thin solid black; /* This makes the borders rounded on Mozilla, which pleases me */ -moz-border-radius: 8px; padding: 4px; } h1 { background-color: #444499; color: #ffffff; border: medium solid black; } h1 a.toc-backref, h2 a.toc-backref { color: #ffffff; } h2 { background-color: #666666; color: #ffffff; border: medium solid black; } h3, h4, h5, h6 { background-color: #cccccc; color: #000000; } h3 a.toc-backref, h4 a.toc-backref, h5 a.toc-backref, h6 a.toc-backref { color: #000000; } h1.title { text-align: center; background-color: #444499; color: #eeeeee; border: thick solid black; -moz-border-radius: 20px; } table.footnote { padding-left: 0.5ex; } table.citation { padding-left: 0.5ex } pre.literal-block, pre.doctest-block { border: thin black solid; padding: 5px; } .image img { border-style : solid; border-width : 2px; } img.align-center { display: block; margin: auto; text-align: center; } h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { font-size: 100%; } code, tt { color: #000066; } configobj-5.0.9/docs/index.rst000066400000000000000000000007261467354007300162740ustar00rootroot00000000000000.. configobj documentation master file, created by sphinx-quickstart on Sat Feb 8 01:26:54 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to configobj's documentation! ===================================== Contents: .. toctree:: :numbered: :maxdepth: 1 configobj validate Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` configobj-5.0.9/docs/make.bat000066400000000000000000000145011467354007300160340ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\configobj.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\configobj.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end configobj-5.0.9/docs/pygments.css000066400000000000000000000061761467354007300170200ustar00rootroot00000000000000.hll { background-color: #ffffcc } .c { color: #408080; font-style: italic } /* Comment */ .err { border: 1px solid #FF0000 } /* Error */ .k { color: #008000; font-weight: bold } /* Keyword */ .o { color: #666666 } /* Operator */ .cm { color: #408080; font-style: italic } /* Comment.Multiline */ .cp { color: #BC7A00 } /* Comment.Preproc */ .c1 { color: #408080; font-style: italic } /* Comment.Single */ .cs { color: #408080; font-style: italic } /* Comment.Special */ .gd { color: #A00000 } /* Generic.Deleted */ .ge { font-style: italic } /* Generic.Emph */ .gr { color: #FF0000 } /* Generic.Error */ .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .gi { color: #00A000 } /* Generic.Inserted */ .go { color: #808080 } /* Generic.Output */ .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .gt { color: #0040D0 } /* Generic.Traceback */ .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ .kp { color: #008000 } /* Keyword.Pseudo */ .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ .kt { color: #B00040 } /* Keyword.Type */ .m { color: #666666 } /* Literal.Number */ .s { color: #BA2121 } /* Literal.String */ .na { color: #7D9029 } /* Name.Attribute */ .nb { color: #008000 } /* Name.Builtin */ .nc { color: #0000FF; font-weight: bold } /* Name.Class */ .no { color: #880000 } /* Name.Constant */ .nd { color: #AA22FF } /* Name.Decorator */ .ni { color: #999999; font-weight: bold } /* Name.Entity */ .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ .nf { color: #0000FF } /* Name.Function */ .nl { color: #A0A000 } /* Name.Label */ .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ .nt { color: #008000; font-weight: bold } /* Name.Tag */ .nv { color: #19177C } /* Name.Variable */ .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ .mf { color: #666666 } /* Literal.Number.Float */ .mh { color: #666666 } /* Literal.Number.Hex */ .mi { color: #666666 } /* Literal.Number.Integer */ .mo { color: #666666 } /* Literal.Number.Oct */ .sb { color: #BA2121 } /* Literal.String.Backtick */ .sc { color: #BA2121 } /* Literal.String.Char */ .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ .s2 { color: #BA2121 } /* Literal.String.Double */ .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ .sh { color: #BA2121 } /* Literal.String.Heredoc */ .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ .sx { color: #008000 } /* Literal.String.Other */ .sr { color: #BB6688 } /* Literal.String.Regex */ .s1 { color: #BA2121 } /* Literal.String.Single */ .ss { color: #19177C } /* Literal.String.Symbol */ .bp { color: #008000 } /* Name.Builtin.Pseudo */ .vc { color: #19177C } /* Name.Variable.Class */ .vg { color: #19177C } /* Name.Variable.Global */ .vi { color: #19177C } /* Name.Variable.Instance */ .il { color: #666666 } /* Literal.Number.Integer.Long */ configobj-5.0.9/docs/template.tmp000066400000000000000000000001621467354007300167620ustar00rootroot00000000000000%(head_prefix)s %(head)s %(stylesheet)s %(body_prefix)s %(body_pre_docinfo)s %(docinfo)s %(body)s %(body_suffix)s configobj-5.0.9/docs/validate.rst000066400000000000000000000463771467354007300167720ustar00rootroot00000000000000.. _validate_doc: =================================== Validation Schema with validate.py =================================== -------------------------- Using the Validator class -------------------------- :Authors: Michael Foord, Nicola Larosa, Rob Dennis, Eli Courtwright, Mark Andrews :Version: Validate 2.0.0 :Date: 2014/02/08 :Homepage: `Github Page`_ :License: `BSD License`_ :Support: `Mailing List`_ .. _Mailing List: http://lists.sourceforge.net/lists/listinfo/configobj-develop .. _This Document: .. _Github Page: https://github.com/DiffSK/configobj .. _BSD License: http://opensource.org/licenses/BSD-3-Clause .. contents:: Validate Manual Introduction ============ Validation is used to check that supplied values conform to a specification. The value can be supplied as a string, e.g. from a config file. In this case the check will also *convert* the value to the required type. This allows you to add validation as a transparent layer to access data stored as strings. The validation checks that the data is correct *and* converts it to the expected type. Checks are also strings, and are easy to write. One generic system can be used to validate information from different sources via a single consistent mechanism. Checks look like function calls, and map to function calls. They can include parameters and keyword arguments. These arguments are passed to the relevant function by the ``Validator`` instance, along with the value being checked. The syntax for checks also allows for specifying a default value. This default value can be ``None``, no matter what the type of the check. This can be used to indicate that a value was missing, and so holds no useful value. Functions either return a new value, or raise an exception. See `Validator Exceptions`_ for the low down on the exception classes that ``validate.py`` defines. Some standard functions are provided, for basic data types; these come built into every validator. Additional checks are easy to write: they can be provided when the ``Validator`` is instantiated, or added afterwards. Validate was primarily written to support ConfigObj, but is designed to be applicable to many other situations. For support and bug reports please use the ConfigObj `Github Page`_ Downloading =========== The current version is **2.0.0**, dated 8th February 2014. You can obtain validate in the following ways : Files ----- * validate.py from `Github Page`_ * The latest development version can be obtained from the `Github Page`_. The standard functions ====================== The standard functions come built-in to every ``Validator`` instance. They work with the following basic data types : * integer * float * boolean * string * ip_addr plus lists of these datatypes. Adding additional checks is done through coding simple functions. The full set of standard checks are : :'integer': matches integer values (including negative). Takes optional 'min' and 'max' arguments:: integer() integer(3, 9) # any value from 3 to 9 integer(min=0) # any positive value integer(max=9) :'float': matches float values Has the same parameters as the integer check. :'boolean': matches boolean values: ``True`` or ``False``. Acceptable string values for True are:: true, on, yes, 1 Acceptable string values for False are:: false, off, no, 0 Any other value raises an error. :'string': matches any string. Takes optional keyword args 'min' and 'max' to specify min and max length of string. :'ip_addr': matches an Internet Protocol address, v.4, represented by a dotted-quad string, i.e. '1.2.3.4'. :'list': matches any list. Takes optional keyword args 'min', and 'max' to specify min and max sizes of the list. The list checks always return a list. :force_list: matches any list, but if a single value is passed in will coerce it into a list containing that value. Useful for configobj if the user forgot the trailing comma to turn a single value into a list. :'tuple': matches any list. This check returns a tuple rather than a list. :'int_list': Matches a list of integers. Takes the same arguments as list. :'float_list': Matches a list of floats. Takes the same arguments as list. :'bool_list': Matches a list of boolean values. Takes the same arguments as list. :'string_list': Matches a list of strings. Takes the same arguments as list. :'ip_addr_list': Matches a list of IP addresses. Takes the same arguments as list. :'mixed_list': Matches a list with different types in specific positions. List size must match the number of arguments. Each position can be one of:: int, str, boolean, float, ip_addr So to specify a list with two strings followed by two integers, you write the check as:: mixed_list(str, str, int, int) :'pass': matches everything: it never fails and the value is unchanged. It is also the default if no check is specified. :'option': matches any from a list of options. You specify this test with:: option('option 1', 'option 2', 'option 3') The following code will work without you having to specifically add the functions yourself. .. code-block:: python from validate import Validator # vtor = Validator() newval1 = vtor.check('integer', value1) newval2 = vtor.check('boolean', value2) # etc ... .. note:: Of course, if these checks fail they raise exceptions. So you should wrap them in ``try...except`` blocks. Better still, use ConfigObj for a higher level interface. Using Validator =============== Using ``Validator`` is very easy. It has one public attribute and one public method. Shown below are the different steps in using ``Validator``. The only additional thing you need to know, is about `Writing check functions`_. Instantiate ----------- .. code-block:: python from validate import Validator vtor = Validator() or even : .. code-block:: python from validate import Validator # fdict = { 'check_name1': function1, 'check_name2': function2, 'check_name3': function3, } # vtor = Validator(fdict) The second method adds a set of your functions as soon as your validator is created. They are stored in the ``vtor.functions`` dictionary. The 'key' you give them in this dictionary is the name you use in your checks (not the original function name). Dictionary keys/functions you pass in can override the built-in ones if you want. Adding functions ---------------- The code shown above, for adding functions on instantiation, has exactly the same effect as the following code : .. code-block:: python from validate import Validator # vtor = Validator() vtor.functions['check_name1'] = function1 vtor.functions['check_name2'] = function2 vtor.functions['check_name3'] = function3 ``vtor.functions`` is just a dictionary that maps names to functions, so we could also have called ``vtor.functions.update(fdict)``. Writing the check ----------------- As we've heard, the checks map to the names in the ``functions`` dictionary. You've got a full list of `The standard functions`_ and the arguments they take. If you're using ``Validator`` from ConfigObj, then your checks will look like:: keyword = int_list(max=6) but the check part will be identical . The check method ---------------- If you're not using ``Validator`` from ConfigObj, then you'll need to call the ``check`` method yourself. If the check fails then it will raise an exception, so you'll want to trap that. Here's the basic example : .. code-block:: python from validate import Validator, ValidateError # vtor = Validator() check = "integer(0, 9)" value = 3 try: newvalue = vtor.check(check, value) except ValidateError: print 'Check Failed.' else: print 'Check passed.' .. caution:: Although the value can be a string, if it represents a list it should already have been turned into a list of strings. Default Values ~~~~~~~~~~~~~~ Some values may not be available, and you may want to be able to specify a default as part of the check. You do this by passing the keyword ``missing=True`` to the ``check`` method, as well as a ``default=value`` in the check. (Constructing these checks is done automatically by ConfigObj: you only need to know about the ``default=value`` part) : .. code-block:: python check1 = 'integer(default=50)' check2 = 'option("val 1", "val 2", "val 3", default="val 1")' assert vtor.check(check1, '', missing=True) == 50 assert vtor.check(check2, '', missing=True) == "val 1" If you pass in ``missing=True`` to the check method, then the actual value is ignored. If no default is specified in the check, a ``ValidateMissingValue`` exception is raised. If a default is specified then that is passed to the check instead. If the check has ``default=None`` (case sensitive) then ``vtor.check`` will *always* return ``None`` (the object). This makes it easy to tell your program that this check contains no useful value when missing, i.e. the value is optional, and may be omitted without harm. .. note:: As of version 0.3.0, if you specify ``default='None'`` (note the quote marks around ``None``) then it will be interpreted as the string ``'None'``. List Values ~~~~~~~~~~~ It's possible that you would like your default value to be a list. It's even possible that you will write your own check functions - and would like to pass them keyword arguments as lists from within the check. To avoid confusing syntax with commas and quotes you use a list constructor to specify that keyword arguments are lists. This includes the ``default`` value. This makes checks look something like:: checkname(default=list('val1', 'val2', 'val3')) get_default_value ----------------- ``Validator`` instances have a ``get_default_value`` method. It takes a ``check`` string (the same string you would pass to the ``check`` method) and returns the default value, converted to the right type. If the check doesn't define a default value then this method raises a ``KeyError``. If the ``check`` has been seen before then it will have been parsed and cached already, so this method is not expensive to call (however the conversion is done each time). Validator Exceptions ==================== .. note:: If you only use Validator through ConfigObj, it traps these Exceptions for you. You will still need to know about them for writing your own check functions. ``vtor.check`` indicates that the check has failed by raising an exception. The appropriate error should be raised in the check function. The base error class is ``ValidateError``. All errors (except for ``VdtParamError``) raised are sub-classes of this. If an unrecognised check is specified then ``VdtUnknownCheckError`` is raised. There are also ``VdtTypeError`` and ``VdtValueError``. If incorrect parameters are passed to a check function then it will (or should) raise ``VdtParamError``. As this indicates *programmer* error, rather than an error in the value, it is a subclass of ``SyntaxError`` instead of ``ValidateError``. .. note:: This means it *won't* be caught by ConfigObj - but propagated instead. If the value supplied is the wrong type, then the check should raise ``VdtTypeError``. e.g. the check requires the value to be an integer (or representation of an integer) and something else was supplied. If the value supplied is the right type, but an unacceptable value, then the check should raise ``VdtValueError``. e.g. the check requires the value to be an integer (or representation of an integer) less than ten and a higher value was supplied. Both ``VdtTypeError`` and ``VdtValueError`` are initialised with the incorrect value. In other words you raise them like this : .. code-block:: python raise VdtTypeError(value) # raise VdtValueError(value) ``VdtValueError`` has the following subclasses, which should be raised if they are more appropriate. * ``VdtValueTooSmallError`` * ``VdtValueTooBigError`` * ``VdtValueTooShortError`` * ``VdtValueTooLongError`` Writing check functions ======================= Writing check functions is easy. The check function will receive the value as its first argument, followed by any other parameters and keyword arguments. If the check fails, it should raise a ``VdtTypeError`` or a ``VdtValueError`` (or an appropriate subclass). All parameters and keyword arguments are *always* passed as strings. (Parsed from the check string). The value might be a string (or list of strings) and need converting to the right type - alternatively it might already be a list of integers. Our function needs to be able to handle either. If the check passes then it should return the value (possibly converted to the right type). And that's it ! Example ------- Here is an example function that requires a list of integers. Each integer must be between 0 and 99. It takes a single argument specifying the length of the list. (Which allows us to use the same check in more than one place). If the length can't be converted to an integer then we need to raise ``VdtParamError``. Next we check that the value is a list. Anything else should raise a ``VdtTypeError``. The list should also have 'length' entries. If the list has more or less entries then we will need to raise a ``VdtValueTooShortError`` or a ``VdtValueTooLongError``. Then we need to check every entry in the list. Each entry should be an integer between 0 and 99, or a string representation of an integer between 0 and 99. Any other type is a ``VdtTypeError``, any other value is a ``VdtValueError`` (either too big, or too small). .. code-block:: python def special_list(value, length): """ Check that the supplied value is a list of integers, with 'length' entries, and each entry between 0 and 99. """ # length is supplied as a string # we need to convert it to an integer try: length = int(length) except ValueError: raise VdtParamError('length', length) # # Check the supplied value is a list if not isinstance(value, list): raise VdtTypeError(value) # # check the length of the list is correct if len(value) > length: raise VdtValueTooLongError(value) elif len(value) < length: raise VdtValueTooShortError(value) # # Next, check every member in the list # converting strings as necessary out = [] for entry in value: if not isinstance(entry, (str, unicode, int)): # a value in the list # is neither an integer nor a string raise VdtTypeError(value) elif isinstance(entry, (str, unicode)): if not entry.isdigit(): raise VdtTypeError(value) else: entry = int(entry) if entry < 0: raise VdtValueTooSmallError(value) elif entry > 99: raise VdtValueTooBigError(value) out.append(entry) # # if we got this far, all is well # return the new list return out If you are only using validate from ConfigObj then the error type (*TooBig*, *TooSmall*, etc) is lost - so you may only want to raise ``VdtValueError``. .. caution:: If your function raises an exception that isn't a subclass of ``ValidateError``, then ConfigObj won't trap it. This means validation will fail. This is why our function starts by checking the type of the value. If we are passed the wrong type (e.g. an integer rather than a list) we get a ``VdtTypeError`` rather than bombing out when we try to iterate over the value. If you are using validate in another circumstance you may want to create your own subclasses of ``ValidateError`` which convey more specific information. Known Issues ============ The following parses and then blows up. The resulting error message is confusing: ``checkname(default=list(1, 2, 3, 4)`` This is because it parses as: ``checkname(default="list(1", 2, 3, 4)``. That isn't actually unreasonable, but the error message won't help you work out what has happened. TODO ==== * A regex check function ? * A timestamp check function ? (Using the ``parse`` function from ``DateUtil`` perhaps). ISSUES ====== .. note:: Please file any bug reports to the `Github Page`_ If we could pull tuples out of arguments, it would be easier to specify arguments for 'mixed_lists'. CHANGELOG ========= 2014/02/08 - Version 2.0.0 -------------------------- * Python 3 single-source compatibility at the cost of a more restrictive set of versions: 2.6, 2.7, 3.2, 3.3 (otherwise unchanged) * New maintainers: Rob Dennis and Eli Courtwright * New home on github 2009/10/25 - Version 1.0.1 -------------------------- * BUGFIX: Fixed compatibility with Python 2.3. 2009/04/13 - Version 1.0.0 -------------------------- * BUGFIX: can now handle multiline strings. * Addition of 'force_list' validation option. As the API is stable and there are no known bugs or outstanding feature requests I am marking this 1.0. 2008/02/24 - Version 0.3.2 -------------------------- BUGFIX: Handling of None as default value fixed. 2008/02/05 - Version 0.3.1 -------------------------- BUGFIX: Unicode checks no longer broken. 2008/02/05 - Version 0.3.0 -------------------------- Improved performance with a parse cache. New ``get_default_value`` method. Given a check it returns the default value (converted to the correct type) or raises a ``KeyError`` if the check doesn't specify a default. Added 'tuple' check and corresponding 'is_tuple' function (which always returns a tuple). BUGFIX: A quoted 'None' as a default value is no longer treated as None, but as the string 'None'. BUGFIX: We weren't unquoting keyword arguments of length two, so an empty string didn't work as a default. BUGFIX: Strings no longer pass the 'is_list' check. Additionally, the list checks always return lists. A couple of documentation bug fixes. Removed CHANGELOG from module. 2007/02/04 Version 0.2.3 ----------------------------- Release of 0.2.3 2006/12/17 Version 0.2.3-alpha1 ------------------------------------ By Nicola Larosa Fixed validate doc to talk of ``boolean`` instead of ``bool``; changed the ``is_bool`` function to ``is_boolean`` (Sourceforge bug #1531525). 2006/04/29 Version 0.2.2 ----------------------------- Addressed bug where a string would pass the ``is_list`` test. (Thanks to Konrad Wojas.) 2005/12/16 Version 0.2.1 ----------------------------- Fixed bug so we can handle keyword argument values with commas. We now use a list constructor for passing list values to keyword arguments (including ``default``):: default=list("val", "val", "val") Added the ``_test`` test. Moved a function call outside a try...except block. 2005/08/18 Version 0.2.0 ----------------------------- Updated by Michael Foord and Nicola Larosa Does type conversion as well. 2005/02/01 Version 0.1.0 ----------------------------- Initial version developed by Michael Foord and Mark Andrews. configobj-5.0.9/pyproject.toml000066400000000000000000000001211467354007300164040ustar00rootroot00000000000000[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" configobj-5.0.9/setup.cfg000066400000000000000000000007351467354007300153240ustar00rootroot00000000000000# # Configuration for setuptools # [egg_info] #tag_build = .dev0 tag_date = false [sdist] formats = gztar [bdist_wheel] # If you set this to 1, make sure you have a proper Travis CI build matrix, # and that your Trove classifiers state you support Python 2 and 3 universal = 1 [tool:pytest] norecursedirs = .* *.egg *.egg-info bin dist include lib local share static docs python_files = src/tests/test_*.py #addopts = [flake8] #ignore = E226,… max-line-length = 132 configobj-5.0.9/setup.py000066400000000000000000000072311467354007300152130ustar00rootroot00000000000000#!/usr/bin/env python # setup.py # -*- coding: utf-8 -*- # pylint: disable=invalid-name """Install script for ConfigObj""" # Copyright (C) 2005-2014: # (name) : (email) # Michael Foord: fuzzyman AT voidspace DOT org DOT uk # Mark Andrews: mark AT la-la DOT com # Nicola Larosa: nico AT tekNico DOT net # Rob Dennis: rdennis AT gmail DOT com # Eli Courtwright: eli AT courtwright DOT org # This software is licensed under the terms of the BSD license. # http://opensource.org/licenses/BSD-3-Clause import io import os import re import sys from contextlib import closing from setuptools import setup if sys.version_info[0] < 2: print('for Python versions < 3 use configobj ' 'version 5.0.8') sys.exit(1) __here__ = os.path.abspath(os.path.dirname(__file__)) NAME = 'configobj' MODULES = [] PACKAGES = ['configobj', 'validate'] DESCRIPTION = 'Config file reading, writing and validation.' URL = 'https://github.com/DiffSK/configobj' VERSION = '' with closing(open(os.path.join(__here__, 'src', PACKAGES[0], '_version.py'), 'r')) as handle: for line in handle.readlines(): if line.startswith('__version__'): VERSION = re.split('''['"]''', line)[1] assert re.match(r"[0-9](\.[0-9]+)", VERSION), "No semantic version found in 'configobj._version'" LONG_DESCRIPTION = """**ConfigObj** is a simple but powerful config file reader and writer: an *ini file round tripper*. Its main feature is that it is very easy to use, with a straightforward programmer's interface and a simple syntax for config files. List of Features ---------------- * Nested sections (subsections), to any level * List values * Multiple line values * Full Unicode support * String interpolation (substitution) * Integrated with a powerful validation system - including automatic type checking/conversion - and allowing default values - repeated sections * All comments in the file are preserved * The order of keys/sections is preserved * Powerful ``unrepr`` mode for storing/retrieving Python data-types """ try: with io.open('CHANGES.rst', encoding='utf-8') as handle: LONG_DESCRIPTION += handle.read() except EnvironmentError as exc: # Build / install anyway print("WARNING: Cannot open/read CHANGES.rst due to {}".format(exc)) CLASSIFIERS = [ # Details at http://pypi.python.org/pypi?:action=list_classifiers 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ] AUTHOR = 'Rob Dennis, Eli Courtwright (Michael Foord & Nicola Larosa original maintainers)' AUTHOR_EMAIL = 'rdennis+configobj@gmail.com, eli@courtwright.org, michael@python.org, nico@tekNico.net' KEYWORDS = "config, ini, dictionary, application, admin, sysadmin, configuration, validation".split(', ') project = dict( name=NAME, version=VERSION, description=DESCRIPTION, long_description=LONG_DESCRIPTION, author=AUTHOR, author_email=AUTHOR_EMAIL, url=URL, py_modules=MODULES, package_dir={'': 'src'}, packages=PACKAGES, python_requires='>=3.7', classifiers=CLASSIFIERS, keywords=KEYWORDS, license='BSD-3-Clause', ) if __name__ == '__main__': setup(**project) configobj-5.0.9/setup_validate.py000066400000000000000000000037051467354007300170660ustar00rootroot00000000000000# setup.py # Install script for validate # Copyright (C) 2005-2014: # (name) : (email) # Michael Foord: fuzzyman AT voidspace DOT org DOT uk # Mark Andrews: mark AT la-la DOT com # Nicola Larosa: nico AT tekNico DOT net # Rob Dennis: rdennis AT gmail DOT com # Eli Courtwright: eli AT courtwright DOT org # This software is licensed under the terms of the BSD license. # http://opensource.org/licenses/BSD-3-Clause import sys from distutils.core import setup from validate import __version__ as VERSION NAME = 'validate' MODULES = 'validate', DESCRIPTION = 'Config file reading, writing, and validation.' URL = 'http://www.voidspace.org.uk/python/validate.html' DOWNLOAD_URL = "http://www.voidspace.org.uk/downloads/validate.py" LONG_DESCRIPTION = """`validate.py `_ is a module for validating values against a specification. It can be used with `ConfigObj `_, or as a standalone module. It is extensible, and as well as doing type conversion from strings, you can easily implement your own functions for transforming values in any way you please.""" CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ] AUTHOR = 'Michael Foord' AUTHOR_EMAIL = 'fuzzyman@voidspace.org.uk' KEYWORDS = "validation, schema, conversion, checking, configobj, config, configuration".split(', ') setup(name=NAME, version=VERSION, description=DESCRIPTION, long_description=LONG_DESCRIPTION, download_url=DOWNLOAD_URL, author=AUTHOR, author_email=AUTHOR_EMAIL, url=URL, py_modules=MODULES, classifiers=CLASSIFIERS, keywords=KEYWORDS ) configobj-5.0.9/src/000077500000000000000000000000001467354007300142655ustar00rootroot00000000000000configobj-5.0.9/src/configobj/000077500000000000000000000000001467354007300162255ustar00rootroot00000000000000configobj-5.0.9/src/configobj/__init__.py000066400000000000000000002557161467354007300203560ustar00rootroot00000000000000# configobj.py # A config file reader/writer that supports nested sections in config files. # Copyright (C) 2005-2014: # (name) : (email) # Michael Foord: fuzzyman AT voidspace DOT org DOT uk # Nicola Larosa: nico AT tekNico DOT net # Rob Dennis: rdennis AT gmail DOT com # Eli Courtwright: eli AT courtwright DOT org # This software is licensed under the terms of the BSD license. # http://opensource.org/licenses/BSD-3-Clause # ConfigObj 5 - main repository for documentation and issue tracking: # https://github.com/DiffSK/configobj import os import re import sys from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE from ._version import __version__ # imported lazily to avoid startup performance hit if it isn't used compiler = None # A dictionary mapping BOM to # the encoding to decode with, and what to set the # encoding attribute to. BOMS = { BOM_UTF8: ('utf_8', None), BOM_UTF16_BE: ('utf16_be', 'utf_16'), BOM_UTF16_LE: ('utf16_le', 'utf_16'), BOM_UTF16: ('utf_16', 'utf_16'), } # All legal variants of the BOM codecs. # TODO: the list of aliases is not meant to be exhaustive, is there a # better way ? BOM_LIST = { 'utf_16': 'utf_16', 'u16': 'utf_16', 'utf16': 'utf_16', 'utf-16': 'utf_16', 'utf16_be': 'utf16_be', 'utf_16_be': 'utf16_be', 'utf-16be': 'utf16_be', 'utf16_le': 'utf16_le', 'utf_16_le': 'utf16_le', 'utf-16le': 'utf16_le', 'utf_8': 'utf_8', 'u8': 'utf_8', 'utf': 'utf_8', 'utf8': 'utf_8', 'utf-8': 'utf_8', } # Map of encodings to the BOM to write. BOM_SET = { 'utf_8': BOM_UTF8, 'utf_16': BOM_UTF16, 'utf16_be': BOM_UTF16_BE, 'utf16_le': BOM_UTF16_LE, None: BOM_UTF8 } def match_utf8(encoding): return BOM_LIST.get(encoding.lower()) == 'utf_8' # Quote strings used for writing values squot = "'%s'" dquot = '"%s"' noquot = "%s" wspace_plus = ' \r\n\v\t\'"' tsquot = '"""%s"""' tdquot = "'''%s'''" # Sentinel for use in getattr calls to replace hasattr MISSING = object() __all__ = ( 'DEFAULT_INDENT_TYPE', 'DEFAULT_INTERPOLATION', 'ConfigObjError', 'NestingError', 'ParseError', 'DuplicateError', 'ConfigspecError', 'ConfigObj', 'SimpleVal', 'InterpolationError', 'InterpolationLoopError', 'MissingInterpolationOption', 'RepeatSectionError', 'ReloadError', 'UnreprError', 'UnknownType', 'flatten_errors', 'get_extra_values' ) DEFAULT_INTERPOLATION = 'configparser' DEFAULT_INDENT_TYPE = ' ' MAX_INTERPOL_DEPTH = 10 OPTION_DEFAULTS = { 'interpolation': True, 'raise_errors': False, 'list_values': True, 'create_empty': False, 'file_error': False, 'configspec': None, 'stringify': True, # option may be set to one of ('', ' ', '\t') 'indent_type': None, 'encoding': None, 'default_encoding': None, 'unrepr': False, 'write_empty_values': False, } def getObj(s): global compiler if compiler is None: import compiler s = "a=" + s p = compiler.parse(s) return p.getChildren()[1].getChildren()[0].getChildren()[1] class UnknownType(Exception): pass class Builder(object): def build(self, o): if m is None: raise UnknownType(o.__class__.__name__) return m(o) def build_List(self, o): return list(map(self.build, o.getChildren())) def build_Const(self, o): return o.value def build_Dict(self, o): d = {} i = iter(map(self.build, o.getChildren())) for el in i: d[el] = next(i) return d def build_Tuple(self, o): return tuple(self.build_List(o)) def build_Name(self, o): if o.name == 'None': return None if o.name == 'True': return True if o.name == 'False': return False # An undefined Name raise UnknownType('Undefined Name') def build_Add(self, o): real, imag = list(map(self.build_Const, o.getChildren())) try: real = float(real) except TypeError: raise UnknownType('Add') if not isinstance(imag, complex) or imag.real != 0.0: raise UnknownType('Add') return real+imag def build_Getattr(self, o): parent = self.build(o.expr) return getattr(parent, o.attrname) def build_UnarySub(self, o): return -self.build_Const(o.getChildren()[0]) def build_UnaryAdd(self, o): return self.build_Const(o.getChildren()[0]) _builder = Builder() def unrepr(s): if not s: return s # this is supposed to be safe import ast return ast.literal_eval(s) class ConfigObjError(SyntaxError): """ This is the base class for all errors that ConfigObj raises. It is a subclass of SyntaxError. """ def __init__(self, message='', line_number=None, line=''): self.line = line self.line_number = line_number SyntaxError.__init__(self, message) class NestingError(ConfigObjError): """ This error indicates a level of nesting that doesn't match. """ class ParseError(ConfigObjError): """ This error indicates that a line is badly written. It is neither a valid ``key = value`` line, nor a valid section marker line. """ class ReloadError(IOError): """ A 'reload' operation failed. This exception is a subclass of ``IOError``. """ def __init__(self): IOError.__init__(self, 'reload failed, filename is not set.') class DuplicateError(ConfigObjError): """ The keyword or section specified already exists. """ class ConfigspecError(ConfigObjError): """ An error occured whilst parsing a configspec. """ class InterpolationError(ConfigObjError): """Base class for the two interpolation errors.""" class InterpolationLoopError(InterpolationError): """Maximum interpolation depth exceeded in string interpolation.""" def __init__(self, option): InterpolationError.__init__( self, 'interpolation loop detected in value "%s".' % option) class RepeatSectionError(ConfigObjError): """ This error indicates additional sections in a section with a ``__many__`` (repeated) section. """ class MissingInterpolationOption(InterpolationError): """A value specified for interpolation was missing.""" def __init__(self, option): msg = 'missing option "%s" in interpolation.' % option InterpolationError.__init__(self, msg) class UnreprError(ConfigObjError): """An error parsing in unrepr mode.""" class InterpolationEngine(object): """ A helper class to help perform string interpolation. This class is an abstract base class; its descendants perform the actual work. """ # compiled regexp to use in self.interpolate() _KEYCRE = re.compile(r"%\(([^)]*)\)s") _cookie = '%' def __init__(self, section): # the Section instance that "owns" this engine self.section = section def interpolate(self, key, value): # short-cut if not self._cookie in value: return value def recursive_interpolate(key, value, section, backtrail): """The function that does the actual work. ``value``: the string we're trying to interpolate. ``section``: the section in which that string was found ``backtrail``: a dict to keep track of where we've been, to detect and prevent infinite recursion loops This is similar to a depth-first-search algorithm. """ # Have we been here already? if (key, section.name) in backtrail: # Yes - infinite loop detected raise InterpolationLoopError(key) # Place a marker on our backtrail so we won't come back here again backtrail[(key, section.name)] = 1 # Now start the actual work match = self._KEYCRE.search(value) while match: # The actual parsing of the match is implementation-dependent, # so delegate to our helper function k, v, s = self._parse_match(match) if k is None: # That's the signal that no further interpolation is needed replacement = v else: # Further interpolation may be needed to obtain final value replacement = recursive_interpolate(k, v, s, backtrail) # Replace the matched string with its final value start, end = match.span() value = ''.join((value[:start], replacement, value[end:])) new_search_start = start + len(replacement) # Pick up the next interpolation key, if any, for next time # through the while loop match = self._KEYCRE.search(value, new_search_start) # Now safe to come back here again; remove marker from backtrail del backtrail[(key, section.name)] return value # Back in interpolate(), all we have to do is kick off the recursive # function with appropriate starting values value = recursive_interpolate(key, value, self.section, {}) return value def _fetch(self, key): """Helper function to fetch values from owning section. Returns a 2-tuple: the value, and the section where it was found. """ # switch off interpolation before we try and fetch anything ! save_interp = self.section.main.interpolation self.section.main.interpolation = False # Start at section that "owns" this InterpolationEngine current_section = self.section while True: # try the current section first val = current_section.get(key) if val is not None and not isinstance(val, Section): break # try "DEFAULT" next val = current_section.get('DEFAULT', {}).get(key) if val is not None and not isinstance(val, Section): break # move up to parent and try again # top-level's parent is itself if current_section.parent is current_section: # reached top level, time to give up break current_section = current_section.parent # restore interpolation to previous value before returning self.section.main.interpolation = save_interp if val is None: raise MissingInterpolationOption(key) return val, current_section def _parse_match(self, match): """Implementation-dependent helper function. Will be passed a match object corresponding to the interpolation key we just found (e.g., "%(foo)s" or "$foo"). Should look up that key in the appropriate config file section (using the ``_fetch()`` helper function) and return a 3-tuple: (key, value, section) ``key`` is the name of the key we're looking for ``value`` is the value found for that key ``section`` is a reference to the section where it was found ``key`` and ``section`` should be None if no further interpolation should be performed on the resulting value (e.g., if we interpolated "$$" and returned "$"). """ raise NotImplementedError() class ConfigParserInterpolation(InterpolationEngine): """Behaves like ConfigParser.""" _cookie = '%' _KEYCRE = re.compile(r"%\(([^)]*)\)s") def _parse_match(self, match): key = match.group(1) value, section = self._fetch(key) return key, value, section class TemplateInterpolation(InterpolationEngine): """Behaves like string.Template.""" _cookie = '$' _delimiter = '$' _KEYCRE = re.compile(r""" \$(?: (?P\$) | # Two $ signs (?P[_a-z][_a-z0-9]*) | # $name format {(?P[^}]*)} # ${name} format ) """, re.IGNORECASE | re.VERBOSE) def _parse_match(self, match): # Valid name (in or out of braces): fetch value from section key = match.group('named') or match.group('braced') if key is not None: value, section = self._fetch(key) return key, value, section # Escaped delimiter (e.g., $$): return single delimiter if match.group('escaped') is not None: # Return None for key and section to indicate it's time to stop return None, self._delimiter, None # Anything else: ignore completely, just return it unchanged return None, match.group(), None interpolation_engines = { 'configparser': ConfigParserInterpolation, 'template': TemplateInterpolation, } def __newobj__(cls, *args): # Hack for pickle return cls.__new__(cls, *args) class Section(dict): """ A dictionary-like object that represents a section in a config file. It does string interpolation if the 'interpolation' attribute of the 'main' object is set to True. Interpolation is tried first from this object, then from the 'DEFAULT' section of this object, next from the parent and its 'DEFAULT' section, and so on until the main object is reached. A Section will behave like an ordered dictionary - following the order of the ``scalars`` and ``sections`` attributes. You can use this to change the order of members. Iteration follows the order: scalars, then sections. """ def __setstate__(self, state): dict.update(self, state[0]) self.__dict__.update(state[1]) def __reduce__(self): state = (dict(self), self.__dict__) return (__newobj__, (self.__class__,), state) def __init__(self, parent, depth, main, indict=None, name=None): """ * parent is the section above * depth is the depth level of this section * main is the main ConfigObj * indict is a dictionary to initialise the section with """ if indict is None: indict = {} dict.__init__(self) # used for nesting level *and* interpolation self.parent = parent # used for the interpolation attribute self.main = main # level of nesting depth of this Section self.depth = depth # purely for information self.name = name # self._initialise() # we do this explicitly so that __setitem__ is used properly # (rather than just passing to ``dict.__init__``) for entry, value in indict.items(): self[entry] = value def _initialise(self): # the sequence of scalar values in this Section self.scalars = [] # the sequence of sections in this Section self.sections = [] # for comments :-) self.comments = {} self.inline_comments = {} # the configspec self.configspec = None # for defaults self.defaults = [] self.default_values = {} self.extra_values = [] self._created = False def _interpolate(self, key, value): try: # do we already have an interpolation engine? engine = self._interpolation_engine except AttributeError: # not yet: first time running _interpolate(), so pick the engine name = self.main.interpolation if name == True: # note that "if name:" would be incorrect here # backwards-compatibility: interpolation=True means use default name = DEFAULT_INTERPOLATION name = name.lower() # so that "Template", "template", etc. all work class_ = interpolation_engines.get(name, None) if class_ is None: # invalid value for self.main.interpolation self.main.interpolation = False return value else: # save reference to engine so we don't have to do this again engine = self._interpolation_engine = class_(self) # let the engine do the actual work return engine.interpolate(key, value) def __getitem__(self, key): """Fetch the item and do string interpolation.""" val = dict.__getitem__(self, key) if self.main.interpolation: if isinstance(val, str): return self._interpolate(key, val) if isinstance(val, list): def _check(entry): if isinstance(entry, str): return self._interpolate(key, entry) return entry new = [_check(entry) for entry in val] if new != val: return new return val def __setitem__(self, key, value, unrepr=False): """ Correctly set a value. Making dictionary values Section instances. (We have to special case 'Section' instances - which are also dicts) Keys must be strings. Values need only be strings (or lists of strings) if ``main.stringify`` is set. ``unrepr`` must be set when setting a value to a dictionary, without creating a new sub-section. """ if not isinstance(key, str): raise ValueError('The key "%s" is not a string.' % key) # add the comment if key not in self.comments: self.comments[key] = [] self.inline_comments[key] = '' # remove the entry from defaults if key in self.defaults: self.defaults.remove(key) # if isinstance(value, Section): if key not in self: self.sections.append(key) dict.__setitem__(self, key, value) elif isinstance(value, dict) and not unrepr: # First create the new depth level, # then create the section if key not in self: self.sections.append(key) new_depth = self.depth + 1 dict.__setitem__( self, key, Section( self, new_depth, self.main, indict=value, name=key)) else: if key not in self: self.scalars.append(key) if not self.main.stringify: if isinstance(value, str): pass elif isinstance(value, (list, tuple)): for entry in value: if not isinstance(entry, str): raise TypeError('Value is not a string "%s".' % entry) else: raise TypeError('Value is not a string "%s".' % value) dict.__setitem__(self, key, value) def __delitem__(self, key): """Remove items from the sequence when deleting.""" dict. __delitem__(self, key) if key in self.scalars: self.scalars.remove(key) else: self.sections.remove(key) del self.comments[key] del self.inline_comments[key] def get(self, key, default=None): """A version of ``get`` that doesn't bypass string interpolation.""" try: return self[key] except KeyError: return default def update(self, indict): """ A version of update that uses our ``__setitem__``. """ for entry in indict: self[entry] = indict[entry] def pop(self, key, default=MISSING): """ 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised' """ try: val = self[key] except KeyError: if default is MISSING: raise val = default else: del self[key] return val def popitem(self): """Pops the first (key,val)""" sequence = (self.scalars + self.sections) if not sequence: raise KeyError(": 'popitem(): dictionary is empty'") key = sequence[0] val = self[key] del self[key] return key, val def clear(self): """ A version of clear that also affects scalars/sections Also clears comments and configspec. Leaves other attributes alone : depth/main/parent are not affected """ dict.clear(self) self.scalars = [] self.sections = [] self.comments = {} self.inline_comments = {} self.configspec = None self.defaults = [] self.extra_values = [] def setdefault(self, key, default=None): """A version of setdefault that sets sequence if appropriate.""" try: return self[key] except KeyError: self[key] = default return self[key] def items(self): """D.items() -> list of D's (key, value) pairs, as 2-tuples""" return list(zip((self.scalars + self.sections), list(self.values()))) def keys(self): """D.keys() -> list of D's keys""" return (self.scalars + self.sections) def values(self): """D.values() -> list of D's values""" return [self[key] for key in (self.scalars + self.sections)] def iteritems(self): """D.iteritems() -> an iterator over the (key, value) items of D""" return iter(list(self.items())) def iterkeys(self): """D.iterkeys() -> an iterator over the keys of D""" return iter((self.scalars + self.sections)) __iter__ = iterkeys def itervalues(self): """D.itervalues() -> an iterator over the values of D""" return iter(list(self.values())) def __repr__(self): """x.__repr__() <==> repr(x)""" def _getval(key): try: return self[key] except MissingInterpolationOption: return dict.__getitem__(self, key) return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) for key in (self.scalars + self.sections)]) __str__ = __repr__ __str__.__doc__ = "x.__str__() <==> str(x)" # Extra methods - not in a normal dictionary def dict(self): """ Return a deepcopy of self as a dictionary. All members that are ``Section`` instances are recursively turned to ordinary dictionaries - by calling their ``dict`` method. >>> n = a.dict() >>> n == a 1 >>> n is a 0 """ newdict = {} for entry in self: this_entry = self[entry] if isinstance(this_entry, Section): this_entry = this_entry.dict() elif isinstance(this_entry, list): # create a copy rather than a reference this_entry = list(this_entry) elif isinstance(this_entry, tuple): # create a copy rather than a reference this_entry = tuple(this_entry) newdict[entry] = this_entry return newdict def merge(self, indict): """ A recursive update - useful for merging config files. >>> a = '''[section1] ... option1 = True ... [[subsection]] ... more_options = False ... # end of file'''.splitlines() >>> b = '''# File is user.ini ... [section1] ... option1 = False ... # end of file'''.splitlines() >>> c1 = ConfigObj(b) >>> c2 = ConfigObj(a) >>> c2.merge(c1) >>> c2 ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) """ for key, val in list(indict.items()): if (key in self and isinstance(self[key], dict) and isinstance(val, dict)): self[key].merge(val) else: self[key] = val def rename(self, oldkey, newkey): """ Change a keyname to another, without changing position in sequence. Implemented so that transformations can be made on keys, as well as on values. (used by encode and decode) Also renames comments. """ if oldkey in self.scalars: the_list = self.scalars elif oldkey in self.sections: the_list = self.sections else: raise KeyError('Key "%s" not found.' % oldkey) pos = the_list.index(oldkey) # val = self[oldkey] dict.__delitem__(self, oldkey) dict.__setitem__(self, newkey, val) the_list.remove(oldkey) the_list.insert(pos, newkey) comm = self.comments[oldkey] inline_comment = self.inline_comments[oldkey] del self.comments[oldkey] del self.inline_comments[oldkey] self.comments[newkey] = comm self.inline_comments[newkey] = inline_comment def walk(self, function, raise_errors=True, call_on_sections=False, **keywargs): """ Walk every member and call a function on the keyword and value. Return a dictionary of the return values If the function raises an exception, raise the errror unless ``raise_errors=False``, in which case set the return value to ``False``. Any unrecognised keyword arguments you pass to walk, will be pased on to the function you pass in. Note: if ``call_on_sections`` is ``True`` then - on encountering a subsection, *first* the function is called for the *whole* subsection, and then recurses into it's members. This means your function must be able to handle strings, dictionaries and lists. This allows you to change the key of subsections as well as for ordinary members. The return value when called on the whole subsection has to be discarded. See the encode and decode methods for examples, including functions. .. admonition:: caution You can use ``walk`` to transform the names of members of a section but you mustn't add or delete members. >>> config = '''[XXXXsection] ... XXXXkey = XXXXvalue'''.splitlines() >>> cfg = ConfigObj(config) >>> cfg ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) >>> def transform(section, key): ... val = section[key] ... newkey = key.replace('XXXX', 'CLIENT1') ... section.rename(key, newkey) ... if isinstance(val, (tuple, list, dict)): ... pass ... else: ... val = val.replace('XXXX', 'CLIENT1') ... section[newkey] = val >>> cfg.walk(transform, call_on_sections=True) {'CLIENT1section': {'CLIENT1key': None}} >>> cfg ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) """ out = {} # scalars first for i in range(len(self.scalars)): entry = self.scalars[i] try: val = function(self, entry, **keywargs) # bound again in case name has changed entry = self.scalars[i] out[entry] = val except Exception: if raise_errors: raise else: entry = self.scalars[i] out[entry] = False # then sections for i in range(len(self.sections)): entry = self.sections[i] if call_on_sections: try: function(self, entry, **keywargs) except Exception: if raise_errors: raise else: entry = self.sections[i] out[entry] = False # bound again in case name has changed entry = self.sections[i] # previous result is discarded out[entry] = self[entry].walk( function, raise_errors=raise_errors, call_on_sections=call_on_sections, **keywargs) return out def as_bool(self, key): """ Accepts a key as input. The corresponding value must be a string or the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to retain compatibility with Python 2.2. If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns ``True``. If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns ``False``. ``as_bool`` is not case sensitive. Any other input will raise a ``ValueError``. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_bool('a') Traceback (most recent call last): ValueError: Value "fish" is neither True nor False >>> a['b'] = 'True' >>> a.as_bool('b') 1 >>> a['b'] = 'off' >>> a.as_bool('b') 0 """ val = self[key] if val == True: return True elif val == False: return False else: try: if not isinstance(val, str): # TODO: Why do we raise a KeyError here? raise KeyError() else: return self.main._bools[val.lower()] except KeyError: raise ValueError('Value "%s" is neither True nor False' % val) def as_int(self, key): """ A convenience method which coerces the specified value to an integer. If the value is an invalid literal for ``int``, a ``ValueError`` will be raised. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_int('a') Traceback (most recent call last): ValueError: invalid literal for int() with base 10: 'fish' >>> a['b'] = '1' >>> a.as_int('b') 1 >>> a['b'] = '3.2' >>> a.as_int('b') Traceback (most recent call last): ValueError: invalid literal for int() with base 10: '3.2' """ return int(self[key]) def as_float(self, key): """ A convenience method which coerces the specified value to a float. If the value is an invalid literal for ``float``, a ``ValueError`` will be raised. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_float('a') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ValueError: invalid literal for float(): fish >>> a['b'] = '1' >>> a.as_float('b') 1.0 >>> a['b'] = '3.2' >>> a.as_float('b') #doctest: +ELLIPSIS 3.2... """ return float(self[key]) def as_list(self, key): """ A convenience method which fetches the specified value, guaranteeing that it is a list. >>> a = ConfigObj() >>> a['a'] = 1 >>> a.as_list('a') [1] >>> a['a'] = (1,) >>> a.as_list('a') [1] >>> a['a'] = [1] >>> a.as_list('a') [1] """ result = self[key] if isinstance(result, (tuple, list)): return list(result) return [result] def restore_default(self, key): """ Restore (and return) default value for the specified key. This method will only work for a ConfigObj that was created with a configspec and has been validated. If there is no default value for this key, ``KeyError`` is raised. """ default = self.default_values[key] dict.__setitem__(self, key, default) if key not in self.defaults: self.defaults.append(key) return default def restore_defaults(self): """ Recursively restore default values to all members that have them. This method will only work for a ConfigObj that was created with a configspec and has been validated. It doesn't delete or modify entries without default values. """ for key in self.default_values: self.restore_default(key) for section in self.sections: self[section].restore_defaults() class ConfigObj(Section): """An object to read, create, and write config files.""" _keyword = re.compile(r'''^ # line start (\s*) # indentation ( # keyword (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'"=].*?) # no quotes ) \s*=\s* # divider (.*) # value (including list values and comments) $ # line end ''', re.VERBOSE) _sectionmarker = re.compile(r'''^ (\s*) # 1: indentation ((?:\[\s*)+) # 2: section marker open ( # 3: section name open (?:"\s*\S.*?\s*")| # at least one non-space with double quotes (?:'\s*\S.*?\s*')| # at least one non-space with single quotes (?:[^'"\s].*?) # at least one non-space unquoted ) # section name close ((?:\s*\])+) # 4: section marker close \s*(\#.*)? # 5: optional comment $''', re.VERBOSE) # this regexp pulls list values out as a single string # or single values and comments # FIXME: this regex adds a '' to the end of comma terminated lists # workaround in ``_handle_value`` _valueexp = re.compile(r'''^ (?: (?: ( (?: (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\#][^,\#]*?) # unquoted ) \s*,\s* # comma )* # match all list items ending in a comma (if any) ) ( (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\#\s][^,]*?)| # unquoted (?:(? 1: msg = "Parsing failed with several errors.\nFirst error %s" % info error = ConfigObjError(msg) else: error = self._errors[0] # set the errors attribute; it's a list of tuples: # (error_type, message, line_number) error.errors = self._errors # set the config attribute error.config = self raise error # delete private attributes del self._errors if configspec is None: self.configspec = None else: self._handle_configspec(configspec) def _initialise(self, options=None): if options is None: options = OPTION_DEFAULTS # initialise a few variables self.filename = None self._errors = [] self.raise_errors = options['raise_errors'] self.interpolation = options['interpolation'] self.list_values = options['list_values'] self.create_empty = options['create_empty'] self.file_error = options['file_error'] self.stringify = options['stringify'] self.indent_type = options['indent_type'] self.encoding = options['encoding'] self.default_encoding = options['default_encoding'] self.BOM = False self.newlines = None self.write_empty_values = options['write_empty_values'] self.unrepr = options['unrepr'] self.initial_comment = [] self.final_comment = [] self.configspec = None if self._inspec: self.list_values = False # Clear section attributes as well Section._initialise(self) def __repr__(self): def _getval(key): try: return self[key] except MissingInterpolationOption: return dict.__getitem__(self, key) return ('ConfigObj({%s})' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) for key in (self.scalars + self.sections)])) def _handle_bom(self, infile): """ Handle any BOM, and decode if necessary. If an encoding is specified, that *must* be used - but the BOM should still be removed (and the BOM attribute set). (If the encoding is wrongly specified, then a BOM for an alternative encoding won't be discovered or removed.) If an encoding is not specified, UTF8 or UTF16 BOM will be detected and removed. The BOM attribute will be set. UTF16 will be decoded to unicode. NOTE: This method must not be called with an empty ``infile``. Specifying the *wrong* encoding is likely to cause a ``UnicodeDecodeError``. ``infile`` must always be returned as a list of lines, but may be passed in as a single string. """ if ((self.encoding is not None) and (self.encoding.lower() not in BOM_LIST)): # No need to check for a BOM # the encoding specified doesn't have one # just decode return self._decode(infile, self.encoding) if isinstance(infile, (list, tuple)): line = infile[0] else: line = infile if isinstance(line, str): # it's already decoded and there's no need to do anything # else, just use the _decode utility method to handle # listifying appropriately return self._decode(infile, self.encoding) if self.encoding is not None: # encoding explicitly supplied # And it could have an associated BOM # TODO: if encoding is just UTF16 - we ought to check for both # TODO: big endian and little endian versions. enc = BOM_LIST[self.encoding.lower()] if enc == 'utf_16': # For UTF16 we try big endian and little endian for BOM, (encoding, final_encoding) in list(BOMS.items()): if not final_encoding: # skip UTF8 continue if infile.startswith(BOM): ### BOM discovered ##self.BOM = True # Don't need to remove BOM return self._decode(infile, encoding) # If we get this far, will *probably* raise a DecodeError # As it doesn't appear to start with a BOM return self._decode(infile, self.encoding) # Must be UTF8 BOM = BOM_SET[enc] if not line.startswith(BOM): return self._decode(infile, self.encoding) newline = line[len(BOM):] # BOM removed if isinstance(infile, (list, tuple)): infile[0] = newline else: infile = newline self.BOM = True return self._decode(infile, self.encoding) # No encoding specified - so we need to check for UTF8/UTF16 for BOM, (encoding, final_encoding) in list(BOMS.items()): if not isinstance(line, bytes) or not line.startswith(BOM): # didn't specify a BOM, or it's not a bytestring continue else: # BOM discovered self.encoding = final_encoding if not final_encoding: self.BOM = True # UTF8 # remove BOM newline = line[len(BOM):] if isinstance(infile, (list, tuple)): infile[0] = newline else: infile = newline # UTF-8 if isinstance(infile, str): return infile.splitlines(True) elif isinstance(infile, bytes): return infile.decode('utf-8').splitlines(True) else: return self._decode(infile, 'utf-8') # UTF16 - have to decode return self._decode(infile, encoding) # No BOM discovered and no encoding specified, default to UTF-8 if isinstance(infile, bytes): return infile.decode('utf-8').splitlines(True) else: return self._decode(infile, 'utf-8') def _a_to_u(self, aString): """Decode ASCII strings to unicode if a self.encoding is specified.""" if isinstance(aString, bytes) and self.encoding: return aString.decode(self.encoding) else: return aString def _decode(self, infile, encoding): """ Decode infile to unicode. Using the specified encoding. if is a string, it also needs converting to a list. """ if isinstance(infile, str): return infile.splitlines(True) if isinstance(infile, bytes): # NOTE: Could raise a ``UnicodeDecodeError`` if encoding: return infile.decode(encoding).splitlines(True) else: return infile.splitlines(True) if encoding: for i, line in enumerate(infile): if isinstance(line, bytes): # NOTE: The isinstance test here handles mixed lists of unicode/string # NOTE: But the decode will break on any non-string values # NOTE: Or could raise a ``UnicodeDecodeError`` infile[i] = line.decode(encoding) return infile def _decode_element(self, line): """Decode element to unicode if necessary.""" if isinstance(line, bytes) and self.default_encoding: return line.decode(self.default_encoding) else: return line # TODO: this may need to be modified def _str(self, value): """ Used by ``stringify`` within validate, to turn non-string values into strings. """ if not isinstance(value, str): # intentially 'str' because it's just whatever the "normal" # string type is for the python version we're dealing with return str(value) else: return value def _parse(self, infile): """Actually parse the config file.""" temp_list_values = self.list_values if self.unrepr: self.list_values = False comment_list = [] done_start = False this_section = self maxline = len(infile) - 1 cur_index = -1 reset_comment = False while cur_index < maxline: if reset_comment: comment_list = [] cur_index += 1 line = infile[cur_index] sline = line.strip() # do we have anything on the line ? if not sline or sline.startswith('#'): reset_comment = False comment_list.append(line) continue if not done_start: # preserve initial comment self.initial_comment = comment_list comment_list = [] done_start = True reset_comment = True # first we check if it's a section marker mat = self._sectionmarker.match(line) if mat is not None: # is a section line (indent, sect_open, sect_name, sect_close, comment) = mat.groups() if indent and (self.indent_type is None): self.indent_type = indent cur_depth = sect_open.count('[') if cur_depth != sect_close.count(']'): self._handle_error("Cannot compute the section depth", NestingError, infile, cur_index) continue if cur_depth < this_section.depth: # the new section is dropping back to a previous level try: parent = self._match_depth(this_section, cur_depth).parent except SyntaxError: self._handle_error("Cannot compute nesting level", NestingError, infile, cur_index) continue elif cur_depth == this_section.depth: # the new section is a sibling of the current section parent = this_section.parent elif cur_depth == this_section.depth + 1: # the new section is a child the current section parent = this_section else: self._handle_error("Section too nested", NestingError, infile, cur_index) continue sect_name = self._unquote(sect_name) if sect_name in parent: self._handle_error('Duplicate section name', DuplicateError, infile, cur_index) continue # create the new section this_section = Section( parent, cur_depth, self, name=sect_name) parent[sect_name] = this_section parent.inline_comments[sect_name] = comment parent.comments[sect_name] = comment_list continue # # it's not a section marker, # so it should be a valid ``key = value`` line mat = self._keyword.match(line) if mat is None: self._handle_error( 'Invalid line ({0!r}) (matched as neither section nor keyword)'.format(line), ParseError, infile, cur_index) else: # is a keyword value # value will include any inline comment (indent, key, value) = mat.groups() if indent and (self.indent_type is None): self.indent_type = indent # check for a multiline value if value[:3] in ['"""', "'''"]: try: value, comment, cur_index = self._multiline( value, infile, cur_index, maxline) except SyntaxError: self._handle_error( 'Parse error in multiline value', ParseError, infile, cur_index) continue else: if self.unrepr: comment = '' try: value = unrepr(value) except Exception as e: if type(e) == UnknownType: msg = 'Unknown name or type in value' else: msg = 'Parse error from unrepr-ing multiline value' self._handle_error(msg, UnreprError, infile, cur_index) continue else: if self.unrepr: comment = '' try: value = unrepr(value) except Exception as e: if isinstance(e, UnknownType): msg = 'Unknown name or type in value' else: msg = 'Parse error from unrepr-ing value' self._handle_error(msg, UnreprError, infile, cur_index) continue else: # extract comment and lists try: (value, comment) = self._handle_value(value) except SyntaxError: self._handle_error( 'Parse error in value', ParseError, infile, cur_index) continue # key = self._unquote(key) if key in this_section: self._handle_error( 'Duplicate keyword name', DuplicateError, infile, cur_index) continue # add the key. # we set unrepr because if we have got this far we will never # be creating a new section this_section.__setitem__(key, value, unrepr=True) this_section.inline_comments[key] = comment this_section.comments[key] = comment_list continue # if self.indent_type is None: # no indentation used, set the type accordingly self.indent_type = '' # preserve the final comment if not self and not self.initial_comment: self.initial_comment = comment_list elif not reset_comment: self.final_comment = comment_list self.list_values = temp_list_values def _match_depth(self, sect, depth): """ Given a section and a depth level, walk back through the sections parents to see if the depth level matches a previous section. Return a reference to the right section, or raise a SyntaxError. """ while depth < sect.depth: if sect is sect.parent: # we've reached the top level already raise SyntaxError() sect = sect.parent if sect.depth == depth: return sect # shouldn't get here raise SyntaxError() def _handle_error(self, text, ErrorClass, infile, cur_index): """ Handle an error according to the error settings. Either raise the error or store it. The error will have occured at ``cur_index`` """ line = infile[cur_index] cur_index += 1 message = '{0} at line {1}.'.format(text, cur_index) error = ErrorClass(message, cur_index, line) if self.raise_errors: # raise the error - parsing stops here raise error # store the error # reraise when parsing has finished self._errors.append(error) def _unquote(self, value): """Return an unquoted version of a value""" if not value: # should only happen during parsing of lists raise SyntaxError if (value[0] == value[-1]) and (value[0] in ('"', "'")): value = value[1:-1] return value def _quote(self, value, multiline=True): """ Return a safely quoted version of a value. Raise a ConfigObjError if the value cannot be safely quoted. If multiline is ``True`` (default) then use triple quotes if necessary. * Don't quote values that don't need it. * Recursively quote members of a list and return a comma joined list. * Multiline is ``False`` for lists. * Obey list syntax for empty and single member lists. If ``list_values=False`` then the value is only quoted if it contains a ``\\n`` (is multiline) or '#'. If ``write_empty_values`` is set, and the value is an empty string, it won't be quoted. """ if multiline and self.write_empty_values and value == '': # Only if multiline is set, so that it is used for values not # keys, and not values that are part of a list return '' if multiline and isinstance(value, (list, tuple)): if not value: return ',' elif len(value) == 1: return self._quote(value[0], multiline=False) + ',' return ', '.join([self._quote(val, multiline=False) for val in value]) if not isinstance(value, str): if self.stringify: # intentially 'str' because it's just whatever the "normal" # string type is for the python version we're dealing with value = str(value) else: raise TypeError('Value "%s" is not a string.' % value) if not value: return '""' no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value )) hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value) check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote if check_for_single: if not self.list_values: # we don't quote if ``list_values=False`` quot = noquot # for normal values either single or double quotes will do elif '\n' in value: # will only happen if multiline is off - e.g. '\n' in key raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) elif ((value[0] not in wspace_plus) and (value[-1] not in wspace_plus) and (',' not in value)): quot = noquot else: quot = self._get_single_quote(value) else: # if value has '\n' or "'" *and* '"', it will need triple quotes quot = self._get_triple_quote(value) if quot == noquot and '#' in value and self.list_values: quot = self._get_single_quote(value) return quot % value def _get_single_quote(self, value): if ("'" in value) and ('"' in value): raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) elif '"' in value: quot = squot else: quot = dquot return quot def _get_triple_quote(self, value): if (value.find('"""') != -1) and (value.find("'''") != -1): raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) if value.find('"""') == -1: quot = tdquot else: quot = tsquot return quot def _handle_value(self, value): """ Given a value string, unquote, remove comment, handle lists. (including empty and single member lists) """ if self._inspec: # Parsing a configspec so don't handle comments return (value, '') # do we look for lists in values ? if not self.list_values: mat = self._nolistvalue.match(value) if mat is None: raise SyntaxError() # NOTE: we don't unquote here return mat.groups() # mat = self._valueexp.match(value) if mat is None: # the value is badly constructed, probably badly quoted, # or an invalid list raise SyntaxError() (list_values, single, empty_list, comment) = mat.groups() if (list_values == '') and (single is None): # change this if you want to accept empty values raise SyntaxError() # NOTE: note there is no error handling from here if the regex # is wrong: then incorrect values will slip through if empty_list is not None: # the single comma - meaning an empty list return ([], comment) if single is not None: # handle empty values if list_values and not single: # FIXME: the '' is a workaround because our regex now matches # '' at the end of a list if it has a trailing comma single = None else: single = single or '""' single = self._unquote(single) if list_values == '': # not a list value return (single, comment) the_list = self._listvalueexp.findall(list_values) the_list = [self._unquote(val) for val in the_list] if single is not None: the_list += [single] return (the_list, comment) def _multiline(self, value, infile, cur_index, maxline): """Extract the value, where we are in a multiline situation.""" quot = value[:3] newvalue = value[3:] single_line = self._triple_quote[quot][0] multi_line = self._triple_quote[quot][1] mat = single_line.match(value) if mat is not None: retval = list(mat.groups()) retval.append(cur_index) return retval elif newvalue.find(quot) != -1: # somehow the triple quote is missing raise SyntaxError() # while cur_index < maxline: cur_index += 1 newvalue += '\n' line = infile[cur_index] if line.find(quot) == -1: newvalue += line else: # end of multiline, process it break else: # we've got to the end of the config, oops... raise SyntaxError() mat = multi_line.match(line) if mat is None: # a badly formed line raise SyntaxError() (value, comment) = mat.groups() return (newvalue + value, comment, cur_index) def _handle_configspec(self, configspec): """Parse the configspec.""" # FIXME: Should we check that the configspec was created with the # correct settings ? (i.e. ``list_values=False``) if not isinstance(configspec, ConfigObj): try: configspec = ConfigObj(configspec, raise_errors=True, file_error=True, _inspec=True) except ConfigObjError as e: # FIXME: Should these errors have a reference # to the already parsed ConfigObj ? raise ConfigspecError('Parsing configspec failed: %s' % e) except IOError as e: raise IOError('Reading configspec failed: %s' % e) self.configspec = configspec def _set_configspec(self, section, copy): """ Called by validate. Handles setting the configspec on subsections including sections to be validated by __many__ """ configspec = section.configspec many = configspec.get('__many__') if isinstance(many, dict): for entry in section.sections: if entry not in configspec: section[entry].configspec = many for entry in configspec.sections: if entry == '__many__': continue if entry not in section: section[entry] = {} section[entry]._created = True if copy: # copy comments section.comments[entry] = configspec.comments.get(entry, []) section.inline_comments[entry] = configspec.inline_comments.get(entry, '') # Could be a scalar when we expect a section if isinstance(section[entry], Section): section[entry].configspec = configspec[entry] def _write_line(self, indent_string, entry, this_entry, comment): """Write an individual line, for the write method""" # NOTE: the calls to self._quote here handles non-StringType values. if not self.unrepr: val = self._decode_element(self._quote(this_entry)) else: val = repr(this_entry) return '%s%s%s%s%s' % (indent_string, self._decode_element(self._quote(entry, multiline=False)), self._a_to_u(' = '), val, self._decode_element(comment)) def _write_marker(self, indent_string, depth, entry, comment): """Write a section marker line""" return '%s%s%s%s%s' % (indent_string, self._a_to_u('[' * depth), self._quote(self._decode_element(entry), multiline=False), self._a_to_u(']' * depth), self._decode_element(comment)) def _handle_comment(self, comment): """Deal with a comment.""" if not comment: return '' start = self.indent_type if not comment.startswith('#'): start += self._a_to_u(' # ') return (start + comment) # Public methods def write(self, outfile=None, section=None): """ Write the current ConfigObj as a file tekNico: FIXME: use StringIO instead of real files >>> filename = a.filename >>> a.filename = 'test.ini' >>> a.write() >>> a.filename = filename >>> a == ConfigObj('test.ini', raise_errors=True) 1 >>> import os >>> os.remove('test.ini') """ if self.indent_type is None: # this can be true if initialised from a dictionary self.indent_type = DEFAULT_INDENT_TYPE out = [] cs = self._a_to_u('#') csp = self._a_to_u('# ') if section is None: int_val = self.interpolation self.interpolation = False section = self for line in self.initial_comment: line = self._decode_element(line) stripped_line = line.strip() if stripped_line and not stripped_line.startswith(cs): line = csp + line out.append(line) indent_string = self.indent_type * section.depth for entry in (section.scalars + section.sections): if entry in section.defaults: # don't write out default values continue for comment_line in section.comments[entry]: comment_line = self._decode_element(comment_line.lstrip()) if comment_line and not comment_line.startswith(cs): comment_line = csp + comment_line out.append(indent_string + comment_line) this_entry = section[entry] comment = self._handle_comment(section.inline_comments[entry]) if isinstance(this_entry, Section): # a section out.append(self._write_marker( indent_string, this_entry.depth, entry, comment)) out.extend(self.write(section=this_entry)) else: out.append(self._write_line( indent_string, entry, this_entry, comment)) if section is self: for line in self.final_comment: line = self._decode_element(line) stripped_line = line.strip() if stripped_line and not stripped_line.startswith(cs): line = csp + line out.append(line) self.interpolation = int_val if section is not self: return out if (self.filename is None) and (outfile is None): # output a list of lines # might need to encode # NOTE: This will *screw* UTF16, each line will start with the BOM if self.encoding: out = [l.encode(self.encoding) for l in out] if (self.BOM and ((self.encoding is None) or (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): # Add the UTF8 BOM if not out: out.append('') out[0] = BOM_UTF8 + out[0] return out # Turn the list to a string, joined with correct newlines newline = self.newlines or os.linesep if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w' and sys.platform == 'win32' and newline == '\r\n'): # Windows specific hack to avoid writing '\r\r\n' newline = '\n' output = self._a_to_u(newline).join(out) if not output.endswith(newline): output += newline if isinstance(output, bytes): output_bytes = output else: output_bytes = output.encode(self.encoding or self.default_encoding or 'ascii') if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)): # Add the UTF8 BOM output_bytes = BOM_UTF8 + output_bytes if outfile is not None: outfile.write(output_bytes) else: with open(self.filename, 'wb') as h: h.write(output_bytes) def validate(self, validator, preserve_errors=False, copy=False, section=None): """ Test the ConfigObj against a configspec. It uses the ``validator`` object from *validate.py*. To run ``validate`` on the current ConfigObj, call: :: test = config.validate(validator) (Normally having previously passed in the configspec when the ConfigObj was created - you can dynamically assign a dictionary of checks to the ``configspec`` attribute of a section though). It returns ``True`` if everything passes, or a dictionary of pass/fails (True/False). If every member of a subsection passes, it will just have the value ``True``. (It also returns ``False`` if all members fail). In addition, it converts the values from strings to their native types if their checks pass (and ``stringify`` is set). If ``preserve_errors`` is ``True`` (``False`` is default) then instead of a marking a fail with a ``False``, it will preserve the actual exception object. This can contain info about the reason for failure. For example the ``VdtValueTooSmallError`` indicates that the value supplied was too small. If a value (or section) is missing it will still be marked as ``False``. You must have the validate module to use ``preserve_errors=True``. You can then use the ``flatten_errors`` function to turn your nested results dictionary into a flattened list of failures - useful for displaying meaningful error messages. """ if section is None: if self.configspec is None: raise ValueError('No configspec supplied.') if preserve_errors: # We do this once to remove a top level dependency on the validate module # Which makes importing configobj faster from configobj.validate import VdtMissingValue self._vdtMissingValue = VdtMissingValue section = self if copy: section.initial_comment = section.configspec.initial_comment section.final_comment = section.configspec.final_comment section.encoding = section.configspec.encoding section.BOM = section.configspec.BOM section.newlines = section.configspec.newlines section.indent_type = section.configspec.indent_type # # section.default_values.clear() #?? configspec = section.configspec self._set_configspec(section, copy) def validate_entry(entry, spec, val, missing, ret_true, ret_false): section.default_values.pop(entry, None) try: section.default_values[entry] = validator.get_default_value(configspec[entry]) except (KeyError, AttributeError, validator.baseErrorClass): # No default, bad default or validator has no 'get_default_value' # (e.g. SimpleVal) pass try: check = validator.check(spec, val, missing=missing ) except validator.baseErrorClass as e: if not preserve_errors or isinstance(e, self._vdtMissingValue): out[entry] = False else: # preserve the error out[entry] = e ret_false = False ret_true = False else: ret_false = False out[entry] = True if self.stringify or missing: # if we are doing type conversion # or the value is a supplied default if not self.stringify: if isinstance(check, (list, tuple)): # preserve lists check = [self._str(item) for item in check] elif missing and check is None: # convert the None from a default to a '' check = '' else: check = self._str(check) if (check != val) or missing: section[entry] = check if not copy and missing and entry not in section.defaults: section.defaults.append(entry) return ret_true, ret_false # out = {} ret_true = True ret_false = True unvalidated = [k for k in section.scalars if k not in configspec] incorrect_sections = [k for k in configspec.sections if k in section.scalars] incorrect_scalars = [k for k in configspec.scalars if k in section.sections] for entry in configspec.scalars: if entry in ('__many__', '___many___'): # reserved names continue if (not entry in section.scalars) or (entry in section.defaults): # missing entries # or entries from defaults missing = True val = None if copy and entry not in section.scalars: # copy comments section.comments[entry] = ( configspec.comments.get(entry, [])) section.inline_comments[entry] = ( configspec.inline_comments.get(entry, '')) # else: missing = False val = section[entry] ret_true, ret_false = validate_entry(entry, configspec[entry], val, missing, ret_true, ret_false) many = None if '__many__' in configspec.scalars: many = configspec['__many__'] elif '___many___' in configspec.scalars: many = configspec['___many___'] if many is not None: for entry in unvalidated: val = section[entry] ret_true, ret_false = validate_entry(entry, many, val, False, ret_true, ret_false) unvalidated = [] for entry in incorrect_scalars: ret_true = False if not preserve_errors: out[entry] = False else: ret_false = False msg = 'Value %r was provided as a section' % entry out[entry] = validator.baseErrorClass(msg) for entry in incorrect_sections: ret_true = False if not preserve_errors: out[entry] = False else: ret_false = False msg = 'Section %r was provided as a single value' % entry out[entry] = validator.baseErrorClass(msg) # Missing sections will have been created as empty ones when the # configspec was read. for entry in section.sections: # FIXME: this means DEFAULT is not copied in copy mode if section is self and entry == 'DEFAULT': continue if section[entry].configspec is None: unvalidated.append(entry) continue if copy: section.comments[entry] = configspec.comments.get(entry, []) section.inline_comments[entry] = configspec.inline_comments.get(entry, '') check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) out[entry] = check if check == False: ret_true = False elif check == True: ret_false = False else: ret_true = False section.extra_values = unvalidated if preserve_errors and not section._created: # If the section wasn't created (i.e. it wasn't missing) # then we can't return False, we need to preserve errors ret_false = False # if ret_false and preserve_errors and out: # If we are preserving errors, but all # the failures are from missing sections / values # then we can return False. Otherwise there is a # real failure that we need to preserve. ret_false = not any(out.values()) if ret_true: return True elif ret_false: return False return out def reset(self): """Clear ConfigObj instance and restore to 'freshly created' state.""" self.clear() self._initialise() # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload) # requires an empty dictionary self.configspec = None # Just to be sure ;-) self._original_configspec = None def reload(self): """ Reload a ConfigObj from file. This method raises a ``ReloadError`` if the ConfigObj doesn't have a filename attribute pointing to a file. """ if not isinstance(self.filename, str): raise ReloadError() filename = self.filename current_options = {} for entry in OPTION_DEFAULTS: if entry == 'configspec': continue current_options[entry] = getattr(self, entry) configspec = self._original_configspec current_options['configspec'] = configspec self.clear() self._initialise(current_options) self._load(filename, configspec) class SimpleVal(object): """ A simple validator. Can be used to check that all members expected are present. To use it, provide a configspec with all your members in (the value given will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` method of your ``ConfigObj``. ``validate`` will return ``True`` if all members are present, or a dictionary with True/False meaning present/missing. (Whole missing sections will be replaced with ``False``) """ def __init__(self): self.baseErrorClass = ConfigObjError def check(self, check, member, missing=False): """A dummy check method, always returns the value unchanged.""" if missing: raise self.baseErrorClass() return member def flatten_errors(cfg, res, levels=None, results=None): """ An example function that will turn a nested dictionary of results (as returned by ``ConfigObj.validate``) into a flat list. ``cfg`` is the ConfigObj instance being checked, ``res`` is the results dictionary returned by ``validate``. (This is a recursive function, so you shouldn't use the ``levels`` or ``results`` arguments - they are used by the function.) Returns a list of keys that failed. Each member of the list is a tuple:: ([list of sections...], key, result) If ``validate`` was called with ``preserve_errors=False`` (the default) then ``result`` will always be ``False``. *list of sections* is a flattened list of sections that the key was found in. If the section was missing (or a section was expected and a scalar provided - or vice-versa) then key will be ``None``. If the value (or section) was missing then ``result`` will be ``False``. If ``validate`` was called with ``preserve_errors=True`` and a value was present, but failed the check, then ``result`` will be the exception object returned. You can use this as a string that describes the failure. For example *The value "3" is of the wrong type*. """ if levels is None: # first time called levels = [] results = [] if res == True: return sorted(results) if res == False or isinstance(res, Exception): results.append((levels[:], None, res)) if levels: levels.pop() return sorted(results) for (key, val) in list(res.items()): if val == True: continue if isinstance(cfg.get(key), dict): # Go down one level levels.append(key) flatten_errors(cfg[key], val, levels, results) continue results.append((levels[:], key, val)) # # Go up one level if levels: levels.pop() # return sorted(results) def get_extra_values(conf, _prepend=()): """ Find all the values and sections not in the configspec from a validated ConfigObj. ``get_extra_values`` returns a list of tuples where each tuple represents either an extra section, or an extra value. The tuples contain two values, a tuple representing the section the value is in and the name of the extra values. For extra values in the top level section the first member will be an empty tuple. For values in the 'foo' section the first member will be ``('foo',)``. For members in the 'bar' subsection of the 'foo' section the first member will be ``('foo', 'bar')``. NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't been validated it will return an empty list. """ out = [] out.extend([(_prepend, name) for name in conf.extra_values]) for name in conf.sections: if name not in conf.extra_values: out.extend(get_extra_values(conf[name], _prepend + (name,))) return out """*A programming language is a medium of expression.* - Paul Graham""" configobj-5.0.9/src/configobj/_version.py000066400000000000000000000000261467354007300204210ustar00rootroot00000000000000__version__ = '5.0.9' configobj-5.0.9/src/configobj/validate.py000066400000000000000000001320501467354007300203710ustar00rootroot00000000000000# validate.py # A Validator object # Copyright (C) 2005-2014: # (name) : (email) # Michael Foord: fuzzyman AT voidspace DOT org DOT uk # Mark Andrews: mark AT la-la DOT com # Nicola Larosa: nico AT tekNico DOT net # Rob Dennis: rdennis AT gmail DOT com # Eli Courtwright: eli AT courtwright DOT org # This software is licensed under the terms of the BSD license. # http://opensource.org/licenses/BSD-3-Clause # ConfigObj 5 - main repository for documentation and issue tracking: # https://github.com/DiffSK/configobj """ The Validator object is used to check that supplied values conform to a specification. The value can be supplied as a string - e.g. from a config file. In this case the check will also *convert* the value to the required type. This allows you to add validation as a transparent layer to access data stored as strings. The validation checks that the data is correct *and* converts it to the expected type. Some standard checks are provided for basic data types. Additional checks are easy to write. They can be provided when the ``Validator`` is instantiated or added afterwards. The standard functions work with the following basic data types : * integers * floats * booleans * strings * ip_addr plus lists of these datatypes Adding additional checks is done through coding simple functions. The full set of standard checks are : * 'integer': matches integer values (including negative) Takes optional 'min' and 'max' arguments : :: integer() integer(3, 9) # any value from 3 to 9 integer(min=0) # any positive value integer(max=9) * 'float': matches float values Has the same parameters as the integer check. * 'boolean': matches boolean values - ``True`` or ``False`` Acceptable string values for True are : true, on, yes, 1 Acceptable string values for False are : false, off, no, 0 Any other value raises an error. * 'ip_addr': matches an Internet Protocol address, v.4, represented by a dotted-quad string, i.e. '1.2.3.4'. * 'string': matches any string. Takes optional keyword args 'min' and 'max' to specify min and max lengths of the string. * 'list': matches any list. Takes optional keyword args 'min', and 'max' to specify min and max sizes of the list. (Always returns a list.) * 'tuple': matches any tuple. Takes optional keyword args 'min', and 'max' to specify min and max sizes of the tuple. (Always returns a tuple.) * 'int_list': Matches a list of integers. Takes the same arguments as list. * 'float_list': Matches a list of floats. Takes the same arguments as list. * 'bool_list': Matches a list of boolean values. Takes the same arguments as list. * 'ip_addr_list': Matches a list of IP addresses. Takes the same arguments as list. * 'string_list': Matches a list of strings. Takes the same arguments as list. * 'mixed_list': Matches a list with different types in specific positions. List size must match the number of arguments. Each position can be one of : 'integer', 'float', 'ip_addr', 'string', 'boolean' So to specify a list with two strings followed by two integers, you write the check as : :: mixed_list('string', 'string', 'integer', 'integer') * 'pass': This check matches everything ! It never fails and the value is unchanged. It is also the default if no check is specified. * 'option': This check matches any from a list of options. You specify this check with : :: option('option 1', 'option 2', 'option 3') You can supply a default value (returned if no value is supplied) using the default keyword argument. You specify a list argument for default using a list constructor syntax in the check : :: checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3')) A badly formatted set of arguments will raise a ``VdtParamError``. """ __version__ = '1.0.1' __all__ = ( '__version__', 'dottedQuadToNum', 'numToDottedQuad', 'ValidateError', 'VdtUnknownCheckError', 'VdtParamError', 'VdtTypeError', 'VdtValueError', 'VdtValueTooSmallError', 'VdtValueTooBigError', 'VdtValueTooShortError', 'VdtValueTooLongError', 'VdtMissingValue', 'Validator', 'is_integer', 'is_float', 'is_boolean', 'is_list', 'is_tuple', 'is_ip_addr', 'is_string', 'is_int_list', 'is_bool_list', 'is_float_list', 'is_string_list', 'is_ip_addr_list', 'is_mixed_list', 'is_option', ) import re import sys from pprint import pprint _list_arg = re.compile(r''' (?: ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\( ( (?: \s* (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s\)][^,\)]*?) # unquoted ) \s*,\s* )* (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s\)][^,\)]*?) # unquoted )? # last one ) \) ) ''', re.VERBOSE | re.DOTALL) # two groups _list_members = re.compile(r''' ( (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s=][^,=]*?) # unquoted ) (?: (?:\s*,\s*)|(?:\s*$) # comma ) ''', re.VERBOSE | re.DOTALL) # one group _paramstring = r''' (?: ( (?: [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\( (?: \s* (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s\)][^,\)]*?) # unquoted ) \s*,\s* )* (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s\)][^,\)]*?) # unquoted )? # last one \) )| (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s=][^,=]*?)| # unquoted (?: # keyword argument [a-zA-Z_][a-zA-Z0-9_]*\s*=\s* (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\s=][^,=]*?) # unquoted ) ) ) ) (?: (?:\s*,\s*)|(?:\s*$) # comma ) ) ''' _matchstring = '^%s*' % _paramstring def dottedQuadToNum(ip): """ Convert decimal dotted quad string to long integer >>> int(dottedQuadToNum('1 ')) 1 >>> int(dottedQuadToNum(' 1.2')) 16777218 >>> int(dottedQuadToNum(' 1.2.3 ')) 16908291 >>> int(dottedQuadToNum('1.2.3.4')) 16909060 >>> dottedQuadToNum('255.255.255.255') 4294967295 >>> dottedQuadToNum('255.255.255.256') Traceback (most recent call last): ValueError: Not a good dotted-quad IP: 255.255.255.256 """ # import here to avoid it when ip_addr values are not used import socket, struct try: return struct.unpack('!L', socket.inet_aton(ip.strip()))[0] except socket.error: raise ValueError('Not a good dotted-quad IP: %s' % ip) return def numToDottedQuad(num): """ Convert int or long int to dotted quad string >>> numToDottedQuad(int(-1)) Traceback (most recent call last): ValueError: Not a good numeric IP: -1 >>> numToDottedQuad(int(1)) '0.0.0.1' >>> numToDottedQuad(int(16777218)) '1.0.0.2' >>> numToDottedQuad(int(16908291)) '1.2.0.3' >>> numToDottedQuad(int(16909060)) '1.2.3.4' >>> numToDottedQuad(int(4294967295)) '255.255.255.255' >>> numToDottedQuad(int(4294967296)) Traceback (most recent call last): ValueError: Not a good numeric IP: 4294967296 >>> numToDottedQuad(-1) Traceback (most recent call last): ValueError: Not a good numeric IP: -1 >>> numToDottedQuad(1) '0.0.0.1' >>> numToDottedQuad(16777218) '1.0.0.2' >>> numToDottedQuad(16908291) '1.2.0.3' >>> numToDottedQuad(16909060) '1.2.3.4' >>> numToDottedQuad(4294967295) '255.255.255.255' >>> numToDottedQuad(4294967296) Traceback (most recent call last): ValueError: Not a good numeric IP: 4294967296 """ # import here to avoid it when ip_addr values are not used import socket, struct # no need to intercept here, 4294967295L is fine if num > int(4294967295) or num < 0: raise ValueError('Not a good numeric IP: %s' % num) try: return socket.inet_ntoa( struct.pack('!L', int(num))) except (socket.error, struct.error, OverflowError): raise ValueError('Not a good numeric IP: %s' % num) class ValidateError(Exception): """ This error indicates that the check failed. It can be the base class for more specific errors. Any check function that fails ought to raise this error. (or a subclass) >>> raise ValidateError Traceback (most recent call last): ValidateError """ class VdtMissingValue(ValidateError): """No value was supplied to a check that needed one.""" class VdtUnknownCheckError(ValidateError): """An unknown check function was requested""" def __init__(self, value): """ >>> raise VdtUnknownCheckError('yoda') Traceback (most recent call last): VdtUnknownCheckError: the check "yoda" is unknown. """ ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,)) class VdtParamError(SyntaxError): """An incorrect parameter was passed""" def __init__(self, name, value): """ >>> raise VdtParamError('yoda', 'jedi') Traceback (most recent call last): VdtParamError: passed an incorrect value "jedi" for parameter "yoda". """ SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name)) class VdtTypeError(ValidateError): """The value supplied was of the wrong type""" def __init__(self, value): """ >>> raise VdtTypeError('jedi') Traceback (most recent call last): VdtTypeError: the value "jedi" is of the wrong type. """ ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,)) class VdtValueError(ValidateError): """The value supplied was of the correct type, but was not an allowed value.""" def __init__(self, value): """ >>> raise VdtValueError('jedi') Traceback (most recent call last): VdtValueError: the value "jedi" is unacceptable. """ ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,)) class VdtValueTooSmallError(VdtValueError): """The value supplied was of the correct type, but was too small.""" def __init__(self, value): """ >>> raise VdtValueTooSmallError('0') Traceback (most recent call last): VdtValueTooSmallError: the value "0" is too small. """ ValidateError.__init__(self, 'the value "%s" is too small.' % (value,)) class VdtValueTooBigError(VdtValueError): """The value supplied was of the correct type, but was too big.""" def __init__(self, value): """ >>> raise VdtValueTooBigError('1') Traceback (most recent call last): VdtValueTooBigError: the value "1" is too big. """ ValidateError.__init__(self, 'the value "%s" is too big.' % (value,)) class VdtValueTooShortError(VdtValueError): """The value supplied was of the correct type, but was too short.""" def __init__(self, value): """ >>> raise VdtValueTooShortError('jed') Traceback (most recent call last): VdtValueTooShortError: the value "jed" is too short. """ ValidateError.__init__( self, 'the value "%s" is too short.' % (value,)) class VdtValueTooLongError(VdtValueError): """The value supplied was of the correct type, but was too long.""" def __init__(self, value): """ >>> raise VdtValueTooLongError('jedie') Traceback (most recent call last): VdtValueTooLongError: the value "jedie" is too long. """ ValidateError.__init__(self, 'the value "%s" is too long.' % (value,)) class Validator(object): """ Validator is an object that allows you to register a set of 'checks'. These checks take input and test that it conforms to the check. This can also involve converting the value from a string into the correct datatype. The ``check`` method takes an input string which configures which check is to be used and applies that check to a supplied value. An example input string would be: 'int_range(param1, param2)' You would then provide something like: >>> def int_range_check(value, min, max): ... # turn min and max from strings to integers ... min = int(min) ... max = int(max) ... # check that value is of the correct type. ... # possible valid inputs are integers or strings ... # that represent integers ... if not isinstance(value, (int, str)): ... raise VdtTypeError(value) ... elif isinstance(value, str): ... # if we are given a string ... # attempt to convert to an integer ... try: ... value = int(value) ... except ValueError: ... raise VdtValueError(value) ... # check the value is between our constraints ... if not min <= value: ... raise VdtValueTooSmallError(value) ... if not value <= max: ... raise VdtValueTooBigError(value) ... return value >>> fdict = {'int_range': int_range_check} >>> vtr1 = Validator(fdict) >>> vtr1.check('int_range(20, 40)', '30') 30 >>> vtr1.check('int_range(20, 40)', '60') Traceback (most recent call last): VdtValueTooBigError: the value "60" is too big. New functions can be added with : :: >>> vtr2 = Validator() >>> vtr2.functions['int_range'] = int_range_check Or by passing in a dictionary of functions when Validator is instantiated. Your functions *can* use keyword arguments, but the first argument should always be 'value'. If the function doesn't take additional arguments, the parentheses are optional in the check. It can be written with either of : :: keyword = function_name keyword = function_name() The first program to utilise Validator() was Michael Foord's ConfigObj, an alternative to ConfigParser which supports lists and can validate a config file using a config schema. For more details on using Validator with ConfigObj see: https://configobj.readthedocs.org/en/latest/configobj.html """ # this regex does the initial parsing of the checks _func_re = re.compile(r'([^\(\)]+?)\((.*)\)', re.DOTALL) # this regex takes apart keyword arguments _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) # this regex finds keyword=list(....) type values _list_arg = _list_arg # this regex takes individual values out of lists - in one pass _list_members = _list_members # These regexes check a set of arguments for validity # and then pull the members out _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) def __init__(self, functions=None): """ >>> vtri = Validator() """ self.functions = { '': self._pass, 'integer': is_integer, 'float': is_float, 'boolean': is_boolean, 'ip_addr': is_ip_addr, 'string': is_string, 'list': is_list, 'tuple': is_tuple, 'int_list': is_int_list, 'float_list': is_float_list, 'bool_list': is_bool_list, 'ip_addr_list': is_ip_addr_list, 'string_list': is_string_list, 'mixed_list': is_mixed_list, 'pass': self._pass, 'option': is_option, 'force_list': force_list, } if functions is not None: self.functions.update(functions) # tekNico: for use by ConfigObj self.baseErrorClass = ValidateError self._cache = {} def check(self, check, value, missing=False): """ Usage: check(check, value) Arguments: check: string representing check to apply (including arguments) value: object to be checked Returns value, converted to correct type if necessary If the check fails, raises a ``ValidateError`` subclass. >>> vtor.check('yoda', '') Traceback (most recent call last): VdtUnknownCheckError: the check "yoda" is unknown. >>> vtor.check('yoda()', '') Traceback (most recent call last): VdtUnknownCheckError: the check "yoda" is unknown. >>> vtor.check('string(default="")', '', missing=True) '' """ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) if missing: if default is None: # no information needed here - to be handled by caller raise VdtMissingValue() value = self._handle_none(default) if value is None: return None return self._check_value(value, fun_name, fun_args, fun_kwargs) def _handle_none(self, value): if value == 'None': return None elif value in ("'None'", '"None"'): # Special case a quoted None value = self._unquote(value) return value def _parse_with_caching(self, check): if check in self._cache: fun_name, fun_args, fun_kwargs, default = self._cache[check] # We call list and dict below to work with *copies* of the data # rather than the original (which are mutable of course) fun_args = list(fun_args) fun_kwargs = dict(fun_kwargs) else: fun_name, fun_args, fun_kwargs, default = self._parse_check(check) fun_kwargs = dict([(str(key), value) for (key, value) in list(fun_kwargs.items())]) self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default return fun_name, fun_args, fun_kwargs, default def _check_value(self, value, fun_name, fun_args, fun_kwargs): try: fun = self.functions[fun_name] except KeyError: raise VdtUnknownCheckError(fun_name) else: return fun(value, *fun_args, **fun_kwargs) def _parse_check(self, check): fun_match = self._func_re.match(check) if fun_match: fun_name = fun_match.group(1) arg_string = fun_match.group(2) arg_match = self._matchfinder.match(arg_string) if arg_match is None: # Bad syntax raise VdtParamError('Bad syntax in check "%s".' % check) fun_args = [] fun_kwargs = {} # pull out args of group 2 for arg in self._paramfinder.findall(arg_string): # args may need whitespace removing (before removing quotes) arg = arg.strip() listmatch = self._list_arg.match(arg) if listmatch: key, val = self._list_handle(listmatch) fun_kwargs[key] = val continue keymatch = self._key_arg.match(arg) if keymatch: val = keymatch.group(2) if not val in ("'None'", '"None"'): # Special case a quoted None val = self._unquote(val) fun_kwargs[keymatch.group(1)] = val continue fun_args.append(self._unquote(arg)) else: # allows for function names without (args) return check, (), {}, None # Default must be deleted if the value is specified too, # otherwise the check function will get a spurious "default" keyword arg default = fun_kwargs.pop('default', None) return fun_name, fun_args, fun_kwargs, default def _unquote(self, val): """Unquote a value if necessary.""" if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]): val = val[1:-1] return val def _list_handle(self, listmatch): """Take apart a ``keyword=list('val, 'val')`` type string.""" out = [] name = listmatch.group(1) args = listmatch.group(2) for arg in self._list_members.findall(args): out.append(self._unquote(arg)) return name, out def _pass(self, value): """ Dummy check that always passes >>> vtor.check('', 0) 0 >>> vtor.check('', '0') '0' """ return value def get_default_value(self, check): """ Given a check, return the default value for the check (converted to the right type). If the check doesn't specify a default value then a ``KeyError`` will be raised. """ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) if default is None: raise KeyError('Check "%s" has no default value.' % check) value = self._handle_none(default) if value is None: return value return self._check_value(value, fun_name, fun_args, fun_kwargs) def _is_num_param(names, values, to_float=False): """ Return numbers from inputs or raise VdtParamError. Lets ``None`` pass through. Pass in keyword argument ``to_float=True`` to use float for the conversion rather than int. >>> _is_num_param(('', ''), (0, 1.0)) [0, 1] >>> _is_num_param(('', ''), (0, 1.0), to_float=True) [0.0, 1.0] >>> _is_num_param(('a'), ('a')) Traceback (most recent call last): VdtParamError: passed an incorrect value "a" for parameter "a". """ fun = to_float and float or int out_params = [] for (name, val) in zip(names, values): if val is None: out_params.append(val) elif isinstance(val, (int, float, str)): try: out_params.append(fun(val)) except ValueError as e: raise VdtParamError(name, val) else: raise VdtParamError(name, val) return out_params # built in checks # you can override these by setting the appropriate name # in Validator.functions # note: if the params are specified wrongly in your input string, # you will also raise errors. def is_integer(value, min=None, max=None): """ A check that tests that a given value is an integer (int) and optionally, between bounds. A negative value is accepted, while a float will fail. If the value is a string, then the conversion is done - if possible. Otherwise a VdtError is raised. >>> vtor.check('integer', '-1') -1 >>> vtor.check('integer', '0') 0 >>> vtor.check('integer', 9) 9 >>> vtor.check('integer', 'a') Traceback (most recent call last): VdtTypeError: the value "a" is of the wrong type. >>> vtor.check('integer', '2.2') Traceback (most recent call last): VdtTypeError: the value "2.2" is of the wrong type. >>> vtor.check('integer(10)', '20') 20 >>> vtor.check('integer(max=20)', '15') 15 >>> vtor.check('integer(10)', '9') Traceback (most recent call last): VdtValueTooSmallError: the value "9" is too small. >>> vtor.check('integer(10)', 9) Traceback (most recent call last): VdtValueTooSmallError: the value "9" is too small. >>> vtor.check('integer(max=20)', '35') Traceback (most recent call last): VdtValueTooBigError: the value "35" is too big. >>> vtor.check('integer(max=20)', 35) Traceback (most recent call last): VdtValueTooBigError: the value "35" is too big. >>> vtor.check('integer(0, 9)', False) 0 """ (min_val, max_val) = _is_num_param(('min', 'max'), (min, max)) if not isinstance(value, (int, str)): raise VdtTypeError(value) if isinstance(value, str): # if it's a string - does it represent an integer ? try: value = int(value) except ValueError: raise VdtTypeError(value) if (min_val is not None) and (value < min_val): raise VdtValueTooSmallError(value) if (max_val is not None) and (value > max_val): raise VdtValueTooBigError(value) return value def is_float(value, min=None, max=None): """ A check that tests that a given value is a float (an integer will be accepted), and optionally - that it is between bounds. If the value is a string, then the conversion is done - if possible. Otherwise a VdtError is raised. This can accept negative values. >>> vtor.check('float', '2') 2.0 From now on we multiply the value to avoid comparing decimals >>> vtor.check('float', '-6.8') * 10 -68.0 >>> vtor.check('float', '12.2') * 10 122.0 >>> vtor.check('float', 8.4) * 10 84.0 >>> vtor.check('float', 'a') Traceback (most recent call last): VdtTypeError: the value "a" is of the wrong type. >>> vtor.check('float(10.1)', '10.2') * 10 102.0 >>> vtor.check('float(max=20.2)', '15.1') * 10 151.0 >>> vtor.check('float(10.0)', '9.0') Traceback (most recent call last): VdtValueTooSmallError: the value "9.0" is too small. >>> vtor.check('float(max=20.0)', '35.0') Traceback (most recent call last): VdtValueTooBigError: the value "35.0" is too big. """ (min_val, max_val) = _is_num_param( ('min', 'max'), (min, max), to_float=True) if not isinstance(value, (int, float, str)): raise VdtTypeError(value) if not isinstance(value, float): # if it's a string - does it represent a float ? try: value = float(value) except ValueError: raise VdtTypeError(value) if (min_val is not None) and (value < min_val): raise VdtValueTooSmallError(value) if (max_val is not None) and (value > max_val): raise VdtValueTooBigError(value) return value bool_dict = { True: True, 'on': True, '1': True, 'true': True, 'yes': True, False: False, 'off': False, '0': False, 'false': False, 'no': False, } def is_boolean(value): """ Check if the value represents a boolean. >>> vtor.check('boolean', 0) 0 >>> vtor.check('boolean', False) 0 >>> vtor.check('boolean', '0') 0 >>> vtor.check('boolean', 'off') 0 >>> vtor.check('boolean', 'false') 0 >>> vtor.check('boolean', 'no') 0 >>> vtor.check('boolean', 'nO') 0 >>> vtor.check('boolean', 'NO') 0 >>> vtor.check('boolean', 1) 1 >>> vtor.check('boolean', True) 1 >>> vtor.check('boolean', '1') 1 >>> vtor.check('boolean', 'on') 1 >>> vtor.check('boolean', 'true') 1 >>> vtor.check('boolean', 'yes') 1 >>> vtor.check('boolean', 'Yes') 1 >>> vtor.check('boolean', 'YES') 1 >>> vtor.check('boolean', '') Traceback (most recent call last): VdtTypeError: the value "" is of the wrong type. >>> vtor.check('boolean', 'up') Traceback (most recent call last): VdtTypeError: the value "up" is of the wrong type. """ if isinstance(value, str): try: return bool_dict[value.lower()] except KeyError: raise VdtTypeError(value) # we do an equality test rather than an identity test # this ensures Python 2.2 compatibilty # and allows 0 and 1 to represent True and False if value == False: return False elif value == True: return True else: raise VdtTypeError(value) def is_ip_addr(value): """ Check that the supplied value is an Internet Protocol address, v.4, represented by a dotted-quad string, i.e. '1.2.3.4'. >>> vtor.check('ip_addr', '1 ') '1' >>> vtor.check('ip_addr', ' 1.2') '1.2' >>> vtor.check('ip_addr', ' 1.2.3 ') '1.2.3' >>> vtor.check('ip_addr', '1.2.3.4') '1.2.3.4' >>> vtor.check('ip_addr', '0.0.0.0') '0.0.0.0' >>> vtor.check('ip_addr', '255.255.255.255') '255.255.255.255' >>> vtor.check('ip_addr', '255.255.255.256') Traceback (most recent call last): VdtValueError: the value "255.255.255.256" is unacceptable. >>> vtor.check('ip_addr', '1.2.3.4.5') Traceback (most recent call last): VdtValueError: the value "1.2.3.4.5" is unacceptable. >>> vtor.check('ip_addr', 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. """ if not isinstance(value, str): raise VdtTypeError(value) value = value.strip() try: dottedQuadToNum(value) except ValueError: raise VdtValueError(value) return value def is_list(value, min=None, max=None): """ Check that the value is a list of values. You can optionally specify the minimum and maximum number of members. It does no check on list members. >>> vtor.check('list', ()) [] >>> vtor.check('list', []) [] >>> vtor.check('list', (1, 2)) [1, 2] >>> vtor.check('list', [1, 2]) [1, 2] >>> vtor.check('list(3)', (1, 2)) Traceback (most recent call last): VdtValueTooShortError: the value "(1, 2)" is too short. >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6)) Traceback (most recent call last): VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4)) [1, 2, 3, 4] >>> vtor.check('list', 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. >>> vtor.check('list', '12') Traceback (most recent call last): VdtTypeError: the value "12" is of the wrong type. """ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) if isinstance(value, str): raise VdtTypeError(value) try: num_members = len(value) except TypeError: raise VdtTypeError(value) if min_len is not None and num_members < min_len: raise VdtValueTooShortError(value) if max_len is not None and num_members > max_len: raise VdtValueTooLongError(value) return list(value) def is_tuple(value, min=None, max=None): """ Check that the value is a tuple of values. You can optionally specify the minimum and maximum number of members. It does no check on members. >>> vtor.check('tuple', ()) () >>> vtor.check('tuple', []) () >>> vtor.check('tuple', (1, 2)) (1, 2) >>> vtor.check('tuple', [1, 2]) (1, 2) >>> vtor.check('tuple(3)', (1, 2)) Traceback (most recent call last): VdtValueTooShortError: the value "(1, 2)" is too short. >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6)) Traceback (most recent call last): VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long. >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4)) (1, 2, 3, 4) >>> vtor.check('tuple', 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. >>> vtor.check('tuple', '12') Traceback (most recent call last): VdtTypeError: the value "12" is of the wrong type. """ return tuple(is_list(value, min, max)) def is_string(value, min=None, max=None): """ Check that the supplied value is a string. You can optionally specify the minimum and maximum number of members. >>> vtor.check('string', '0') '0' >>> vtor.check('string', 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. >>> vtor.check('string(2)', '12') '12' >>> vtor.check('string(2)', '1') Traceback (most recent call last): VdtValueTooShortError: the value "1" is too short. >>> vtor.check('string(min=2, max=3)', '123') '123' >>> vtor.check('string(min=2, max=3)', '1234') Traceback (most recent call last): VdtValueTooLongError: the value "1234" is too long. """ if not isinstance(value, str): raise VdtTypeError(value) (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) try: num_members = len(value) except TypeError: raise VdtTypeError(value) if min_len is not None and num_members < min_len: raise VdtValueTooShortError(value) if max_len is not None and num_members > max_len: raise VdtValueTooLongError(value) return value def is_int_list(value, min=None, max=None): """ Check that the value is a list of integers. You can optionally specify the minimum and maximum number of members. Each list member is checked that it is an integer. >>> vtor.check('int_list', ()) [] >>> vtor.check('int_list', []) [] >>> vtor.check('int_list', (1, 2)) [1, 2] >>> vtor.check('int_list', [1, 2]) [1, 2] >>> vtor.check('int_list', [1, 'a']) Traceback (most recent call last): VdtTypeError: the value "a" is of the wrong type. """ return [is_integer(mem) for mem in is_list(value, min, max)] def is_bool_list(value, min=None, max=None): """ Check that the value is a list of booleans. You can optionally specify the minimum and maximum number of members. Each list member is checked that it is a boolean. >>> vtor.check('bool_list', ()) [] >>> vtor.check('bool_list', []) [] >>> check_res = vtor.check('bool_list', (True, False)) >>> check_res == [True, False] 1 >>> check_res = vtor.check('bool_list', [True, False]) >>> check_res == [True, False] 1 >>> vtor.check('bool_list', [True, 'a']) Traceback (most recent call last): VdtTypeError: the value "a" is of the wrong type. """ return [is_boolean(mem) for mem in is_list(value, min, max)] def is_float_list(value, min=None, max=None): """ Check that the value is a list of floats. You can optionally specify the minimum and maximum number of members. Each list member is checked that it is a float. >>> vtor.check('float_list', ()) [] >>> vtor.check('float_list', []) [] >>> vtor.check('float_list', (1, 2.0)) [1.0, 2.0] >>> vtor.check('float_list', [1, 2.0]) [1.0, 2.0] >>> vtor.check('float_list', [1, 'a']) Traceback (most recent call last): VdtTypeError: the value "a" is of the wrong type. """ return [is_float(mem) for mem in is_list(value, min, max)] def is_string_list(value, min=None, max=None): """ Check that the value is a list of strings. You can optionally specify the minimum and maximum number of members. Each list member is checked that it is a string. >>> vtor.check('string_list', ()) [] >>> vtor.check('string_list', []) [] >>> vtor.check('string_list', ('a', 'b')) ['a', 'b'] >>> vtor.check('string_list', ['a', 1]) Traceback (most recent call last): VdtTypeError: the value "1" is of the wrong type. >>> vtor.check('string_list', 'hello') Traceback (most recent call last): VdtTypeError: the value "hello" is of the wrong type. """ if isinstance(value, str): raise VdtTypeError(value) return [is_string(mem) for mem in is_list(value, min, max)] def is_ip_addr_list(value, min=None, max=None): """ Check that the value is a list of IP addresses. You can optionally specify the minimum and maximum number of members. Each list member is checked that it is an IP address. >>> vtor.check('ip_addr_list', ()) [] >>> vtor.check('ip_addr_list', []) [] >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8')) ['1.2.3.4', '5.6.7.8'] >>> vtor.check('ip_addr_list', ['a']) Traceback (most recent call last): VdtValueError: the value "a" is unacceptable. """ return [is_ip_addr(mem) for mem in is_list(value, min, max)] def force_list(value, min=None, max=None): """ Check that a value is a list, coercing strings into a list with one member. Useful where users forget the trailing comma that turns a single value into a list. You can optionally specify the minimum and maximum number of members. A minumum of greater than one will fail if the user only supplies a string. >>> vtor.check('force_list', ()) [] >>> vtor.check('force_list', []) [] >>> vtor.check('force_list', 'hello') ['hello'] """ if not isinstance(value, (list, tuple)): value = [value] return is_list(value, min, max) fun_dict = { 'integer': is_integer, 'float': is_float, 'ip_addr': is_ip_addr, 'string': is_string, 'boolean': is_boolean, } def is_mixed_list(value, *args): """ Check that the value is a list. Allow specifying the type of each member. Work on lists of specific lengths. You specify each member as a positional argument specifying type Each type should be one of the following strings : 'integer', 'float', 'ip_addr', 'string', 'boolean' So you can specify a list of two strings, followed by two integers as : mixed_list('string', 'string', 'integer', 'integer') The length of the list must match the number of positional arguments you supply. >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')" >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True)) >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] 1 >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True')) >>> check_res == [1, 2.0, '1.2.3.4', 'a', True] 1 >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True)) Traceback (most recent call last): VdtTypeError: the value "b" is of the wrong type. >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a')) Traceback (most recent call last): VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short. >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b')) Traceback (most recent call last): VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long. >>> vtor.check(mix_str, 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. >>> vtor.check('mixed_list("yoda")', ('a')) Traceback (most recent call last): VdtParamError: passed an incorrect value "KeyError('yoda',)" for parameter "'mixed_list'" """ try: length = len(value) except TypeError: raise VdtTypeError(value) if length < len(args): raise VdtValueTooShortError(value) elif length > len(args): raise VdtValueTooLongError(value) try: return [fun_dict[arg](val) for arg, val in zip(args, value)] except KeyError as e: raise VdtParamError('mixed_list', e) def is_option(value, *options): """ This check matches the value to any of a set of options. >>> vtor.check('option("yoda", "jedi")', 'yoda') 'yoda' >>> vtor.check('option("yoda", "jedi")', 'jed') Traceback (most recent call last): VdtValueError: the value "jed" is unacceptable. >>> vtor.check('option("yoda", "jedi")', 0) Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. """ if not isinstance(value, str): raise VdtTypeError(value) if not value in options: raise VdtValueError(value) return value def _test(value, *args, **keywargs): """ A function that exists for test purposes. >>> checks = [ ... '3, 6, min=1, max=3, test=list(a, b, c)', ... '3', ... '3, 6', ... '3,', ... 'min=1, test="a b c"', ... 'min=5, test="a, b, c"', ... 'min=1, max=3, test="a, b, c"', ... 'min=-100, test=-99', ... 'min=1, max=3', ... '3, 6, test="36"', ... '3, 6, test="a, b, c"', ... '3, max=3, test=list("a", "b", "c")', ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''', ... "test='x=fish(3)'", ... ] >>> v = Validator({'test': _test}) >>> for entry in checks: ... pprint(v.check(('test(%s)' % entry), 3)) (3, ('3', '6'), {'max': '3', 'min': '1', 'test': ['a', 'b', 'c']}) (3, ('3',), {}) (3, ('3', '6'), {}) (3, ('3',), {}) (3, (), {'min': '1', 'test': 'a b c'}) (3, (), {'min': '5', 'test': 'a, b, c'}) (3, (), {'max': '3', 'min': '1', 'test': 'a, b, c'}) (3, (), {'min': '-100', 'test': '-99'}) (3, (), {'max': '3', 'min': '1'}) (3, ('3', '6'), {'test': '36'}) (3, ('3', '6'), {'test': 'a, b, c'}) (3, ('3',), {'max': '3', 'test': ['a', 'b', 'c']}) (3, ('3',), {'max': '3', 'test': ["'a'", 'b', 'x=(c)']}) (3, (), {'test': 'x=fish(3)'}) >>> v = Validator() >>> v.check('integer(default=6)', '3') 3 >>> v.check('integer(default=6)', None, True) 6 >>> v.get_default_value('integer(default=6)') 6 >>> v.get_default_value('float(default=6)') 6.0 >>> v.get_default_value('pass(default=None)') >>> v.get_default_value("string(default='None')") 'None' >>> v.get_default_value('pass') Traceback (most recent call last): KeyError: 'Check "pass" has no default value.' >>> v.get_default_value('pass(default=list(1, 2, 3, 4))') ['1', '2', '3', '4'] >>> v = Validator() >>> v.check("pass(default=None)", None, True) >>> v.check("pass(default='None')", None, True) 'None' >>> v.check('pass(default="None")', None, True) 'None' >>> v.check('pass(default=list(1, 2, 3, 4))', None, True) ['1', '2', '3', '4'] Bug test for unicode arguments >>> v = Validator() >>> v.check('string(min=4)', 'test') == 'test' True >>> v = Validator() >>> v.get_default_value('string(min=4, default="1234")') == '1234' True >>> v.check('string(min=4, default="1234")', 'test') == 'test' True >>> v = Validator() >>> default = v.get_default_value('string(default=None)') >>> default == None 1 """ return (value, args, keywargs) def _test2(): """ >>> >>> v = Validator() >>> v.get_default_value('string(default="#ff00dd")') '#ff00dd' >>> v.get_default_value('integer(default=3) # comment') 3 """ def _test3(): r""" >>> vtor.check('string(default="")', '', missing=True) '' >>> vtor.check('string(default="\n")', '', missing=True) '\n' >>> print(vtor.check('string(default="\n")', '', missing=True)) >>> vtor.check('string()', '\n') '\n' >>> vtor.check('string(default="\n\n\n")', '', missing=True) '\n\n\n' >>> vtor.check('string()', 'random \n text goes here\n\n') 'random \n text goes here\n\n' >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")', ... '', missing=True) ' \nrandom text\ngoes \n here\n\n ' >>> vtor.check("string(default='\n\n\n')", '', missing=True) '\n\n\n' >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True) '\n' >>> vtor.check("string_list()", ['foo', '\n', 'bar']) ['foo', '\n', 'bar'] >>> vtor.check("string_list(default=list('\n'))", '', missing=True) ['\n'] """ if __name__ == '__main__': # run the code tests in doctest format import sys import doctest m = sys.modules.get('__main__') globs = m.__dict__.copy() globs.update({ 'vtor': Validator(), }) failures, tests = doctest.testmod( m, globs=globs, optionflags=doctest.IGNORE_EXCEPTION_DETAIL | doctest.ELLIPSIS) assert not failures, '{} failures out of {} tests'.format(failures, tests) configobj-5.0.9/src/tests/000077500000000000000000000000001467354007300154275ustar00rootroot00000000000000configobj-5.0.9/src/tests/__init__.py000066400000000000000000000000001467354007300175260ustar00rootroot00000000000000configobj-5.0.9/src/tests/conf.ini000066400000000000000000000001611467354007300170530ustar00rootroot00000000000000 extra = 3 [extra-section] [section] [[sub-section]] extra = 3 [[extra-sub-section]] extra = 3 configobj-5.0.9/src/tests/conf.spec000066400000000000000000000002671467354007300172350ustar00rootroot00000000000000 value = integer [section] value = integer [[sub-section]] value = integer [[missing-subsection]] value = integer [missing-section] value = integer configobj-5.0.9/src/tests/configobj_doctests.py000066400000000000000000000672071467354007300216650ustar00rootroot00000000000000# configobj_test.py # doctests for ConfigObj # A config file reader/writer that supports nested sections in config files. # Copyright (C) 2005-2014: # (name) : (email) # Michael Foord: fuzzyman AT voidspace DOT org DOT uk # Nicola Larosa: nico AT tekNico DOT net # Rob Dennis: rdennis AT gmail DOT com # Eli Courtwright: eli AT courtwright DOT org # This software is licensed under the terms of the BSD license. # http://opensource.org/licenses/BSD-3-Clause # ConfigObj 5 - main repository for documentation and issue tracking: # https://github.com/DiffSK/configobj import sys from io import StringIO import sys from configobj import * from configobj.validate import Validator def _test_validate(): """ >>> val = Validator() >>> a = ['foo = fish'] >>> b = ['foo = integer(default=3)'] >>> c = ConfigObj(a, configspec=b) >>> c ConfigObj({'foo': 'fish'}) >>> from configobj.validate import Validator >>> v = Validator() >>> c.validate(v) 0 >>> c.default_values {'foo': 3} >>> c.restore_default('foo') 3 Now testing with repeated sections : BIG TEST >>> repeated_1 = ''' ... [dogs] ... [[__many__]] # spec for a dog ... fleas = boolean(default=True) ... tail = option(long, short, default=long) ... name = string(default=rover) ... [[[__many__]]] # spec for a puppy ... name = string(default="son of rover") ... age = float(default=0.0) ... [cats] ... [[__many__]] # spec for a cat ... fleas = boolean(default=True) ... tail = option(long, short, default=short) ... name = string(default=pussy) ... [[[__many__]]] # spec for a kitten ... name = string(default="son of pussy") ... age = float(default=0.0) ... '''.split('\\n') >>> repeated_2 = ''' ... [dogs] ... ... # blank dogs with puppies ... # should be filled in by the configspec ... [[dog1]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [[dog2]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [[dog3]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [cats] ... ... # blank cats with kittens ... # should be filled in by the configspec ... [[cat1]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... [[cat2]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... [[cat3]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... '''.split('\\n') >>> repeated_3 = ''' ... [dogs] ... ... [[dog1]] ... [[dog2]] ... [[dog3]] ... [cats] ... ... [[cat1]] ... [[cat2]] ... [[cat3]] ... '''.split('\\n') >>> repeated_4 = ''' ... [__many__] ... ... name = string(default=Michael) ... age = float(default=0.0) ... sex = option(m, f, default=m) ... '''.split('\\n') >>> repeated_5 = ''' ... [cats] ... [[__many__]] ... fleas = boolean(default=True) ... tail = option(long, short, default=short) ... name = string(default=pussy) ... [[[description]]] ... height = float(default=3.3) ... weight = float(default=6) ... [[[[coat]]]] ... fur = option(black, grey, brown, "tortoise shell", default=black) ... condition = integer(0,10, default=5) ... '''.split('\\n') >>> val= Validator() >>> repeater = ConfigObj(repeated_2, configspec=repeated_1) >>> repeater.validate(val) 1 >>> repeater == { ... 'dogs': { ... 'dog1': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... 'dog2': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... 'dog3': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... }, ... 'cats': { ... 'cat1': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... 'cat2': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... 'cat3': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... }, ... } 1 >>> repeater = ConfigObj(repeated_3, configspec=repeated_1) >>> repeater.validate(val) 1 >>> repeater == { ... 'cats': { ... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... }, ... 'dogs': { ... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... }, ... } 1 >>> repeater = ConfigObj(configspec=repeated_4) >>> repeater['Michael'] = {} >>> repeater.validate(val) 1 >>> repeater == { ... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'}, ... } 1 >>> repeater = ConfigObj(repeated_3, configspec=repeated_5) >>> repeater == { ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}}, ... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}}, ... } 1 >>> repeater.validate(val) 1 >>> repeater == { ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}}, ... 'cats': { ... 'cat1': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... 'cat2': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... 'cat3': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... }, ... } 1 Test that interpolation is preserved for validated string values. Also check that interpolation works in configspecs. >>> t = ConfigObj(configspec=['test = string']) >>> t['DEFAULT'] = {} >>> t['DEFAULT']['def_test'] = 'a' >>> t['test'] = '%(def_test)s' >>> t['test'] 'a' >>> v = Validator() >>> t.validate(v) 1 >>> t.interpolation = False >>> t ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}}) >>> specs = [ ... 'interpolated string = string(default="fuzzy-%(man)s")', ... '[DEFAULT]', ... 'man = wuzzy', ... ] >>> c = ConfigObj(configspec=specs) >>> c.validate(v) 1 >>> c['interpolated string'] 'fuzzy-wuzzy' Test SimpleVal >>> val = SimpleVal() >>> config = ''' ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [section] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [[sub section]] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... '''.split('\\n') >>> configspec = ''' ... test1='' ... test2='' ... test3='' ... test4='' ... [section] ... test1='' ... test2='' ... test3='' ... test4='' ... [[sub section]] ... test1='' ... test2='' ... test3='' ... test4='' ... '''.split('\\n') >>> o = ConfigObj(config, configspec=configspec) >>> o.validate(val) 1 >>> o = ConfigObj(configspec=configspec) >>> o.validate(val) 0 Test Flatten Errors >>> vtor = Validator() >>> my_ini = ''' ... option1 = True ... [section1] ... option1 = True ... [section2] ... another_option = Probably ... [section3] ... another_option = True ... [[section3b]] ... value = 3 ... value2 = a ... value3 = 11 ... ''' >>> my_cfg = ''' ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section1] ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section2] ... another_option = boolean() ... [section3] ... another_option = boolean() ... [[section3b]] ... value = integer ... value2 = integer ... value3 = integer(0, 10) ... [[[section3b-sub]]] ... value = string ... [section4] ... another_option = boolean() ... ''' >>> cs = my_cfg.split('\\n') >>> ini = my_ini.split('\\n') >>> cfg = ConfigObj(ini, configspec=cs) >>> res = cfg.validate(vtor, preserve_errors=True) >>> errors = [] >>> for entry in flatten_errors(cfg, res): ... section_list, key, error = entry ... section_list.insert(0, '[root]') ... if key is not None: ... section_list.append(key) ... section_string = ', '.join(section_list) ... errors.append('%s%s%s' % (section_string, ' = ', error or 'missing')) >>> errors.sort() >>> for entry in errors: ... print(entry) [root], option2 = missing [root], option3 = the value "Bad_value" is of the wrong type. [root], section1, option2 = missing [root], section1, option3 = the value "Bad_value" is of the wrong type. [root], section2, another_option = the value "Probably" is of the wrong type. [root], section3, section3b, section3b-sub = missing [root], section3, section3b, value2 = the value "a" is of the wrong type. [root], section3, section3b, value3 = the value "11" is too big. [root], section4 = missing """ def _test_errors(): """ Test the error messages and objects, in normal mode and unrepr mode. >>> bad_syntax = ''' ... key = "value" ... key2 = "value ... '''.splitlines() >>> c = ConfigObj(bad_syntax) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(bad_syntax, raise_errors=True) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Parse error in value at line 3. >>> try: ... c = ConfigObj(bad_syntax) ... except Exception as exc: ... e = exc >>> assert(isinstance(e, ConfigObjError)) >>> print(e) Parse error in value at line 3. >>> len(e.errors) == 1 1 >>> try: ... c = ConfigObj(bad_syntax, unrepr=True) ... except Exception as exc: ... e = exc >>> assert(isinstance(e, ConfigObjError)) >>> print(e) Parse error from unrepr-ing value at line 3. >>> len(e.errors) == 1 1 >>> the_error = e.errors[0] >>> assert(isinstance(the_error, UnreprError)) >>> multiple_bad_syntax = ''' ... key = "value" ... key2 = "value ... key3 = "value2 ... '''.splitlines() >>> try: ... c = ConfigObj(multiple_bad_syntax) ... except ConfigObjError as e: ... str(e) 'Parsing failed with several errors.\\nFirst error at line 3.' >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Parse error in value at line 3. >>> try: ... c = ConfigObj(multiple_bad_syntax) ... except Exception as exc: ... e = exc >>> assert(isinstance(e, ConfigObjError)) >>> print(e) Parsing failed with several errors. First error at line 3. >>> len(e.errors) == 2 1 >>> try: ... c = ConfigObj(multiple_bad_syntax, unrepr=True) ... except Exception as exc: ... e = exc >>> assert(isinstance(e, ConfigObjError)) >>> print(e) Parsing failed with several errors. First error at line 3. >>> len(e.errors) == 2 1 >>> the_error = e.errors[1] >>> assert(isinstance(the_error, UnreprError)) >>> unknown_name = ''' ... key = "value" ... key2 = value ... '''.splitlines() >>> c = ConfigObj(unknown_name) >>> c = ConfigObj(unknown_name, unrepr=True) Traceback (most recent call last): UnreprError: Unknown name or type in value at line 3. >>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Unknown name or type in value at line 3. """ def _test_validate_with_copy_and_many(): """ >>> spec = ''' ... [section] ... [[__many__]] ... value = string(default='nothing') ... ''' >>> config = ''' ... [section] ... [[something]] ... ''' >>> c = ConfigObj(StringIO(config), configspec=StringIO(spec)) >>> v = Validator() >>> r = c.validate(v, copy=True) >>> c['section']['something']['value'] == 'nothing' True """ def _test_configspec_with_hash(): """ >>> spec = ['stuff = string(default="#ff00dd")'] >>> c = ConfigObj(spec, _inspec=True) >>> c['stuff'] 'string(default="#ff00dd")' >>> c = ConfigObj(configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> c['stuff'] '#ff00dd' >>> spec = ['stuff = string(default="fish") # wooble'] >>> c = ConfigObj(spec, _inspec=True) >>> c['stuff'] 'string(default="fish") # wooble' """ def _test_many_check(): """ >>> spec = ['__many__ = integer()'] >>> config = ['a = 6', 'b = 7'] >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['a'], int) True >>> isinstance(c['b'], int) True >>> spec = ['[name]', '__many__ = integer()'] >>> config = ['[name]', 'a = 6', 'b = 7'] >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['name']['a'], int) True >>> isinstance(c['name']['b'], int) True >>> spec = ['[__many__]', '__many__ = integer()'] >>> config = ['[name]', 'hello = 7', '[thing]', 'fish = 0'] >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['name']['hello'], int) True >>> isinstance(c['thing']['fish'], int) True >>> spec = ''' ... ___many___ = integer ... [__many__] ... ___many___ = boolean ... [[__many__]] ... __many__ = float ... '''.splitlines() >>> config = ''' ... fish = 8 ... buggle = 4 ... [hi] ... one = true ... two = false ... [[bye]] ... odd = 3 ... whoops = 9.0 ... [bye] ... one = true ... two = true ... [[lots]] ... odd = 3 ... whoops = 9.0 ... '''.splitlines() >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['fish'], int) True >>> isinstance(c['buggle'], int) True >>> c['hi']['one'] 1 >>> c['hi']['two'] 0 >>> isinstance(c['hi']['bye']['odd'], float) True >>> isinstance(c['hi']['bye']['whoops'], float) True >>> c['bye']['one'] 1 >>> c['bye']['two'] 1 >>> isinstance(c['bye']['lots']['odd'], float) True >>> isinstance(c['bye']['lots']['whoops'], float) True >>> spec = ['___many___ = integer()'] >>> config = ['a = 6', 'b = 7'] >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['a'], int) True >>> isinstance(c['b'], int) True >>> spec = ''' ... [__many__] ... [[__many__]] ... __many__ = float ... '''.splitlines() >>> config = ''' ... [hi] ... [[bye]] ... odd = 3 ... whoops = 9.0 ... [bye] ... [[lots]] ... odd = 3 ... whoops = 9.0 ... '''.splitlines() >>> c = ConfigObj(config, configspec=spec) >>> v = Validator() >>> c.validate(v) 1 >>> isinstance(c['hi']['bye']['odd'], float) True >>> isinstance(c['hi']['bye']['whoops'], float) True >>> isinstance(c['bye']['lots']['odd'], float) True >>> isinstance(c['bye']['lots']['whoops'], float) True >>> s = ['[dog]', '[[cow]]', 'something = boolean', '[[__many__]]', ... 'fish = integer'] >>> c = ['[dog]', '[[cow]]', 'something = true', '[[ob]]', ... 'fish = 3', '[[bo]]', 'fish = 6'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v) 1 >>> ini['dog']['cow']['something'] 1 >>> ini['dog']['ob']['fish'] 3 >>> ini['dog']['bo']['fish'] 6 >>> s = ['[cow]', 'something = boolean', '[__many__]', ... 'fish = integer'] >>> c = ['[cow]', 'something = true', '[ob]', ... 'fish = 3', '[bo]', 'fish = 6'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v) 1 >>> ini['cow']['something'] 1 >>> ini['ob']['fish'] 3 >>> ini['bo']['fish'] 6 """ def _unexpected_validation_errors(): """ Although the input is nonsensical we should not crash but correctly report the failure to validate # section specified, got scalar >>> from configobj.validate import ValidateError >>> s = ['[cow]', 'something = boolean'] >>> c = ['cow = true'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v) 0 >>> ini = ConfigObj(c, configspec=s) >>> res = ini.validate(v, preserve_errors=True) >>> check = flatten_errors(ini, res) >>> for entry in check: ... isinstance(entry[2], ValidateError) ... print(str(entry[2])) True Section 'cow' was provided as a single value # scalar specified, got section >>> s = ['something = boolean'] >>> c = ['[something]', 'cow = true'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v) 0 >>> ini = ConfigObj(c, configspec=s) >>> res = ini.validate(v, preserve_errors=True) >>> check = flatten_errors(ini, res) >>> for entry in check: ... isinstance(entry[2], ValidateError) ... print(str(entry[2])) True Value 'something' was provided as a section # unexpected section >>> s = [] >>> c = ['[cow]', 'dog = true'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v) 1 >>> s = ['[cow]', 'dog = boolean'] >>> c = ['[cow]', 'dog = true'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> ini.validate(v, preserve_errors=True) 1 """ def _test_pickle(): """ >>> import pickle >>> s = ['[cow]', 'dog = boolean'] >>> c = ['[cow]', 'dog = true'] >>> ini = ConfigObj(c, configspec=s) >>> v = Validator() >>> string = pickle.dumps(ini) >>> new = pickle.loads(string) >>> new.validate(v) 1 """ def _test_as_list(): """ >>> a = ConfigObj() >>> a['a'] = 1 >>> a.as_list('a') [1] >>> a['a'] = (1,) >>> a.as_list('a') [1] >>> a['a'] = [1] >>> a.as_list('a') [1] """ def _test_list_interpolation(): """ >>> c = ConfigObj() >>> c['x'] = 'foo' >>> c['list'] = ['%(x)s', 3] >>> c['list'] ['foo', 3] """ def _test_extra_values(): """ >>> spec = ['[section]'] >>> infile = ['bar = 3', '[something]', 'foo = fish', '[section]', 'foo=boo'] >>> c = ConfigObj(infile, configspec=spec) >>> c.extra_values [] >>> c.extra_values = ['bar', 'gosh', 'what'] >>> c.validate(Validator()) 1 >>> c.extra_values ['bar', 'something'] >>> c['section'].extra_values ['foo'] >>> c['something'].extra_values [] """ def _test_reset_and_clear_more(): """ >>> c = ConfigObj() >>> c.extra_values = ['foo'] >>> c.defaults = ['bar'] >>> c.default_values = {'bar': 'baz'} >>> c.clear() >>> c.defaults [] >>> c.extra_values [] >>> c.default_values {'bar': 'baz'} >>> c.extra_values = ['foo'] >>> c.defaults = ['bar'] >>> c.reset() >>> c.defaults [] >>> c.extra_values [] >>> c.default_values {} """ def _test_invalid_lists(): """ >>> v = ['string = val, val2, , val3'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = val, val2,, val3'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = val, val2,,'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = val, ,'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = val, , '] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = ,,'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = ,, '] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = ,foo'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. >>> v = ['string = foo, '] >>> c = ConfigObj(v) >>> c['string'] ['foo'] >>> v = ['string = foo, "'] >>> c = ConfigObj(v) Traceback (most recent call last): ParseError: Parse error in value at line 1. """ def _test_validation_with_preserve_errors(): """ >>> v = Validator() >>> spec = ['[section]', 'foo = integer'] >>> c = ConfigObj(configspec=spec) >>> c.validate(v, preserve_errors=True) {'section': False} >>> c = ConfigObj(['[section]'], configspec=spec) >>> c.validate(v) False >>> c.validate(v, preserve_errors=True) {'section': {'foo': False}} """ # test _created on Section # TODO: Test BOM handling # TODO: Test error code for badly built multiline values # TODO: Test handling of StringIO # TODO: Test interpolation with writing if __name__ == '__main__': # run the code tests in doctest format # testconfig1 = """\ key1= val # comment 1 key2= val # comment 2 # comment 3 [lev1a] # comment 4 key1= val # comment 5 key2= val # comment 6 # comment 7 [lev1b] # comment 8 key1= val # comment 9 key2= val # comment 10 # comment 11 [[lev2ba]] # comment 12 key1= val # comment 13 # comment 14 [[lev2bb]] # comment 15 key1= val # comment 16 # comment 17 [lev1c] # comment 18 # comment 19 [[lev2c]] # comment 20 # comment 21 [[[lev3c]]] # comment 22 key1 = val # comment 23""" # testconfig2 = b"""\ key1 = 'val1' key2 = "val2" key3 = val3 ["section 1"] # comment keys11 = val1 keys12 = val2 keys13 = val3 [section 2] keys21 = val1 keys22 = val2 keys23 = val3 [['section 2 sub 1']] fish = 3 """ # testconfig6 = b''' name1 = """ a single line value """ # comment name2 = \''' another single line value \''' # comment name3 = """ a single line value """ name4 = \''' another single line value \''' [ "multi section" ] name1 = """ Well, this is a multiline value """ name2 = \''' Well, this is a multiline value \''' name3 = """ Well, this is a multiline value """ # a comment name4 = \''' Well, this is a multiline value \''' # I guess this is a comment too ''' # # these cannot be put among the doctests, because the doctest module # does a string.expandtabs() on all of them, sigh # oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar'] # twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar'] # tabsAndSpacesCfg = [b'[sect]', b'\t \t [[sect]]', b'\t \t \t \t foo = bar'] # import doctest m = sys.modules.get('__main__') globs = m.__dict__.copy() a = ConfigObj(testconfig1.split('\n'), raise_errors=True) b = ConfigObj(testconfig2.split(b'\n'), raise_errors=True) i = ConfigObj(testconfig6.split(b'\n'), raise_errors=True) globs.update({'a': a, 'b': b, 'i': i}) pre_failures, pre_tests = doctest.testmod( m, globs=globs, optionflags=doctest.IGNORE_EXCEPTION_DETAIL | doctest.ELLIPSIS) import configobj post_failures, post_tests = doctest.testmod( configobj, globs=globs, optionflags=doctest.IGNORE_EXCEPTION_DETAIL | doctest.ELLIPSIS) assert not (pre_failures or post_failures), ( '{} failures out of {} tests'.format(post_failures + pre_failures, post_tests + pre_tests)) # Man alive I prefer unittest ;-) configobj-5.0.9/src/tests/conftest.py000066400000000000000000000003241467354007300176250ustar00rootroot00000000000000# coding=utf-8 import pytest from configobj import ConfigObj from configobj.validate import Validator @pytest.fixture def empty_cfg(): return ConfigObj() @pytest.fixture def val(): return Validator() configobj-5.0.9/src/tests/test_configobj.py000066400000000000000000001157751467354007300210200ustar00rootroot00000000000000# coding=utf-8 from __future__ import unicode_literals import os import re from codecs import BOM_UTF8 from warnings import catch_warnings from tempfile import NamedTemporaryFile import pytest import io import configobj as co from configobj import ConfigObj, flatten_errors, ReloadError, DuplicateError, MissingInterpolationOption, InterpolationLoopError, ConfigObjError from configobj.validate import Validator, VdtValueTooSmallError def cfg_lines(config_string_representation): """ :param config_string_representation: string representation of a config file (typically a triple-quoted string) :type config_string_representation: str or unicode :return: a list of lines of that config. Whitespace on the left will be trimmed based on the indentation level to make it a bit saner to assert content of a particular line :rtype: str or unicode """ lines = config_string_representation.splitlines() for idx, line in enumerate(lines): if line.strip(): line_no_with_content = idx break else: raise ValueError('no content in provided config file: ' '{!r}'.format(config_string_representation)) first_content = lines[line_no_with_content] if isinstance(first_content, bytes): first_content = first_content.decode('utf-8') ws_chars = len(re.search(r'^(\s*)', first_content).group(1)) def yield_stringified_line(): for line in lines: if isinstance(line, bytes): yield line.decode('utf-8') else: yield line return [re.sub(r'^\s{0,%s}' % ws_chars, '', line).encode('utf-8') for line in yield_stringified_line()] @pytest.fixture def cfg_contents(request): def make_file_with_contents_and_return_name(config_string_representation): """ :param config_string_representation: string representation of a config file (typically a triple-quoted string) :type config_string_representation: str or unicode :return: a list of lines of that config. Whitespace on the left will be trimmed based on the indentation level to make it a bit saner to assert content of a particular line :rtype: basestring """ lines = cfg_lines(config_string_representation) with NamedTemporaryFile(delete=False, mode='wb') as cfg_file: for line in lines: if isinstance(line, bytes): cfg_file.write(line + os.linesep.encode('utf-8')) else: cfg_file.write((line + os.linesep).encode('utf-8')) request.addfinalizer(lambda : os.unlink(cfg_file.name)) return cfg_file.name return make_file_with_contents_and_return_name def test_order_preserved(): c = ConfigObj() c['a'] = 1 c['b'] = 2 c['c'] = 3 c['section'] = {} c['section']['a'] = 1 c['section']['b'] = 2 c['section']['c'] = 3 c['section']['section'] = {} c['section']['section2'] = {} c['section']['section3'] = {} c['section2'] = {} c['section3'] = {} c2 = ConfigObj(c) assert c2.scalars == ['a', 'b', 'c'] assert c2.sections == ['section', 'section2', 'section3'] assert c2['section'].scalars == ['a', 'b', 'c'] assert c2['section'].sections == ['section', 'section2', 'section3'] assert c['section'] is not c2['section'] assert c['section']['section'] is not c2['section']['section'] def test_options_deprecation(): with catch_warnings(record=True) as log: ConfigObj(options={}) # unpack the only member of log try: warning, = log except ValueError: assert len(log) == 1 assert warning.category == DeprecationWarning def test_list_members(): c = ConfigObj() c['a'] = [] c['a'].append('foo') assert c['a'] == ['foo'] def test_list_interpolation_with_pop(): c = ConfigObj() c['a'] = [] c['a'].append('%(b)s') c['b'] = 'bar' assert c.pop('a') == ['bar'] def test_with_default(): c = ConfigObj() c['a'] = 3 assert c.pop('a') == 3 assert c.pop('b', 3) == 3 with pytest.raises(KeyError): c.pop('c') def test_interpolation_with_section_names(cfg_contents): cfg = cfg_contents(""" item1 = 1234 [section] [[item1]] foo='bar' [[DEFAULT]] [[[item1]]] why = would you do this? [[other-subsection]] item2 = '$item1'""") c = ConfigObj(cfg, interpolation='Template') # This raises an exception in 4.7.1 and earlier due to the section # being found as the interpolation value repr(c) def test_interoplation_repr(): c = ConfigObj(['foo = $bar'], interpolation='Template') c['baz'] = {} c['baz']['spam'] = '%(bar)s' # This raises a MissingInterpolationOption exception in 4.7.1 and earlier repr(c) class TestEncoding(object): @pytest.fixture def ant_cfg(self): return """ [tags] [[bug]] translated = \U0001f41c """ #issue #18 def test_unicode_conversion_when_encoding_is_set(self, cfg_contents): cfg = cfg_contents(b"test = some string") c = ConfigObj(cfg, encoding='utf8') assert isinstance(c['test'], str) #issue #18 def test_no_unicode_conversion_when_encoding_is_omitted(self, cfg_contents): cfg = cfg_contents(b"test = some string") c = ConfigObj(cfg) assert isinstance(c['test'], str) #issue #44 def test_that_encoding_using_list_of_strings(self): cfg = [b'test = \xf0\x9f\x90\x9c'] c = ConfigObj(cfg, encoding='utf8') assert isinstance(c['test'], str) assert c['test'] == '\U0001f41c' #issue #44 def test_encoding_in_subsections(self, ant_cfg, cfg_contents): c = cfg_contents(ant_cfg) cfg = ConfigObj(c, encoding='utf-8') assert isinstance(cfg['tags']['bug']['translated'], str) #issue #44 and #55 def test_encoding_in_config_files(self, request, ant_cfg): # the cfg_contents fixture is doing this too, but be explicit with NamedTemporaryFile(delete=False, mode='wb') as cfg_file: cfg_file.write(ant_cfg.encode('utf-8')) request.addfinalizer(lambda : os.unlink(cfg_file.name)) cfg = ConfigObj(cfg_file.name, encoding='utf-8') assert isinstance(cfg['tags']['bug']['translated'], str) cfg.write() @pytest.fixture def testconfig1(): """ copied from the main doctest """ return """\ key1= val # comment 1 key2= val # comment 2 # comment 3 [lev1a] # comment 4 key1= val # comment 5 key2= val # comment 6 # comment 7 [lev1b] # comment 8 key1= val # comment 9 key2= val # comment 10 # comment 11 [[lev2ba]] # comment 12 key1= val # comment 13 # comment 14 [[lev2bb]] # comment 15 key1= val # comment 16 # comment 17 [lev1c] # comment 18 # comment 19 [[lev2c]] # comment 20 # comment 21 [[[lev3c]]] # comment 22 key1 = val # comment 23""" @pytest.fixture def testconfig2(): return """\ key1 = 'val1' key2 = "val2" key3 = val3 ["section 1"] # comment keys11 = val1 keys12 = val2 keys13 = val3 [section 2] keys21 = val1 keys22 = val2 keys23 = val3 [['section 2 sub 1']] fish = 3 """ @pytest.fixture def testconfig6(): return b''' name1 = """ a single line value """ # comment name2 = \''' another single line value \''' # comment name3 = """ a single line value """ name4 = \''' another single line value \''' [ "multi section" ] name1 = """ Well, this is a multiline value """ name2 = \''' Well, this is a multiline value \''' name3 = """ Well, this is a multiline value """ # a comment name4 = \''' Well, this is a multiline value \''' # I guess this is a comment too ''' @pytest.fixture def a(testconfig1, cfg_contents): """ also copied from main doc tests """ return ConfigObj(cfg_contents(testconfig1), raise_errors=True) @pytest.fixture def b(testconfig2, cfg_contents): """ also copied from main doc tests """ return ConfigObj(cfg_contents(testconfig2), raise_errors=True) @pytest.fixture def i(testconfig6, cfg_contents): """ also copied from main doc tests """ return ConfigObj(cfg_contents(testconfig6), raise_errors=True) def test_configobj_dict_representation(a, b, cfg_contents): assert a.depth == 0 assert a == { 'key2': 'val', 'key1': 'val', 'lev1c': { 'lev2c': { 'lev3c': { 'key1': 'val', }, }, }, 'lev1b': { 'key2': 'val', 'key1': 'val', 'lev2ba': { 'key1': 'val', }, 'lev2bb': { 'key1': 'val', }, }, 'lev1a': { 'key2': 'val', 'key1': 'val', }, } assert b.depth == 0 assert b == { 'key3': 'val3', 'key2': 'val2', 'key1': 'val1', 'section 1': { 'keys11': 'val1', 'keys13': 'val3', 'keys12': 'val2', }, 'section 2': { 'section 2 sub 1': { 'fish': '3', }, 'keys21': 'val1', 'keys22': 'val2', 'keys23': 'val3', }, } t = cfg_lines(""" 'a' = b # !"$%^&*(),::;'@~#= 33 "b" = b #= 6, 33 """) t2 = ConfigObj(t) assert t2 == {'a': 'b', 'b': 'b'} t2.inline_comments['b'] = '' del t2['a'] assert t2.write() == ['','b = b', ''] def test_behavior_when_list_values_is_false(): c = ''' key1 = no quotes key2 = 'single quotes' key3 = "double quotes" key4 = "list", 'with', several, "quotes" ''' cfg = ConfigObj(cfg_lines(c), list_values=False) assert cfg == { 'key1': 'no quotes', 'key2': "'single quotes'", 'key3': '"double quotes"', 'key4': '"list", \'with\', several, "quotes"' } cfg2 = ConfigObj(list_values=False) cfg2['key1'] = 'Multiline\nValue' cfg2['key2'] = '''"Value" with 'quotes' !''' assert cfg2.write() == [ "key1 = '''Multiline\nValue'''", 'key2 = "Value" with \'quotes\' !' ] cfg2.list_values = True assert cfg2.write() == [ "key1 = '''Multiline\nValue'''", 'key2 = \'\'\'"Value" with \'quotes\' !\'\'\'' ] def test_flatten_errors(val, cfg_contents): config = cfg_contents(""" test1=40 test2=hello test3=3 test4=5.0 [section] test1=40 test2=hello test3=3 test4=5.0 [[sub section]] test1=40 test2=hello test3=3 test4=5.0 """) configspec = cfg_contents(""" test1= integer(30,50) test2= string test3=integer test4=float(6.0) [section] test1=integer(30,50) test2=string test3=integer test4=float(6.0) [[sub section]] test1=integer(30,50) test2=string test3=integer test4=float(6.0) """) c1 = ConfigObj(config, configspec=configspec) res = c1.validate(val) assert flatten_errors(c1, res) == [([], 'test4', False), (['section'], 'test4', False), (['section', 'sub section'], 'test4', False)] res = c1.validate(val, preserve_errors=True) check = flatten_errors(c1, res) assert check[0][:2] == ([], 'test4') assert check[1][:2] == (['section'], 'test4') assert check[2][:2] == (['section', 'sub section'], 'test4') for entry in check: assert isinstance(entry[2], VdtValueTooSmallError) assert str(entry[2]) == 'the value "5.0" is too small.' def test_unicode_handling(): u_base = ''' # initial comment # inital comment 2 test1 = some value # comment test2 = another value # inline comment # section comment [section] # inline comment test = test # another inline comment test2 = test2 # final comment # final comment2 ''' # needing to keep line endings means this isn't a good candidate # for the cfg_lines utility method u = u_base.encode('utf_8').splitlines(True) u[0] = BOM_UTF8 + u[0] uc = ConfigObj(u) uc.encoding = None assert uc.BOM assert uc == {'test1': 'some value', 'test2': 'another value', 'section': {'test': 'test', 'test2': 'test2'}} uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1') assert uc.BOM assert isinstance(uc['test1'], str) assert uc.encoding == 'utf_8' assert uc.newlines == '\n' assert len(uc.write()) == 13 uc['latin1'] = "This costs lot's of " a_list = uc.write() assert 'latin1' in str(a_list) assert len(a_list) == 14 assert isinstance(a_list[0], bytes) assert a_list[0].startswith(BOM_UTF8) u = u_base.replace('\n', '\r\n').encode('utf-8').splitlines(True) uc = ConfigObj(u) assert uc.newlines == '\r\n' uc.newlines = '\r' file_like = io.BytesIO() uc.write(file_like) file_like.seek(0) uc2 = ConfigObj(file_like) assert uc2 == uc assert uc2.filename == None assert uc2.newlines == '\r' class TestWritingConfigs(object): def test_validate(self, val): spec = [ '# Initial Comment', '', 'key1 = string(default=Hello)', '', '# section comment', '[section] # inline comment', '# key1 comment', 'key1 = integer(default=6)', '# key2 comment', 'key2 = boolean(default=True)', '# subsection comment', '[[sub-section]] # inline comment', '# another key1 comment', 'key1 = float(default=3.0)' ] blank_config = ConfigObj(configspec=spec) assert blank_config.validate(val, copy=True) assert blank_config.dict() == { 'key1': 'Hello', 'section': {'key1': 6, 'key2': True, 'sub-section': {'key1': 3.0}} } assert blank_config.write() == [ '# Initial Comment', '', 'key1 = Hello', '', '# section comment', '[section]# inline comment', '# key1 comment', 'key1 = 6', '# key2 comment', 'key2 = True', '# subsection comment', '[[sub-section]]# inline comment', '# another key1 comment', 'key1 = 3.0' ] def test_writing_empty_values(self): config_with_empty_values = [ '', 'key1 =', 'key2 =# a comment', ] cfg = ConfigObj(config_with_empty_values) assert cfg.write() == ['', 'key1 = ""', 'key2 = ""# a comment'] cfg.write_empty_values = True assert cfg.write() == ['', 'key1 = ', 'key2 = # a comment'] class TestUnrepr(object): def test_in_reading(self): config_to_be_unreprd = cfg_lines(""" key1 = (1, 2, 3) # comment key2 = True key3 = 'a string' key4 = [1, 2, 3, 'a mixed list'] """) cfg = ConfigObj(config_to_be_unreprd, unrepr=True) assert cfg == { 'key1': (1, 2, 3), 'key2': True, 'key3': 'a string', 'key4': [1, 2, 3, 'a mixed list'] } assert cfg == ConfigObj(cfg.write(), unrepr=True) def test_in_multiline_values(self, cfg_contents): config_with_multiline_value = cfg_contents(''' k = \"""{ 'k1': 3, 'k2': 6.0}\""" ''') cfg = ConfigObj(config_with_multiline_value, unrepr=True) assert cfg == {'k': {'k1': 3, 'k2': 6.0}} def test_with_a_dictionary(self): config_with_dict_value = ['k = {"a": 1}'] cfg = ConfigObj(config_with_dict_value, unrepr=True) assert isinstance(cfg['k'], dict) def test_with_hash(self): config_with_a_hash_in_a_list = [ 'key1 = (1, 2, 3) # comment', 'key2 = True', "key3 = 'a string'", "key4 = [1, 2, 3, 'a mixed list#']" ] cfg = ConfigObj(config_with_a_hash_in_a_list, unrepr=True) assert cfg == { 'key1': (1, 2, 3), 'key2': True, 'key3': 'a string', 'key4': [1, 2, 3, 'a mixed list#'] } class TestValueErrors(object): def test_bool(self, empty_cfg): empty_cfg['a'] = 'fish' with pytest.raises(ValueError) as excinfo: empty_cfg.as_bool('a') assert str(excinfo.value) == 'Value "fish" is neither True nor False' empty_cfg['b'] = 'True' assert empty_cfg.as_bool('b') is True empty_cfg['b'] = 'off' assert empty_cfg.as_bool('b') is False def test_int(self, empty_cfg): for bad in ('fish', '3.2'): empty_cfg['a'] = bad with pytest.raises(ValueError) as excinfo: empty_cfg.as_int('a') assert str(excinfo.value).startswith('invalid literal for int()') empty_cfg['b'] = '1' assert empty_cfg.as_bool('b') is True empty_cfg['b'] = '3.2' def test_float(self, empty_cfg): empty_cfg['a'] = 'fish' with pytest.raises(ValueError): empty_cfg.as_float('a') empty_cfg['b'] = '1' assert empty_cfg.as_float('b') == 1 empty_cfg['b'] = '3.2' assert empty_cfg.as_float('b') == 3.2 def test_error_types(): # errors that don't have interesting messages test_value = 'what' for ErrorClass in (co.ConfigObjError, co.NestingError, co.ParseError, co.DuplicateError, co.ConfigspecError, co.RepeatSectionError): with pytest.raises(ErrorClass) as excinfo: # TODO: assert more interesting things # now that we're not using doctest raise ErrorClass(test_value) assert str(excinfo.value) == test_value for ErrorClassWithMessage, msg in ( (co.InterpolationLoopError, 'interpolation loop detected in value "{0}".'), (co.MissingInterpolationOption, 'missing option "{0}" in interpolation.'), ): with pytest.raises(ErrorClassWithMessage) as excinfo: raise ErrorClassWithMessage(test_value) assert str(excinfo.value) == msg.format(test_value) # ReloadError is raised as IOError with pytest.raises(IOError): raise co.ReloadError() class TestSectionBehavior(object): def test_dictionary_representation(self, a): n = a.dict() assert n == a assert n is not a def test_merging(self, cfg_contents): config_with_subsection = cfg_contents(""" [section1] option1 = True [[subsection]] more_options = False # end of file """) config_that_overwrites_parameter = cfg_contents(""" # File is user.ini [section1] option1 = False # end of file """) c1 = ConfigObj(config_that_overwrites_parameter) c2 = ConfigObj(config_with_subsection) c2.merge(c1) assert c2.dict() == {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}} def test_walking_with_in_place_updates(self, cfg_contents): config = cfg_contents(""" [XXXXsection] XXXXkey = XXXXvalue """) cfg = ConfigObj(config) assert cfg.dict() == {'XXXXsection': {'XXXXkey': 'XXXXvalue'}} def transform(section, key): val = section[key] newkey = key.replace('XXXX', 'CLIENT1') section.rename(key, newkey) if isinstance(val, str): val = val.replace('XXXX', 'CLIENT1') section[newkey] = val assert cfg.walk(transform, call_on_sections=True) == { 'CLIENT1section': {'CLIENT1key': None} } assert cfg.dict() == { 'CLIENT1section': {'CLIENT1key': 'CLIENT1value'} } def test_reset_a_configobj(): something = object() cfg = ConfigObj() cfg['something'] = something cfg['section'] = {'something': something} cfg.filename = 'fish' cfg.raise_errors = something cfg.list_values = something cfg.create_empty = something cfg.file_error = something cfg.stringify = something cfg.indent_type = something cfg.encoding = something cfg.default_encoding = something cfg.BOM = something cfg.newlines = something cfg.write_empty_values = something cfg.unrepr = something cfg.initial_comment = something cfg.final_comment = something cfg.configspec = something cfg.inline_comments = something cfg.comments = something cfg.defaults = something cfg.default_values = something cfg.reset() assert cfg.filename is None assert cfg.raise_errors is False assert cfg.list_values is True assert cfg.create_empty is False assert cfg.file_error is False assert cfg.interpolation is True assert cfg.configspec is None assert cfg.stringify is True assert cfg.indent_type is None assert cfg.encoding is None assert cfg.default_encoding is None assert cfg.unrepr is False assert cfg.write_empty_values is False assert cfg.inline_comments == {} assert cfg.comments == {} assert cfg.defaults == [] assert cfg.default_values == {} assert cfg == ConfigObj() assert repr(cfg) == 'ConfigObj({})' class TestReloading(object): @pytest.fixture def reloadable_cfg_content(self): content = ''' test1=40 test2=hello test3=3 test4=5.0 [section] test1=40 test2=hello test3=3 test4=5.0 [[sub section]] test1=40 test2=hello test3=3 test4=5.0 [section2] test1=40 test2=hello test3=3 test4=5.0 ''' return content def test_handle_no_filename(self): for bad_args in ([io.BytesIO()], [], [[]]): cfg = ConfigObj(*bad_args) with pytest.raises(ReloadError) as excinfo: cfg.reload() assert str(excinfo.value) == 'reload failed, filename is not set.' def test_reloading_with_an_actual_file(self, request, reloadable_cfg_content, cfg_contents): with NamedTemporaryFile(delete=False, mode='wb') as cfg_file: cfg_file.write(reloadable_cfg_content.encode('utf-8')) request.addfinalizer(lambda : os.unlink(cfg_file.name)) configspec = cfg_contents(""" test1= integer(30,50) test2= string test3=integer test4=float(4.5) [section] test1=integer(30,50) test2=string test3=integer test4=float(4.5) [[sub section]] test1=integer(30,50) test2=string test3=integer test4=float(4.5) [section2] test1=integer(30,50) test2=string test3=integer test4=float(4.5) """) cfg = ConfigObj(cfg_file.name, configspec=configspec) cfg.configspec['test1'] = 'integer(50,60)' backup = ConfigObj(cfg_file.name) del cfg['section'] del cfg['test1'] cfg['extra'] = '3' cfg['section2']['extra'] = '3' cfg.reload() assert cfg == backup assert cfg.validate(Validator()) class TestDuplicates(object): def test_duplicate_section(self): cfg = ''' [hello] member = value [hello again] member = value [ "hello" ] member = value ''' with pytest.raises(DuplicateError) as excinfo: ConfigObj(cfg.splitlines(), raise_errors=True) assert str(excinfo.value) == 'Duplicate section name at line 6.' def test_duplicate_members(self): d = ''' [hello] member=value [helloagain] member1=value member2=value 'member1'=value ["andagain"] member=value ''' with pytest.raises(DuplicateError) as excinfo: ConfigObj(d.splitlines(),raise_errors=True) assert str(excinfo.value) == 'Duplicate keyword name at line 7.' class TestInterpolation(object): """ tests various interpolation behaviors using config par """ @pytest.fixture def config_parser_cfg(self): cfg = ConfigObj() cfg['DEFAULT'] = { 'b': 'goodbye', 'userdir': r'c:\\home', 'c': '%(d)s', 'd': '%(c)s' } cfg['section'] = { 'a': r'%(datadir)s\\some path\\file.py', 'b': r'%(userdir)s\\some path\\file.py', 'c': 'Yo %(a)s', 'd': '%(not_here)s', 'e': '%(e)s', } cfg['section']['DEFAULT'] = { 'datadir': r'c:\\silly_test', 'a': 'hello - %(b)s', } return cfg @pytest.fixture def template_cfg(self, cfg_contents): interp_cfg = ''' [DEFAULT] keyword1 = value1 'keyword 2' = 'value 2' reference = ${keyword1} foo = 123 [ section ] templatebare = $keyword1/foo bar = $$foo dollar = $$300.00 stophere = $$notinterpolated with_braces = ${keyword1}s (plural) with_spaces = ${keyword 2}!!! with_several = $keyword1/$reference/$keyword1 configparsersample = %(keyword 2)sconfig deep = ${reference} [[DEFAULT]] baz = $foo [[ sub-section ]] quux = '$baz + $bar + $foo' [[[ sub-sub-section ]]] convoluted = "$bar + $baz + $quux + $bar" ''' return ConfigObj(cfg_contents(interp_cfg), interpolation='Template') def test_interpolation(self, config_parser_cfg): test_section = config_parser_cfg['section'] assert test_section['a'] == r'c:\\silly_test\\some path\\file.py' assert test_section['b'] == r'c:\\home\\some path\\file.py' assert test_section['c'] == r'Yo c:\\silly_test\\some path\\file.py' def test_interpolation_turned_off(self, config_parser_cfg): config_parser_cfg.interpolation = False test_section = config_parser_cfg['section'] assert test_section['a'] == r'%(datadir)s\\some path\\file.py' assert test_section['b'] == r'%(userdir)s\\some path\\file.py' assert test_section['c'] == r'Yo %(a)s' def test_handle_errors(self, config_parser_cfg): with pytest.raises(MissingInterpolationOption) as excinfo: print(config_parser_cfg['section']['d']) assert (str(excinfo.value) == 'missing option "not_here" in interpolation.') with pytest.raises(InterpolationLoopError) as excinfo: print(config_parser_cfg['section']['e']) assert (str(excinfo.value) == 'interpolation loop detected in value "e".') def test_template_interpolation(self, template_cfg): test_sec = template_cfg['section'] assert test_sec['templatebare'] == 'value1/foo' assert test_sec['dollar'] == '$300.00' assert test_sec['stophere'] == '$notinterpolated' assert test_sec['with_braces'] == 'value1s (plural)' assert test_sec['with_spaces'] == 'value 2!!!' assert test_sec['with_several'] == 'value1/value1/value1' assert test_sec['configparsersample'] == '%(keyword 2)sconfig' assert test_sec['deep'] == 'value1' assert test_sec['sub-section']['quux'] == '123 + $foo + 123' assert (test_sec['sub-section']['sub-sub-section']['convoluted'] == '$foo + 123 + 123 + $foo + 123 + $foo') class TestQuotes(object): """ tests what happens whn dealing with quotes """ def assert_bad_quote_message(self, empty_cfg, to_quote, **kwargs): #TODO: this should be use repr instead of str message = 'Value "{0}" cannot be safely quoted.' with pytest.raises(ConfigObjError) as excinfo: empty_cfg._quote(to_quote, **kwargs) assert str(excinfo.value) == message.format(to_quote) def test_handle_unbalanced(self, i): self.assert_bad_quote_message(i, '"""\'\'\'') def test_handle_unallowed_newline(self, i): newline = '\n' self.assert_bad_quote_message(i, newline, multiline=False) def test_handle_unallowed_open_quote(self, i): open_quote = ' "\' ' self.assert_bad_quote_message(i, open_quote, multiline=False) def test_handle_multiple_bad_quote_values(self): testconfig5 = ''' config = "hello # comment test = 'goodbye fish = 'goodbye # comment dummy = "hello again ''' with pytest.raises(ConfigObjError) as excinfo: ConfigObj(testconfig5.splitlines()) assert len(excinfo.value.errors) == 4 def test_handle_stringify_off(): c = ConfigObj() c.stringify = False with pytest.raises(TypeError) as excinfo: c['test'] = 1 assert str(excinfo.value) == 'Value is not a string "1".' class TestValues(object): """ Tests specifics about behaviors with types of values """ @pytest.fixture def testconfig3(self, cfg_contents): return cfg_contents(""" a = , b = test, c = test1, test2 , test3 d = test1, test2, test3, """) def test_empty_values(self, cfg_contents): cfg_with_empty = cfg_contents(""" k = k2 =# comment test val = test val2 = , val3 = 1, val4 = 1, 2 val5 = 1, 2, """) cwe = ConfigObj(cfg_with_empty) # see a comma? it's a list assert cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [], 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']} # not any more cwe = ConfigObj(cfg_with_empty, list_values=False) assert cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',', 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'} def test_list_values(self, testconfig3): cfg = ConfigObj(testconfig3, raise_errors=True) assert cfg['a'] == [] assert cfg['b'] == ['test'] assert cfg['c'] == ['test1', 'test2', 'test3'] assert cfg['d'] == ['test1', 'test2', 'test3'] def test_list_values_off(self, testconfig3): cfg = ConfigObj(testconfig3, raise_errors=True, list_values=False) assert cfg['a'] == ',' assert cfg['b'] == 'test,' assert cfg['c'] == 'test1, test2 , test3' assert cfg['d'] == 'test1, test2, test3,' def test_handle_multiple_list_value_errors(self): testconfig4 = ''' config = 3,4,, test = 3,,4 fish = ,, dummy = ,,hello, goodbye ''' with pytest.raises(ConfigObjError) as excinfo: ConfigObj(testconfig4.splitlines()) assert len(excinfo.value.errors) == 4 def test_creating_with_a_dictionary(): dictionary_cfg_content = { 'key1': 'val1', 'key2': 'val2', 'section 1': { 'key1': 'val1', 'key2': 'val2', 'section 1b': { 'key1': 'val1', 'key2': 'val2', }, }, 'section 2': { 'key1': 'val1', 'key2': 'val2', 'section 2b': { 'key1': 'val1', 'key2': 'val2', }, }, 'key3': 'val3', } cfg = ConfigObj(dictionary_cfg_content) assert dictionary_cfg_content == cfg assert dictionary_cfg_content is not cfg assert dictionary_cfg_content == cfg.dict() assert dictionary_cfg_content is not cfg.dict() class TestComments(object): @pytest.fixture def comment_filled_cfg(self, cfg_contents): return cfg_contents(""" # initial comments # with two lines key = "value" # section comment [section] # inline section comment # key comment key = "value" # final comment # with two lines""" ) def test_multiline_comments(self, i): expected_multiline_value = '\nWell, this is a\nmultiline value\n' assert i == { 'name4': ' another single line value ', 'multi section': { 'name4': expected_multiline_value, 'name2': expected_multiline_value, 'name3': expected_multiline_value, 'name1': expected_multiline_value, }, 'name2': ' another single line value ', 'name3': ' a single line value ', 'name1': ' a single line value ', } def test_starting_and_ending_comments(self, a, testconfig1, cfg_contents): filename = a.filename a.filename = None values = a.write() index = 0 while index < 23: index += 1 line = values[index-1] assert line.endswith('# comment ' + str(index)) a.filename = filename start_comment = ['# Initial Comment', '', '#'] end_comment = ['', '#', '# Final Comment'] newconfig = start_comment + testconfig1.splitlines() + end_comment nc = ConfigObj(newconfig) assert nc.initial_comment == ['# Initial Comment', '', '#'] assert nc.final_comment == ['', '#', '# Final Comment'] assert nc.initial_comment == start_comment assert nc.final_comment == end_comment def test_inline_comments(self): c = ConfigObj() c['foo'] = 'bar' c.inline_comments['foo'] = 'Nice bar' assert c.write() == ['foo = bar # Nice bar'] def test_unrepr_comments(self, comment_filled_cfg): c = ConfigObj(comment_filled_cfg, unrepr=True) assert c == { 'key': 'value', 'section': { 'key': 'value'}} assert c.initial_comment == [ '', '# initial comments', '# with two lines' ] assert c.comments == {'section': ['# section comment'], 'key': []} assert c.inline_comments == { 'section': '# inline section comment', 'key': '' } assert c['section'].comments == { 'key': ['# key comment']} assert c.final_comment == ['', '# final comment', '# with two lines'] def test_comments(self, comment_filled_cfg): c = ConfigObj(comment_filled_cfg) assert c == { 'key': 'value', 'section': { 'key': 'value'}} assert c.initial_comment == [ '', '# initial comments', '# with two lines' ] assert c.comments == {'section': ['# section comment'], 'key': []} assert c.inline_comments == { 'section': '# inline section comment', 'key': None } assert c['section'].comments == { 'key': ['# key comment']} assert c.final_comment == ['', '# final comment', '# with two lines'] def test_overwriting_filenames(a, b, i): #TODO: I'm not entirely sure what this test is actually asserting filename = a.filename a.filename = 'test.ini' a.write() a.filename = filename assert a == ConfigObj('test.ini', raise_errors=True) os.remove('test.ini') b.filename = 'test.ini' b.write() assert b == ConfigObj('test.ini', raise_errors=True) os.remove('test.ini') i.filename = 'test.ini' i.write() assert i == ConfigObj('test.ini', raise_errors=True) os.remove('test.ini') def test_interpolation_using_default_sections(): c = ConfigObj() c['DEFAULT'] = {'a' : 'fish'} c['a'] = '%(a)s' assert c.write() == ['a = %(a)s', '[DEFAULT]', 'a = fish'] class TestIndentation(object): @pytest.fixture def max_tabbed_cfg(self): return ['[sect]', ' [[sect]]', ' foo = bar'] def test_write_dictionary(self): assert ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write() == [ '[sect]', ' [[sect]]', ' foo = bar' ] def test_indentation_preserved(self, max_tabbed_cfg): for cfg_content in ( ['[sect]', '[[sect]]', 'foo = bar'], ['[sect]', ' [[sect]]', ' foo = bar'], max_tabbed_cfg ): assert ConfigObj(cfg_content).write() == cfg_content def test_handle_tabs_vs_spaces(self, max_tabbed_cfg): one_tab = ['[sect]', '\t[[sect]]', '\t\tfoo = bar'] two_tabs = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar'] tabs_and_spaces = [b'[sect]', b'\t \t [[sect]]', b'\t \t \t \t foo = bar'] assert ConfigObj(one_tab).write() == one_tab assert ConfigObj(two_tabs).write() == two_tabs assert ConfigObj(tabs_and_spaces).write() == [s.decode('utf-8') for s in tabs_and_spaces] assert ConfigObj(max_tabbed_cfg, indent_type=chr(9)).write() == one_tab assert ConfigObj(one_tab, indent_type=' ').write() == max_tabbed_cfg class TestEdgeCasesWhenWritingOut(object): def test_newline_terminated(self, empty_cfg): empty_cfg.newlines = '\n' empty_cfg['a'] = 'b' collector = io.BytesIO() empty_cfg.write(collector) assert collector.getvalue() == b'a = b\n' def test_hash_escaping(self, empty_cfg): empty_cfg.newlines = '\n' empty_cfg['#a'] = 'b # something' collector = io.BytesIO() empty_cfg.write(collector) assert collector.getvalue() == b'"#a" = "b # something"\n' empty_cfg = ConfigObj() empty_cfg.newlines = '\n' empty_cfg['a'] = 'b # something', 'c # something' collector = io.BytesIO() empty_cfg.write(collector) assert collector.getvalue() == b'a = "b # something", "c # something"\n' def test_detecting_line_endings_from_existing_files(self): for expected_line_ending in ('\r\n', '\n'): with open('temp', 'w') as h: h.write(expected_line_ending) c = ConfigObj('temp') assert c.newlines == expected_line_ending os.remove('temp') def test_writing_out_dict_value_with_unrepr(self): # issue #42 cfg = [str('thing = {"a": 1}')] c = ConfigObj(cfg, unrepr=True) assert repr(c) == "ConfigObj({'thing': {'a': 1}})" assert c.write() == ["thing = {'a': 1}"] configobj-5.0.9/src/tests/test_validate.py000066400000000000000000000127601467354007300206370ustar00rootroot00000000000000# coding=utf-8 from configobj import ConfigObj import pytest from configobj.validate import Validator, VdtValueTooSmallError class TestImporting(object): def test_top_level(self, val): import validate assert val.__class__ is validate.Validator def test_within_configobj_using_from(self, val): from configobj import validate assert val.__class__ is validate.Validator def test_within_configobj(self, val): import configobj.validate assert val.__class__ is configobj.validate.Validator class TestBasic(object): def test_values_too_small(self, val): config = ''' test1=40 test2=hello test3=3 test4=5.0 [section] test1=40 test2=hello test3=3 test4=5.0 [[sub section]] test1=40 test2=hello test3=3 test4=5.0 '''.splitlines() configspec = ''' test1= integer(30,50) test2= string test3=integer test4=float(6.0) [section ] test1=integer(30,50) test2=string test3=integer test4=float(6.0) [[sub section]] test1=integer(30,50) test2=string test3=integer test4=float(6.0) '''.splitlines() c1 = ConfigObj(config, configspec=configspec) test = c1.validate(val) assert test == { 'test1': True, 'test2': True, 'test3': True, 'test4': False, 'section': { 'test1': True, 'test2': True, 'test3': True, 'test4': False, 'sub section': { 'test1': True, 'test2': True, 'test3': True, 'test4': False, }, }, } with pytest.raises(VdtValueTooSmallError) as excinfo: val.check(c1.configspec['test4'], c1['test4']) assert str(excinfo.value) == 'the value "5.0" is too small.' def test_values(self, val): val_test_config = ''' key = 0 key2 = 1.1 [section] key = some text key2 = 1.1, 3.0, 17, 6.8 [[sub-section]] key = option1 key2 = True'''.splitlines() val_test_configspec = ''' key = integer key2 = float [section] key = string key2 = float_list(4) [[sub-section]] key = option(option1, option2) key2 = boolean'''.splitlines() val_test = ConfigObj(val_test_config, configspec=val_test_configspec) assert val_test.validate(val) val_test['key'] = 'text not a digit' val_res = val_test.validate(val) assert val_res == {'key2': True, 'section': True, 'key': False} def test_defaults(self, val): configspec = ''' test1=integer(30,50, default=40) test2=string(default="hello") test3=integer(default=3) test4=float(6.0, default=6.0) [section ] test1=integer(30,50, default=40) test2=string(default="hello") test3=integer(default=3) test4=float(6.0, default=6.0) [[sub section]] test1=integer(30,50, default=40) test2=string(default="hello") test3=integer(default=3) test4=float(6.0, default=6.0) '''.splitlines() default_test = ConfigObj(['test1=30'], configspec=configspec) assert repr(default_test) == "ConfigObj({'test1': '30'})" assert default_test.defaults == [] assert default_test.default_values == {} assert default_test.validate(val) assert default_test == { 'test1': 30, 'test2': 'hello', 'test3': 3, 'test4': 6.0, 'section': { 'test1': 40, 'test2': 'hello', 'test3': 3, 'test4': 6.0, 'sub section': { 'test1': 40, 'test3': 3, 'test2': 'hello', 'test4': 6.0, }, }, } assert default_test.defaults == ['test2', 'test3', 'test4'] assert default_test.default_values == { 'test1': 40, 'test2': 'hello', 'test3': 3, 'test4': 6.0 } assert default_test.restore_default('test1') == 40 assert default_test['test1'] == 40 assert 'test1' in default_test.defaults def change(section, key): section[key] = 3 default_test.walk(change) assert default_test['section']['sub section']['test4'] == 3 default_test.restore_defaults() assert default_test == { 'test1': 40, 'test2': "hello", 'test3': 3, 'test4': 6.0, 'section': { 'test1': 40, 'test2': "hello", 'test3': 3, 'test4': 6.0, 'sub section': { 'test1': 40, 'test2': "hello", 'test3': 3, 'test4': 6.0 }}} configobj-5.0.9/src/tests/test_validate_errors.py000066400000000000000000000043621467354007300222320ustar00rootroot00000000000000import os import pytest from configobj import ConfigObj, get_extra_values, ParseError, NestingError from configobj.validate import Validator, VdtUnknownCheckError @pytest.fixture() def thisdir(): return os.path.dirname(os.path.join(os.getcwd(), __file__)) @pytest.fixture() def inipath(thisdir): return os.path.join(thisdir, 'conf.ini') @pytest.fixture() def specpath(thisdir): return os.path.join(thisdir, 'conf.spec') @pytest.fixture() def conf(inipath, specpath): return ConfigObj(inipath, configspec=specpath) def test_validate_no_valid_entries(conf): validator = Validator() result = conf.validate(validator) assert not result def test_validate_preserve_errors(conf): validator = Validator() result = conf.validate(validator, preserve_errors=True) assert not result['value'] assert not result['missing-section'] section = result['section'] assert not section['value'] assert not section['sub-section']['value'] assert not section['missing-subsection'] def test_validate_extra_values(conf): conf.validate(Validator(), preserve_errors=True) assert conf.extra_values == ['extra', 'extra-section'] assert conf['section'].extra_values == ['extra-sub-section'] assert conf['section']['sub-section'].extra_values == ['extra'] def test_get_extra_values(conf): conf.validate(Validator(), preserve_errors=True) extra_values = get_extra_values(conf) expected = sorted([ ((), 'extra'), ((), 'extra-section'), (('section', 'sub-section'), 'extra'), (('section',), 'extra-sub-section'), ]) assert sorted(extra_values) == expected def test_invalid_lines_with_percents(tmpdir, specpath): ini = tmpdir.join('config.ini') ini.write('extra: %H:%M\n') with pytest.raises(ParseError): conf = ConfigObj(str(ini), configspec=specpath, file_error=True) def test_no_parent(tmpdir, specpath): ini = tmpdir.join('config.ini') ini.write('[[haha]]') with pytest.raises(NestingError): conf = ConfigObj(str(ini), configspec=specpath, file_error=True) def test_re_dos(val): value = "aaa" i = 165100 attack = '\x00'*i + ')' + '('*i with pytest.raises(VdtUnknownCheckError): val.check(attack, value) configobj-5.0.9/src/validate/000077500000000000000000000000001467354007300160565ustar00rootroot00000000000000configobj-5.0.9/src/validate/__init__.py000066400000000000000000000004361467354007300201720ustar00rootroot00000000000000""" This is a backwards compatibility-shim to support: ``` import validate ``` in a future release, we'd expect this to no longer work and instead using: ``` import configobj.validate ``` or: ``` from configobj import validate ``` """ from configobj.validate import *