-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate argparse
CLI definition to a pydantic
basis for most important commands
#438
Comments
We (@zz1874, I) wrote a hacky demo script that uses a CLI based on |
Going forward, I (this has not been discussed with neither @nsheff nor @zz1874) propose to organize the At the core, we introduce a
We then create a data structure (a tuple, or an enum) that holds an |
Given that we're running into issues with packages that don't support pydantic 2.0 (see here for example: pepkit/pipestat#127), and that pydantic-argparse appears to be at a dead-end, unresponsive as to whether pydantic 2.0 will ever be supported (SupImDos/pydantic-argparse#48), we might want to rethink using pydantic-argparse, and instead, it might make more sense to just roll our own argparser from the ttps://stackoverflow.com/questions/72741663/argument-parser-from-a-pydantic-model |
See also: https://github.com/edornd/argdantic |
I just ran into this issue with pydantic > 2.0.0 support. Recently, we forced requirement for pephubclient >=0.4.0 ( see #453, 1c8a8ad). As I continue working on migrating the arguments to the pydantic models, I am now running into dependency issues because pephubclient requires pydantic greater than 2.5.0 but pydantic-argparse depends on pydantic<2.0.0:
Unfortunately, I've gotten quite far in finishing the migration before realizing this issue (I realized it when I merged some downstream changes upstream). |
There is a fork of pydantic-argparse that uses pydantic2: https://github.com/anastasds/pydantic2-argparse I was able to implement it here and resolve dependency issues: Interestingly, the fork (and now my looper implementation) requires calling the still available pydantic.v1 api:
|
Another issue as I rework the tests: For arguments that can be given a list (e.g. selecting or excluding based on multiple flags), the argparser cannot seem to handle a list:
I've tried different permutations of the syntax as well as changing the Field type but am still striking out. |
@donaldcampbelljr interesting issue! I investigated a bit and the problem seems to be that from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--somelist", nargs="+")
parser.add_argument("-v", "-voo")
subparsers = parser.add_subparsers(title="subcommands")
cmd_parser = subparsers.add_parser("cmd")
cmd_parser.add_argument("--somestr", type=str)
args = parser.parse_args() yields $ python argtest.py --somelist a b cmd --somestr "Asdf"
usage: argtest.py [-h] [--somelist SOMELIST [SOMELIST ...]] [-v V] {cmd} ...
argtest.py: error: argument {cmd}: invalid choice: 'Asdf' (choose from 'cmd') One way to "fix" ( 🙄 ) this is to use a non-list-valued argument before the subcommand, for example: $ python argtest.py --somelist a b -v bla cmd --somestr "Asdf" which parses correctly. |
@simeoncarstens Interesting! Thank you for taking a look. I was also digging into it this morning. I noticed that the compute argument (also a list) does appear to be working,but it is added to the Run parser and, thus, comes after the run command:
this also works (moving the --dry-run after the list):
I was thinking that one workaround would be to add all the shared arguments to the individual commands so that the list arguments can be used after the command parser. Not ideal but it might be necessary for this setup. |
So, I tried out my proposed suggestion here: 9fdf85a It does seem to work. However, the position of the arguments (do they come before or after the command?) is still a bit opaque, e.g.:
I think I will proceed to place all of the arguments such that they come after the command. This will make the syntax similar to the previous CLI such that it will begin with Is there any reason we should not structure it in this way? We will will have copies of the shared arguments among the parsers but I believe that is the only downside. |
Another option is to monkey-patch the def parse_field(
parser: argparse.ArgumentParser,
field: pydantic.fields.ModelField,
) -> Optional[utils.pydantic.PydanticValidator]:
"""Adds container pydantic field to argument parser.
Args:
parser (argparse.ArgumentParser): Argument parser to add to.
field (pydantic.fields.ModelField): Field to be added to parser.
Returns:
Optional[utils.pydantic.PydanticValidator]: Possible validator method.
"""
import pydantic2_argparse.utils as utils
class SplitArgs(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values.split(','))
# Add Container Field
parser.add_argument(
utils.arguments.name(field),
action=SplitArgs,
help=utils.arguments.description(field),
dest=field.alias,
metavar=field.alias.upper(),
required=bool(field.required),
)
# Construct and Return Validator
return utils.pydantic.as_validator(field, lambda v: v)
pydantic2_argparse.argparse.parser.parsers.container.parse_field = parse_field This is ugly, but would have the advantage that the actual command / argument hierarchy is maintained. If we do this, then of course the mandatory comma separation (e.g. |
Putting the majority of shared arguments after the subcommand would also be possible, of course. As you say, there's then a duplication of arguments across various subcommands. But then again, it might even be preferred if you had a good reason to do so for the existing CLI. Note that then some of our changes would need to be reversed. In the end, I think it's as you and the users prefer 🙂 |
Removing This was brought up previously in a PR: But to reiterate here: We will hold on releasing the next version of Looper (1.8.0) until we incorporate the short-form arguments. |
It appears that I believe it was mentioned in a meeting that we decided not to use clipstick for some reason, but I cannot seem to track down my notes on why that was decided. |
I'm sorry about the late realization that short arguments are not supported by
That doesn't exactly inspire confidence with me, but that's just opinionated on my side. |
Yes, clipstick doesn't use argparse, which I considered a fatal downside, since we use argparse in all our other projects so we're familiar with it. |
Ok, with the latest PR I've added the short arguments. Marking this as likely solved. |
As described in #433, we would like to add a HTTP API for
looper
. The challenge here is to keep HTTP API and CLI in sync, and in a call, @nsheff, @zz1874 and I decided to address this by replacing theargparse
-based CLI command / argument definition with a definition based onpydantic
models: several libraries (for example,pydantic-argparse
,tyro
andclipstick
) allow to build CLI parsers with type checking frompydantic.BaseModel
definitions.So we agreed that our first task towards a HTTP API for
looper
is to redefine import CLI commands (looper {run,runp,check}
) viapydantic
models. Building an HTTP API that consumes data compliant with these models is then straightforward.For now, we assume we will be using
pydantic-argparse
, and, as agreed with @nsheff, will buildpydantic
models that are compatible with it. This issue and comments below track the progress of this task.The text was updated successfully, but these errors were encountered: