Skip to main content

A package for storing service config and secrets

Project description

Service Configurator

Coverage Status

Service Configurator is a python library created for managing settings and secrets for your service.

Installation

Use package manager pip to install service-configurator.

pip install service-configurator

Usage

Creating settings classes

All settings classes should derive from BaseSettings or its subclass.

from configurator import BaseSettings, Integer, String


class MySettings(BaseSettings):
    user_id = Integer()
    password = String()


config = MySettings()

config.user_id = 10
config.password = '123'

print(f'User: {config.user_id} logged with password: {config.password}')

Settings class could have other settings objects as members.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var1 = Integer()


class MySettings2(BaseSettings):
    my_settings = MySettings()
    var2 = Integer()

Inheriting from another settings class is also possible. Subclass treats parent's settings as its own.

from configurator import BaseSettings, Integer


class ParentSettings(BaseSettings):
    var1 = Integer()


class MySettings(ParentSettings):
    var2 = Integer()

equals

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var1 = Integer()
    var2 = Integer()

Available settings fields

All implemented settings fields are presented below.

from configurator import BaseSettings, Integer, PositiveInteger, String, Email, Boolean, Float, Url


class MySettings(BaseSettings):
    var1 = Integer()
    var2 = PositiveInteger()
    var3 = String()
    var4 = Email()
    var5 = Boolean()
    var6 = Float()
    var7 = Url()

Remember to always include parentheses () when creating settings fields.

Optional fields and default values

Setting fields can be marked as optional using required parameter. In addition to that default value can be set, the default value is returned if field wasn't modified. When these arguments are not provided field is required and it's default value is field type specific.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer(required=False, default=50)

Importing settings

You can import settings using:

  • python dict object
  • json file
  • yaml file
from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer()


config = MySettings()

config.from_dict({'var': 12})
config.from_json('file.json')
config.from_yaml('file.yaml')

When importing from python dict object you can use partial_update argument if you don't want to provide all required values, which could be useful when unit testing. By default, an exception is thrown if required values are missing. This option is not accessible for json and yaml imports.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var1 = Integer()
    var2 = Integer()


config = MySettings()

config.from_dict({'var1': 12}, partial_update=True)

Exporting settings

Similar to import you have few export options:

  • python dict object
  • json file
  • yaml file
from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer()


config = MySettings()
config.var = 10

config.to_dict()  # {'var': 10}
config.to_json('file.json')
config.to_yaml('file.yaml')

Yaml files are recommended option for storing your configuration.

Generating template files

Template/skeleton config file can be simply generated by creating a new instance of a settings class and using export method.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer()


config = MySettings()

config.to_json('file.json.skel')
config.to_yaml('file.yaml.skel')

Setting and getting single field

If you want get or set single attribute you can access is as normal class member.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer()


config = MySettings()

config.var = 4
print(config.var)  # prints 4

Private settings fields

If you add a non-field member to your config class, it won't be exported or imported. However you can still use it as a normal class member.

from configurator import BaseSettings, Integer


class MySettings(BaseSettings):
    var = Integer(default=5)
    multiplier = 2

    def multiply(self):
        return self.var * self.multiplier


config = MySettings()
print(config.multiply())  # prints 10
config.to_dict()  # {'var': 5}

Handling exceptions

Configurator throws following exceptions when something goes wrong:

  • ValidationError - provided value didn't pass validation checks for the field
  • SettingsError - a generic Settings error, read an error message for more information
from configurator import BaseSettings, PositiveInteger, ValidationError, SettingsError


class MySettings(BaseSettings):
    var = PositiveInteger()


config = MySettings()

try:
    config.var = -1
except ValidationError as ex:
    # ValidationError -1 can't be assigned to PositiveInteger
    print(f'Validation error: {ex}')

try:
    config.from_dict({})
except SettingsError as ex:
    # SettingsError missing required field 'var'
    print(f'Settings error: {ex}')

Exceptions shouldn't pass silently.

Utility classes

For commonly used sets of settings utility classes were implemented to avoid unnecessary code repetition in multiple services.

All currently implemented utility classes are presented below. Check docs for class destiny.

from configurator import BaseSettings
from configurator.utils import OracleConnectorSettings, BoxSettings


class MySettings(BaseSettings):
    oracle_db = OracleConnectorSettings()
    box = BoxSettings()

Complete example

from configurator import BaseSettings, String, Email
from configurator.utils import OracleConnectorSettings


class MySettings(BaseSettings):
    api_key = String()
    report_email = Email(required=False)
    oracle_db = OracleConnectorSettings()


config = MySettings()
config.from_yaml('config.yml')

config.yaml

api_key: '123qwerty'
oracle_db:
  host: 'http://localhost'
  password: 'pass123'
  port: 1521
  sid: 'db2'
  user: 'admin'
report_email: 'report@example.com'

Contributing

Creating new settings fields

  • All fields classes are located in the fields.py file.
  • Every field should be inherited from Field class or its subclass.
  • All fields should implement default, type, and validate, unless it's implemented in parent class and changes aren't needed

Example field classes:

# implemented in fields.py

class String(Field):
    """
    Class for string type fields.
    """
    type_ = str
    default = ''


class Email(String):
    """
    Class for email fields.
    """

    def validate(self, value: str) -> str:
        """Validates email using simple regex."""
        value = super().validate(value)
        regex = re.compile(r'^\S+@\S+\.\S+$')
        if regex.fullmatch(value) is None:
            raise ValidationError('Provided string is not a valid email.')
        return value

Creating new utility settings

Common sets of parameters used in many services shouldn't be copy-pasted. Instead, a new utility class should be created in a utils.py. Create the utility class as a normal settings class.

# implemented in utils.py

class OracleConnectorSettings(BaseSettings):
    """Setting required for connection to oracle database."""
    host = Url()
    port = Integer()
    user = String()
    password = String()
    sid = String()

    def get_connection_url(self):
        """Get connection url for sql alchemy"""
        return f"oracle://{self.user}:{self.password}@{self.host}:{self.port}/{self.sid}"

Additional things to consider

  • Every class and function should be documented. In addition to that run pydoc3 to generate html documentation after modifications.
    pdoc --html -o docs configurator
    
  • Test coverage of this package is 100% try your best to not lower it.
  • Update __all__ in __init__.py if needed

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

service-configurator-1.0.0.tar.gz (7.5 kB view hashes)

Uploaded Source

Built Distribution

service_configurator-1.0.0-py3-none-any.whl (8.5 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