Skip to main content
Help us improve Python packaging – donate today!

Autoload configuration from multiple sources. Autotranslate config into usable object

Project Description

# Flange

![CircleCI](https://circleci.com/gh/flashashen/flange.svg?style=svg)
[![PyPI version](https://badge.fury.io/py/flange.svg)](https://badge.fury.io/py/flange)
![Python versions](https://img.shields.io/pypi/pyversions/flange.svg)
![MIT License](https://img.shields.io/github/license/flashashen/flange.svg)


---------------------------------------------------------

Convenient configuration search and load with a model based object registry.

*With bits of config you may already have lying around..*
``` yaml

# somewhere in a config file ..
my_logger:
name: myapp
level: DEBUG
format: "%(asctime)s:%(levelname)s:%(name)s %(message)s"


# somewhere in a different config file ..
my_mssql_db:
driver: mssql+pymssql
name: dbname
user: devuser
pass: devpass
host: dbhost.dev.corp
port: '1111'
desc: dev db
args: {'login_timeout':6}


# from env vars
export my_mssql_db__pass=devpass
```

*you can do this..*

```
sh> python -c "from flange import cfg, dbengine; result = cfg.mget('my_mssql_db').execute('USE master SELECT @@version').first()[0]; cfg.mget('my_logger').debug(result)"
2018-03-09 15:49:55,726:DEBUG:myapp Microsoft SQL Server 2008 (SP4) - 10.0.6000.29 (X64)
Sep 3 2014 04:11:34
Copyright (c) 1988-2008 Microsoft Corporation
Enterprise Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (VM)
```

**You want this if**:

- You're tired of boilerplate configuration code in your python projects
- You're tired of boilerplate logger setup in your python projects
- You're tired of boilerplate data access setup in your python projects
- You're tired of boilerplate *{{fill in the blank}}* setup in your python projects
- you want to hack in the python console and don't remember where you put all your bits
of config and credentials
- You want to keep passwords separate from main config


## What it does

- Automatically searches for and loads (configuration) data in various formats using Anyconfig
- Merges configuration from various sources using Anyconfig
- Pluggable, automatic object detection/creation from config data
- Object registry with lazy init and cache
- Convenient object access

This is partially inspired by the Spring framework. On init, the main configuration object
will search for a given set of file pattern at a given directory to a given depth for config
data and will merge this data into a single configuration object.

Additionally, an object registry is provided that can recognize patterns in the config data
and return cached instances on demand of any type of object. Object initialization is automatic and lazy.
Recognition of instances currently employs json schema
to identify patterns and a python function is provided that serves as the factory function.
The factory method can be given explicity in python or specified as url that resolves to a
python function. The combination of a schema and a factory function along with a name are
called a 'model'.


## Usage

##### Model - Logger

This shows access to a logger object which is a built-in model of flange. The built-in logger schema looks like this:

```
{
"type" : "object",
"properties" : {
'name':{'type':'string'},
'level': { "enum": ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG']},
'format':{'type':'string'},
},
"required": ["name", "level"]
}
```

the config looks like this (can appear anywhere in your config files):

```
{
..
my_logger:
name: myapp
level: DEBUG
format: "%(asctime)s:%(levelname)s %(message)s"
}
```

the object is accessed with the mget(..) method *(model get)* like this:

```
In [1]: from flange import cfg

In [2]: log = cfg.obj('my_logger')

In [3]: log.debug('hello')
2018-03-09 14:08:17,261:DEBUG:myapp hello
```

if the key in the configuration is not known, then the instance can be fetched
with just the model name *(provided there is only one instance)*:

```
In [4]: cfg.obj(model='logger').debug('hello')
2018-03-09 14:43:07,514:DEBUG:myapp hello
```

.. or just by specifying a value in the instance config with 'values' parameter:

```
In [5]: cfg.mget(values='myapp').debug('hello')
2018-03-09 14:42:50,785:DEBUG:myapp hello
```

.. or by specifying multiple values in the instance config with 'values' parameter:

```
In [6]: cfg.mget(values=['myapp','DEBUG']).debug('hello')
2018-03-09 14:51:36,742:DEBUG:myapp hello
```

Any combination of key, model, and values terms can be given to select a
unique instance with the mget(..) method.


the raw config can also be accessed with the value(..) method:

```
In [7]: cfg.value('my_logger')
Out[7]:
{'name', 'dshlog',
'level', 'DEBUG'),
'format', '%(asctime)s:%(levelname)s %(message)s'}
```


##### Model - dbengine / sqlalchemy

This is another example with the default settings. The loaded data is described
with the info() method. The the dbengine module is imported which automatically registers
an sqlalchemy based model and searches for any configuration that is a valid/sufficient for a
sqlalchemy engine. Note: sqlalchemy is an example built-in model. Any sort of model can be
registered. **Note that after the import of dbengine module, the 'dbengine' model and it's instances
appear in the output.**

```
In [2]: from flange import cfg

In [3]: cfg.info()

models:
logger instances: logger

base dir: .
search depth: 1
file include patterns: ['*.yml', '*cfg', '*settings', '*config', '*properties', '*props']
file exclude patterns: ['*.tar', '*.jar', '*.zip', '*.gz', '*.swp', 'node_modules', 'target', '.idea', '*.hide', '*save']

sources:
None os_env
shellvars /Users/myuser/.gitconfig
yml /Users/myuser/config_example.yml
yml /Users/myuser/.cmd.yml
shellvars /Users/myuser/.ansible.cfg
yml /Users/myuser/.flangetest.yml
shellvars /Users/myuser/.bundle/config
shellvars /Users/myuser/.git/config
yml /Users/myuser/.nyttth/config.yml
shellvars /Users/myuser/.plotly/.config
shellvars /Users/myuser/.ScreamingFrogSEOSpider/spider.config
shellvars /Users/myuser/.ssh/config
shellvars /Users/myuser/.subversion/config
shellvars /Users/myuser/airflow/airflow.cfg
shellvars /Users/myuser/airflow/unittests.cfg
yml /Users/myuser/Downloads/config_example.yml
yml /Users/myuser/workspace/docker-compose-swarm.yml

In [4]: from flange import dbengine

In [5]: cfg.info()

models:
dbengine instances: testdb,db1
logger instances: logger

base dir: .
search depth: 1
file include patterns: ['*.yml', '*cfg', '*settings', '*config', '*properties', '*props']
file exclude patterns: ['*.tar', '*.jar', '*.zip', '*.gz', '*.swp', 'node_modules', 'target', '.idea', '*.hide', '*save']

sources:
None os_env
yml /Users/myuser/config_example.yml
yml /Users/myuser/.cmd.yml
shellvars /Users/myuser/.ansible.cfg
yml /Users/myuser/.flangetest.yml
shellvars /Users/myuser/.bundle/config
yml /Users/myuser/.nyttth/config.yml
shellvars /Users/myuser/.plotly/.config
shellvars /Users/myuser/.ScreamingFrogSEOSpider/spider.config
shellvars /Users/myuser/.ssh/config
shellvars /Users/myuser/.subversion/config
shellvars /Users/myuser/airflow/airflow.cfg
shellvars /Users/myuser/airflow/unittests.cfg
yml /Users/myuser/Downloads/config_example.yml
yml /Users/myuser/workspace/docker-compose-swarm.yml


In [6]: cfg.obj('db1')
Out[6]: Engine(mssql+pymssql://corpdomain\corpuser:***@dbhost:1111/dbname?charset=utf8)
```

## Plugins

Here is how the dbengine (sqlalchemy) model is defined:

``` Python
from . import cfg
from sqlalchemy import create_engine

dbengine_schema = {
"type" : "object",
"properties" : {
'driver':{'type':'string'},
'name':{'type':'string'},
'user':{'type':'string'},
'pass':{'type':'string'},
'port':{'type':'string'},
},
"required": ["driver", "name", "user", "pass"]
}

def dbengine_create_func(config):
url_format_string = "{:s}://{:s}:{:s}@{:s}:{:s}/{:s}?charset=utf8"
engine = create_engine(url_format_string.format(
config['driver'],
config['user'],
config['pass'],
config['host'],
config['port'],
config['name']), convert_unicode=True)

return engine


cfg.register_default_model(
'dbengine',
model.Model('dbengine',
model.Model.get_schema_validator(dbengine_schema),
dbengine_create_func))

```

The example above showed explicit registration from python. Plugin registration can also be accomplished
with just configuration. Here is an example from the
tests in this project. For this to work, a python factory function must exist in the python
path, resolved via a local url *(see example for url format)*. This config must also appear somewhere in the loaded
config data loaded by flange. With those caveats, The following is all
that is required to register a custom model and start accessing instances:

```
config_with_plugin = {

'test_instance_key': {
'only_TestPlugin_would_match_this': 'some value'
},

'test_plugin_config_key': {
'type': 'FLANGE.TYPE.PLUGIN',
'schema': {
'type': 'object',
'properties':{
'only_TestPlugin_would_match_this': {'type': 'string'}
},
'required': ['only_TestPlugin_would_match_this']
},
'factory': 'python://flange.test.TestPlugin().get_instance'
}
}
```


## Installation

```
pip install flange
```



Release history Release notifications

This version
History Node

0.0.7

History Node

0.0.4

History Node

0.0.3

History Node

0.0.2

Download files

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

Filename, size & hash SHA256 hash help File type Python version Upload date
flange-0.0.7-py2-none-any.whl (37.0 kB) Copy SHA256 hash SHA256 Wheel py2 Apr 1, 2018
flange-0.0.7.tar.gz (31.9 kB) Copy SHA256 hash SHA256 Source None Apr 1, 2018

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging CloudAMQP CloudAMQP RabbitMQ AWS AWS Cloud computing Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page