{"id":50259315,"url":"https://github.com/confarg/confarg","last_synced_at":"2026-05-27T08:01:32.372Z","repository":{"id":358037150,"uuid":"1239609152","full_name":"confarg/confarg","owner":"confarg","description":"Load and resolve complex configurations from files, environment variables, and command line arguments. Keep your data structures and favorite CLI library.","archived":false,"fork":false,"pushed_at":"2026-05-27T06:14:13.000Z","size":717,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-27T08:00:28.110Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://confarg.github.io/confarg/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/confarg.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-15T09:02:28.000Z","updated_at":"2026-05-27T06:13:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/confarg/confarg","commit_stats":null,"previous_names":["confarg/confarg"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/confarg/confarg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/confarg%2Fconfarg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/confarg%2Fconfarg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/confarg%2Fconfarg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/confarg%2Fconfarg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/confarg","download_url":"https://codeload.github.com/confarg/confarg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/confarg%2Fconfarg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33556551,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2026-05-27T08:01:31.530Z","updated_at":"2026-05-27T08:01:32.347Z","avatar_url":"https://github.com/confarg.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A tool to manage complex configurations\r\n\r\n\u003e Load and resolve complex configurations from files, environment variables and command line arguments. Keep your data structures and favorite CLI library.\r\n\r\n`confarg` is a Python library that helps you load configurations in a modular fashion from multiple sources: files, environment variables, and command line arguments.\r\n\r\nIt can handle deeply nested configurations, type unions, derived classes, expressions and variable interpolation, configuration compositions and more, and can integrate with your favorite argument parser library such as `argparse`, `click` or `typer`.\r\n\r\n`confarg` is not a framework. No decorator, base class or special annotation type required: none are provided. It is just a tool for the deserialization and serialization of complex configurations. Its footprint in your code is typically a few lines of code, making it easy to switch to it, or away from it.\r\n\r\n## Install\r\n\r\n```bash\r\npip install confarg\r\n```\r\n\r\n`confarg` comes with no dependency, but installing additional libraries such as `pyyaml` unlocks the support of extra configuration file formats.\r\n\r\n## TL;DR\r\n\r\nThis library lets you read configurations stored in classes like this\r\n\r\n```python\r\n@dataclass\r\nclass Config:\r\n  value: float\r\n  flag: bool\r\n  subconfig: SubConfig1 | SubConfig2\r\n```\r\n\r\nwith this kind of code\r\n\r\n```python\r\nconfig = confarg.load(Config)\r\n```\r\n\r\nwhich lets you build a configuration object from files,\r\n\r\n```yaml\r\nvalue: 1.0\r\nflag: false\r\nsubconfig:\r\n  foo: 42\r\n```\r\n\r\nbut also and simultaneously from environment variables,\r\n\r\n```properties\r\nMYAPP_VALUE=0.0\r\n```\r\n\r\nand command line arguments,\r\n\r\n```bash\r\nmyapp --subconfig.foo=33\r\n```\r\n\r\nStill here? Read along, we are just getting started.\r\n\r\n## Getting started\r\n\r\n\u003e All the examples presented in this section (and more) are available in the `examples/` folder.\r\n\r\nImagine that you have an app that depends on some parameters that you have collected into a `dataclass` like so:\r\n\r\n```python\r\n@dataclass\r\nclass DBConfig:\r\n    host: str\r\n    port: int\r\n    name: str\r\n```\r\n\r\nIn your app, you use `confarg` to instantiate this configuration:\r\n\r\n```python\r\ndb_config = confarg.load(DBConfig)\r\n```\r\n\r\nThis allows you to construct a `DBConfig` object by collecting data from three possible sources.\r\n\r\n1. From a configuration file. By passing `--config \u003cconfig_file\u003e` to your app, `confarg` will load the content of the file and fill the `DBConfig` object. For example, a config file could look like so:\r\n\r\n   ```yaml\r\n   # config.yaml\r\n   host: example.com\r\n   port: 1234\r\n   name: mydb\r\n   ```\r\n\r\n   You would then call your application as\r\n\r\n   ```console notest\r\n   $ myapp.py --config config.yaml\r\n   DBConfig(host='example.com', port=1234, name='mydb')\r\n   ```\r\n\r\n   Configuration files in TOML and JSON formats are also supported.\r\n\r\n   \u003e You can change the default `config` flag to something else using the `config_flag` parameter.\r\n\r\n2. From environment variables. You can declare\r\n\r\n   ```properties\r\n   MYAPP_HOST=example.com\r\n   MYAPP_PORT=1234\r\n   MYAPP_NAME=mydb\r\n   ```\r\n\r\n   for the same effect.\r\n\r\n   \u003e Note that the environment variable prefix of your app should actually be passed to `confarg.load` like so:\r\n   \u003e\r\n   \u003e ```python\r\n   \u003e db_config = confarg.load(DBConfig, env_prefix=\"MYAPP_\")\r\n   \u003e ```\r\n\r\n3. From command line arguments.\r\n\r\n   ```console notest\r\n   $ my_app --host example.com --port 1234 --name mydb\r\n   DBConfig(host='example.com', port=1234, name='mydb')\r\n   ```\r\n\r\n### Progressive build-up\r\n\r\nThe examples above presented different sources to feed your configuration. They are not mutually exclusive — in fact, they are intended to be used simultaneously.\r\n\r\nNote that no one source needs to provide a complete configuration, as long as the configuration resulting from this progressive build-up is complete.\r\n\r\nFor example, taking our previous example, you could have a partial configuration file containing only host information,\r\n\r\n```yaml\r\n# partial_config.yaml\r\nhost: example.com\r\nport: 1234\r\n```\r\n\r\nand provide the schema name from the command line:\r\n\r\n```console notest\r\n$ myapp.py --config partial_config.yaml --name mydb\r\nDBConfig(host='example.com', port=1234, name='mydb')\r\n```\r\n\r\n### Source precedence\r\n\r\nConfiguration data is read in the following order, later read overwriting existing data:\r\n\r\n1. configuration files are read first;\r\n2. then environment variables;\r\n3. finally, command line arguments.\r\n\r\nThis allows for surgical modifications of configuration files. For example, one could overwrite the schema configuration from our existing `full_config` from the command line like so:\r\n\r\n```console notest\r\n$ # Overwrite the schema name defined in the config file from the command line\r\n$ myapp.py --config config.yaml --name otherdb\r\nDBConfig(host='example.com', port=1234, name='otherdb')\r\n```\r\n\r\n### Unions\r\n\r\nLet's say your app needs to support SQLite databases. You now have two different, incompatible DB configurations:\r\n\r\n```python\r\n@dataclass\r\nclass DBServerConfig:\r\n    host: str\r\n    port: int\r\n    name: str\r\n\r\n@dataclass\r\nclass SQLiteConfig:\r\n    dbpath: str\r\n```\r\n\r\nThe DB configuration needs to be either one or the other, which we declare like so:\r\n\r\n```python\r\ntype DBConfig = SQLiteConfig | DBServerConfig\r\n```\r\n\r\n`confarg` can handle this new union type and figure out which configuration is desired based on the arguments it got:\r\n\r\n```console notest\r\n$ # Pass DBServerConfig parameters, and you get a DBServerConfig\r\n$ myapp.py --host example.com --port 1234 --name mydb\r\nDBServerConfig(host='example.com', port=1234, name='mydb')\r\n$ # Pass SQLiteConfig parameters, and you get a SQLiteConfig\r\n$ myapp.py --dbpath db.sqlite\r\nSQLiteConfig(dbpath='db.sqlite')\r\n```\r\n\r\n### Disambiguation tags\r\n\r\nFor simple configurations, the above automatic disambiguation is enough and convenient.\r\n\r\nIn more complex configuration scenarios, this automatic disambiguation may not be not possible. For example, different configurations may share the exact same fields.\r\n\r\nEven when disambiguation is possible, it may not be obvious to the human eye which object class should be return from the provided parameters.\r\n\r\nTherefore, by necessity or for the sake of clarity, you can provide the class path of the required configuration by using the `class` tag, like so\r\n\r\n```console notest\r\n$ # Explicitly ask for a SQLiteConfig\r\n$ myapp.py --class myapp.SQLiteConfig --dbpath db.sqlite\r\nSQLiteConfig(dbpath='db.sqlite')\r\n```\r\n\r\nOne example where it is necessary to provide the `class` path is to overwrite the configuration with a new class. Without it, command line arguments are added to the configuration, resulting in an invalid input.\r\n\r\n```console notest\r\n$ # Config file contains a DBServerConfig\r\n$ myapp.py --config db_server.yaml\r\nDBServerConfig(host='example.com', port=1234, name='mydb')\r\n$ # Fails:  dbpath is not a DBServerConfig key\r\n$ myapp.py --config db_server.yaml --dbpath db.sqlite\r\n...\r\n$ # OK: using class signals overwrite existing DB config\r\n$ myapp.py --config db_server.yaml --class myapp.SQLiteConfig --dbpath db.sqlite\r\nSQLiteConfig(dbpath='db.sqlite')\r\n```\r\n\r\n### Inheritance\r\n\r\nAnother way to provide a flexible configuration is to derive akin configuration classes from a common base class.\r\n\r\n```python\r\n@dataclass\r\nclass DBConfig:\r\n    pass\r\n\r\n@dataclass\r\nclass DBServerConfig(DBConfig):\r\n    host: str\r\n    port: int\r\n    name: str\r\n\r\n@dataclass\r\nclass SQLiteConfig(DBConfig):\r\n    dbpath: str\r\n```\r\n\r\nThis allows configurations to be easily extensible. Contrast with unions, where a class must be explicitly listed to be supported.\r\n\r\nThe downside is that the concrete class must be tagged, as `confarg` cannot discover classes derived from a given class.\r\n\r\n```console notest\r\n$ # Fails:  derived class not specified\r\n$ uv run myapp.py --dbpath db.sqlite\r\n...\r\n$ # OK: explicit class path provided\r\n$ uv run myapp.py --dbpath db.sqlite --class myapp.SQLiteConfig\r\nSQLiteConfig(dbpath='db.sqlite')\r\n```\r\n\r\n### Configuration hierarchies\r\n\r\nThe configurations discussed so far has been rather simple, composed of values grouped together in a `dataclass`. However, it needs not be. Configurations are generally deeply nested hierarchies, which `confarg` supports.\r\n\r\nLet's say you want to add a log level to your application. You place it at the root level of a new `Config` object, along with the DB configuration, that is now one level down under the `db` key.\r\n\r\n```python\r\n@dataclass\r\nclass Config:\r\n    db: DBConfig\r\n    log_level: Literal[\"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\"] = \"INFO\"\r\n```\r\n\r\nYou now parse your new top-level `Config` instead of `DBConfig`.\r\n\r\n```python\r\nconfig = confarg.load(Config)\r\n```\r\n\r\nOur DB configuration, which used to be the root configuration, is now located under the `db` key. This has the following impact.\r\n\r\nFor command line arguments, we follow the common convention of using dot-separated paths to address nested fields. Previous command line arguments for `DBConfig` are now prefixed by `db.`, like so:\r\n\r\n```console notest\r\n$ myapp.py --db.class myapp.SQLiteConfig --db.dbpath db.sqlite\r\nConfig(db=SQLiteConfig(dbpath='db.sqlite'), log_level='INFO')\r\n```\r\n\r\nThe configuration file is also modified accordingly,\r\n\r\n```yaml\r\n# config.yaml\r\ndb:\r\n  class: myapp.DBServerConfig\r\n  host: example.com\r\n  name: mydb\r\n  port: 1234\r\n```\r\n\r\nand is used just like before:\r\n\r\n```console notest\r\n$ myapp.py --config config.yaml\r\nConfig(db=DBServerConfig(host='example.com', port=1234, name='mydb'),\r\n       log_level='DEBUG')\r\n```\r\n\r\n### Leaf data type and type coercion\r\n\r\nYou may have noticed that the previous section introduced a `log_level` parameter that has two interesting features: first, it is not of a simple type (`str`, `int`, `float`, `bool` or `None`); second, it comes with a default value.\r\n\r\nDefault values are honored, and you may have noticed that we did not provide any value to `log_level`. You can of course override a default value.\r\n\r\nAs for leaf node data type, `confarg` coerces `Enum` and `Path` types as special exceptions to simple types. Other types are treated as classes and must follow the same rules.\r\n\r\n### Expressions and variable interpolation\r\n\r\nYour application is becoming more complex by the day, and is now requiring a resources configuration.\r\n\r\n```python\r\n@dataclass\r\nclass Resources:\r\n    cpu_count: int\r\n    memory_gb: int\r\n    max_heap_size_mb: int\r\n```\r\n\r\nIt is added to the global configuration under the `resources` key:\r\n\r\n```python\r\n@dataclass\r\nclass Config:\r\n    db: DBConfig\r\n    resources: Resources\r\n    log_level: Literal[\"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\"] = \"INFO\"\r\n```\r\n\r\nYour configuration file has become,\r\n\r\n```yaml\r\n# config.yaml\r\ndb:\r\n  class: myapp.DBServerConfig\r\n  host: example.com\r\n  name: mydb\r\n  port: 1234\r\n\r\nresources:\r\n  cpu_count: 4\r\n  memory_gb: 16\r\n  max_heap_size_mb: 131072\r\n```\r\n\r\nThis works fine. However, you want to better express the fact that `max_heap_size_mb` is chosen to be 80% of the host memory by default. To achieve this, you can write expressions relying on variable interpolation using the `${...}` syntax, like so:\r\n\r\n```yaml\r\n# expression_config.yaml\r\ndb:\r\n  class: myapp.DBServerConfig\r\n  host: example.com\r\n  name: mydb\r\n  port: 1234\r\n\r\nresources:\r\n  cpu_count: 4\r\n  memory_gb: 16\r\n  max_heap_size_mb: ${int(resources.memory_gb * 1024 * 0.8)}\r\n```\r\n\r\n```console notest\r\n$ myapp.py --config expression_config.yaml\r\nConfig(db=SQLiteConfig(dbpath='db.sqlite'),\r\n       resources=Resources(cpu_count=4, memory_gb=16, max_heap_size_mb=13107),\r\n       log_level='INFO')\r\n```\r\n\r\nNote that variable interpolation occurs after all configuration data is read. This means here that you can override `memory_gb` from the command line, and `max_heap_size_mb` will be adjusted accordingly, even though the expression is defined in the configuration file.\r\n\r\n```console notest\r\n$ # Max heap is recomputed according to the expression in the config file\r\n$ myapp.py --config expression_config.yaml --resources.memory_gb 8\r\nConfig(db=SQLiteConfig(dbpath='db.sqlite'),\r\n       resources=Resources(cpu_count=4, memory_gb=8, max_heap_size_mb=6553),\r\n       log_level='INFO')\r\n```\r\n\r\n### Building large configurations from parts\r\n\r\nLarge configurations are often made up of independent components, and as such, you may want to split them accordingly. It is easier to navigate, but it also makes it possible to reuse configuration parts and to build multiple complex configurations from the same set of atomic configuration components.\r\n\r\nSome configuration components may even be generated automatically, in which case being able to isolate those parts from the rest is a must.\r\n\r\n`confarg` lets you do this in different ways.\r\n\r\nFrom the command line, the `--config` flag can be suffixed with a key path to load configurations there. For example,\r\n\r\n```console notest\r\n# Load a config file specific to the `db` key\r\n$ myapp.py --config.db db_config.yaml\r\nConfig(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')\r\n```\r\n\r\nA similar pattern applies to environment variables:\r\n\r\n```console notest\r\n$ MYAPP_CONFIG_DB=db_config.py myapp.py\r\nConfig(db=DBServerConfig(host='example.com', port=1234, name='mydb'), log_level='INFO')\r\n```\r\n\r\n\u003e Note that `db_config.yaml` does *not* contain the `db` key. It does not need to know the path it is loaded to.\r\n\r\nIn config files, you can load a configuration by specifying the special `__include__` key, followed by the path to the sub-configuration to load, like so:\r\n\r\n```yaml\r\n# set everything under the `db` key from another file\r\ndb:\r\n  __include__: ./db_config.yaml\r\n```\r\n\r\nThe `__include__` keyword can also be used at the top-level, to create a new config that amends an existing config.\r\n\r\n```yaml\r\n# start from this base configuration\r\n__include__: base_config.yaml\r\n\r\n# set or overwrite everything under the `db` key\r\ndb:\r\n  __include__: ./db_config.yaml\r\n```\r\n\r\n## Next steps\r\n\r\nWe have more than scratched the surface, and you should have enough knowledge to cover most of your needs.\r\n\r\nAgain, all of the examples above and more are in the `examples/` folder, which is a great way to discover and experiment with the library features.\r\n\r\nA documentation is also currently being written at https://confarg.github.io/confarg/.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconfarg%2Fconfarg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconfarg%2Fconfarg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconfarg%2Fconfarg/lists"}