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

THRIFT-5139/THRIFT-4181: Python3 type hints #2929

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

arkuhn
Copy link

@arkuhn arkuhn commented Feb 16, 2024

Add type hinting and native enums (IntEnum) for python generated code

  1. Squashed and rebased changes from stale PR
  2. Removed the controversial runtime type check as it goes beyond strictly annotations
  3. Added type_hints as a default option to RunClientServer.py and generate.cmake.

Note: Appveyor x86 / Python 3.5 build is failing since this PR generates variable type hints (x: int = 1), which are only python 3.6 and up. Do we have any patterns established for skipping tests based on platform/python version?

Example difference in generated code after running thrift --gen py:type_hints against:

struct OneOfEachZZ {
  1: required bool im_true,
  2: bool im_false,
  3: byte a_bite,
  4: i16 integer16,
  5: i32 integer32,
  6: i64 integer64,
  7: double double_precision,
  8: string some_characters,
  9: string zomg_unicode,
  10: optional bool what_who
}

service UnderscoreSrv {
  i64 some_rpc_call(1: string message, 2: OneOfEachZZ sample_struct)
}
ttypes.py

root@37314fef4537:/thrift/src/test# diff gen-py/FullCamelTest/ttypes.py gen-py-old/FullCamelTest/ttypes.py 
6c6
< #  options string: py
---
> #  options string: py:type_hints
8a9,10
> from __future__ import annotations
> import typing
12a15
> from enum import IntEnum
37,47c40,50
<     def __init__(self, im_true = None, im_false = None, a_bite = None, integer16 = None, integer32 = None, integer64 = None, double_precision = None, some_characters = None, zomg_unicode = None, what_who = None,):
<         self.im_true = im_true
<         self.im_false = im_false
<         self.a_bite = a_bite
<         self.integer16 = integer16
<         self.integer32 = integer32
<         self.integer64 = integer64
<         self.double_precision = double_precision
<         self.some_characters = some_characters
<         self.zomg_unicode = zomg_unicode
<         self.what_who = what_who
---
>     def __init__(self, im_true: bool = None, im_false: typing.Optional[bool] = None, a_bite: typing.Optional[int] = None, integer16: typing.Optional[int] = None, integer32: typing.Optional[int] = None, integer64: typing.Optional[int] = None, double_precision: typing.Optional[float] = None, some_characters: typing.Optional[str] = None, zomg_unicode: typing.Optional[str] = None, what_who: typing.Optional[bool] = None,):
>         self.im_true: bool = im_true
>         self.im_false: typing.Optional[bool] = im_false
>         self.a_bite: typing.Optional[int] = a_bite
>         self.integer16: typing.Optional[int] = integer16
>         self.integer32: typing.Optional[int] = integer32
>         self.integer64: typing.Optional[int] = integer64
>         self.double_precision: typing.Optional[float] = double_precision
>         self.some_characters: typing.Optional[str] = some_characters
>         self.zomg_unicode: typing.Optional[str] = zomg_unicode
>         self.what_who: typing.Optional[bool] = what_who

UnderscoreSrv.py

root@37314fef4537:/thrift/src/test# diff gen-py/FullCamelTest/UnderscoreSrv.py gen-py-old/FullCamelTest/UnderscoreSrv.py 
6c6
< #  options string: py
---
> #  options string: py:type_hints
8a9,10
> from __future__ import annotations
> import typing
12a15
> from enum import IntEnum
23c26
<     def some_rpc_call(self, message, sample_struct):
---
>     def some_rpc_call(self, message: str, sample_struct: OneOfEachZZ) -> int:
40c43
<     def some_rpc_call(self, message, sample_struct):
---
>     def some_rpc_call(self, message: str, sample_struct: OneOfEachZZ) -> int:
50c53
<     def send_some_rpc_call(self, message, sample_struct):
---
>     def send_some_rpc_call(self, message: str, sample_struct: OneOfEachZZ):
59c62
<     def recv_some_rpc_call(self):
---
>     def recv_some_rpc_call(self) -> int:
137,139c140,142
<     def __init__(self, message = None, sample_struct = None,):
<         self.message = message
<         self.sample_struct = sample_struct
---
>     def __init__(self, message: typing.Optional[str] = None, sample_struct: typing.Optional[OneOfEachZZ] = None,):
>         self.message: typing.Optional[str] = message
>         self.sample_struct: typing.Optional[OneOfEachZZ] = sample_struct
212,213c215,216
<     def __init__(self, success = None,):
<         self.success = success
---
>     def __init__(self, success: typing.Optional[int] = None,):
>         self.success: typing.Optional[int] = success

Unrelated to these changes, I noticed that even if a field is marked as required, declare_argument generates a default value in the function arguments of None.

Open questions: Since the original PR opened,

  • Did you create an Apache Jira ticket? (Request account here, not required for trivial changes)
  • If a ticket exists: Does your pull request title follow the pattern "THRIFT-NNNN: describe my issue"?
  • Did you squash your changes to a single commit? (not required, but preferred)
  • Did you do your best to avoid breaking changes? If one was needed, did you label the Jira ticket with "Breaking-Change"?
  • If your change does not involve any code, include [skip ci] anywhere in the commit message to free up build resources.

@arkuhn arkuhn force-pushed the akuhn/python_3_type_hints branch 4 times, most recently from c14eb9f to f30be35 Compare February 16, 2024 19:30
@arkuhn arkuhn marked this pull request as ready for review February 16, 2024 19:31
@arkuhn arkuhn marked this pull request as draft February 17, 2024 21:08
@Jens-G Jens-G added the python label Feb 19, 2024
args not optional type hint

UUID generator support

Remove runtime type check
@arkuhn arkuhn marked this pull request as ready for review February 24, 2024 01:59
@salomon-smekecohen
Copy link

It would be great to have this, it would almost enable transpilation via mypyc. I believe the only missing piece would be to add to all structures:

thrift_spec: Any

gen-py-type_hints/%/__init__.py: ../%.thrift $(THRIFT)
test -d gen-py-type_hints || $(MKDIR_P) gen-py-type_hints
test ../v0.16/$(notdir $<) \
&& $(THRIFT) --gen py:type_hints -out gen-py-type_hints ../v0.16/$(notdir $<) \
Copy link
Member

@Jens-G Jens-G May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 LGTM just one question: UUIDs are handled (at least from what i see above) but you still want to test against the old uuid-free v0.16 version of IDL files?

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