{"id":19724358,"url":"https://github.com/jungerm2/multiplex","last_synced_at":"2026-06-13T15:36:05.873Z","repository":{"id":83530277,"uuid":"253884772","full_name":"jungerm2/multiplex","owner":"jungerm2","description":"Simple, elegant and composable configurations","archived":false,"fork":false,"pushed_at":"2020-04-30T17:38:47.000Z","size":92,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-10T16:45:45.643Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jungerm2.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-07T18:53:38.000Z","updated_at":"2022-05-10T06:12:17.000Z","dependencies_parsed_at":null,"dependency_job_id":"30d16dd9-39fc-4676-907e-f7e0b1db04ca","html_url":"https://github.com/jungerm2/multiplex","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jungerm2%2Fmultiplex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jungerm2%2Fmultiplex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jungerm2%2Fmultiplex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jungerm2%2Fmultiplex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jungerm2","download_url":"https://codeload.github.com/jungerm2/multiplex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241045662,"owners_count":19899694,"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":[],"created_at":"2024-11-11T23:25:35.779Z","updated_at":"2026-06-13T15:36:00.803Z","avatar_url":"https://github.com/jungerm2.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Multiplex: Simple, elegant and composable configurations.\n\nThis library relies on [argparse](https://docs.python.org/3.8/library/argparse.html) \nand [configurator](https://configurator.readthedocs.io/en/latest/index.html) in order to \nbring you command line interfaces (CLIs) that are dynamically created, with arguments that are \nautomatically parsed and merged with any default configurations the program may have. The result \nis a well-structured config object that can be used in a wide range of applications.\n\n## Simple Use Cases\n\n\u003cdetails\u003e\n\u003csummary\u003eGenerate CLI from config\u003c/summary\u003e\n\u003cp\u003e\n\nIf you already use a configuration object (dict, string, a yaml file or otherwise), \nyou can generate a simple CLI from it. Here's a simple example using flask:\n\n```python\nfrom flask import Flask\nfrom multiplex import Multiplexor\n\napp = Flask(__name__)\n\nm = Multiplexor(dict(app.config))\nargs = m.get_conf()\napp.config.update(args.data)\n```\n\nThis takes flask's default config, exposes a corresponding CLI that \nlet's you override any parameters and then updates these defaults. \nHere's the generated CLI:\n\n```\nmultiplex\\examples\u003e python3 server.py -h\nusage: server.py [-h] [--ENV] [--DEBUG] [--TESTING] [--PROPAGATE_EXCEPTIONS]\n                 [--PRESERVE_CONTEXT_ON_EXCEPTION] [--SECRET_KEY]\n                 [--PERMANENT_SESSION_LIFETIME] [--USE_X_SENDFILE]\n                 [--SERVER_NAME] [--APPLICATION_ROOT] [--SESSION_COOKIE_NAME]\n                 [--SESSION_COOKIE_DOMAIN] [--SESSION_COOKIE_PATH]\n                 [--SESSION_COOKIE_HTTPONLY] [--SESSION_COOKIE_SECURE]\n                 [--SESSION_COOKIE_SAMESITE] [--SESSION_REFRESH_EACH_REQUEST]\n                 [--MAX_CONTENT_LENGTH] [--SEND_FILE_MAX_AGE_DEFAULT]\n                 [--TRAP_BAD_REQUEST_ERRORS] [--TRAP_HTTP_EXCEPTIONS]\n                 [--EXPLAIN_TEMPLATE_LOADING] [--PREFERRED_URL_SCHEME]\n                 [--JSON_AS_ASCII] [--JSON_SORT_KEYS]\n                 [--JSONIFY_PRETTYPRINT_REGULAR] [--JSONIFY_MIMETYPE]\n                 [--TEMPLATES_AUTO_RELOAD] [--MAX_COOKIE_SIZE]\n\noptional arguments:\n  -h, --help            show this help message and exit\n\ndefault parameters:\n  --ENV                 default is 'production'\n  --DEBUG               default is False\n  --TESTING             default is False\n  --PROPAGATE_EXCEPTIONS\n                        default is None\n  --PRESERVE_CONTEXT_ON_EXCEPTION\n                        default is None\n  --SECRET_KEY          default is None\n  --PERMANENT_SESSION_LIFETIME\n                        default is datetime.timedelta(days=31)\n  --USE_X_SENDFILE      default is False\n  --SERVER_NAME         default is None\n  --APPLICATION_ROOT    default is '/'\n  --SESSION_COOKIE_NAME\n                        default is 'session'\n  --SESSION_COOKIE_DOMAIN\n                        default is None\n  --SESSION_COOKIE_PATH\n                        default is None\n  --SESSION_COOKIE_HTTPONLY\n                        default is True\n  --SESSION_COOKIE_SECURE\n                        default is False\n  --SESSION_COOKIE_SAMESITE\n                        default is None\n  --SESSION_REFRESH_EACH_REQUEST\n                        default is True\n  --MAX_CONTENT_LENGTH\n                        default is None\n  --SEND_FILE_MAX_AGE_DEFAULT\n                        default is datetime.timedelta(seconds=43200)\n  --TRAP_BAD_REQUEST_ERRORS\n                        default is None\n  --TRAP_HTTP_EXCEPTIONS\n                        default is False\n  --EXPLAIN_TEMPLATE_LOADING\n                        default is False\n  --PREFERRED_URL_SCHEME\n                        default is 'http'\n  --JSON_AS_ASCII       default is True\n  --JSON_SORT_KEYS      default is True\n  --JSONIFY_PRETTYPRINT_REGULAR\n                        default is False\n  --JSONIFY_MIMETYPE    default is 'application/json'\n  --TEMPLATES_AUTO_RELOAD\n                        default is None\n  --MAX_COOKIE_SIZE     default is 4093\n```\n\nThe `Multiplexor` constructor can take in a path to a config file, \na config object (that subclasses a dictionary), or a string.\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eConvert from Argparse\u003c/summary\u003e\n\u003cp\u003e\n\nIt is very common to parse arguments with python's `argparse` \nand then pass the resulting `Namespace` as a parameter to a \nfunction or class. \n\nHere's a simple example. Say you have a calculator function like so:\n```python\ndef calculator(value1, value2, operation):\n    op = getattr(operator, operation)\n    result = op(value1, value2)\n    print(result)\n```\n\nA typical way to run this as a CLI is to define all argparse arguments \nand run calculator like so:\n\n```python\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description='Simple Calculator CLI')\n    parser.add_argument('operation', type=str, choices=['add', 'sub'],\n                        help='what operation to perform')\n    parser.add_argument('value1', type=float,\n                        help='first value')\n    parser.add_argument('value2', type=float,\n                        help='second value')\n    args = parser.parse_args()\n    calculator(**vars(args))\n```\n\n_Note_: Notice that using getattr on operator might not be safe as the \n`operation` can be anything? This is fine because the `operation` is actually \nrestricted to a few choices by argparse. \n\nWith `multiplex` one can simply create a configuration \nfile containing something like the following:\n\n```yaml\nargparse:\n  parser:\n    description: 'Simple Calculator CLI'\n  arguments:\n    - name_or_flags: operation\n      choices: [add, sub]\n      help: 'what operation to perform'\n    - name_or_flags: value1\n      type: float\n      help: 'first value'\n    - name_or_flags: value2\n      type: float\n      help: 'second value'\n\n```\n\nAnd replace the whole argparse CLI creation with an automated one:\n\n```python\nif __name__ == \"__main__\":\n    from multiplex import Multiplexor\n\n    m = Multiplexor('calculator.yaml')\n    args = m.get_conf()\n    calculator(**args.data)\n```\n\nWith this in place, it is now much easier to extend the functionality of \nyour calculator application, as any config changes will be mirrored in the CLI.\nTo add more operations, like say, multiplication and modulus, we can simply\nchange one line in `calculator.yaml`:\n\n```yaml\nchoices: [add, sub, mul, mod]\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNested configuration files\u003c/summary\u003e\n\u003cp\u003e\nMore to come!\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMultiple sub-programs\u003c/summary\u003e\n\u003cp\u003e\nMore to come!\n\u003c/p\u003e\n\u003c/details\u003e\n\n\nYou can view these examples in the [examples](examples) directory.  \n\n## Changelog\n* April 28, 2020:\n    * Add support for nested config files\n    * For nested configs of depth \u003e 2, the argument resolution order is as follows: File first, Folder Next\n* April 26, 2020:\n    * Infer config filename based on `__file__` for simple cases.\n    * Add `parse_args` and `execute` which enable top-level app to \n    dispatch arguments and execute subprograms dynamically.\n* April 23, 2020:\n    * Add outline of how to perform subprogram dispatching in an [mnist example](examples/mnist/mnist.py).\n    It seems to work as a proof of concept. We need to perform this dynamically based on a config. This \n    method relies heavily on `parse_known_args` to simulate dynamic dispatching, as well \n    as dynamic package imports based on `importlib` (standard library) and registration \n    decorators defines in [utils](multiplex/utils.py).\n    * Move argparse parser creation logic to [`ArgparseEngine`](multiplex/engines.py).\n    * Add capabilities to create custom parser object as well, see the new calculator example.\n* April 22, 2020: \n    * Add basic `setup.py` to enable local install of package. \n    To do this, run `pip3 install -e path/to/multiplex`. \n    * Change the examples accordingly, they now import from `multiplex` directly. \n    * Add `__init__.py` to enable simplified imports\n(i.e: `from multiplex.parser import Multiplexor` is now `from multiplex import Multiplexor`) \n* April 22, 2020: Added support for running subprograms entered through command line using eval, added a simple example program sample_ML_program.py\n* April 16, 2020: Added flask example.\n* April 14, 2020: Refactored codebase into `multiplex/config` and `multiplex/parser`, added calculator example.\n\n## TODO:\n\n- [ ] Remove subprogram max depth\n- [ ] Add default argument resolution and parsing when using subprograms\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjungerm2%2Fmultiplex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjungerm2%2Fmultiplex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjungerm2%2Fmultiplex/lists"}