diff options
author | Peter Pentchev <roam@ringlet.net> | 2019-01-12 23:01:07 +0200 |
---|---|---|
committer | Peter Pentchev <roam@ringlet.net> | 2019-01-12 23:01:07 +0200 |
commit | 7515de39b089395b94d54869466dcefd08742c18 (patch) | |
tree | fdb325a1a0a30fe85df09018b1f118e33dec79de | |
parent | 367327ed2e148878c968973fba209ce19b7f64cc (diff) |
Add Python setuptools infrastructure.
-rw-r--r-- | python/README.md | 81 | ||||
-rw-r--r-- | python/confget/__init__.py | 107 | ||||
-rwxr-xr-x | python/confget/__main__.py | 12 | ||||
-rw-r--r-- | python/confget/defs.py | 6 | ||||
-rw-r--r-- | python/confget/py.typed | 0 | ||||
-rw-r--r-- | python/setup.cfg | 2 | ||||
-rw-r--r-- | python/setup.py | 132 | ||||
-rw-r--r-- | python/tox.ini | 8 |
8 files changed, 335 insertions, 13 deletions
diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..890f138 --- /dev/null +++ b/python/README.md @@ -0,0 +1,81 @@ +confget - parse configuration files +=================================== + +The `confget` library parses configuration files (currently INI-style +files and CGI `QUERY_STRING` environment variable) and allows a program +to use the values defined in them. It provides various options for +selecting the variable names and values to return and the configuration +file sections to fetch them from. + +The `confget` library may also be used as a command-line tool with +the same interface as the C implementation. + +The `confget` library is fully typed. + +Specifying configuration values for the backends +------------------------------------------------ + +The `confget.defs` module defines the `Config` class that is used to +control the behavior of the various `confget` backends. Its main +purpose is to specify the filename and, optionally, the section name for +INI-style files, but other backends may use its fields in different ways. + +A `Config` object is created using the following parameters: +- a list of variable names to query (may be empty) +- `filename` (str, optional): the name of the file to open +- `section` (str, default ""): the name of the section within the file +- `section_specified` (bool, default false): if `section` is an empty + string, only fetch variables from the unnamed section at the start of + the file instead of defaulting to the first section in the file + +Parsing INI-style configuration files +------------------------------------- + +The `confget` library's "ini" backend parses an INI-style configuration +file. Its `read_file()` method parses the file and returns a dictionary +of sections and the variables and their values within them: + + import confget + + cfg = confget.Config([], filename='config.ini') + ini = confget.BACKENDS['ini'](cfg) + data = ini.read_file() + print('Section names: {names}'.format(names=sorted(data.keys()))) + print(data['server']['address']) + +Letting variables in a section override the default ones +-------------------------------------------------------- + +In some cases it is useful to have default values before the first +named section in a file and then override some values in various +sections. This may be useful for e.g. host-specific configuration +kept in a section with the same name as the host. + +The `format` module in the `confget` library allows, among other +filtering modes, to get the list of variables with a section +overriding the default ones: + + from confget import backend, format + + cfg = format.FormatConfig(['foo'], filename='config.ini', section='first', + section_override=True) + ini = backend.BACKENDS['ini'](cfg) + data = ini.read_file() + res = format.filter_vars(cfg, data) + assert len(res) == 1, repr(res) + print(res[0].output_full) + + cfg = format.FormatConfig(['foo'], filename='config.ini', section='second', + section_override=True) + ini = backend.BACKENDS['ini'](cfg) + data = ini.read_file() + res = format.filter_vars(cfg, data) + assert len(res) == 1, repr(res) + print(res[0].output_full) + +See the documentation of the `FormatConfig` class and the `filter_vars()` +function in the `confget.format` module for more information and for +a list of the various other filtering modes, all supported when +the library is used as a command-line tool. + +Comments: Peter Pentchev <roam@ringlet.net> diff --git a/python/confget/__init__.py b/python/confget/__init__.py index e69de29..cbd8ee9 100644 --- a/python/confget/__init__.py +++ b/python/confget/__init__.py @@ -0,0 +1,107 @@ +# Copyright (c) 2019 Peter Pentchev <roam@ringlet.net> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + +""" +confget - a library for parsing configuration files + +The `confget` library parses configuration files (currently INI-style +files and CGI `QUERY_STRING` environment variable) and allows a program +to use the values defined in them. It provides various options for +selecting the variable names and values to return and the configuration +file sections to fetch them from. + +The `confget` library may also be used as a command-line tool with +the same interface as the C implementation. + +The `confget` library is fully typed. + +Specifying configuration values for the backends +------------------------------------------------ + +The `confget.defs` module defines the `Config` class that is used to +control the behavior of the various `confget` backends. Its main +purpose is to specify the filename and, optionally, the section name for +INI-style files, but other backends may use its fields in different ways. + +A `Config` object is created using the following parameters: +- a list of variable names to query (may be empty) +- `filename` (str, optional): the name of the file to open +- `section` (str, default ""): the name of the section within the file +- `section_specified` (bool, default false): if `section` is an empty + string, only fetch variables from the unnamed section at the start of + the file instead of defaulting to the first section in the file + +Parsing INI-style configuration files +------------------------------------- + +The `confget` library's "ini" backend parses an INI-style configuration +file. Its `read_file()` method parses the file and returns a dictionary +of sections and the variables and their values within them: + + import confget + + cfg = confget.Config([], filename='config.ini') + ini = confget.BACKENDS['ini'](cfg) + data = ini.read_file() + print('Section names: {names}'.format(names=sorted(data.keys()))) + print(data['server']['address']) + +Letting variables in a section override the default ones +-------------------------------------------------------- + +In some cases it is useful to have default values before the first +named section in a file and then override some values in various +sections. This may be useful for e.g. host-specific configuration +kept in a section with the same name as the host. + +The `format` module in the `confget` library allows, among other +filtering modes, to get the list of variables with a section +overriding the default ones: + + from confget import backend, format + + cfg = format.FormatConfig(['foo'], filename='config.ini', section='first', + section_override=True) + ini = backend.BACKENDS['ini'](cfg) + data = ini.read_file() + res = format.filter_vars(cfg, data) + assert len(res) == 1, repr(res) + print(res[0].output_full) + + cfg = format.FormatConfig(['foo'], filename='config.ini', section='second', + section_override=True) + ini = backend.BACKENDS['ini'](cfg) + data = ini.read_file() + res = format.filter_vars(cfg, data) + assert len(res) == 1, repr(res) + print(res[0].output_full) + +See the documentation of the `FormatConfig` class and the `filter_vars()` +function in the `confget.format` module for more information and for +a list of the various other filtering modes, all supported when +the library is used as a command-line tool. +""" + +from .defs import Config, VERSION_STRING # noqa: F401 +from .backend import BACKENDS # noqa: F401 diff --git a/python/confget/__main__.py b/python/confget/__main__.py index 905877d..919fcae 100755 --- a/python/confget/__main__.py +++ b/python/confget/__main__.py @@ -44,12 +44,6 @@ except ImportError: pass -VERSION_STRING = '2.2.0' -FEATURES = [ - ('BASE', VERSION_STRING), -] - - class MainConfig(fmt.FormatConfig): # pylint: disable=too-few-public-methods,too-many-instance-attributes """ @@ -101,7 +95,7 @@ def version(): """ Display program version information. """ - print('confget ' + VERSION_STRING) + print('confget ' + defs.VERSION_STRING) def features(name): @@ -112,10 +106,10 @@ def features(name): if name is None: print(' '.join([ '{name}={version}'.format(name=item[0], version=item[1]) - for item in FEATURES + for item in defs.FEATURES ])) else: - ver = dict(FEATURES).get(name, None) + ver = dict(defs.FEATURES).get(name, None) if ver is None: sys.exit(1) print(ver) diff --git a/python/confget/defs.py b/python/confget/defs.py index 7f6a796..21ae7c5 100644 --- a/python/confget/defs.py +++ b/python/confget/defs.py @@ -36,6 +36,12 @@ except ImportError: pass +VERSION_STRING = '2.2.0' +FEATURES = [ + ('BASE', VERSION_STRING), +] + + class Config(object): # pylint: disable=too-few-public-methods """ Base class for the internal confget configuration. """ diff --git a/python/confget/py.typed b/python/confget/py.typed new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/confget/py.typed diff --git a/python/setup.cfg b/python/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/python/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..2d9f620 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,132 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2019 Peter Pentchev <roam@ringlet.net> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + +""" +Setup infrastructure for confget, the configuration file parser. +""" + +import re +import setuptools # type: ignore + + +RE_VERSION = r'''^ + \s* VERSION_STRING \s* = \s* ' + (?P<version> + (?: 0 | [1-9][0-9]* ) # major + \. (?: 0 | [1-9][0-9]* ) # minor + \. (?: 0 | [1-9][0-9]* ) # patchlevel + (?: \. [a-zA-Z0-9]+ )? # optional addendum (dev1, beta3, etc.) + ) + ' \s* + $''' + + +def get_version(): + # type: () -> str + """ Get the version string from the module's __init__ file. """ + found = None + re_semver = re.compile(RE_VERSION, re.X) + with open('confget/defs.py') as init: + for line in init.readlines(): + match = re_semver.match(line) + if not match: + continue + assert found is None + found = match.group('version') + + assert found is not None + return found + + +def get_long_description(): + # type: () -> str + """ Get the package long description from the README file. """ + with open('README.md') as readme: + return readme.read() + + +setuptools.setup( + name='confget', + version=get_version(), + + description='Parse configuration files and extract values from them', + long_description=get_long_description(), + long_description_content_type='text/markdown', + + author='Peter Pentchev', + author_email='roam@ringlet.net', + url='https://devel.ringlet.net/textproc/confget/', + + packages=['confget', 'confget.backend'], + package_data={ + 'confget': [ + # The typed module marker + 'py.typed', + ], + }, + + install_requires=[ + 'six', + ], + + license='BSD-2', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + + 'Environment :: Console', + + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + + 'License :: DFSG approved', + 'License :: Freely Distributable', + 'License :: OSI Approved :: BSD License', + + 'Operating System :: POSIX', + 'Operating System :: Unix', + + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Utilities', + ], + + entry_points={ + 'console_scripts': [ + 'confget=confget.__main__:main', + ], + }, + + zip_safe=True, +) diff --git a/python/tox.ini b/python/tox.ini index 757d546..57f11d4 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -31,7 +31,7 @@ basepython = python3 deps = flake8 commands = - flake8 confget unit_tests ../t/defs/tools/generate.py + flake8 confget setup.py unit_tests ../t/defs/tools/generate.py [testenv:mypy2] basepython = python3 @@ -40,7 +40,7 @@ deps = setenv = MYPYPATH={toxinidir}/stubs commands = - mypy --strict --py2 confget + mypy --strict --py2 confget setup.py mypy --strict --py2 --allow-untyped-decorators unit_tests [testenv:mypy3] @@ -50,7 +50,7 @@ deps = setenv = MYPYPATH={toxinidir}/stubs commands = - mypy --strict confget ../t/defs/tools/generate.py + mypy --strict confget setup.py ../t/defs/tools/generate.py mypy --strict --allow-untyped-decorators unit_tests [testenv:pylint] @@ -60,7 +60,7 @@ deps = pylint pytest commands = - pylint --disable=useless-object-inheritance,duplicate-code confget unit_tests ../t/defs/tools/generate.py + pylint --disable=useless-object-inheritance,duplicate-code confget setup.py unit_tests ../t/defs/tools/generate.py [testenv:unit_tests_2] basepython = python2 |