PyFiguration

PyFiguration is a configuration tool for Python. It allows you to define which configuration are used from right inside your code. The PyFiguration command line tool helps you inspect which configurations are available, what the allowed values are, and helps to inspect the validity of a configuration file for a specific script.

Basic usage

In your code you can define which configurations should be available. This example creates a simple Flask server. The port on which the server should start depends on the configuration.

""" script.py
"""
from pyfiguration import conf
from flask import Flask


@conf.addIntField(
    field="server.port",
    description="The port on which the server will start",
    default=8000,
    minValue=80,
    maxValue=9999,
)
def startServer():
    app = Flask(__name__)
    port = conf["server"]["port"]
    print(f"Starting on port {port}")
    app.run(port=port)


if __name__ == "__main__":
    startServer()

You can use the PyFiguration command line tool to inspect this module/script:

$ pyfiguration inspect script -s script.py
The following options can be used in a configuration file for the module 'script.py':
server:
  port:
    allowedDataType: int
    default: 8000
    description: The port on which the server will start
    maxValue: 9999
    minValue: 80
    required: true

This tells you that the default value for server.port is 8000, and that it should be an integer between 80 and 9999. Running the script (python script.py) will start the server on the default port. Lets create a configuration file to overwrite the default:

# config.yaml
server:
  port: 5000

Now we can start the script again, pointing to the config file to use it:

$ python script.py --config ./config.yaml
Starting on port 5000
 * Serving Flask app "script" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Success! The script has used the configuration we’ve defined in config.yaml file. It’s possible to use multiple configuration files, and both in YAML and JSON formats. Note that the keys will be overwritten if there are duplicates. This is a useful feature that you can use, for example, to set defaults in defaults.yaml and then overwrite with deployment specific settings in deployment.yaml. It’s also possible to reference a full folder. PyFiguration will read all the files in the folder. For a full example checkout the ./examples folder of this repository.

If you have a configuration file and a script, you can also use the PyFiguration command line to check the config file for errors. Imaging this configuration file:

# config_with_warnings.yaml
server:
  port: 500.0
  not_needed_key: some random value

We’ve obviously made 2 mistakes here: 1: the port is a float, 2: there is a key that is not being used by our script. Lets use the command line tool to investigate.

$ pyfiguration inspect config -s script.py -c config_with_warnings.yaml
--------
 Errors
--------
   ✗ Value '500.0' is not of the correct type. The allowed data type is: int
----------
 Warnings
----------
   ! Key 'server.not_needed_key' doesn't exist in the definition and is not used in the module.

Quickstart

The following instructions will get you up and running in no time.

Install PyFiguration

Run the following command to install PyFiguration:

$ pip install pyfiguration

Or add PyFiguration to your requirements.txt or setup.py.

Import PyFiguration in your script

Add the following line to any script to start making use of PyFiguration:

from pyfiguration import conf

Create new configuration options

Decorate your functions to define which configurations should be made available, and how to check if the provided values are valid. Example:

@conf.add_int_field("port", minValue=80, maxValue=9999, default=8000)
def my_function():
    ...

Use configuration in your code

The conf object will contain all the parsed configurations. Just access the value like this:

port = conf["port"]

Write a config file

Running with default values can be fine, but of course you want to be able to overwrite them. Define a YAML or JSON file with your config, and provide the file as an argument to your script when running:

# config.yaml
port: 6000
$ python my_script.py --config config.yaml

Installation guide

Installing PyFiguration

PyFiguration runs on Python 3.7+ and is available on PyPi. Run the following command to install PyFiguration on your machine:

$ pip install pyfiguration

External dependencies

The following external depencies will be installed automatically:

  • click
  • colorama
  • pyyaml

Writing configuration

Specifying configurations

The recommended way to define configurations with PyFiguration is to decorate the function that uses the configuration. This way you always have the definition of the configuration close to where the configuration is used. PyFiguration comes with a set of decorators that create different types of fields.

The sections below show examples of how fields can be specified. For the full list of options per type, check out the API section of this documentation.

Integers
@conf.add_int_field("port", description="The port number of a server")
@conf.add_int_field("port", minValue=80, maxValue=999)
@conf.add_int_field("port", default=10)
@conf.add_int_field("port", required=False)
Floats
@conf.add_float_field("length", description="The length in cm")
@conf.add_float_field("length", minValue=10.0, maxValue=100.0)
@conf.add_float_field("length", default=10.0)
@conf.add_float_field("length", required=False)
Strings
@conf.add_string_field("host", description="The url of the host to connect to")
@conf.add_string_field("host", allowedValues=["localhost", "remotehost"])
@conf.add_string_field("host", required=False)
Booleans
@conf.add_boolean_field("isRemote", description="Whether or not something is true")
@conf.add_boolean_field("isRemote", required=False)
Lists
@conf.add_list_field("users", description="A list of users that should have access")
@conf.add_list_field("users", allowedValues=["admin", "myusername"])
@conf.add_list_field("users", required=False)

Nested fields

It is possible to nest configurations (e.g. in sections). This is standard practice in YAML and JSON files and is supported by PyFiguration. Just define the configuration field in “dot-notation”. Combine all the sections of the path to the key you would like to define with “.”. Here is an example:

# config.yaml
database:
  host: localhost
  port: 8000
# script.py
from pyconfiguration import conf

@conf.add_string_field("database.host", description="The database URL")
@conf.add_int_field("database.port", description="The port to connect to on the database")
def connect():
    host = conf["database"]["host"]
    port = conf["database"]["port"]

Using configurations

When you’ve defined the configuration your script needs, using the decorators, and you’ve written a configuration file it’s time to use this configuration with your script. PyFiguration will automatically parse the --config CONFIGFILES arguments to your script and use them (in order!) to load the configuration for your script.

Simple configurations

In the simplest form you can use a single config file with your script like this:

# config.yaml
database:
  host: localhost
  port: 8000
# script.py
from pyconfiguration import conf

@conf.add_string_field("database.host", description="The database URL")
@conf.add_int_field("database.port", description="The port to connect to on the database")
def connect():
    host = conf["database"]["host"]
    port = conf["database"]["port"]
    print(f"Host: {host}")
    print(f"Port: {port}")
    ...
$ python script.py --config config.yaml
Host: localhost
Port: 8000
...

Configuration inheritance

When you specify more than 1 configuration file to be used with your script, PyFiguration will go over the specified files one by one, and load all of them. The order in which you provide the configuration files matters! Configurations from the first file will be overwritten with configurations from the second, and so on.

This allows patterns like this:

$ ls .
config.yaml        defaults.yaml       script.py

$ python script.py --config defaults.yaml config.yaml

In this example PyFiguration will first load the defaults from defaults.yaml and will then overwrite the configuration that are also specified in config.yaml.

From directories

PyFiguration is also able to accept a directory of configuration files on the command line. If a directory is specified, PyFiguration will go through the directory and load all .yaml, .yml, and .json files, also from subdirectories. Note that the order is not guaranteed!

This allows patterns like this:

$ ls
defaults/          deployments/        script.py

$ ls defaults/
database.yaml      server.yaml

$ ls deployments/
deployment_a.yaml  deployment_b.yaml

$ python script.py --config defaults/ deployments/deployment_a.yaml

In this example we have default configurations for the server and the database in separate files in the defaults/ folder. We provide our script with the folder so it will load both. Then we provide the script with one of the deployment files to overwrite some of the defaults.

Command line tool

PyFiguration ships with a convenient command line tool that can generate documentation for the configuration of a script, and that can inspect a give configuration for errors and potential mistakes.

Getting help

Run the following command to get the full usage instructions for the command line tool:

$ pyfiguration --help
usage: pyfiguration [-h] {inspect} ...

PyFiguration commandline tool

Use this commandline tool to inspect scripts and have a look at the
configuration options that the script provides. Furthermore you can
inspect configuration files directly.

optional arguments:
  -h, --help  show this help message and exit

Commands:
  {inspect}
    inspect   Inspect configurations and scripts



$ pyfiguration inspect --help
usage: pyfiguration inspect [-h] {config,script} ...

Inspect configuration files, scripts or modules to see which values are
allowed, or to check if a provided configuration file is valid for a specific
script.

optional arguments:
  -h, --help       show this help message and exit

Commands:
  The type object you would like to inspect

  {config,script}
    config         Inspect a configuration file to see if it is valid for a
                   given script
    script         Inspect a script to see what configuration options are
                   available



$ pyfiguration inspect script --help
usage: pyfiguration inspect script [-h] [-s SCRIPT]

Provide a file or script to inspect it with PyFiguration. This command will
load the script from file and inspect the PyFiguration decorators to find out
what the configuration options are. Then, it will display all the option as
the output of this command. SCRIPT is the filename of the script to inspect
with PyFiguraton

optional arguments:
  -h, --help            show this help message and exit
  -s SCRIPT, --script SCRIPT
                        The script against which to inspect the config



$ pyfiguration inspect config --help
usage: pyfiguration inspect config [-h] [-c [CONFIG [CONFIG ...]]] [-s SCRIPT]

This command will load the SCRIPT and look at the defintion. Then it will load
the CONFIG file and makes sure the CONFIG file is valid for the provided
SCRIPT. SCRIPT is the filename of the SCRIPT to inspect with PyFiguraton,
CONFIG file is the configuration file to inspect, against the SCRIPT.

optional arguments:
  -h, --help            show this help message and exit
  -c [CONFIG [CONFIG ...]], --config [CONFIG [CONFIG ...]]
                        The configuration file to inspect
  -s SCRIPT, --script SCRIPT
                        The script against which to inspect the config

Inspect a script/module

To inspect a script or module and find out what the allowed configurations are, the command line tool can be used. An example:

$ pyfiguration inspect script -s basic.py
The following options can be used in a configuration file for the module 'basic.py':
db:
  host:
    allowedDataType: str
    default: localhost
    description: Location of the database, e.g. localhost
    required: true
  port:
    allowedDataType: int
    default: 8000
    description: Port of the database to connect on
    maxValue: 9999
    minValue: 80
    required: true

In this example we’ve inspected a script called basic.py. The output shows (in YAML format) which fields can be specified in a configuration file, what type the value should have, and how this value is checked (e.g. minValue: 80 will check any configuration file to make sure the value for the field port is >= 80).

Inspect a configuration file

If you have a script, and a configuration file, you can check the configuration file to see if it’s valid for your script. Example:

$ pyfiguration inspect config -s script.py -c config.yaml

$ pyfiguration inspect config -s script.py -c config_with_warnings.yaml
--------
 Errors
--------
   ✗ Value '500.0' is not of the correct type. The allowed data type is: int
----------
 Warnings
----------
   ! Key 'server.not_needed_key' doesn't exist in the definition and is not used in the module.

In this example we run the inspector 2 times. The first time with a valid configuration file (so nothing is returned). The second time we have a configuration file with errors. Apparently we’ve specified a float where we should have specified an integer. Also, we’ve defined a key in our configuration that is not used by the script. This will not break the script, but because it might result in unexpected behaviour it’s raised as a warning.

API

PyFiguration

The PyFiguration class is the class that is used for the conf object that is imported (from pyfiguration import conf). This class can be used to define what the configurations should look like, and to access the configurations once the’re set.

class pyfiguration.pyfiguration.PyFiguration

Load and document configuration files the right way!

NOTE: All functions are implemented in snake case and have an alias in camel case (e.g. add_int_field() and addIntField())

addBooleanField(field: str, default: Optional[Any] = None, required: bool = True, description: Optional[str] = None)

Add a boolean field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

addFloatField(field: str, allowedValues: Optional[List[float]] = None, minValue: Optional[float] = None, maxValue: Optional[float] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a float field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • minValue – The minimum value for this field
  • maxValue – The maximum value for this field
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

addIntField(field: str, allowedValues: Optional[List[int]] = None, minValue: Optional[int] = None, maxValue: Optional[int] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a integer field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • minValue – The minimum value for this field
  • maxValue – The maximum value for this field
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

addListField(field: str, default: Optional[Any] = None, required: bool = True, description: Optional[str] = None)

Add a list field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

addStringField(field: str, allowedValues: Optional[List[str]] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a string field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

add_boolean_field(field: str, default: Optional[Any] = None, required: bool = True, description: Optional[str] = None)

Add a boolean field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

add_float_field(field: str, allowedValues: Optional[List[float]] = None, minValue: Optional[float] = None, maxValue: Optional[float] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a float field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • minValue – The minimum value for this field
  • maxValue – The maximum value for this field
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

add_int_field(field: str, allowedValues: Optional[List[int]] = None, minValue: Optional[int] = None, maxValue: Optional[int] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a integer field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • minValue – The minimum value for this field
  • maxValue – The maximum value for this field
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

add_list_field(field: str, default: Optional[Any] = None, required: bool = True, description: Optional[str] = None)

Add a list field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

add_string_field(field: str, allowedValues: Optional[List[str]] = None, required: bool = True, default: Optional[Any] = None, description: Optional[str] = None)

Add a string field to the definition of the configuration.

Parameters:
  • field – The field to add to the definition
  • allowedValues – The allowed values for this field in the configuration (optional)
  • required – Whether this field is required or not
  • default – The default value for this field if no value is specified in the configuration
Returns:

A wrapped method to use this method as a decorator

Return type:

wrapped

setConfiguration(sources: Optional[List[str]] = None)

Method to set the configuration for this PyFiguration object. Configuration is loaded from a YAML or JSON file.

set_configuration(sources: Optional[List[str]] = None)

Method to set the configuration for this PyFiguration object. Configuration is loaded from a YAML or JSON file.

Utils

Utility methods that are used by PyFiguration.

pyfiguration.utils.fromDotNotation(field: str, obj: Dict[Any, Any]) → Any

Method to retrieve a value from the configuration using dot-notation. Dot-notation means nested fields can be accessed by concatenating all the parents and the key with a “.” (e.g. db.driver.name).

Parameters:
  • field – The field (in dot-notation) to access
  • obj – The object to access using dot-notation
Returns:

The value at the specified key, in the specified obj

Return type:

value

pyfiguration.utils.from_dot_notation(field: str, obj: Dict[Any, Any]) → Any

Method to retrieve a value from the configuration using dot-notation. Dot-notation means nested fields can be accessed by concatenating all the parents and the key with a “.” (e.g. db.driver.name).

Parameters:
  • field – The field (in dot-notation) to access
  • obj – The object to access using dot-notation
Returns:

The value at the specified key, in the specified obj

Return type:

value

pyfiguration.utils.mergeDictionaries(a: dict, b: dict, path: Optional[List[str]] = None) → dict

Merges dictionary b into a, prefering keys in b over keys in a.

Parameters:
  • a – The destination dictionary
  • b – The source dictionary
  • path – The full path in the destination dictionary (for recursion)
Returns:

The merged dictionaries

Return type:

merged

pyfiguration.utils.merge_dictionaries(a: dict, b: dict, path: Optional[List[str]] = None) → dict

Merges dictionary b into a, prefering keys in b over keys in a.

Parameters:
  • a – The destination dictionary
  • b – The source dictionary
  • path – The full path in the destination dictionary (for recursion)
Returns:

The merged dictionaries

Return type:

merged

Contributor Covenant Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others’ private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq

License

Copyright (c) 2020

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.