clize icon indicating copy to clipboard operation
clize copied to clipboard

Use `Annotated` to add aliases to options

Open richieadler opened this issue 2 years ago • 4 comments

The documentation gives an example of how to create aliases:

def echo(*text:Parameter.REQUIRED,
         prefix:'p'='', suffix:'s'='', reverse:'r'=False, repeat:'n'=1):
    ...

This, however, causes most linters to warn:

Unresolved reference 'p'

for all the aliases thus defined.

Since Python 3.9, typing.Annotated is available to add metadata to annotations, so this could be defined as

def echo(*text:Parameter.REQUIRED,
         prefix: Annotated[str, 'p'] ='', 
         suffix: Annotated[str, 's'] = '', 
         reverse: Annotated[str, 'r'] = False, 
         repeat: Annotated[int, 'n'] = 1,
):
    ...

and linters wouldn't complain.

richieadler avatar Jul 05 '23 20:07 richieadler

Hello!

Currently there is rudimentary support for using typing.Annotated in 5.0.0. It is not in the documentation, but there is an example that uses it: https://github.com/epsy/clize/blob/v5.0.0/examples/typed_cli.py

You have to wrap parts of the annotation meant for Clize with Clize[], for example your example would read:

def echo(*text:Parameter.REQUIRED,
         prefix: Annotated[str, Clize['p']] ='', 
         suffix: Annotated[str, Clize['s']] = '', 
         reverse: Annotated[bool, Clize['r']] = False, 
         repeat: Annotated[int, Clize['n']] = 1,
):
    ...

There's room to make this better, but this is a start. Let me know if you have feedback or ideas.

epsy avatar Jul 06 '23 04:07 epsy

Is Clize needed? Can't just be a Literal?

richieadler avatar Jul 12 '23 20:07 richieadler

It is kind of needed for me to require it, yes.

See PEP 593 which introduces typings.Annotated, which says:

Ultimately, the responsibility of how to interpret the annotations (if at all) is the responsibility of the tool or library encountering the Annotated type. A tool or library encountering an Annotated type can scan through the annotations to determine if they are of interest (e.g., using isinstance()).

and then later:

Namespaces are not needed for annotations since the class used by the annotations acts as a namespace.

So it seems like the PEP expects library authors to distinguish different annotations by their type, so unfortunately str would be too ambiguous.

I hear you on this being very verbose. The PEP has a suggestion on the subject:

T = TypeVar('T')
Const = Annotated[T, my_annotations.CONST]

class C:
    def const_method(self: Const[List[int]]) -> int:

Perhaps in the future it could be applied like:

ValueType = TypeVar("ValueType")
ClizeAnnotationsT = TypeVar("ClizeAnnotationsT")
TypeAndClize = typings.Annotated[T, ClizeAnnotationsT]

def main(prefix: TypeAndClize[str, "p"]):
    ...

Though I think Clize may have to be updated to understand TypeVar/ClizeAnnotationsT

epsy avatar Jul 17 '23 02:07 epsy

Though I think Clize may have to be updated to understand TypeVar/ClizeAnnotationsT

Agreed. And we may need to say something like

def main(a: str, b: Annotated[int, Parameter.REQUIRED]):
   ...

I don't know how to handle this case without Annotated...

richieadler avatar Jul 19 '23 16:07 richieadler