Module awsrun.config
Provides a YAML/JSON config file reader with type-checked values.
Overview
Config
is a convenient representation of values stored within a dict, which
may contain other dicts. It provides for default values, mandatory values, as
well as the ability to type-check values using type specifications.
Config
can be subclassed to provide parsers for various configuration file types. This
module includes JSONConfig
and YAMLConfig
implementations. These file types
are registered with the Config
class, so users can use the Config.from_file()
factory method, which takes a filename and loads the configuration using the
appropriate implementation based on the file extension. Users can also register
their own subclasses via Config.register_filetype()
.
Type Checking
Type checking of values is done via a set of type objects and classes defined in
this module. This provides a means to ensure that values in the configuration
are of the correct type.
Numerous simple types are provided by pre-defined type
objects: Str
, Int
, Bool
, Float
, File
, IP
, and Dotted
. Several type
classes are provided that can be instantiated to create more complex types:
StrMatch
, Any
, List
, and Dict
. In addition, the combinators Not
,
And
, and Or
can be used to combine any of these types.
For example, the
following type matches a dict with keys as strings and values as a list of ints
or floats:
Dict(Str, List(Or(Int, Float)))
Reading Values
Assuming the file 'test.yaml' contains the following YAML:
verbose: true
engine:
cpus: 4
threads: 10
ip_addr: 10.0.0.1
directories:
- /tmp
- /var/tmp
Config.get()
is used to read values from the configuration. For example, to load
the above file and read values from it:
c = Config.from_file('test.yaml')
# Read top-level keys
assert c.get('verbose', type=Bool) == True
assert c.get('ip_addr', type=IP, must_exist=True) == '10.0.0.1'
assert c.get('directories', type=List(Str), default=[]) == ['/tmp', '/var/tmp']
# Read a hierarchical value by specifying multiple keys
assert c.get('engine', 'threads', type=Int, default=5) == 10
If any of the values do not match the expected type, a TypeError
is raised.
Users can define their own custom types by subclassing Type
. Only two methods
need to be implemented: type_check
and __str__
. Review the implementation of
the included types if building your own.
If multiple keys with the same name exist, the behavior is undefined when retrieving values. Depending on your Python version, you may get one or the other.
Expand source code
#
# Copyright 2019 FMR LLC <opensource@fidelity.com>
#
# SPDX-License-Identifier: Apache-2.0
#
"""Provides a YAML/JSON config file reader with type-checked values.
## Overview
`Config` is a convenient representation of values stored within a dict, which
may contain other dicts. It provides for default values, mandatory values, as
well as the ability to type-check values using type specifications. `Config`
can be subclassed to provide parsers for various configuration file types. This
module includes `JSONConfig` and `YAMLConfig` implementations. These file types
are registered with the `Config` class, so users can use the `Config.from_file`
factory method, which takes a filename and loads the configuration using the
appropriate implementation based on the file extension. Users can also register
their own subclasses via `Config.register_filetype`.
## Type Checking
Type checking of values is done via a set of type objects and classes defined in
this module. This provides a means to ensure that values in the configuration
are of the correct type. Numerous simple types are provided by pre-defined type
objects: `Str`, `Int`, `Bool`, `Float`, `File`, `IP`, and `Dotted`. Several type
classes are provided that can be instantiated to create more complex types:
`StrMatch`, `Any`, `List`, and `Dict`. In addition, the combinators `Not`,
`And`, and `Or` can be used to combine any of these types. For example, the
following type matches a dict with keys as strings and values as a list of ints
or floats:
Dict(Str, List(Or(Int, Float)))
## Reading Values
Assuming the file 'test.yaml' contains the following YAML:
verbose: true
engine:
cpus: 4
threads: 10
ip_addr: 10.0.0.1
directories:
- /tmp
- /var/tmp
`Config.get` is used to read values from the configuration. For example, to load
the above file and read values from it:
c = Config.from_file('test.yaml')
# Read top-level keys
assert c.get('verbose', type=Bool) == True
assert c.get('ip_addr', type=IP, must_exist=True) == '10.0.0.1'
assert c.get('directories', type=List(Str), default=[]) == ['/tmp', '/var/tmp']
# Read a hierarchical value by specifying multiple keys
assert c.get('engine', 'threads', type=Int, default=5) == 10
If any of the values do not match the expected type, a `TypeError` is raised.
Users can define their own custom types by subclassing `Type`. Only two methods
need to be implemented: `type_check` and `__str__`. Review the implementation of
the included types if building your own.
If multiple keys with the same name exist, the behavior is undefined when
retrieving values. Depending on your Python version, you may get one or the
other.
"""
import ipaddress
import json
import logging
import re
from functools import reduce
from pathlib import Path
import yaml
LOG = logging.getLogger(__name__)
# pylint: disable=unidiomatic-typecheck
#
# Because isinstance(True, int) is true, we do not rely on isinstance for our
# type checking in this module as we want to match exact types. We don't want to
# consider subclasses and True should not type check successfully against an
# int.
class Config:
"""A `Config` can read type-checked values from a Python dictionary.
This class provides an interface to read values from a dictionary while
providing for default values, mandatory values, as well as the ability to
type-check values. In addition, it can be used to dynamically instantiate
classes specified in the configuration. Finally, the class also contains a
registry of configuration parsers based on file extensions, so users can
load configs from files.
"""
_filetypes = {}
@classmethod
def register_filetype(cls, config_class, *extensions):
"""Register a parser for files with one of the specified extensions.
The registry is used to find the appropriate config parser when a
user invokes the `Config.from_file` factory method. Extensions should be
specified as '.ext'. Subsequent registrations for the same extension
will override the prior registration.
"""
for ext in extensions:
cls._filetypes[ext] = config_class
@classmethod
def from_file(cls, filename, must_exist=False):
"""Factory method to Load a `Config` from a filename.
This method uses the extension of the filename to determine the
configuration parser that should be used to instantiate a `Config`
object. If `must_exist` is true, a `FileNotFoundError` is raised if the
filename does not exist, otherwise an empty `Config` is returned.
"""
path = Path(filename)
if not path.is_file():
if must_exist:
raise FileNotFoundError(f"Config file not found: {filename}")
return Config({})
if path.suffix not in cls._filetypes:
raise ValueError(f"Unregistered file type extension: {path.suffix}")
with path.open(encoding="utf-8") as f:
return cls._filetypes[path.suffix](f)
def __init__(self, d):
self.conf = d
def get(self, *keys, default=None, type=None, must_exist=False):
"""Return the specified value from the `Config`.
Specify the value to read by providing the keys required to reach the
value in the configuration. If the value is not found at the specified
key path, `None` or the `default` value is returned unless the
`must_exist` flag is `True`, in which case a `ValueError` is raised.
Values can be optionally type-checked to ensure it matches the specified
type. If the `type` matches the value in the configuration, the value is
returned, otherwise a `TypeError` is raised. Types are specified by
passing a `Type` object. There are numerous type objects defined in this
module. For example:
c.get('path', 'to', 'value', type=Int)
c.get('path', 'to', 'value', type=Bool)
c.get('path', 'to', 'value', type=Float)
c.get('path', 'to', 'value', type=Str)
c.get('path', 'to', 'value', type=StrMatch(r'^\\d+-\\d+$'))
c.get('path', 'to', 'value', type=IP)
c.get('path', 'to', 'value', type=List(IP))
c.get('path', 'to', 'value', type=List(Str))
c.get('path', 'to', 'value', type=List(Dict(Int, Str)))
c.get('path', 'to', 'value', type=Dict(Str, Int))
c.get('path', 'to', 'value', type=Or(Int, Float))
c.get('path', 'to', 'value', type=And(StrMatch(r'\\d+'), StrMatch(r'[A-Z]')))
c.get('path', 'to', 'value', type=Not(Or(Int, Float)))
"""
# pylint: disable=redefined-builtin
# This one-liner will recursively follow a list of keys into a
# dictionary and return the value. If a key does not exist, return an
# empty dict.
try:
value = reduce(lambda a, p: a.get(p, {}), keys, self.conf)
except AttributeError as e:
raise ValueError(
f"Error in config: {'->'.join(keys[:-1])}: not a dictionary"
) from e
# If value is {} that means the key doesn't exist. If the must_exist
# flag was passed, then we raise a descriptive ValueError, otherwise we
# set it to the default.
if value == {}:
if must_exist:
raise ValueError(f"Error in config: {'->'.join(keys)}: must be set")
value = default
# If no value has been set in the config and none has been provided as a
# default, then return None.
if value is None:
return value
# If no type has been specified, then return the value in the config or
# the default without doing any type checking.
if not type:
return value
# Only return the value if it type checks correctly.
if type.type_check(value):
return value
# Finally, all other cases indicate a type error.
raise TypeError(
f"Error in config: {'->'.join(keys)}: not a {type}: {repr(value)}"
)
EmptyConfig = Config({})
"""Singleton representing an empty `Config`."""
class YAMLConfig(Config):
"""Loads a YAML configuration from a stream."""
def __init__(self, stream):
super().__init__(yaml.safe_load(stream))
class JSONConfig(Config):
"""Loads a JSON configuration from a stream."""
def __init__(self, stream):
super().__init__(json.load(stream))
Config.register_filetype(JSONConfig, ".json", ".jsn")
Config.register_filetype(YAMLConfig, ".yaml", ".yml")
class Type:
"""Represents a type that can be used in type-check comparisons."""
def type_check(self, obj):
"""Returns true if obj is a type matching this `Type`."""
raise NotImplementedError
def __str__(self):
"""Returns a string representing this `Type`."""
raise NotImplementedError
class Not(Type):
"""Represents a type that is not a type of `config_type`.
`config_type` must be an instance of `Type`. For example:
Not(Str)
Not(StrMatch(r'\\d+'))
Not(List(Int))
"""
def __init__(self, config_type):
self.config_type = config_type
def type_check(self, obj):
return not self.config_type.type_check(obj)
def __str__(self):
return "not " + str(self.config_type)
class Or(Type):
"""Represents a type that is one of the `config_types`.
`config_type` must be an instance of `Type`. For example:
Or(Int, Float)
Or(StrMatch(r'^file:'), StrMatch(r'^https?:'))
"""
def __init__(self, *config_types):
self.config_types = config_types
def type_check(self, obj):
return any(t.type_check(obj) for t in self.config_types)
def __str__(self):
s = " or ".join(str(t) for t in self.config_types)
return "(" + s + ")"
class And(Type):
"""Represents a type that is all of the `config_types`.
`config_type` must be an instance of `Type`. For example:
And(StrMatch(r'\\d'), StrMatch(r'[!@#$%^&*()]'))
"""
def __init__(self, *config_types):
self.config_types = config_types
def type_check(self, obj):
return all(t.type_check(obj) for t in self.config_types)
def __str__(self):
s = " and ".join(str(t) for t in self.config_types)
return "(" + s + ")"
class Const(Type):
"""Represents a constant value."""
def __init__(self, const):
self.const = const
def type_check(self, obj):
# Why do we bother checking the types if we are just going to test
# equality of the objects afterwards? Because True == 1 in python, so if
# we did not check types, then this would report incorrect results.
# Likewise, we cannot use isinstance here either as a bool is a subclass
# of int, so it would also report incorrect results.
if type(obj) != type(self.const): # noqa: E721
return False
return obj == self.const
def __str__(self):
return f"constant '{self.const}'"
class Choice(Or):
"""Represents a choice of constants."""
def __init__(self, *constants):
super().__init__(*[Const(c) for c in constants])
class Scalar(Type):
"""Represents a type that is a scalar matching `type`.
`type` must be one of the builtin Python scalar types. For example:
Scalar(str)
Scalar(int)
Scalar(bool)
"""
def __init__(self, type_):
self.type = type_
def type_check(self, obj):
return type(obj) == self.type # noqa: E721
def __str__(self):
return self.type.__name__
class StrMatch(Type):
"""Represents a string matching `pattern`.
`pattern` is matched using `re.search` so anchors should be explicit.
"""
def __init__(self, pattern):
self.pattern = pattern
def type_check(self, obj):
if type(obj) != str: # noqa: E721
return False
return bool(re.search(self.pattern, obj))
def __str__(self):
return f"str matching '{self.pattern}'"
class IpAddress(Type):
"""Represents a string matching an IP address (v4 or v6)."""
def type_check(self, obj):
if type(obj) != str: # noqa: E721
return False
try:
ipaddress.ip_address(obj)
return True
except ValueError:
return False
def __str__(self):
return "IPv4 or IPv6 address"
class IpNetwork(Type):
"""Represents a string matching an IP network (v4 or v6)."""
def type_check(self, obj):
if type(obj) != str: # noqa: E721
return False
try:
ipaddress.ip_network(obj)
return True
except ValueError:
return False
def __str__(self):
return "IPv4 or IPv6 network"
class FileType(Type):
"""Represents a string pointing to an existing file."""
def type_check(self, obj):
if type(obj) != str: # noqa: E721
return False
return Path(obj).exists()
def __str__(self):
return "existing file"
class AnyType(Type):
"""Represents any type."""
def type_check(self, obj):
return True
def __str__(self):
return "any type"
Str = Scalar(str)
"""Singleton representing a str."""
Int = Scalar(int)
"""Singleton representing an int."""
Bool = Scalar(bool)
"""Singleton representing a bool."""
Float = Scalar(float)
"""Singleton representing a float."""
Any = AnyType()
"""Singleton representing any type."""
File = FileType()
"""Singleton representing an existing filename."""
IP = IpAddress()
"""Singleton representing an IP address (v4 or v6)."""
IPNet = IpNetwork()
"""Singleton representing an IP network (v4 or v6)."""
Dotted = StrMatch(r"^[^.]+(\.[^.]+)*$")
"""Singleton representing a dotted Python path."""
URL = StrMatch(r"^[^:]+://")
"""Singleton representing a URL in the form of xxxx://."""
class List(Type):
"""Represents a list containing elements of `element_type`.
`element_type` must be an instance of `Type`. For example:
List(Str)
List(Int)
List(Dict(Str, Int))
List(StrMatch(r'^https?://'))
"""
def __init__(self, element_type):
self.element_type = element_type
def type_check(self, obj):
if type(obj) != list: # noqa: E721
return False
return all(self.element_type.type_check(e) for e in obj)
def __str__(self):
return f"list of {self.element_type}"
class Dict(Type):
"""Represents a dict containing keys of `key_type` and values of `value_type`.
`key_type` and `value_type` must be instances of `Type`. For example:
Dict(Str, Str)
Dict(Str, List(IP))
Dict(Str, List(Or(Int, Float)))
"""
def __init__(self, key_type, value_type):
self.key_type = key_type
self.value_type = value_type
def type_check(self, obj):
if type(obj) != dict: # noqa: E721
return False
return all(self.key_type.type_check(k) for k in obj.keys()) and all(
self.value_type.type_check(v) for v in obj.values()
)
def __str__(self):
return f"dict with {self.key_type} keys and {self.value_type} values"
Global variables
var EmptyConfig
-
Singleton representing an empty
Config
. var Str
-
Singleton representing a str.
var Int
-
Singleton representing an int.
var Bool
-
Singleton representing a bool.
var Float
-
Singleton representing a float.
var Any
-
Singleton representing any type.
var File
-
Singleton representing an existing filename.
var IP
-
Singleton representing an IP address (v4 or v6).
var IPNet
-
Singleton representing an IP network (v4 or v6).
var Dotted
-
Singleton representing a dotted Python path.
var URL
-
Singleton representing a URL in the form of xxxx://.
Classes
class Config (d)
-
A
Config
can read type-checked values from a Python dictionary.This class provides an interface to read values from a dictionary while providing for default values, mandatory values, as well as the ability to type-check values. In addition, it can be used to dynamically instantiate classes specified in the configuration. Finally, the class also contains a registry of configuration parsers based on file extensions, so users can load configs from files.
Expand source code
class Config: """A `Config` can read type-checked values from a Python dictionary. This class provides an interface to read values from a dictionary while providing for default values, mandatory values, as well as the ability to type-check values. In addition, it can be used to dynamically instantiate classes specified in the configuration. Finally, the class also contains a registry of configuration parsers based on file extensions, so users can load configs from files. """ _filetypes = {} @classmethod def register_filetype(cls, config_class, *extensions): """Register a parser for files with one of the specified extensions. The registry is used to find the appropriate config parser when a user invokes the `Config.from_file` factory method. Extensions should be specified as '.ext'. Subsequent registrations for the same extension will override the prior registration. """ for ext in extensions: cls._filetypes[ext] = config_class @classmethod def from_file(cls, filename, must_exist=False): """Factory method to Load a `Config` from a filename. This method uses the extension of the filename to determine the configuration parser that should be used to instantiate a `Config` object. If `must_exist` is true, a `FileNotFoundError` is raised if the filename does not exist, otherwise an empty `Config` is returned. """ path = Path(filename) if not path.is_file(): if must_exist: raise FileNotFoundError(f"Config file not found: {filename}") return Config({}) if path.suffix not in cls._filetypes: raise ValueError(f"Unregistered file type extension: {path.suffix}") with path.open(encoding="utf-8") as f: return cls._filetypes[path.suffix](f) def __init__(self, d): self.conf = d def get(self, *keys, default=None, type=None, must_exist=False): """Return the specified value from the `Config`. Specify the value to read by providing the keys required to reach the value in the configuration. If the value is not found at the specified key path, `None` or the `default` value is returned unless the `must_exist` flag is `True`, in which case a `ValueError` is raised. Values can be optionally type-checked to ensure it matches the specified type. If the `type` matches the value in the configuration, the value is returned, otherwise a `TypeError` is raised. Types are specified by passing a `Type` object. There are numerous type objects defined in this module. For example: c.get('path', 'to', 'value', type=Int) c.get('path', 'to', 'value', type=Bool) c.get('path', 'to', 'value', type=Float) c.get('path', 'to', 'value', type=Str) c.get('path', 'to', 'value', type=StrMatch(r'^\\d+-\\d+$')) c.get('path', 'to', 'value', type=IP) c.get('path', 'to', 'value', type=List(IP)) c.get('path', 'to', 'value', type=List(Str)) c.get('path', 'to', 'value', type=List(Dict(Int, Str))) c.get('path', 'to', 'value', type=Dict(Str, Int)) c.get('path', 'to', 'value', type=Or(Int, Float)) c.get('path', 'to', 'value', type=And(StrMatch(r'\\d+'), StrMatch(r'[A-Z]'))) c.get('path', 'to', 'value', type=Not(Or(Int, Float))) """ # pylint: disable=redefined-builtin # This one-liner will recursively follow a list of keys into a # dictionary and return the value. If a key does not exist, return an # empty dict. try: value = reduce(lambda a, p: a.get(p, {}), keys, self.conf) except AttributeError as e: raise ValueError( f"Error in config: {'->'.join(keys[:-1])}: not a dictionary" ) from e # If value is {} that means the key doesn't exist. If the must_exist # flag was passed, then we raise a descriptive ValueError, otherwise we # set it to the default. if value == {}: if must_exist: raise ValueError(f"Error in config: {'->'.join(keys)}: must be set") value = default # If no value has been set in the config and none has been provided as a # default, then return None. if value is None: return value # If no type has been specified, then return the value in the config or # the default without doing any type checking. if not type: return value # Only return the value if it type checks correctly. if type.type_check(value): return value # Finally, all other cases indicate a type error. raise TypeError( f"Error in config: {'->'.join(keys)}: not a {type}: {repr(value)}" )
Subclasses
Static methods
def register_filetype(config_class, *extensions)
-
Register a parser for files with one of the specified extensions.
The registry is used to find the appropriate config parser when a user invokes the
Config.from_file()
factory method. Extensions should be specified as '.ext'. Subsequent registrations for the same extension will override the prior registration.Expand source code
@classmethod def register_filetype(cls, config_class, *extensions): """Register a parser for files with one of the specified extensions. The registry is used to find the appropriate config parser when a user invokes the `Config.from_file` factory method. Extensions should be specified as '.ext'. Subsequent registrations for the same extension will override the prior registration. """ for ext in extensions: cls._filetypes[ext] = config_class
def from_file(filename, must_exist=False)
-
Factory method to Load a
Config
from a filename.This method uses the extension of the filename to determine the configuration parser that should be used to instantiate a
Config
object. Ifmust_exist
is true, aFileNotFoundError
is raised if the filename does not exist, otherwise an emptyConfig
is returned.Expand source code
@classmethod def from_file(cls, filename, must_exist=False): """Factory method to Load a `Config` from a filename. This method uses the extension of the filename to determine the configuration parser that should be used to instantiate a `Config` object. If `must_exist` is true, a `FileNotFoundError` is raised if the filename does not exist, otherwise an empty `Config` is returned. """ path = Path(filename) if not path.is_file(): if must_exist: raise FileNotFoundError(f"Config file not found: {filename}") return Config({}) if path.suffix not in cls._filetypes: raise ValueError(f"Unregistered file type extension: {path.suffix}") with path.open(encoding="utf-8") as f: return cls._filetypes[path.suffix](f)
Methods
def get(self, *keys, default=None, type=None, must_exist=False)
-
Return the specified value from the
Config
.Specify the value to read by providing the keys required to reach the value in the configuration. If the value is not found at the specified key path,
None
or thedefault
value is returned unless themust_exist
flag isTrue
, in which case aValueError
is raised.Values can be optionally type-checked to ensure it matches the specified type. If the
type
matches the value in the configuration, the value is returned, otherwise aTypeError
is raised. Types are specified by passing aType
object. There are numerous type objects defined in this module. For example:c.get('path', 'to', 'value', type=Int) c.get('path', 'to', 'value', type=Bool) c.get('path', 'to', 'value', type=Float) c.get('path', 'to', 'value', type=Str) c.get('path', 'to', 'value', type=StrMatch(r'^\d+-\d+$')) c.get('path', 'to', 'value', type=IP) c.get('path', 'to', 'value', type=List(IP)) c.get('path', 'to', 'value', type=List(Str)) c.get('path', 'to', 'value', type=List(Dict(Int, Str))) c.get('path', 'to', 'value', type=Dict(Str, Int)) c.get('path', 'to', 'value', type=Or(Int, Float)) c.get('path', 'to', 'value', type=And(StrMatch(r'\d+'), StrMatch(r'[A-Z]'))) c.get('path', 'to', 'value', type=Not(Or(Int, Float)))
Expand source code
def get(self, *keys, default=None, type=None, must_exist=False): """Return the specified value from the `Config`. Specify the value to read by providing the keys required to reach the value in the configuration. If the value is not found at the specified key path, `None` or the `default` value is returned unless the `must_exist` flag is `True`, in which case a `ValueError` is raised. Values can be optionally type-checked to ensure it matches the specified type. If the `type` matches the value in the configuration, the value is returned, otherwise a `TypeError` is raised. Types are specified by passing a `Type` object. There are numerous type objects defined in this module. For example: c.get('path', 'to', 'value', type=Int) c.get('path', 'to', 'value', type=Bool) c.get('path', 'to', 'value', type=Float) c.get('path', 'to', 'value', type=Str) c.get('path', 'to', 'value', type=StrMatch(r'^\\d+-\\d+$')) c.get('path', 'to', 'value', type=IP) c.get('path', 'to', 'value', type=List(IP)) c.get('path', 'to', 'value', type=List(Str)) c.get('path', 'to', 'value', type=List(Dict(Int, Str))) c.get('path', 'to', 'value', type=Dict(Str, Int)) c.get('path', 'to', 'value', type=Or(Int, Float)) c.get('path', 'to', 'value', type=And(StrMatch(r'\\d+'), StrMatch(r'[A-Z]'))) c.get('path', 'to', 'value', type=Not(Or(Int, Float))) """ # pylint: disable=redefined-builtin # This one-liner will recursively follow a list of keys into a # dictionary and return the value. If a key does not exist, return an # empty dict. try: value = reduce(lambda a, p: a.get(p, {}), keys, self.conf) except AttributeError as e: raise ValueError( f"Error in config: {'->'.join(keys[:-1])}: not a dictionary" ) from e # If value is {} that means the key doesn't exist. If the must_exist # flag was passed, then we raise a descriptive ValueError, otherwise we # set it to the default. if value == {}: if must_exist: raise ValueError(f"Error in config: {'->'.join(keys)}: must be set") value = default # If no value has been set in the config and none has been provided as a # default, then return None. if value is None: return value # If no type has been specified, then return the value in the config or # the default without doing any type checking. if not type: return value # Only return the value if it type checks correctly. if type.type_check(value): return value # Finally, all other cases indicate a type error. raise TypeError( f"Error in config: {'->'.join(keys)}: not a {type}: {repr(value)}" )
class YAMLConfig (stream)
-
Loads a YAML configuration from a stream.
Expand source code
class YAMLConfig(Config): """Loads a YAML configuration from a stream.""" def __init__(self, stream): super().__init__(yaml.safe_load(stream))
Ancestors
Inherited members
class JSONConfig (stream)
-
Loads a JSON configuration from a stream.
Expand source code
class JSONConfig(Config): """Loads a JSON configuration from a stream.""" def __init__(self, stream): super().__init__(json.load(stream))
Ancestors
Inherited members
class Type
-
Represents a type that can be used in type-check comparisons.
Expand source code
class Type: """Represents a type that can be used in type-check comparisons.""" def type_check(self, obj): """Returns true if obj is a type matching this `Type`.""" raise NotImplementedError def __str__(self): """Returns a string representing this `Type`.""" raise NotImplementedError
Subclasses
Methods
def type_check(self, obj)
-
Returns true if obj is a type matching this
Type
.Expand source code
def type_check(self, obj): """Returns true if obj is a type matching this `Type`.""" raise NotImplementedError
class Not (config_type)
-
Represents a type that is not a type of
config_type
.config_type
must be an instance ofType
. For example:Not(Str) Not(StrMatch(r'\d+')) Not(List(Int))
Expand source code
class Not(Type): """Represents a type that is not a type of `config_type`. `config_type` must be an instance of `Type`. For example: Not(Str) Not(StrMatch(r'\\d+')) Not(List(Int)) """ def __init__(self, config_type): self.config_type = config_type def type_check(self, obj): return not self.config_type.type_check(obj) def __str__(self): return "not " + str(self.config_type)
Ancestors
Inherited members
class Or (*config_types)
-
Represents a type that is one of the
config_types
.config_type
must be an instance ofType
. For example:Or(Int, Float) Or(StrMatch(r'^file:'), StrMatch(r'^https?:'))
Expand source code
class Or(Type): """Represents a type that is one of the `config_types`. `config_type` must be an instance of `Type`. For example: Or(Int, Float) Or(StrMatch(r'^file:'), StrMatch(r'^https?:')) """ def __init__(self, *config_types): self.config_types = config_types def type_check(self, obj): return any(t.type_check(obj) for t in self.config_types) def __str__(self): s = " or ".join(str(t) for t in self.config_types) return "(" + s + ")"
Ancestors
Subclasses
Inherited members
class And (*config_types)
-
Represents a type that is all of the
config_types
.config_type
must be an instance ofType
. For example:And(StrMatch(r'\d'), StrMatch(r'[!@#$%^&*()]'))
Expand source code
class And(Type): """Represents a type that is all of the `config_types`. `config_type` must be an instance of `Type`. For example: And(StrMatch(r'\\d'), StrMatch(r'[!@#$%^&*()]')) """ def __init__(self, *config_types): self.config_types = config_types def type_check(self, obj): return all(t.type_check(obj) for t in self.config_types) def __str__(self): s = " and ".join(str(t) for t in self.config_types) return "(" + s + ")"
Ancestors
Inherited members
class Const (const)
-
Represents a constant value.
Expand source code
class Const(Type): """Represents a constant value.""" def __init__(self, const): self.const = const def type_check(self, obj): # Why do we bother checking the types if we are just going to test # equality of the objects afterwards? Because True == 1 in python, so if # we did not check types, then this would report incorrect results. # Likewise, we cannot use isinstance here either as a bool is a subclass # of int, so it would also report incorrect results. if type(obj) != type(self.const): # noqa: E721 return False return obj == self.const def __str__(self): return f"constant '{self.const}'"
Ancestors
Inherited members
class Choice (*constants)
-
Represents a choice of constants.
Expand source code
class Choice(Or): """Represents a choice of constants.""" def __init__(self, *constants): super().__init__(*[Const(c) for c in constants])
Ancestors
Inherited members
class Scalar (type_)
-
Represents a type that is a scalar matching
type
.type
must be one of the builtin Python scalar types. For example:Scalar(str) Scalar(int) Scalar(bool)
Expand source code
class Scalar(Type): """Represents a type that is a scalar matching `type`. `type` must be one of the builtin Python scalar types. For example: Scalar(str) Scalar(int) Scalar(bool) """ def __init__(self, type_): self.type = type_ def type_check(self, obj): return type(obj) == self.type # noqa: E721 def __str__(self): return self.type.__name__
Ancestors
Inherited members
class StrMatch (pattern)
-
Represents a string matching
pattern
.pattern
is matched usingre.search
so anchors should be explicit.Expand source code
class StrMatch(Type): """Represents a string matching `pattern`. `pattern` is matched using `re.search` so anchors should be explicit. """ def __init__(self, pattern): self.pattern = pattern def type_check(self, obj): if type(obj) != str: # noqa: E721 return False return bool(re.search(self.pattern, obj)) def __str__(self): return f"str matching '{self.pattern}'"
Ancestors
Inherited members
class IpAddress
-
Represents a string matching an IP address (v4 or v6).
Expand source code
class IpAddress(Type): """Represents a string matching an IP address (v4 or v6).""" def type_check(self, obj): if type(obj) != str: # noqa: E721 return False try: ipaddress.ip_address(obj) return True except ValueError: return False def __str__(self): return "IPv4 or IPv6 address"
Ancestors
Inherited members
class IpNetwork
-
Represents a string matching an IP network (v4 or v6).
Expand source code
class IpNetwork(Type): """Represents a string matching an IP network (v4 or v6).""" def type_check(self, obj): if type(obj) != str: # noqa: E721 return False try: ipaddress.ip_network(obj) return True except ValueError: return False def __str__(self): return "IPv4 or IPv6 network"
Ancestors
Inherited members
class FileType
-
Represents a string pointing to an existing file.
Expand source code
class FileType(Type): """Represents a string pointing to an existing file.""" def type_check(self, obj): if type(obj) != str: # noqa: E721 return False return Path(obj).exists() def __str__(self): return "existing file"
Ancestors
Inherited members
class AnyType
-
Represents any type.
Expand source code
class AnyType(Type): """Represents any type.""" def type_check(self, obj): return True def __str__(self): return "any type"
Ancestors
Inherited members
class List (element_type)
-
Represents a list containing elements of
element_type
.element_type
must be an instance ofType
. For example:List(Str) List(Int) List(Dict(Str, Int)) List(StrMatch(r'^https?://'))
Expand source code
class List(Type): """Represents a list containing elements of `element_type`. `element_type` must be an instance of `Type`. For example: List(Str) List(Int) List(Dict(Str, Int)) List(StrMatch(r'^https?://')) """ def __init__(self, element_type): self.element_type = element_type def type_check(self, obj): if type(obj) != list: # noqa: E721 return False return all(self.element_type.type_check(e) for e in obj) def __str__(self): return f"list of {self.element_type}"
Ancestors
Inherited members
class Dict (key_type, value_type)
-
Represents a dict containing keys of
key_type
and values ofvalue_type
.key_type
andvalue_type
must be instances ofType
. For example:Dict(Str, Str) Dict(Str, List(IP)) Dict(Str, List(Or(Int, Float)))
Expand source code
class Dict(Type): """Represents a dict containing keys of `key_type` and values of `value_type`. `key_type` and `value_type` must be instances of `Type`. For example: Dict(Str, Str) Dict(Str, List(IP)) Dict(Str, List(Or(Int, Float))) """ def __init__(self, key_type, value_type): self.key_type = key_type self.value_type = value_type def type_check(self, obj): if type(obj) != dict: # noqa: E721 return False return all(self.key_type.type_check(k) for k in obj.keys()) and all( self.value_type.type_check(v) for v in obj.values() ) def __str__(self): return f"dict with {self.key_type} keys and {self.value_type} values"
Ancestors
Inherited members