{ "info": { "author": "Heureka.cz", "author_email": "vyvoj@heureka.cz", "bugtrack_url": null, "classifiers": [], "description": "llconfig\n========\n\nLightweight layered configuration library.\n\nAll you need is Python >= 3.5 (and a keyboard).\n\n------\n\n## Basic concept\n\nThere is a `Config` object holding several layers of configuration keys and their values (= configuration directives).\nFrom top to bottom:\n\n1. Override layer = holds runtime directive overrides, if any.\n2. Env layer = directives loaded from environment variables are kept in this layer, if any.\n3. File layer = directives loaded from configuration file(s) are kept in this layer, if any.\n4. Default layer = holds a default value for **every** initialized directive.\n\n## Learn by example\n\nThe behavior is best shown on following example:\n\n```python\nfrom llconfig import Config\n\nc = Config('local/override.cnf.py', '/etc/my_app/conf.d', env_prefix='MY_', config_files_env_var='CONFIG')\nc.init('PORT', int, 80)\nc.load() # recommended, but not required (see docstrings)\nc['PORT']\n```\n\nThe returned value is `80`, given that there is no `MY_PORT` env\nvariable, no `MY_CONFIG` env variable and no `PORT = 1234` line in any of `local/override.cnf.py` or\n`/etc/my_app/conf.d/*.cnf.py` configuration files.\n\n### Search process\n\nFirst, the override layer is searched, but there is no runtime override (no `c['PORT'] = 1234`) in this example.\n\nThe environment layer is searched next. If there is an env variable called `MY_PORT`, its value is taken\nand **converted** using `int` function (this is necessary otherwise it wouldn't be possible to load anything\nelse than `str` from env variables).\n\nThen, if the env variable is not present, the file layer is searched. There can be multiple files in this layer\n(forming sub-layers) and all of them must be Python-executable. File sub-layers are processed in following order:\n\n1. Files loaded from `MY_CONFIG` env variable. Its value is splitted using `:` (colon) as a delimiter and\n each part is handled the same way as if it would be passed to constructor (see bellow). The handling\n preserves order (so the leftmost part is always handled first).\n2. Files passed to constructor (`local/override.cnf.py` and `/etc/my_app/conf.d` in this example). If there\n is a path pointing to directory instead of simple file, the **directory is expanded** (non-recursively). The\n expansion lists all files in given directory using `expansion_glob_pattern` attribute **sorted by file name\n in reverse order** (you can change this behavior by extending this class and overriding `_expand_dir`\n method). The expanded files are used as separate sub-layers in place of original directory.\n\nWhen all of the file sub-layers are created, each configuration file **is executed** and each file's global\nnamespace is searched for the `PORT` directive (still preserving the order). If found, the directive is returned\nas is (without conversion).\n\nThe default layer is searched as a last resort. As it contains values from directives' `init`, there is always a\ndefault value (unless a search for non-initialized directive is performed). The default value is returned as is.\n\n## Directive initialization\n\nDirective is initialized using `init` method. It takes directive name, converter function (see bellow) and\na default value (which is `None` by default). It is recommended to name directives using upper-case only.\n**Any directive you want to use must be initialized,** otherwise it is ignored (unknown env variables, unknown\ndirectives in configuration files, etc.).\n\nThis means that once you initialize a directive you can safely use it without `KeyError`s or without calling\n`c.get('PORT', 'default')`. There will always be at least the default value.\n\n## Converters\n\nConverters are arbitrary callables taking single `str` argument and returning anything. The converter is called\nonly for conversion from env variable. There are some predefined converters available in `llconfig.converters`,\nbut it is easy to create own. For example:\n\n```python\nfrom llconfig import Config\nfrom llconfig.converters import json, bool_like\nfrom pathlib import Path\n\nc = Config()\nc.init('PORT', int) # \"443\" => 443\nc.init('HOSTNAME', str) # \"localhost\" => \"localhost\"\nc.init('DEBUG', bool_like) # \"off\" => False\nc.init('DEBUG_2', bool) # BEWARE: \"0\" => True \nc.init('FLEXIBLE', json) # '{\"hello\": 1}' => {\"hello\": 1}\nc.init('PICTURES', lambda raw: [Path(p) for p in raw.split(':')]) # \"a.jpg:b.jpg\" => [Path(\"a.jpg\"), Path(\"b.jpg\")]\n```\n\nAny exception raised during conversion is re-raised as a `ValueError`.\n\n## Getting the values out\n\nThe `Config` object implements a mapping protocol, so you can use it as if it was a `dict`. In addition, there is a\n`get_namespace` method [taken from Flask framework](http://flask.pocoo.org/docs/1.0/api/#flask.Config.get_namespace)\nwith exact same behavior (see their docs for more examples).\n\n```python\nfrom llconfig import Config\n\nc = Config()\nc.init('DB_HOST', str, 'localhost')\nc.init('DB_PORT', int, 3306)\nc.init('DB_USER', str)\n\nc['DB_HOST'] # => 'localhost'\nc['DB_USER'] # => None\n\nc.get_namespace('DB_') # => {'host': 'localhost', 'port': 3306, 'user': None}\nc['DB_':] # syntactic sugar - does the same as `c.get_namespace('DB_')`\n\ndict(c) # => {'DB_HOST': 'localhost', 'DB_PORT': 3306, 'DB_USER': None}\n```\n\n## Security\n\n**In short: do not use this library in untrusted environment,** unless you completely understand how it works and\nwhat possible attack vectors are. The main concern is that each file forming the file layer is executed. There\nis also a possibility to load files using `config_files_env_var` environment variable (`APP_CONFIG` by default),\nunless disabled. On top of that, you can compromise your application using badly written converter.\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/heureka/llconfig", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "llconfig", "package_url": "https://pypi.org/project/llconfig/", "platform": "", "project_url": "https://pypi.org/project/llconfig/", "project_urls": { "Homepage": "https://github.com/heureka/llconfig" }, "release_url": "https://pypi.org/project/llconfig/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "Lightweight layered configuration library.", "version": "1.0.0" }, "last_serial": 4100450, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "5c310e86b53ad694bccac5a5cc6db11e", "sha256": "1671ac3d73819c036af1f133072484fb103ac79f5215a788fd4df4447830bddf" }, "downloads": -1, "filename": "llconfig-1.0.0-py3.6.egg", "has_sig": false, "md5_digest": "5c310e86b53ad694bccac5a5cc6db11e", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": null, "size": 6419, "upload_time": "2018-07-25T12:43:52", "url": "https://files.pythonhosted.org/packages/60/92/0a5f03218a20ba81a1f5b918aaaef947201d8b7eb3f8365cc1a6a90f22f2/llconfig-1.0.0-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "2ff8a70655475fd7be24127342a10519", "sha256": "97d23ebd5f6c7f25f2c485078f5fae063d9ce3c62a4341e75b42fbe10c5516d5" }, "downloads": -1, "filename": "llconfig-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "2ff8a70655475fd7be24127342a10519", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 6602, "upload_time": "2018-07-25T12:43:51", "url": "https://files.pythonhosted.org/packages/5b/9e/2d2899a0a870fddb97493559c4f60e9421ed60160397d463ff3fa9e9fb47/llconfig-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "06356d3809e218a7601d277836a1417c", "sha256": "13a2e7e9491eea316e1b4bfcce6a57437732d6411332bd220be32f4c3826b3b7" }, "downloads": -1, "filename": "llconfig-1.0.0.tar.gz", "has_sig": false, "md5_digest": "06356d3809e218a7601d277836a1417c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6203, "upload_time": "2018-07-25T12:43:53", "url": "https://files.pythonhosted.org/packages/da/8d/0809818f6938617e6aab8c2fd259f3aab8092ec62c8e1c4a74a906409234/llconfig-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "5c310e86b53ad694bccac5a5cc6db11e", "sha256": "1671ac3d73819c036af1f133072484fb103ac79f5215a788fd4df4447830bddf" }, "downloads": -1, "filename": "llconfig-1.0.0-py3.6.egg", "has_sig": false, "md5_digest": "5c310e86b53ad694bccac5a5cc6db11e", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": null, "size": 6419, "upload_time": "2018-07-25T12:43:52", "url": "https://files.pythonhosted.org/packages/60/92/0a5f03218a20ba81a1f5b918aaaef947201d8b7eb3f8365cc1a6a90f22f2/llconfig-1.0.0-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "2ff8a70655475fd7be24127342a10519", "sha256": "97d23ebd5f6c7f25f2c485078f5fae063d9ce3c62a4341e75b42fbe10c5516d5" }, "downloads": -1, "filename": "llconfig-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "2ff8a70655475fd7be24127342a10519", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 6602, "upload_time": "2018-07-25T12:43:51", "url": "https://files.pythonhosted.org/packages/5b/9e/2d2899a0a870fddb97493559c4f60e9421ed60160397d463ff3fa9e9fb47/llconfig-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "06356d3809e218a7601d277836a1417c", "sha256": "13a2e7e9491eea316e1b4bfcce6a57437732d6411332bd220be32f4c3826b3b7" }, "downloads": -1, "filename": "llconfig-1.0.0.tar.gz", "has_sig": false, "md5_digest": "06356d3809e218a7601d277836a1417c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6203, "upload_time": "2018-07-25T12:43:53", "url": "https://files.pythonhosted.org/packages/da/8d/0809818f6938617e6aab8c2fd259f3aab8092ec62c8e1c4a74a906409234/llconfig-1.0.0.tar.gz" } ] }