{"action":"created","issue":{"url":"https://api.github.com/repos/ipython/traitlets/issues/322","repository_url":"https://api.github.com/repos/ipython/traitlets","labels_url":"https://api.github.com/repos/ipython/traitlets/issues/322/labels{/name}","comments_url":"https://api.github.com/repos/ipython/traitlets/issues/322/comments","events_url":"https://api.github.com/repos/ipython/traitlets/issues/322/events","html_url":"https://github.com/ipython/traitlets/pull/322","id":178648579,"number":322,"title":"Config container-traits from command-line using *argparse* syntax","user":{"login":"ankostis","id":501585,"avatar_url":"https://avatars.githubusercontent.com/u/501585?v=3","gravatar_id":"","url":"https://api.github.com/users/ankostis","html_url":"https://github.com/ankostis","followers_url":"https://api.github.com/users/ankostis/followers","following_url":"https://api.github.com/users/ankostis/following{/other_user}","gists_url":"https://api.github.com/users/ankostis/gists{/gist_id}","starred_url":"https://api.github.com/users/ankostis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ankostis/subscriptions","organizations_url":"https://api.github.com/users/ankostis/orgs","repos_url":"https://api.github.com/users/ankostis/repos","events_url":"https://api.github.com/users/ankostis/events{/privacy}","received_events_url":"https://api.github.com/users/ankostis/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2016-09-22T15:53:01Z","updated_at":"2016-09-23T09:06:57Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/ipython/traitlets/pulls/322","html_url":"https://github.com/ipython/traitlets/pull/322","diff_url":"https://github.com/ipython/traitlets/pull/322.diff","patch_url":"https://github.com/ipython/traitlets/pull/322.patch"},"body":"NOT READY, pushed to get TravisCI and PR id!!\r\n\r\n---\r\n\r\n(extracted as a new proposal from #180)\r\n\r\nThe idea is to  scan all conf-classes to collect container-traits and append any matching aliases into `ArgParseConfigLoader` either with action='append' or nargs='*'`.\r\n\r\nLet's start with aliases, where *argparse* lib is already used, and not for all config options (for flags this proposal does not make sense).\r\n\r\n## Proposal\r\nI'm drafting a plan, to see if I have a solid understanding:\r\n\r\n1. The `ArgParseConfigLoader` constructor needs at first the list of user-specified configurable classes;\r\n2. The `Application` on `parse_command_line()` uses its `classes` field to collects all relevant `HasTraits` classes and subclasses and pushes them into the `KVArgParseConfigLoader` while creating it;\r\n3. For every alias, the `KVArgParseConfigLoader._add_arguments()` searched first in the collected classes if they contain a container-trait, and if so, appends into `ArgumentParser` an argument dependning on any `multiplicity` metadata found on the container-trait:\r\n\r\n    1. `multiplicity == 'append'`: use `add_argument(..., action='append')` (default if metadata missing)\r\n    2.  `multiplicity == 'nargs'`: use `add_argument(..., nargs='*')`.\r\n   \r\n4. The `KVArgParseConfigLoader._convert_to_config()` is handles those specific args that should be list-of-values.\r\n\r\n## Example\r\nAssuming this 2 configurables + 1 app:\r\n\r\n```python\r\nclass Base(HasTraits): \r\n    a = List([])\r\n\r\nclass Sub(Base): \r\n  pass\r\n\r\nclass Cmd(Application):\r\n    classes = [Sub]  ## Note on #289: frequently list given like that.\r\n    aliases\r\n```\r\n\r\nThen, in command-line it should be possible to enter all 3 commands:\r\n\r\n```\r\ncmd  --Base.a=1 --Base.a=foo  ## Base.a = [1, 'foo']\r\ncmd  --Sub.a=1  --Sub.a=foo   ## Sub.a  = [1, 'foo']\r\ncmd  --Base.a=1  --Sub.a=foo  ## Base.a = [1], Sub.a = ['foo']\r\n```\r\n* Notice that in the last one, `Sub` class trumps over any `Base` values.\r\n\r\nAncmd-line would be like that:\r\n\r\n    foocmd   --alias1=1  --alias1=ab --alias`='[\"dfffg\", 3]'   --alias2 a 2 c\r\n\r\nand the collected config tuples/list (depending on the trait) to be\r\n\r\n- alias1: `(1, \"ab\", [\"dfffg\", 3])`\r\n- alias2: `(\"a\", \"b\", \"c\")`\r\n\r\n\r\n## Future\r\nExpanded the above mini-spec to support also populating `Dict` traits:\r\n\r\n    foocmd   -D key1=v1  -D key2=2"},"comment":{"url":"https://api.github.com/repos/ipython/traitlets/issues/comments/249141111","html_url":"https://github.com/ipython/traitlets/pull/322#issuecomment-249141111","issue_url":"https://api.github.com/repos/ipython/traitlets/issues/322","id":249141111,"user":{"login":"ankostis","id":501585,"avatar_url":"https://avatars.githubusercontent.com/u/501585?v=3","gravatar_id":"","url":"https://api.github.com/users/ankostis","html_url":"https://github.com/ankostis","followers_url":"https://api.github.com/users/ankostis/followers","following_url":"https://api.github.com/users/ankostis/following{/other_user}","gists_url":"https://api.github.com/users/ankostis/gists{/gist_id}","starred_url":"https://api.github.com/users/ankostis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ankostis/subscriptions","organizations_url":"https://api.github.com/users/ankostis/orgs","repos_url":"https://api.github.com/users/ankostis/repos","events_url":"https://api.github.com/users/ankostis/events{/privacy}","received_events_url":"https://api.github.com/users/ankostis/received_events","type":"User","site_admin":false},"created_at":"2016-09-23T09:06:57Z","updated_at":"2016-09-23T09:06:57Z","body":"@minrk At [`traitlets/traitlests.py#1378`](https://github.com/ipython/traitlets/blob/master/traitlets/traitlets.py#L1378) it tests whether a variable can be called; why is it using `type(var) is types.FunctionType` instead of `iscallable(var)`?\r\n\r\nThe same question applies for [all such tests](http://stackoverflow.com/questions/624926/how-to-detect-whether-a-python-variable-is-a-function)."}}
{"actor":{"display_login":"ankostis"}}