Skip to content
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

Typechecking fails #1

Open
daa opened this issue Apr 17, 2020 · 7 comments
Open

Typechecking fails #1

daa opened this issue Apr 17, 2020 · 7 comments

Comments

@daa
Copy link

daa commented Apr 17, 2020

Hi! I saw your package on Reddit, was interested, tried to check it and found that it does not pass type checking with mypy:

  1. It is not considered as being typed - py.typed is missing:
$ ./bin/mypy example.py 
example.py:5: error: Skipping analyzing 'apix': found module but no type hints or library stubs
example.py:5: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
Found 1 error in 1 file (checked 1 source file)
  1. If I add py.typed manually to mark that apix is typed module, example from README doesn't pass type check:
$ ./bin/mypy example.py 
example.py:31: error: Incompatible default for argument "paging" (default has type "Query", argument has type "Paging")
Found 1 error in 1 file (checked 1 source file)
  1. apix itself fails to pass type check:
$ ./bin/mypy apix
apix/utils.py:65: error: Cannot resolve name "NestedArgs" (possible cyclic definition)
apix/utils.py:77: error: Need type annotation for 'flattened_args'
apix/dependencies/utils.py:139: error: "FieldInfo" has no attribute "in_"
apix/dependencies/utils.py:243: error: Need type annotation for 'errors' (hint: "errors: List[<type>] = ...")
apix/dependencies/utils.py:249: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "get"
apix/endpoint.py:22: error: Name 'Service' is not defined
apix/endpoint.py:77: error: Item "None" of "Optional[Signature]" has no attribute "bind"
apix/endpoint.py:96: error: Argument 1 to "dict" has incompatible type "List[Tuple[str, Union[str, int, float, bool, None]]]"; expected "Iterable[Tuple[str, str]]"
apix/endpoint.py:101: error: Item "None" of "Optional[Signature]" has no attribute "return_annotation"
apix/service.py:38: error: Incompatible types in assignment (expression has type "Client", variable has type "AsyncClient")
Found 10 errors in 4 files (checked 10 source files)

3a. It's better to check in strict mode and then mypy gives 160 type errors for apix

Overall I like an idea of typed http client, also I see similarities with uplink's approach which I personally like too, but I have to say that apix is far from being typed - type annotations without type checking are not very useful.

@songzhi
Copy link
Owner

songzhi commented Apr 18, 2020

Thanks for your feedback, I think the internal type errors of apix are fixable. But I'm not sure that the api definition likedef foo(item: Item = Body(..)): will work fine with typechecking tools.I'll try to work it out.Or can you give me some advices?Like how to work around to make item: Item = Body(..) possible.

By the way, It's the first time I heard of uplink 😂, maybe I won't make this project if I heard it before.But I saw a lot of differences between them. So maybe I'll implement all features of uplink if I have the ablility.

@daa
Copy link
Author

daa commented Apr 20, 2020

def foo(item: Item = Body(..)):

I'm affraid this can't pass type check without special support from typechecker - Body() value cannot be member of all possible types. If you look at similarly looking dataclasses or attrs definitions you'll find that they pass type check with the help of mypy plugin only. Also such abuse of default values of keyword arguments presents 2 problems:

  • it prevents from defining actual default values for parameter
  • it makes impossible to use positional arguments

I see that fastapi uses this approach but with regard to mentioned problems they are in another situation - their functions are callbacks called by the framework but not directly by the user of the library which is the case for http client. Also I'm not sure if their code from examples showing how to use Body and Query default values will pass type check, maybe pydantic plugin for mypy will help.
To my mind the most clean way to define placement of function arguments inside of request and to keep type checking possible is to use decorators similarly to uplink.args() or define an args argument for get() decorator - https://uplink.readthedocs.io/en/stable/user/tips.html#using-uplink-args, https://uplink.readthedocs.io/en/stable/dev/decorators.html#args .

@songzhi
Copy link
Owner

songzhi commented Apr 20, 2020

In fact I found a way to let Body(...) pass type check, which is let it inherits Any. But maybe it's even not a 'way'.
Here's another solution:

class Query:
    pass

def foo(bar: Query[int, str](...) = 'bar'):
    ...
# translated to
def foo(bar: _GenericAlias['Query', (int, str)] = 'bar'):
    ...

Which means, Query,Body... just behave like Union.Then type checks pass and we have runtime type checking, and "real" default value.

@daa
Copy link
Author

daa commented Apr 20, 2020

I like the second solution, it's great that you figured out how to provide required information with types without compromising their original purpose. This approach may be not the most obvious at first glance but seems to be clear and I'd like to see if it will work.

@Imaclean74
Copy link

btw - I see that uplink has a long standing issue request to support / leverage Pydantic.

prkumar/uplink#146

@songzhi
Copy link
Owner

songzhi commented Jun 4, 2020

@Imaclean74 I found that typical is a better choice,

@anentropic
Copy link

I like the design of apix 👍

I think you need to add typical to your pyproject.toml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants