Remix.run Logo
m463 3 days ago

This kind of stuff is what makes me appreciate python's argparse.

It's a genuine pleasure to use, and I use it often.

If you dig a little deeper into it, it does all the type and value validation, file validation, it does required and mutually exclusive args, it does subargs. And it lets you do special cases of just about anything.

And of course it does the "normal" stuff like short + long args, boolean args, args that are lists, default values, and help strings.

MrJohz 3 days ago | parent [-]

Actually, I think argparse falls into the same trap that the author is talking about. You can define lots of invariants in the parser, and say that these two arguments can't be passed together, or that this argument, if specified, requires these arguments to also be specified, etc. But the end result is a namespace with a bunch of key-value pairs on it, and argparse doesn't play well with typing systems like mypy or pyright. So the rest of the tool has to assume that the invariants were correctly specified up-front.

The result is that you often still this kind of defensive programming, where argparse ensures that an invariant holds, but other functions still check the same invariant later on because they might have been called a different way or just because the developer isn't sure whether everything was checked where they are in the program.

What I think the author is looking for is a combination of argparse and Pydantic, such that when you define a parser using argparse, it automatically creates the relevant Pydantic classes that define the type of the parsed arguments.

bvrmn 3 days ago | parent | next [-]

In general case generating CLI options from app models leads to horrible CLI UX. Opposite is also true. Working with "nice" CLI options as direct app models is horrendous.

You need a boundary to convert nice opts into nice types. Like pydantic models could take argparse namespace and convert it to something manageable.

MrJohz 3 days ago | parent [-]

I mean, that's much the same as working with web APIs or any other kind of interface. Your DTO will probably be different from your internal models. But that doesn't mean it can't contain invariants, or that you can't parse it into a meaningful type. A DTO that's just a grab-bag of optional values is a pain to work with.

Although in practice, I find clap's approach works pretty well: define an object that represents the parsed arguments as you want them, with annotations for details that can't be represented in the type system, and then derive a parser from that. Because Rust has ADTs and other tools for building meaningful types, and because the derive process can do so much. That creates an arguments object that you can quite easily pass to a function which runs the command.

sgarland 3 days ago | parent | prev | next [-]

Precisely my thought. I love argparse, but you can really back yourself into a corner if you aren’t careful.

js2 3 days ago | parent | prev | next [-]

> What I think the author is looking for is a combination of argparse and Pydantic

Not quite that, but https://typer.tiangolo.com/ is fully type driven.

hahn-kev 3 days ago | parent | prev [-]

It's almost like you want compile time type safety

MrJohz 3 days ago | parent [-]

You can have that with Mypy and friends in Python, and Typescript in the JS world. The problem is that older libraries often don't utilise that type safety very well because their API wasn't designed for it.

The library in the original post is essentially a Javascript library, but it's one designed so that if you use it with Typescript, it provides that type safety.