Skip to main content

No project description provided

Project description

ConfigSage

configsage is a pyyaml wrapper converting the configuration parameters taken by a yaml file in a python object.
The object reflects the yaml hierarchy, allowing the access to the config items both by a dotted notation and/or as dictionary.

!!! example
The following example shows a simple yaml configuration file with all the use-cases and how they can be accessed by python using the configsage as configuration parser.

```yaml  
paths:  # List of directories to scan
  - path: "/path/to/the/first/structure"  # Path to the first folder to scan
    folder_structure:
    - artist  # First folder is artist
    - album   # Second folder is album
    - disc    # Third folder is disc (optional, e.g., for multi-disc albums)
    file_name: ^(?P<nr>\d\d)(?P<title>.*)(?P<ext>\.mp3)  # regex with named groups to get nr = track number, title = track title and extension
    album_name: ^(?P<album>.*) \((?P<year>\d{4})\) # regex with named groups to get album name and year from the "second folder" name
    album_cover: "http://album_covers.org/mycover.jpg" # URL or local path to the cover image
    

  - path: "/path/to/the/second/structure"  # Path to the second folder to scan
    folder_structure:
    - artist  # First folder is artist
    - album   # Second folder is album
    - disc    # Third folder is disc (optional, e.g., for multi-disc albums)
    file_name: ^(?P<nr>\d\d) - (?P<title>.*)(?P<ext>\.mp3)  # regex with named groups to get nr = track number, title = track title and extension
    album_name: ^(?P<album>.*) - (?P<year>\d{4})
    album_cover: "http://album_covers.org/mycover2.jpg" # URL or local path to the cover image

  - path: "/path/to/another/structure"  # Path to another folder to scan (this won't have any parsing in file name and album name so taken as they are)
    folder_structure:
    - genre   # First folder is genre
    - artist  # Second folder is artist
    - album   # Third folder is album

```

Schema

When a schema is not provided the library just returns the objectified configuration. When a schema is provided, the following steps are done:

  • A value marked in the schema as default (by _default: True) is added in the configuration if missing. This is done before any check.
  • If normalization is enabled, all the values by the flag are normalized according to the functions listed. This is done on scalar values and list of scalars.

The following checks are performed:

  • if the config file has unknown keys
  • if the config file has some missing mandatory key
  • if the value for the fileds marked as _enum are in the enum list
  • if the scalar types are the expected in _type

The schema supports the following metadata keys, all starting by underscore (_)

  • _type: defines the strict item type (if scalar is str, int, etc. or list[str] - or dict). This is mandatory on list as they need to be list of something. If omitted the check on item type is not performed.
  • _default: default value to consider even if the field is not in the config file. This is applied only to scalar values
  • _enum: allowed possible values for that field. This is applied to scalar values and list of scalar values
  • _normalizers: functions to call in sequence to normalize. This is applied to scalar values and list of scalar values
  • _validators: functions to call in sequence to validate. This applies to scalar values and list of scalar values.
  • _required: if the field is missing or False then the field can be omitted, - otherwise the validation fails

The check workflow is the following:

_default → _normalizers → _enum → _validators → return

!!! example
A schema file example for the previous yaml example is

```py
"""
App-defined schema (with Python types & callables) to validate the configuration
"""

# schema.py
from configsage.normalizers import trim_lower
from configsage.validators import path_exists


SCHEMA = {
    # list of directories to scan
    "paths": {
        "_type": list[dict],
        "path": {
            "_type": str,
            "_required": True,
            "_validators": [path_exists]
        },
        "folder_structure": {
            "_type": list[str],
            "_enum": {"genre","artist","album","disc"},
            "_normalizers": trim_lower,
        },
        "file_name": {"_type": str},
        "album_name": {"_type": str},
        "album_cover": {"_type": str}
    }
}
```  

So:  

- the schema must import the validators and the normalizers written by the user
- `path` must be a string, must be always present and must exist as path on the file system, as checked by the `path_exists` function.
- `folder_structure` is a list of strings containing a limited set of values ("genre","artist","album","disc") and the content is trimmed and set to low-case before any check
- the other fields are all strings

Usage

To use the package it can be installed via pip

pip install configsage

or via poetry

poetry add configsage

then it can be used by

  • creating a configuration schema like in the example (schema must import the validators and the normalizers if applicable as they're callables)
  • importing your configuration schema into your application
  • importing configsage in your application
  • creating the Config object with your parameters
  • accessing the configuration via dotted-notation and/or as dictionary
import schema
from configsage import Config

cfg_file = "myconfig.yaml"

cfg = Config(
        config_src=cfg_file,
        schema=schema,
        validate=True,
        normalize=True, 
    )

# access as object
print(cfg.paths[0].path) # returns "/path/to/the/first/structure"
print(cfg.paths[1].folder_structure[1]) # returns "album"
# access as dictionary
cfg_dict = cfg.config_dict
print(cfg_dict["paths"][0]["path"]) # returns "/path/to/the/first/structure"

Validation & Normailzers

To write a custom validation or normalizing logic you can create your own functions in a separate module to import.
Normalizing functions must return the normalized value.
Validating functions rather than retunring should raise an Exception
In both cases the function must receive a value as parameter, this is automatically injected by ConfigSage.

!!! example The example shows a normalization module

```py 
#normalizers.py

def trim_lower(value: str) -> str:
"""
Trim and convert to lower the characters only if it's a string
Params:
    value(str): value to trim and convert
Returns:
    the modifed string or the value as is if not a string
"""
if isinstance(value, str):
    return value.strip().lower()
return value

```

And a validation module

```py
# validators.py
def path_exists(value: str) -> None:
"""
Validates the path exists
Params:
    value(str): path to verify, it must exist
Returns:
    None
Raises: 
    ValueError if the folder pointed by the path does not exist
"""
print("Path exists check on",value)
# You can relax this for cross-platform tests or inject a base dir as needed
if not isinstance(value, str) or not os.path.exists(value):
    raise ValueError(f"path does not exist: {value!r}")
```

and import it in your schema, then you can apply them to the single scalar value. The function receives the value it is applied to automatically

!!! example The example shows how these custom functions can be used in the schema

```py
from validators import path_exists
from normalizers import trim_lower

SCHEMA = {
    "sources": {
        "_type": list[dict],
        "folder": {
            "_type": str,
            "_validators": [ensure_nonempty, path_exists],
        },

        "format": {
            "_type": str,
            "_enum": {"txt", "json", "xml"},
            "_normalizers": trim_lower,
        },

        "log_type": {
            "_type": str,
            "_enum": {
                "test_log1",
                "test_log2",
                "log_ifin"
            },
            "_normalizers": trim_lower,
        },

        "dest_folder": {
            "_type": str,
            "_required": True,
        },


        "root_log_name": {
            "_type": str,
        },

        "root_log_separator": {
            "_type": str,
            "_default": "-"
        },

        "new_name": {
            "_type": str,
        },
        
    }
}
```

then it can be applied to a scalar value. The function will receive the value automatically. In case, besides the value, you must pass some other parameter to the function (e.g.: some threshold), your function may use a factory pattern.

!!! example Factory pattern used to apply a value validation passing not only the value to check but also the min and max value thresholds.

```py
# validators.py

def num_limits(min_value: int, max_value: int) -> None:
"""
Validator factory: callable calling a callable
Checks if the eval number exceeds the min and max threshold
Params:
    min_value(int): minimum threshold
    max_value(int): maximum threshold
Raises:
    Value error if eval exceeds the min and max limits 
"""
def validator(value: int):
    if value < min_value or value > max_value:
        raise ValueError(f"value {value} must be between {min_value} and {max_value}")
return validator
```

Then the schema can be:

```py
SCHEMA = {
    "icon": { 
        "_type": dict, 
        "icon_path": {                                                       
            "_type": str,
        },
        "size_%": {                                                     # glue icon size
            "_type": int,
            "_default":10,
            "_validators": [num_limits(0,100)],
        }, 
        "margins_mm": {"_type": int, "_default":5},  
    }
}
```

So, the num_limit is called passing the thresholds.

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

configsage-0.2.1.tar.gz (14.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

configsage-0.2.1-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

Details for the file configsage-0.2.1.tar.gz.

File metadata

  • Download URL: configsage-0.2.1.tar.gz
  • Upload date:
  • Size: 14.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.11.2 Linux/6.1.0-41-amd64

File hashes

Hashes for configsage-0.2.1.tar.gz
Algorithm Hash digest
SHA256 e95f21f2762fe6a7df570617f7a77936195288cb81cdf119c50813a12f5520bb
MD5 7de388a40771b32179c121f7705f582f
BLAKE2b-256 03ad91c254500ec304326995ae68e93ba77e92f5ad4dccf99cd1e57521e0870f

See more details on using hashes here.

File details

Details for the file configsage-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: configsage-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 15.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.11.2 Linux/6.1.0-41-amd64

File hashes

Hashes for configsage-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 da37731fbc309b446703d1dd00abdae3301f6716b927816ce3f2081016166670
MD5 7b4e489c77cece4d887c00da3420ec5b
BLAKE2b-256 de52d97c6586500fb923edfdc31a520e31660539592fa97baab748f4689a0e85

See more details on using hashes here.

Supported by

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