{"id":17132737,"url":"https://github.com/vduseev/ilexconf","last_synced_at":"2025-04-13T07:55:55.138Z","repository":{"id":62570462,"uuid":"300857741","full_name":"vduseev/ilexconf","owner":"vduseev","description":"Configuration Library for Python 🔧 Load and merge from multiple sources","archived":false,"fork":false,"pushed_at":"2021-01-29T23:10:43.000Z","size":1181,"stargazers_count":15,"open_issues_count":41,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-13T07:55:49.892Z","etag":null,"topics":["config","configuration","env","json","merge","python","settings","toml","yaml"],"latest_commit_sha":null,"homepage":"https://ilexconf.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vduseev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-03T10:51:41.000Z","updated_at":"2024-02-02T20:06:41.000Z","dependencies_parsed_at":"2022-11-03T17:15:35.547Z","dependency_job_id":null,"html_url":"https://github.com/vduseev/ilexconf","commit_stats":null,"previous_names":["ilexconf/ilexconf"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vduseev%2Filexconf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vduseev%2Filexconf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vduseev%2Filexconf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vduseev%2Filexconf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vduseev","download_url":"https://codeload.github.com/vduseev/ilexconf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248681490,"owners_count":21144700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config","configuration","env","json","merge","python","settings","toml","yaml"],"created_at":"2024-10-14T19:28:02.308Z","updated_at":"2025-04-13T07:55:55.114Z","avatar_url":"https://github.com/vduseev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://ilexconf.com\"\u003e\u003cimg alt=\"ilexconf\" src=\"https://raw.githubusercontent.com/ilexconf/ilexconf/master/docs/_static/github-logo.png\"\u003e\u003c/a\u003e\n\n\u003ch2 align=\"center\"\u003eConfiguration Library 🔧 for Python\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://travis-ci.org/ilexconf/ilexconf\"\u003e\u003cimg alt=\"Build status of package\" src=\"https://img.shields.io/travis/ilexconf/ilexconf?logo=travis\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/ilexconf/ilexconf/actions?query=workflow%3Adocs\"\u003e\u003cimg alt=\"Build status of GitHub pages docs\" src=\"https://img.shields.io/github/workflow/status/ilexconf/ilexconf/docs?label=docs\u0026logo=github\"\u003e\u003c/a\u003e\n\u003ca href=\"https://ilexconf.readthedocs.io/\"\u003e\u003cimg alt=\"Build status of Read the Docs\" src=\"https://img.shields.io/readthedocs/ilexconf?label=readthedocs\u0026logo=read-the-docs\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/ilexconf/ilexconf\"\u003e\u003cimg alt=\"Code coverage report\" src=\"https://img.shields.io/codecov/c/github/ilexconf/ilexconf?logo=codecov\"\u003e\u003c/a\u003e\n\u003ca href=\"https://pypi.org/project/ilexconf/\"\u003e\u003cimg alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/ilexconf?logo=pypi\u0026color=blue\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n`ilexconf` is a Python library to load and merge configs from multiple sources, access \u0026 change the values, and write them back, if needed. It has no dependencies by default but provides additional functions, relying on popular libraries to parse `yaml`, `toml`, provide `CLI` app, etc.\n\n📝 Full documentation at [ilexconf.com](https://ilexconf.com)\n\n## Table of contents \n\n* \u003ca href=\"#quick_start\"\u003e🚀 Quick Start\u003c/a\u003e\n  * \u003ca href=\"#quick_start_install\"\u003eInstallation\u003c/a\u003e\n  * \u003ca href=\"#quick_start_create\"\u003eCreate `Config` object\u003c/a\u003e\n  * \u003ca href=\"#quick_start_read\"\u003eRead using `from_` functions\u003c/a\u003e\n  * \u003ca href=\"#quick_start_access\"\u003eAccess values\u003c/a\u003e\n  * \u003ca href=\"#quick_start_change_create\"\u003eChange \u0026 create values\u003c/a\u003e\n  * \u003ca href=\"#quick_start_merge\"\u003e`merge` another `Mapping` into config\u003c/a\u003e\n  * \u003ca href=\"#quick_start_as_dict\"\u003eConvert to simple `dict` using `as_dict`\u003c/a\u003e\n  * \u003ca href=\"#quick_start_write\"\u003eSave to file using `to_` functions\u003c/a\u003e\n  * \u003ca href=\"#quick_start_subclass\"\u003eSubclass `Config` to customize\u003c/a\u003e\n* \u003ca href=\"#internals\"\u003e⚙️ Internals – How it Works\u003c/a\u003e\n  * \u003ca href=\"#internals_implementation\"\u003eSubclassing `dict` and overriding methods\u003c/a\u003e\n  * \u003ca href=\"#internals_documentation\"\u003eBuilding and hosting docs\u003c/a\u003e\n\n\u003ca id=\"quick_start\"\u003e\u003c/a\u003e\n## 🚀 Quick Start\n\n\u003ca id=\"quick_start_install\"\u003e\u003c/a\u003e\n### Install\n\n```shell\n$ pip install ilexconf\n```\n\n\u003ca id=\"quick_start_create\"\u003e\u003c/a\u003e\n### Populate Config with values\n\nConfig object is initialized using arbitrary number of Mapping objects and keyword arguments. It can even be empty. \n\n```python\nfrom ilexconf import Config, from_json\n\n# All of these are valid methods to initialize a config\nconfig = Config()\nconfig = Config({ \"database\": { \"connection\": { \"host\": \"test.local\" } } })\nconfig = Config(database__connection__port=4000)\nconfig = from_json(\"settings.json\")\nconfig = from_env()\n\n# Or, you can combine them\nconfig = Config(\n    # Take the basic settings from JSON file\n    from_json(\"settings.json\"),\n\n    # Merge the dictionary into that\n    { \"database\": { \"connection\": { \"host\": \"test.local\" } } },\n\n    # Merge the keyword arguments on top\n    database__connection__port=4000\n)\n```\n\nWhen we initialize config all the values are merged. Arguments are merged in order. Every next argument is merged on top of the previous mapping values. And keyword arguments override even that. _For more details read about \u003ca href=\"#quick_start_merge\"\u003emerging\u003c/a\u003e strategy below_.\n\nFor a settings file `settings.json` with the following content ...\n\n```json\n{\n    \"database\": {\n        \"connection\": {\n            \"host\": \"localhost\",\n            \"port\": 5432\n        }\n    }\n}\n```\n\nThe code above will produce a merged `config` with merged values:\n\n```json\n{\n    \"database\": {\n        \"connection\": {\n            \"host\": \"test.local\",\n            \"port\": 4000\n        }\n    }\n}\n```\n\n\u003ca id=\"quick_start_read\"\u003e\u003c/a\u003e\n### Read from files \u0026 environment variables\n\nFiles like `.json`, `.yaml`, `.toml`, `.ini`, `.env`, `.py` as well as environment variables can all be read \u0026 loaded using a set of `from_` functions.\n\n```python\nfrom ilexconf import (\n    from_json,      # from JSON file or string\n    from_yaml,      # from YAML file or string\n    from_toml,      # from TOML file or string\n    from_ini,       # from INI file or string\n    from_python,    # from .py module\n    from_dotenv,    # from .env file\n    from_env        # from environment variables\n)\n\ncfg1 = from_json(\"settings.json\")\n\ncfg2 = Config(\n    from_yaml(\"settings.yaml\"),\n    from_toml(\"settings.toml\")\n)\n\ncfg3 = Config(\n    from_ini(\"settings.ini\"),\n    from_python(\"settings.py\"),\n    from_dotenv(\".env\"),\n    from_env()\n)\n```\n\n\u003ca id=\"quick_start_access\"\u003e\u003c/a\u003e\n### Access values however you like\n\nYou can access any key in the hierarchical structure using classical Python dict notation, dotted keys, attributes, or any combination of this methods.\n\n```python\n# Classic way\nassert config[\"database\"][\"conection\"][\"host\"] == \"test.local\"\n\n# Dotted key\nassert config[\"database.connection.host\"] == \"test.local\"\n\n# Attributes\nassert config.database.connection.host == \"test.local\"\n\n# Any combination of the above\nassert config[\"database\"].connection.host == \"test.local\"\nassert config.database[\"connection.host\"] == \"test.local\"\nassert config.database[\"connection\"].host == \"test.local\"\nassert config.database.connection[\"host\"] == \"test.local\"\n```\n\n\u003ca id=\"quick_start_change_create\"\u003e\u003c/a\u003e\n### Change existing values and create new ones\n\nSimilarly, you can set values of any key (_even if it doesn't exist in the Config_) using all of the ways above.\n\n**Notice**, _contrary to what you would expect from the Python dictionaries, setting nested keys that do not exist is **allowed**_.\n\n```python\n# Classic way\nconfig[\"database\"][\"connection\"][\"port\"] = 8080\nassert config[\"database\"][\"connection\"][\"port\"] == 8080\n\n# Dotted key (that does not exist yet)\nconfig[\"database.connection.user\"] = \"root\"\nassert config[\"database.connection.user\"] == \"root\"\n\n# Attributes (also does not exist yet)\nconfig.database.connection.password = \"secret stuff\"\nassert config.database.connection.password == \"secret stuff\"\n```\n\n\u003ca id=\"quick_start_merge\"\u003e\u003c/a\u003e\n### Merge with another Mapping object\n\nIf you just assign a value to any key, you override any previous value of that key.\n\nIn order to merge assigned value with an existing one, use `merge` method.\n\n```python\nconfig.database.connection.merge({ \"password\": \"different secret\" })\nassert config.database.connection.password == \"different secret\"\n```\n\n`merge` respects the contents of each value. For example, merging two dictionaries with the same key would not override that key completely. Instead, it will recursively look into each key and try to merge the contents. Take this example:\n\n```python\nconfig = Config(\n    { \"a1\": { \"c1\": 1, \"c2\": 2, \"c3\": 3 } },\n    { \"a1\": { \"c3\": \"other\" } }\n)\n\n# Instead of overriding the value of the \"a1\" key completely, `merge` method\n# will recursively look inside and merge nested values.\nassert config.as_dict() == { \"a1\": { \"c1\": 1, \"c2\": 2, \"c3\": 3 } }\n```\n\n\u003ca id=\"quick_start_as_dict\"\u003e\u003c/a\u003e\n### Represent as dictionary\n\nFor any purposes you might find fit you can convert entire structure of the Config object into dictionary, which will be essentially returned to you as a deep copy of the object.\n\n```python\nassert config.as_dict() == {\n    \"database\": {\n        \"connection\": {\n            \"host\": \"test.local\",\n            \"port\": 8080,\n            \"user\": \"root\",\n            \"password\": \"different secret\"\n        }\n    }\n}\n```\n\n\u003ca id=\"quick_start_write\"\u003e\u003c/a\u003e\n### Write to file\n\nYou can serialize the file as JSON or other types any time using the `to_` functions.\n\n```python\n# Write updated config back as JSON file\nfrom ilexconf import to_json\n\nto_json(config, \"settings.json\")\n```\n\n**WARNING**: _This might throw a serialization error if any of the values contained in the Config are custom objects that cannot be converted to `str`. Also, obviously, you might not be able to correctly parse an object back, if it's saved to JSON as `MyObject(\u003cfunction MyObject.__init__.\u003clocals\u003e.\u003clambda\u003e at 0x108927af0\u003e, {})` or something._\n\n\u003ca id=\"quick_start_subclass\"\u003e\u003c/a\u003e\n### Subclass\n\nSubclassing `Config` class is very convenient for implementation of your own config classes with custom logic.\n\nConsider this example:\n\n```python\nimport ilexconf\n\nclass Config(ilexconf.Config):\n    \"\"\"\n    Your custom Configuration class\n    \"\"\"\n\n    def __init__(self, do_stuff=False):\n        # Initialize your custom config with JSON by default\n        super().__init__(self, ilexconf.from_json(\"setting.json\"))\n\n        # Add some custom value depending on some logic\n        if do_stuff:\n            self.my.custom.key = \"Yes, do stuff\"\n\n        self.merge({\n            \"Horizon\": \"Up\"\n        })\n\n# Now you can use your custom Configuration everywhere\nconfig = Config(do_stuff=True)\nassert config.my.custom.key == \"Yes, do stuff\"\nassert config.Horizon == \"Up\"\n```\n\n\u003ca id=\"internals\"\u003e\u003c/a\u003e\n## ⚙️ Internals\n\n\u003ca id=\"internals_implementation\"\u003e\u003c/a\u003e\n### Implementation\n\nUnder the hood `ilexconf` is implemented as a `defaultdict` where every key with Mapping value is represented as another `Config` object. This creates a hierarchy of `Config` objects.\n\n`__getitem__`, `__setitem__`, `__getattr__`, and `__setattr__` methods are overloaded with custom logic to support convenient get/set approach presented by the library.\n\n\u003ca id=\"internals_documentation\"\u003e\u003c/a\u003e\n### Documentation\n\nFull documentation is available at **[ilexconf.com](https://ilexconf.com)** and on ilexconf.readthedocs.io.\n\n* Documentation is written using `reStructuredText` and uses real code snippets from the unit tests and source code.\n* Documentation is built using [Sphinx](https://www.sphinx-doc.org/) using [sphinx-material](https://github.com/bashtage/sphinx-material) theme.\n* Documentation is hosted on Github Pages at [ilexconf.com](https://ilexconf.com) and on \"Read the Docs\" at [ilexconf.readthedocs.io](https://ilexconf.readthedocs.io/).\n* For Github Pages documentation is built using Github Actions.\n* Read the Docs builds their version automatically based on the `.readthedocs.yml` config in the project root directory.\n\n## Contributing\n\nContributions are welcome!\n\n## Kudos\n\n`ilexconf` ideas are heavily borrowed from amazing [`python-configuration`](https://github.com/tr11/python-configuration) library by [Tiago Requeijo](https://github.com/tr11).\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvduseev%2Filexconf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvduseev%2Filexconf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvduseev%2Filexconf/lists"}