Coder Social home page Coder Social logo

clidantic's Introduction

Hi there, I'm Edoardo. ๐Ÿ‘‹

Currently a researcher @ LINKS Foundation and PhD student @ PoliTo.

  • ๐Ÿ”ญ Learning and working on Computer Vision (mainly) applied to aerial and satellite imagery.

  • โœจ Passionate about computer graphics and game development.

  • ๐Ÿ“ซ edoardo.arnaudo[at]polito.it

clidantic's People

Contributors

edornd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

gusutabopb

clidantic's Issues

Investigate dynamic model creation for multiple input args in commands

Is your feature request related to a problem? Please describe.
Currently, clidantic commands only accept one argument, the single configuration.
This is not an issue per se, the model must be a single entity, however for multiple configs it will require the user to combine every sub-model into a wrapper model to work with clidantic.

Describe the solution you'd like
We should be able to pass multiple models as command inputs (and possibly primitives, to be evaluated), automatically wrapping them into a single entity at runtime.

Describe alternatives you've considered
Just keep it as it is if it becomes cumbersome, by simply informing the user to wrap each model inside a single one.

Additional context
See https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation

Type of basemodel is evaluated as 'str' when using `from __future__ import annotations`

Describe the bug
from __future__ import annotations is a nice thing for many reasons, especially when using MyPy. See PEP 563: https://mypy.readthedocs.io/en/stable/runtime_troubles.html

But it causes an issue with this library. The library expects a subclass of BaseModel, but with this change above, types are evaluated to strings automatically and need to be eval()'ed to unwrap them for appropriate discovery.

To Reproduce
Steps to reproduce the behavior:

  1. Go to test_types.py
  2. Add line to top of file: from __future__ import annotations
  3. Run poetry run pytest -s
  4. See error AssertionError: Configuration must be a pydantic model

Expected behavior
I should be able to use from __future__ import annotations

Screenshots
N/A

Desktop (please complete the following information):

  • OS: Macos
  • Python Version 3.10
  • Library Version 0.0.0

Additional context
This is fairly easily resolved by changing this line to be

func_arguments = inspect.signature(f, eval_str = True).parameters

I am happy to submit a PR if you agree. Here's the relevant discussion between Larry, Guido and others about this feature: https://bugs.python.org/issue43817

Support additional parameter names (single-dash, etc)

It'd be great to be able to use single-dash parameter names (as well as alternative names) as natively supported by click/argparse. Here's an example from click's documentation:

@click.command()
@click.option('-s', '--string-to-echo', 'string')
def echo(string):
    click.echo(string)

In clidantic, this would mean being able to achieve something like:

Usage: mycli.py [OPTIONS]

Options:
  -n, --name, --old-name TEXT  [required]
  -c, --count INTEGER          [required]
  --help                       Show this message and exit.

Given a model such as:

class Config(BaseModel):
    name: str
    count: int

Implementation idea

I'm not sure this is an approach you'd like, but it can be achieved by exploiting pydantic's FieldInfo's extra attribute to attach arbitrary metadata to the field:

class Config(BaseModel):
    name: str = Field(cli={"names": ("-n", "--old-name")})
    count: int = Field(cli={"names": ("-c",)})

Besides some type annotation changes (Tuple[str, str] to Tuple[str, ...]), this can be achieved by changing just two lines in param_from_field

def param_from_field(
     # example.test-attribute
     base_option_name = delimiter.join(parent_path + (kebab_name,))
     full_option_name = f"--{base_option_name}"
+    extra_names = field.field_info.extra.get("cli", {}).get("names", ())
     # Early out of non-boolean fields
     if field.outer_type_ is bool:
         full_disable_flag = delimiter.join(parent_path + (f"no-{kebab_name}",))
         full_option_name += f"/--{full_disable_flag}"
     # example.test-attribute -> example__test_attribute
     identifier = base_option_name.replace(delimiter, internal_delimiter).replace("-", "_")
-    return identifier, full_option_name
+    return identifier, full_option_name, *extra_names

Here's a full working example: gusutabopb@67847df

Further extension

The cli dictionary in FieldInfo.extra can also be expanded in the future to allow users further customizing how to map Pydantic fields to CLI parameters (in ways not covered by Pydantic's native fields). In that case, it might be a good idea to make that a Pydantic model for validation purposes (but that's beyond the scope of this issue).

PS: Thanks for the great library!

Literal type not supported

When the type declared is literal, the library crashes.

Example:


from pydantic import Field

from src.config.base import EnvConfig


class RunConfig(EnvConfig):
    input_source: str = Field(description="Path of the file or streaming link")
    input_type: Literal['image', 'video',
                        'stream'] = Field('video', description="Select which type of media needs to be processed")

Produces the following output:
literal_error

Missing description in commands

Click allows description strings for commands as well.
They are derived from the docstrings of the decorated function, see https://click.palletsprojects.com/en/8.1.x/documentation/.

This should be as straightforward as taking the docstring during the command construction and pass it along the other options.
Ideally, this:

@click.command()
@click.option('--count', default=1, help='number of greetings')
@click.argument('name')
def hello(count, name):
    """This script prints hello NAME COUNT times."""
    for x in range(count):
        click.echo(f"Hello {name}!")

Should translate into this:

class Config(BaseModel):
    name: str = Field(description="name to print")
    count: int = Field(1, description="number of greetings")

@cli.command()
def hello(config: Config):
    """This script prints hello NAME COUNT times."""
    for x in range(config.count):
        click.echo(f"Hello {config.name}!")

Whil producing comparable descriptions:

$ hello --help
Usage: hello [OPTIONS]

  This script prints hello NAME COUNT times.

Options:
  --name TEXT        name to print
  --count INTEGER  number of greetings
  --help           Show this message and exit.

Custom length for help texts

Since click's character limit is set to 80 by default, allow for a customizable help text length.
See pallets/click#2253 for a quick solution, the main CLI simply requires the correct content width.
Also, consider increasing the default to higher values (e.g., 119).

Clidantic gets extra argument when only one command is set

When having a main file with only one command, this is not subgrouped by default, and if the user tries to use it as subcommand, it gets an error which is not self explainatory.

Example:

from pydantic import BaseModel

from clidantic import Parser

class Arguments(BaseModel):
    field_a: str
    field_b: int
    field_c: Optional[bool] = False

cli = Parser()


@cli.command()
def run(args: Arguments):
    print(args)


if __name__ == "__main__":
    cli()

when the following command is run:

python3 main.py run a=hola b=1337

the following error is returned:

Usage: main.py [OPTIONS]
Try 'main.py --help' for help.

Error: Got unexpected extra argument (run)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.