iotile / typedargs

API typechecking and command line shell generation package
GNU Lesser General Public License v3.0
1 stars 2 forks source link

Add support for parsing recursive return value formatters #61

Closed timburke closed 4 years ago

timburke commented 4 years ago

See #60 for the basic context that precedes this issue. There is an additional wrinkle on top of that issue though. Often the case where we most need control over how a return value is formatted is when the return value is a container type and we need to control how each item in the container is formatted.

We already support declaring complex types as composition of simple types, so we should also support "complex formatters" for those types that make reference to formatters for the subtypes.

For example, if we are returning a Dict[User, Property], we should be able to specify a show-as [formatter] string on that return value of:

show as one_line[short_name, default]

This should be interpreted as:

Some caveats:

  1. We should not worry about implementing more than one level of type composition since we do not use that in practice and it could add a lot of complexity to the implementation.

The goal should be that if we return a list of objects, we can control how each object in that list is turned into a string.

dvksirin commented 4 years ago

I've saved some changes for this issue in branch "recursive-parsing-return-value-formatters". Will continue on this after issue #62

dvksirin commented 4 years ago

@timburke I want to clarify to make sure I am on the right way.. For described case, if we have docstring Returns stanza like

Returns:
    Dict[User, Property] show as one_line[short_name, default]: description

We have to look for format_one_line formatter in typedargs.types.basic_dict module, right? I have added a formatter there, you can take a look:

https://github.com/iotile/typedargs/blob/6af2840286605aee18e7281b2a283c779fc5524e/typedargs/types/basic_dict.py#L31

timburke commented 4 years ago

@dvksirin Correct.

Take a look also at the new epic #70 which contains some more details on the intended direction that we want to move toward. The goal is ultimately to have typedargs work basically like function annotations where the annotated class contains all of the information and methods you need to convert it to / from a string or validate it.

Then we would have a legacy mechanism for mapping strings from a docstring to a corresponding class like we do now but eventually we would drop those once there was no longer any code that needed them.

Eventually all of the type extensions would go away since we wouldn't need them anymore.

timburke commented 4 years ago

@dvksirin Some more information on the intended use case here and why its important.

Here's a concrete example.

I have a function that queries AWS and returns the list of IAM groups that exist. There's a Group class for each one. When called from the shell, I want to show the group name for each group but when called programmatically, I want to just return a list of Group classes.

Right now the code is hacked:

    @docannotate
    def list_groups(self):
        """List all groups.

        Returns:
            list(str) show-as string: The list of all groups.
        """

        raw_users = []

        paginator = self._client.get_paginator('list_groups')
        for response in paginator.paginate():
            user_chunk = response.get('Groups', [])
            raw_users.extend(user_chunk)

        #TODO: Once typedargs#61 is resolved, return the actual class here.
        return [x['GroupName'] for x in raw_users]

Ideally I would be able to do:

    @docannotate
    def list_groups(self):
        """List all groups.

        Returns:
            List[Group] show-as unordered(short_name): The list of all groups.
        """

        raw_users = []

        paginator = self._client.get_paginator('list_groups')
        for response in paginator.paginate():
            user_chunk = response.get('Groups', [])
            raw_users.extend(user_chunk)

        return raw_users

The result when calling from a script should be that you just get a normal list of Group objects but when calling interactively from the shell, you get that list printed as:

- group1
- group2
- group3