Merge branch 'master_gh'
This commit is contained in:
		| @@ -66,8 +66,7 @@ if sys.version_info >= (3, 10): | ||||
|     from types import UnionType as _types_UnionType | ||||
| else: | ||||
|  | ||||
|     class _types_UnionType: | ||||
|         ... | ||||
|     class _types_UnionType: ... | ||||
|  | ||||
|  | ||||
| # Proto 3 data types | ||||
| @@ -2014,10 +2013,10 @@ class _Timestamp(Timestamp): | ||||
|             return f"{result}Z" | ||||
|         if (nanos % 1e6) == 0: | ||||
|             # Serialize 3 fractional digits. | ||||
|             return f"{result}.{int(nanos // 1e6) :03d}Z" | ||||
|             return f"{result}.{int(nanos // 1e6):03d}Z" | ||||
|         if (nanos % 1e3) == 0: | ||||
|             # Serialize 6 fractional digits. | ||||
|             return f"{result}.{int(nanos // 1e3) :06d}Z" | ||||
|             return f"{result}.{int(nanos // 1e3):06d}Z" | ||||
|         # Serialize 9 fractional digits. | ||||
|         return f"{result}.{nanos:09d}" | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import sys | ||||
| from enum import ( | ||||
|     EnumMeta, | ||||
|     IntEnum, | ||||
| @@ -90,15 +89,8 @@ class EnumType(EnumMeta if TYPE_CHECKING else type): | ||||
|         def __iter__(cls) -> Generator[Enum, None, None]: | ||||
|             yield from cls._member_map_.values() | ||||
|  | ||||
|         if sys.version_info >= (3, 8):  # 3.8 added __reversed__ to dict_values | ||||
|  | ||||
|             def __reversed__(cls) -> Generator[Enum, None, None]: | ||||
|                 yield from reversed(cls._member_map_.values()) | ||||
|  | ||||
|         else: | ||||
|  | ||||
|             def __reversed__(cls) -> Generator[Enum, None, None]: | ||||
|                 yield from reversed(tuple(cls._member_map_.values())) | ||||
|         def __reversed__(cls) -> Generator[Enum, None, None]: | ||||
|             yield from reversed(cls._member_map_.values()) | ||||
|  | ||||
|         def __getitem__(cls, key: str) -> Enum: | ||||
|             return cls._member_map_[key] | ||||
| @@ -140,6 +132,9 @@ class Enum(IntEnum if TYPE_CHECKING else int, metaclass=EnumType): | ||||
|             super().__setattr__(self, "value", value) | ||||
|             return self | ||||
|  | ||||
|     def __getnewargs_ex__(self) -> Tuple[Tuple[()], Dict[str, Any]]: | ||||
|         return (), {"name": self.name, "value": self.value} | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         return self.name or "None" | ||||
|  | ||||
|   | ||||
| @@ -1204,9 +1204,9 @@ class EnumDescriptorProto(betterproto.Message): | ||||
|     name: str = betterproto.string_field(1) | ||||
|     value: List["EnumValueDescriptorProto"] = betterproto.message_field(2) | ||||
|     options: "EnumOptions" = betterproto.message_field(3) | ||||
|     reserved_range: List[ | ||||
|         "EnumDescriptorProtoEnumReservedRange" | ||||
|     ] = betterproto.message_field(4) | ||||
|     reserved_range: List["EnumDescriptorProtoEnumReservedRange"] = ( | ||||
|         betterproto.message_field(4) | ||||
|     ) | ||||
|     """ | ||||
|     Range of reserved numeric values. Reserved numeric values may not be used | ||||
|      by enum values in the same enum declaration. Reserved ranges may not | ||||
| @@ -1792,9 +1792,9 @@ class FeatureSetDefaults(betterproto.Message): | ||||
|      for the closest matching edition, followed by proto merges. | ||||
|     """ | ||||
|  | ||||
|     defaults: List[ | ||||
|         "FeatureSetDefaultsFeatureSetEditionDefault" | ||||
|     ] = betterproto.message_field(1) | ||||
|     defaults: List["FeatureSetDefaultsFeatureSetEditionDefault"] = ( | ||||
|         betterproto.message_field(1) | ||||
|     ) | ||||
|     minimum_edition: "Edition" = betterproto.enum_field(4) | ||||
|     """ | ||||
|     The minimum supported edition (inclusive) when this was constructed. | ||||
|   | ||||
| @@ -53,9 +53,9 @@ class CodeGeneratorRequest(betterproto.Message): | ||||
|     parameter: str = betterproto.string_field(2) | ||||
|     """The generator parameter passed on the command-line.""" | ||||
|  | ||||
|     proto_file: List[ | ||||
|         "betterproto_lib_pydantic_google_protobuf.FileDescriptorProto" | ||||
|     ] = betterproto.message_field(15) | ||||
|     proto_file: List["betterproto_lib_pydantic_google_protobuf.FileDescriptorProto"] = ( | ||||
|         betterproto.message_field(15) | ||||
|     ) | ||||
|     """ | ||||
|     FileDescriptorProtos for all files in files_to_generate and everything | ||||
|      they import.  The files will appear in topological order, so each file | ||||
| @@ -195,9 +195,9 @@ class CodeGeneratorResponseFile(betterproto.Message): | ||||
|     content: str = betterproto.string_field(15) | ||||
|     """The file contents.""" | ||||
|  | ||||
|     generated_code_info: ( | ||||
|         "betterproto_lib_pydantic_google_protobuf.GeneratedCodeInfo" | ||||
|     ) = betterproto.message_field(16) | ||||
|     generated_code_info: "betterproto_lib_pydantic_google_protobuf.GeneratedCodeInfo" = betterproto.message_field( | ||||
|         16 | ||||
|     ) | ||||
|     """ | ||||
|     Information describing the file content being inserted. If an insertion | ||||
|      point is used, this information will be appropriately offset and inserted | ||||
|   | ||||
| @@ -1064,9 +1064,9 @@ class EnumDescriptorProto(betterproto.Message): | ||||
|     name: str = betterproto.string_field(1) | ||||
|     value: List["EnumValueDescriptorProto"] = betterproto.message_field(2) | ||||
|     options: "EnumOptions" = betterproto.message_field(3) | ||||
|     reserved_range: List[ | ||||
|         "EnumDescriptorProtoEnumReservedRange" | ||||
|     ] = betterproto.message_field(4) | ||||
|     reserved_range: List["EnumDescriptorProtoEnumReservedRange"] = ( | ||||
|         betterproto.message_field(4) | ||||
|     ) | ||||
|     """ | ||||
|     Range of reserved numeric values. Reserved numeric values may not be used | ||||
|      by enum values in the same enum declaration. Reserved ranges may not | ||||
| @@ -1688,9 +1688,9 @@ class FeatureSetDefaults(betterproto.Message): | ||||
|      for the closest matching edition, followed by proto merges. | ||||
|     """ | ||||
|  | ||||
|     defaults: List[ | ||||
|         "FeatureSetDefaultsFeatureSetEditionDefault" | ||||
|     ] = betterproto.message_field(1) | ||||
|     defaults: List["FeatureSetDefaultsFeatureSetEditionDefault"] = ( | ||||
|         betterproto.message_field(1) | ||||
|     ) | ||||
|     minimum_edition: "Edition" = betterproto.enum_field(4) | ||||
|     """ | ||||
|     The minimum supported edition (inclusive) when this was constructed. | ||||
|   | ||||
| @@ -46,9 +46,9 @@ class CodeGeneratorRequest(betterproto.Message): | ||||
|     parameter: str = betterproto.string_field(2) | ||||
|     """The generator parameter passed on the command-line.""" | ||||
|  | ||||
|     proto_file: List[ | ||||
|         "betterproto_lib_google_protobuf.FileDescriptorProto" | ||||
|     ] = betterproto.message_field(15) | ||||
|     proto_file: List["betterproto_lib_google_protobuf.FileDescriptorProto"] = ( | ||||
|         betterproto.message_field(15) | ||||
|     ) | ||||
|     """ | ||||
|     FileDescriptorProtos for all files in files_to_generate and everything | ||||
|      they import.  The files will appear in topological order, so each file | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import os.path | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| from .module_validation import ModuleValidator | ||||
| @@ -6,8 +7,6 @@ from .module_validation import ModuleValidator | ||||
|  | ||||
| try: | ||||
|     # betterproto[compiler] specific dependencies | ||||
|     import black | ||||
|     import isort.api | ||||
|     import jinja2 | ||||
| except ImportError as err: | ||||
|     print( | ||||
| @@ -32,6 +31,7 @@ def outputfile_compiler(output_file: OutputTemplate) -> str: | ||||
|         trim_blocks=True, | ||||
|         lstrip_blocks=True, | ||||
|         loader=jinja2.FileSystemLoader(templates_folder), | ||||
|         undefined=jinja2.StrictUndefined, | ||||
|     ) | ||||
|     # Load the body first so we have a compleate list of imports needed. | ||||
|     body_template = env.get_template("template.py.j2") | ||||
| @@ -39,20 +39,17 @@ def outputfile_compiler(output_file: OutputTemplate) -> str: | ||||
|  | ||||
|     code = body_template.render(output_file=output_file) | ||||
|     code = header_template.render(output_file=output_file) + code | ||||
|     code = isort.api.sort_code_string( | ||||
|         code=code, | ||||
|         show_diff=False, | ||||
|         py_version=37, | ||||
|         profile="black", | ||||
|         combine_as_imports=True, | ||||
|         lines_after_imports=2, | ||||
|         quiet=True, | ||||
|         force_grid_wrap=2, | ||||
|         known_third_party=["grpclib", "betterproto"], | ||||
|  | ||||
|     # Sort imports, delete unused ones | ||||
|     code = subprocess.check_output( | ||||
|         ["ruff", "check", "--select", "I,F401", "--fix", "--silent", "-"], | ||||
|         input=code, | ||||
|         encoding="utf-8", | ||||
|     ) | ||||
|     code = black.format_str( | ||||
|         src_contents=code, | ||||
|         mode=black.Mode(), | ||||
|  | ||||
|     # Format the code | ||||
|     code = subprocess.check_output( | ||||
|         ["ruff", "format", "-"], input=code, encoding="utf-8" | ||||
|     ) | ||||
|  | ||||
|     # Validate the generated code. | ||||
|   | ||||
| @@ -153,11 +153,33 @@ def get_comment( | ||||
| ) -> str: | ||||
|     pad = " " * indent | ||||
|     for sci_loc in proto_file.source_code_info.location: | ||||
|         if list(sci_loc.path) == path and sci_loc.leading_comments: | ||||
|             lines = sci_loc.leading_comments.strip().split("\n") | ||||
|         if list(sci_loc.path) == path: | ||||
|             all_comments = list(sci_loc.leading_detached_comments) | ||||
|             if sci_loc.leading_comments: | ||||
|                 all_comments.append(sci_loc.leading_comments) | ||||
|             if sci_loc.trailing_comments: | ||||
|                 all_comments.append(sci_loc.trailing_comments) | ||||
|  | ||||
|             lines = [] | ||||
|  | ||||
|             for comment in all_comments: | ||||
|                 lines += comment.split("\n") | ||||
|                 lines.append("") | ||||
|  | ||||
|             # Remove consecutive empty lines | ||||
|             lines = [ | ||||
|                 line for i, line in enumerate(lines) if line or (i == 0 or lines[i - 1]) | ||||
|             ] | ||||
|  | ||||
|             if lines and not lines[-1]: | ||||
|                 lines.pop()  # Remove the last empty line | ||||
|  | ||||
|             # It is common for one line comments to start with a space, for example: // comment | ||||
|             # We don't add this space to the generated file. | ||||
|             lines = [line[1:] if line and line[0] == " " else line for line in lines] | ||||
|  | ||||
|             # This is a field, message, enum, service, or method | ||||
|             if len(lines) == 1 and len(lines[0]) < 79 - indent - 6: | ||||
|                 lines[0] = lines[0].strip('"') | ||||
|                 return f'{pad}"""{lines[0]}"""' | ||||
|             else: | ||||
|                 joined = f"\n{pad}".join(lines) | ||||
| @@ -238,7 +260,7 @@ class OutputTemplate: | ||||
|     parent_request: PluginRequestCompiler | ||||
|     package_proto_obj: FileDescriptorProto | ||||
|     input_files: List[str] = field(default_factory=list) | ||||
|     imports: Set[str] = field(default_factory=set) | ||||
|     imports_end: Set[str] = field(default_factory=set) | ||||
|     datetime_imports: Set[str] = field(default_factory=set) | ||||
|     pydantic_imports: Set[str] = field(default_factory=set) | ||||
|     builtins_import: bool = False | ||||
| @@ -328,12 +350,6 @@ class MessageCompiler(ProtoContentBase): | ||||
|     def py_name(self) -> str: | ||||
|         return pythonize_class_name(self.proto_name) | ||||
|  | ||||
|     @property | ||||
|     def annotation(self) -> str: | ||||
|         if self.repeated: | ||||
|             return self.typing_compiler.list(self.py_name) | ||||
|         return self.py_name | ||||
|  | ||||
|     @property | ||||
|     def deprecated_fields(self) -> Iterator[str]: | ||||
|         for f in self.fields: | ||||
| @@ -484,13 +500,6 @@ class FieldCompiler(MessageCompiler): | ||||
|     def optional(self) -> bool: | ||||
|         return self.proto_obj.proto3_optional | ||||
|  | ||||
|     @property | ||||
|     def mutable(self) -> bool: | ||||
|         """True if the field is a mutable type, otherwise False.""" | ||||
|         return self.annotation.startswith( | ||||
|             ("typing.List[", "typing.Dict[", "dict[", "list[", "Dict[", "List[") | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def field_type(self) -> str: | ||||
|         """String representation of proto field type.""" | ||||
| @@ -532,7 +541,7 @@ class FieldCompiler(MessageCompiler): | ||||
|             # Type referencing another defined Message or a named enum | ||||
|             return get_type_reference( | ||||
|                 package=self.output_file.package, | ||||
|                 imports=self.output_file.imports, | ||||
|                 imports=self.output_file.imports_end, | ||||
|                 source_type=self.proto_obj.type_name, | ||||
|                 typing_compiler=self.typing_compiler, | ||||
|                 pydantic=self.output_file.pydantic_dataclasses, | ||||
| @@ -661,6 +670,7 @@ class EnumDefinitionCompiler(MessageCompiler): | ||||
|  | ||||
| @dataclass | ||||
| class ServiceCompiler(ProtoContentBase): | ||||
|     source_file: FileDescriptorProto | ||||
|     parent: OutputTemplate = PLACEHOLDER | ||||
|     proto_obj: DescriptorProto = PLACEHOLDER | ||||
|     path: List[int] = PLACEHOLDER | ||||
| @@ -682,6 +692,7 @@ class ServiceCompiler(ProtoContentBase): | ||||
|  | ||||
| @dataclass | ||||
| class ServiceMethodCompiler(ProtoContentBase): | ||||
|     source_file: FileDescriptorProto | ||||
|     parent: ServiceCompiler | ||||
|     proto_obj: MethodDescriptorProto | ||||
|     path: List[int] = PLACEHOLDER | ||||
| @@ -730,7 +741,7 @@ class ServiceMethodCompiler(ProtoContentBase): | ||||
|         """ | ||||
|         return get_type_reference( | ||||
|             package=self.output_file.package, | ||||
|             imports=self.output_file.imports, | ||||
|             imports=self.output_file.imports_end, | ||||
|             source_type=self.proto_obj.input_type, | ||||
|             typing_compiler=self.output_file.typing_compiler, | ||||
|             unwrap=False, | ||||
| @@ -760,7 +771,7 @@ class ServiceMethodCompiler(ProtoContentBase): | ||||
|         """ | ||||
|         return get_type_reference( | ||||
|             package=self.output_file.package, | ||||
|             imports=self.output_file.imports, | ||||
|             imports=self.output_file.imports_end, | ||||
|             source_type=self.proto_obj.output_type, | ||||
|             typing_compiler=self.output_file.typing_compiler, | ||||
|             unwrap=False, | ||||
|   | ||||
| @@ -143,7 +143,7 @@ def generate_code(request: CodeGeneratorRequest) -> CodeGeneratorResponse: | ||||
|     for output_package_name, output_package in request_data.output_packages.items(): | ||||
|         for proto_input_file in output_package.input_files: | ||||
|             for index, service in enumerate(proto_input_file.service): | ||||
|                 read_protobuf_service(service, index, output_package) | ||||
|                 read_protobuf_service(proto_input_file, service, index, output_package) | ||||
|  | ||||
|     # Generate output files | ||||
|     output_paths: Set[pathlib.Path] = set() | ||||
| @@ -249,12 +249,21 @@ def read_protobuf_type( | ||||
|  | ||||
|  | ||||
| def read_protobuf_service( | ||||
|     service: ServiceDescriptorProto, index: int, output_package: OutputTemplate | ||||
|     source_file: FileDescriptorProto, | ||||
|     service: ServiceDescriptorProto, | ||||
|     index: int, | ||||
|     output_package: OutputTemplate, | ||||
| ) -> None: | ||||
|     service_data = ServiceCompiler( | ||||
|         parent=output_package, proto_obj=service, path=[6, index] | ||||
|         source_file=source_file, | ||||
|         parent=output_package, | ||||
|         proto_obj=service, | ||||
|         path=[6, index], | ||||
|     ) | ||||
|     for j, method in enumerate(service.method): | ||||
|         ServiceMethodCompiler( | ||||
|             parent=service_data, proto_obj=method, path=[6, index, 2, j] | ||||
|             source_file=source_file, | ||||
|             parent=service_data, | ||||
|             proto_obj=method, | ||||
|             path=[6, index, 2, j], | ||||
|         ) | ||||
|   | ||||
| @@ -2,13 +2,26 @@ | ||||
| # sources: {{ ', '.join(output_file.input_filenames) }} | ||||
| # plugin: python-betterproto | ||||
| # This file has been @generated | ||||
|  | ||||
| __all__ = ( | ||||
|     {%- for enum in output_file.enums -%} | ||||
|         "{{ enum.py_name }}", | ||||
|     {%- endfor -%} | ||||
|     {%- for message in output_file.messages -%} | ||||
|         "{{ message.py_name }}", | ||||
|     {%- endfor -%} | ||||
|     {%- for service in output_file.services -%} | ||||
|         "{{ service.py_name }}Stub", | ||||
|         "{{ service.py_name }}Base", | ||||
|     {%- endfor -%} | ||||
| ) | ||||
|  | ||||
| {% for i in output_file.python_module_imports|sort %} | ||||
| import {{ i }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% if output_file.pydantic_dataclasses %} | ||||
| from pydantic.dataclasses import dataclass | ||||
| from pydantic.dataclasses import rebuild_dataclass | ||||
| {%- else -%} | ||||
| from dataclasses import dataclass | ||||
| {% endif %} | ||||
| @@ -35,10 +48,6 @@ from betterproto.grpc.grpclib_server import ServiceBase | ||||
| import grpclib | ||||
| {% endif %} | ||||
|  | ||||
| {% for i in output_file.imports|sort %} | ||||
| {{ i }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% if output_file.imports_type_checking_only %} | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
|   | ||||
| @@ -77,14 +77,14 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): | ||||
|             , {{ method.py_input_message_param }}: "{{ method.py_input_message_type }}" | ||||
|         {%- else -%} | ||||
|             {# Client streaming: need a request iterator instead #} | ||||
|             , {{ method.py_input_message_param }}_iterator: {{ output_file.typing_compiler.union(output_file.typing_compiler.async_iterable(method.py_input_message_type), output_file.typing_compiler.iterable(method.py_input_message_type)) }} | ||||
|             , {{ method.py_input_message_param }}_iterator: "{{ output_file.typing_compiler.union(output_file.typing_compiler.async_iterable(method.py_input_message_type), output_file.typing_compiler.iterable(method.py_input_message_type)) }}" | ||||
|         {%- endif -%} | ||||
|             , | ||||
|             * | ||||
|             , timeout: {{ output_file.typing_compiler.optional("float") }} = None | ||||
|             , deadline: {{ output_file.typing_compiler.optional('"Deadline"') }} = None | ||||
|             , metadata: {{ output_file.typing_compiler.optional('"MetadataLike"') }} = None | ||||
|             ) -> {% if method.server_streaming %}{{ output_file.typing_compiler.async_iterator(method.py_output_message_type ) }}{% else %}"{{ method.py_output_message_type }}"{% endif %}: | ||||
|             ) -> "{% if method.server_streaming %}{{ output_file.typing_compiler.async_iterator(method.py_output_message_type ) }}{% else %}{{ method.py_output_message_type }}{% endif %}": | ||||
|         {% if method.comment %} | ||||
| {{ method.comment }} | ||||
|  | ||||
| @@ -143,6 +143,10 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): | ||||
|     {% endfor %} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for i in output_file.imports_end %} | ||||
| {{ i }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for service in output_file.services %} | ||||
| class {{ service.py_name }}Base(ServiceBase): | ||||
|     {% if service.comment %} | ||||
| @@ -211,11 +215,3 @@ class {{ service.py_name }}Base(ServiceBase): | ||||
|         } | ||||
|  | ||||
| {% endfor %} | ||||
|  | ||||
| {% if output_file.pydantic_dataclasses %} | ||||
| {% for message in output_file.messages %} | ||||
| {% if message.has_message_field %} | ||||
| rebuild_dataclass({{ message.py_name }})  # type: ignore | ||||
| {% endif %} | ||||
| {% endfor %} | ||||
| {% endif %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user