Skip to main content

Modify configuration files of various formats with the same simple API.

Project description

Config File

Simple manipulation of ini, json, yaml, or toml files

Python Version Version Style Build Status Codecov

About Config File

The Config File project is designed to allow you to easily manipulate your configuration files with the same simple API whether they are in INI, JSON, YAML, or TOML.

Installation

Config File is available to download through PyPI.

$ pip install config-file

Installing Extras

If you want to manipulate YAML and TOML, you'll want to download the extras as well.

$ pip install config-file[yaml, toml]

You can also use Poetry.

$ poetry install config-file -E yaml -E toml

Usage

For this overview, let's say you have the following ini file you want to manipulate.

Do note, however, that the ini format is the oddest format that ConfigFile supports in that it has no formal specification and is not type aware. When retrieving items from the file, it will return them as strings by default. Others are more type aware and do not require as much type coercion.

[section]
num_key = 5
str_key = blah
bool_key = true
list_key = [1, 2]

[second_section]
dict_key = { "another_num": 5 }

It must have a .ini extension in order for the package to recognize it and use the correct parser for it.

Setting up ConfigFile

To use the package, we import in the ConfigFile object. This object is the only thing considered to be part of the public API.

We can set it up by giving it a string or pathlib.Path as the argument. Any home tildes ~ in the string or Path are recognized and converted to the full path for us.

from config_file import ConfigFile

config = ConfigFile("~/some-project/config.ini")

Handling ConfigFile Initialization Errors

from config_file import ConfigFile, ParsingError

try:
    config = ConfigFile("~/some-file.ini")
except ParsingError:
    print("could not parse the file")
except ValueError:
    print("extension that isn't supported was used or is a directory")
except FileNotFoundError:
    print("file does not exist")

Using get()

A recurring pattern you'll see here is that all methods that need to specify something inside your configuration file will do so using a dot syntax.

Retrieving keys and sections

So to retrieve our num_key, we'd specify the heading and the key separated by a dot. All values will then be retrieved as strings.

config.get('section.num_key')
>>> '5'

While we can retrieves keys, we can also retrieve the entire section, which will be returned back to us as a dictionary.

config.get('section')
>>> {'num_key': '5', 'str_key': 'blah', 'bool_key': 'true', 'list_key': '[1, 2]'}

Furthermore, you can also index into the ConfigFile object to retrieve keys if that is preferred.

config['section']['num_key']
>>> '5'

Coercing the return types

However, some of these keys are obviously not strings natively. If we are retrieving a particular value of a key, we may want to coerce it right away without doing clunky type conversions after each time we retrieve a value. To do this, we can utilize the return_type keyword argument.

config.get('section.num_key', return_type=int)
>>> 5

Sometimes we don't have structures quite that simple though. What if we wanted all the values in section coerced? For that, we can utilize a parse_types keyword argument.

config.get('section', parse_types=True)
>>> {'num_key': 5, 'str_key': 'blah', 'bool_key': True, 'list_key': [1, 2]}

It also works for regular keys.

config.get('section.num_key', parse_types=True)
>>> 5

Handling non-existent keys

Sometimes we want to retrieve a key but are unsure of if it will exist. There are two ways we could handle that.

The first is the one we're used to seeing: catch the error.

try:
    important_value = config.get('section.i_do_not_exist')
except KeyError:
    important_value = 42

However, the get method comes with a default keyword argument that we can utilze for this purpose.

config.get('section.i_do_not_exist', default=42)
>>> 42

This can be handy if you have a default for a particular configuration value.

Using set()

We can use set() to set a existing key's value.

config.set('section.num_key', 6)

The method does not return anything, since there is nothing useful to return. If something goes wrong where it is unable to set the value, an exception will be raised instead. This is the case for most methods on ConfigFile, such as delete() or save(), where there would be no useful return value to utilize.

With set(), we can also create and set keys that don't exist yet.

config.set('new_section.new_key', 'New key value!')

Would then result in the following section being added to our original file:

[new_section]
new_key = New key value!

The exact behavior of how these new keys or sections are added are a bit dependent on the file format we're using, since every format is a little different in it's structure and in what it supports. Mostly though, ini is just the odd one.

If we try the following in ini, which does not support subsections or nested keys, we simply get a single section.

config.set("section.sub_section.sub_sub_section.key", 5)
[section.sub_section.sub_sub_section]
key = 5

Lastly, we can set values using an array notation as well. The underlying content is all manipulated as a dictionary for every file type. If we wanted to create a new section, we'd simply set it to be an empty dictionary.

config['new_section'] = {}

Which would result to be an empty section:

[new_section]

Using delete()

delete() allows us to delete entire sections or specific keys.

config.delete('section')

Would result in the entire section being removed from our configuration file. However, we can also just delete a single key.

config.delete('section.num_key')

We can also use the array notation here as well.

del config['section']['num_key']

Using has()

has() allows us to check whether a given key exists in our file. There are two ways to use has().

The first is using the dot syntax.

config.has('section.str_key')
>>> True
config.has('does_not_exist')
>>> False

This will check if our specific key or section exists. However, we can also check in general if a given key or sections exists anywhere in our file with the wild keyword argument.

config.has('str_key', wild=True)
>>> True

Using save()

For any changes we make to our configuration file, they are not written out to the filesystem until we call save(). This is to avoid unnecessary write calls after each operation until we actually need to save.

config.delete('section.list_key')
config.save()

Stringifying our ConfigFile

To retrieve the file as a string, with any changes we've made, we can use the built-in str() method on the ConfigFile. This will always show us our latest changes since it is stringify-ing our internal representation of the configuration file, not just the file we've read in.

str(config)
>>> '[section]\nnum_key = 5\nstr_key = blah\nbool_key = true\nlist_key = [1, 2]\n\n[second_section]\ndict_key = { "another_num": 5 }\n\n'

Using restore_original()

If we have a initial configuration file state, we could keep a copy of that initial file and restore back to it whenever needed using restore_original().

By default, if we created our ConfigFile object with the path of ~/some-project/config.ini, restore_original() will look for our original file at ~/some-project/config.original.ini.

config.restore_original()

However, if we have a specific path elsewhere that this original configuration file is or it is named differently than what the default expects, we can utilize the original_path keyword argument.

config.restore_original(original_path="~/some-project/original-configs/config.ini")

Format Versions Supported

Format Specification version supported
INI No official specification.
JSON RFC 7159
YAML v1.2
TOML v1.0.0-rc.1

For ini and json, Python's standard library modules are used. Regarding ini, there is no formal specification so the syntax that configparser supports is what is supported here.

License

The MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

config_file-0.13.1.tar.gz (32.0 kB view details)

Uploaded Source

Built Distribution

config_file-0.13.1-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file config_file-0.13.1.tar.gz.

File metadata

  • Download URL: config_file-0.13.1.tar.gz
  • Upload date:
  • Size: 32.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.12.0

File hashes

Hashes for config_file-0.13.1.tar.gz
Algorithm Hash digest
SHA256 dd0b00c0f38ef5754d2ce7dc242db90fba21e8f1e9bbc8fa939d8e9bf22c8133
MD5 dc4effacded4428eb97e5b4c7eeefcce
BLAKE2b-256 305c4046f33205e7751475b72d4eb0db95bfe1e6a8ed3ff9fc35af296bbe3da2

See more details on using hashes here.

File details

Details for the file config_file-0.13.1-py3-none-any.whl.

File metadata

File hashes

Hashes for config_file-0.13.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aef074f8b91a413c6fc4ce51e7206baf6963b3662bcb9ae48c0eaba464fda813
MD5 2a80054568c7f643e8ad8650812a9f03
BLAKE2b-256 c28dfc9d115061b41f8f4cd9fafe6ed5dd05a62d185739321d65e615a9b951fb

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page