Skip to main content

A Python library for using INI files to instantiate classes.

Project description

Vivify

Code style: black Keep a Changelog v1.1.0 badge Contributor Covenant Conventional Commits License img

Overview

Vivify is a lightweight Python library for INI style object initialisation. Using Vivify, you can instantiate classes and set attributes using values provided from an external configuration. Vivify supports both POD types as well as more complex objects and relationships. This is particularly useful in domains where the configuration is very closely coupled to the objects.

Installation

Installing Vivify is easy! Vivify is available on the Python Packaging Index. If you're using Poetry then poetry add vivify, otherwise:

pip install vivify

Usage

The snippets shown below are provided in full in the examples directory and demonstrate using an external configuration to instantiate some pre-defined classes. Further examples are also supplied in the examples directory.

This example showcases how Vivify supports complex relationships between classes. In this case, there is a bidirectional associative relationship between classes A and B and B has an additional variable of type V.

class V(list[int], Vivifiable):
    @classmethod
    def vivify(cls, object):
        return cls(map(int, object.split(",")))


class A(object):
    b: B


class B(object):
    a: A
    v: V

Note that in the code above we should specify the types of any instance variables. This is so that Vivify can instantiate the correct type for a specified variable, e.g. to distinguish between a string and a reference to another object in the configuration. It is also important that any attributes specified in the configuration should be settable (or at the very least an attempt to set the attribute should not fail).

The following configuration INI file (example.ini) contains the options we want to use to instantiate two instances of these classes named foo and bar.

[main]
foo = A
bar = B

[foo]
i = 1
b = bar

[bar]
a = foo
v = 1, 2, 3

The main section where variables are named can be given any valid section name other than that which is reserved for supplying default values for sections - this is defined as "DEFAULT" in the standard configuration file parsing library. Note how the other section names correspond to the variable names provided in the main section.

# initialise the config parser and read the ini file
config = ConfigParser()
with open("example.ini") as f:
    config.read_file(f)

# specify the valid objects for vivification
vivifier = Vivifier(types=[A, B])
vivified = vivifier.vivify(instances="main", config=config)
# verify that the vivification occurred as expected
assert isinstance(vivified["foo"], A)
assert isinstance(vivified["bar"], B)
assert vivified["foo"].b == vivified["bar"]
assert vivified["bar"].a == vivified["foo"]
assert vivified["bar"].v == [1, 2, 3]

Limitations

Most simple cases should work without issue. Things get complicated and more care should be taken when there are multiple possible types for a value. Consider an attribute with the type annotation Union[list[int], Optional[str]]. With a configuration value of None, there are two possible ways this could be interpreted: (i) None, and (ii) "None" (because str(None) returns "None"). The list[int] annotation will be skipped because it is a parameterised generic and cannot be instantiated. Currently, the actual type chosen for the is entirely dependent on the ordering of the result of a call to get_args from the typing extensions library. The first valid matching type is picked for setting the attributes value.

from typing import Optional, Union
from typing_extensions import get_args

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)],
)

assert (
    str(get_args(Union[list[int], Optional[str]]))
    == "(list[int], <class 'str'>, <class 'NoneType'>)"
)

In this case, the value would be set to the string "None" because this matches first (after the invalid list[int]) and the permissive str type coercion accepts None as an argument.

If you are especially concerned about which type is used then it is a good idea to either make the typing more strict or use Vivifiable objects (preferred approaches) or alternatively check a call to get_args yourself on the type to verify that the implementation will work as you expect. Vivify provides extensive logging so you can see how variables are handled internally.

Licence

Copyright © 2022 Adam D Walker

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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

vivify-1.0.1.tar.gz (11.5 kB view hashes)

Uploaded Source

Built Distribution

vivify-1.0.1-py3-none-any.whl (11.6 kB view hashes)

Uploaded Python 3

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