{"id":18358521,"url":"https://github.com/schireson/configly","last_synced_at":"2025-04-06T13:31:34.232Z","repository":{"id":38327649,"uuid":"209383719","full_name":"schireson/configly","owner":"schireson","description":"Simplify and centralize the loading of configuration, such as environment variables.","archived":false,"fork":false,"pushed_at":"2023-08-04T19:38:03.000Z","size":102,"stargazers_count":9,"open_issues_count":1,"forks_count":0,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-22T00:44:13.970Z","etag":null,"topics":["config","configuration","json","schiresonip","tidepod","yaml"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/schireson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-18T19:02:27.000Z","updated_at":"2024-10-23T06:42:15.000Z","dependencies_parsed_at":"2023-01-25T11:45:30.607Z","dependency_job_id":null,"html_url":"https://github.com/schireson/configly","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schireson%2Fconfigly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schireson%2Fconfigly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schireson%2Fconfigly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schireson%2Fconfigly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/schireson","download_url":"https://codeload.github.com/schireson/configly/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247488531,"owners_count":20946956,"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","json","schiresonip","tidepod","yaml"],"created_at":"2024-11-05T22:18:16.450Z","updated_at":"2025-04-06T13:31:33.630Z","avatar_url":"https://github.com/schireson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Github Actions](https://github.com/schireson/configly/actions/workflows/build.yml/badge.svg) [![codecov](https://codecov.io/gh/schireson/configly/branch/main/graph/badge.svg)](https://codecov.io/gh/schireson/configly) [![Documentation Status](https://readthedocs.org/projects/configly/badge/?version=latest)](https://configly.readthedocs.io/en/latest/?badge=latest)\n\n## TL;DR\n\n```yaml\n# config.yml\nfoo:\n  bar: \u003c% ENV[REQUIRED] %\u003e\n  baz: \u003c% ENV[OPTIONAL, true] %\u003e\nlist_of_stuff:\n  - fun\u003c% ENV[NICE, dament] %\u003eal\n  - fun\u003c% ENV[AGH, er] %\u003eal\n  - more/\u003c% ENV[THAN, er] %\u003e/one/\u003c% ENV[interpolation, er] %\u003e!\n```\n\n```python\n# app.py\nfrom configly import Config\n\n\nconfig = Config.from_yaml('config.yml')\n\nprint(config.foo.bar)\nprint(config.foo['baz'])\nfor item in config.list_of_stuff:\n    print(item)\n```\n\n```bash\npip install configly[yaml]\n```\n\n## Introduction\n\nLoading configuration is done in every (application) project, and yet it is often\noverlooked and condidered too easy or straightforward to bother using a library\nto manage doing it.\n\nTherefore, we often see code like this:\n\n```python\n# config.py\nimport os\n\n# Maybe it's following 12factor and loading all the config from the environment.\nconfig = {\n    'log_level': os.getenv('LOG_LEVEL'),\n    'database': {\n        # At least here, I can nest values if I want to organize things.\n        'password': os.environ['DATABASE_PASSWORD'],\n        'port': int(os.environ['DATABASE_PORT']),\n    }\n}\n```\n\nor this\n\n```python\n# config.py\nimport os\n\nclass Config:\n    log_level = os.getenv('LOG_LEVEL')\n\n    # Here it's not so easy to namespace\n    database_password = os.environ['DATABASE_PASSWORD']\n    database_port = int(os.environ['DATABASE_PORT'])\n\n\n# Oh goodness!\nclass DevConfig(Config):\n    environment = 'dev'\n```\n\nor this\n\n```python\nimport configparser\n# ...🤢... Okay I dont even want to get into this one.\n```\n\nAnd this is all assuming that everyone is loading configuration at the outermost entrypoint!\nThe two worst possible outcomes in configuration are:\n\n- You are loading configuration lazily and/or deeply within your application, such that it\n  hits a critical failure after having seemingly successfully started up.\n- There is not a singular location at which you can go to see all configuration your app might\n  possibly be reading from.\n\n## The pitch\n\n`Configly` asserts configuration should:\n\n- Be centralized\n  - One should be able to look at one file to see all (env vars, files, etc) which must exist for the\n    application to function.\n- Be comprehensive\n  - One should not find configuration being loaded secretly elsewhere\n- Be declarative/static\n  - code-execution (e.g. the class above) in the definition of the config inevitably makes it\n    hard to interpret, as the config becomes more complex.\n- Be namespacable\n  - One should not have to prepend `foo_` namespaces to all `foo` related config names\n- Be loaded, once, at app startup\n  - (At least the _definition_ of the configuration you're loading)\n- (Ideally) have structured output\n  - If something is an `int`, ideally it would be read as an int.\n\nTo that end, the `configly.Config` class exposes a series of classmethods from which your config\ncan be loaded. It's largely unimportant what the input format is, but we started with formats\nthat deserialize into at least `str`, `float`, `int`, `bool` and `None` types.\n\n```python\nfrom configly import Config\n\n\n# Currently supported input formats.\nconfig = Config.from_yaml('config.yml')\nconfig = Config.from_json('config.json')\nconfig = Config.from_toml('config.toml')\n```\n\nGiven an input `config.yml` file:\n\n```yaml\n# config.yml\nfoo:\n  bar: \u003c% ENV[REQUIRED] %\u003e\n  baz: \u003c% ENV[OPTIONAL, true] %\u003e\nlist_of_stuff:\n  - fun\u003c% ENV[NICE, dament] %\u003eal\n  - fun\u003c% ENV[AGH, er] %\u003eal\n  - more/\u003c% ENV[THAN, er] %\u003e/one/\u003c% ENV[interpolation, er] %\u003e!\n```\n\nA number of things are exemplified in the example above:\n\n- Each `\u003c% ... %\u003e` section indicates an interpolated value, the interpolation can\n  be a fragment of the overall value, and multiple values can be interpolated\n  within a single value.\n\n- `ENV` is an \"interpolator\" which knows how to obtain environment variables\n\n- `[VAR]` Will raise an error if that piece of config is not found, whereas\n  `[VAR, true]` will default `VAR` to the value after the comma\n\n- Whatever the final value is, it's interpreted as a literal value in the\n  format of the file which loads it. I.E. `true` -\u003e python `True`, `1` -\u003e\n  python `1`, and `null` -\u003e python `None`.\n\nNow that you've loaded the above configuration:\n\n```python\n# app.py\nfrom configly import Config\n\n\nconfig = Config.from_yaml('config.yml')\n\n# You can access namespaced config using dot access\nprint(config.foo.bar)\n\n# You have use index syntax for dynamic, or non-attribute-safe key values.\nprint(config.foo['baz'])\n\n# You can iterate over lists\nfor item in config.list_of_stuff:\n    print(item)\n\n# You can *generally* treat key-value maps as dicts\nfor key, value in config.foo.items():\n    print(key, value)\n\n# You can *actually* turn key-value maps into dicts\ndict(config.foo) == config.foo.to_dict()\n```\n\n## Installing\n\n```bash\n# Basic installation\npip install configly\n\n# To use the yaml config loader\npip install configly[yaml]\n\n# To use the toml config loader\npip install configly[toml]\n\n# To use the vault config loader\npip install configly[vault]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschireson%2Fconfigly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschireson%2Fconfigly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschireson%2Fconfigly/lists"}