diff --git a/src/betterproto/compile/importing.py b/src/betterproto/compile/importing.py index b216dfc59..0998b8c03 100644 --- a/src/betterproto/compile/importing.py +++ b/src/betterproto/compile/importing.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: + from ..plugin.models import PluginRequestCompiler from ..plugin.typing_compiler import TypingCompiler WRAPPER_TYPES: Dict[str, Type] = { @@ -32,20 +33,42 @@ } -def parse_source_type_name(field_type_name: str) -> Tuple[str, str]: +def parse_source_type_name( + field_type_name: str, request: "PluginRequestCompiler" +) -> Tuple[str, str]: """ Split full source type name into package and type name. E.g. 'root.package.Message' -> ('root.package', 'Message') 'root.Message.SomeEnum' -> ('root', 'Message.SomeEnum') + + The function goes through the symbols that have been defined (names, enums, packages) to find the actual package and + name of the object that is referenced. """ - package_match = re.match(r"^\.?([^A-Z]+)\.(.+)", field_type_name) - if package_match: - package = package_match.group(1) - name = package_match.group(2) - else: - package = "" - name = field_type_name.lstrip(".") - return package, name + if field_type_name[0] != ".": + raise RuntimeError("relative names are not supported") + field_type_name = field_type_name[1:] + parts = field_type_name.split(".") + + answer = None + + # a.b.c: + # i=0: "", "a.b.c" + # i=1: "a", "b.c" + # i=2: "a.b", "c" + for i in range(len(parts)): + package_name, object_name = ".".join(parts[:i]), ".".join(parts[i:]) + + if package := request.output_packages.get(package_name): + if object_name in package.messages or object_name in package.enums: + if answer: + # This should have already been handeled by protoc + raise ValueError(f"ambiguous definition: {field_type_name}") + answer = package_name, object_name + + if answer: + return answer + + raise ValueError(f"can't find type name: {field_type_name}") def get_type_reference( @@ -54,6 +77,7 @@ def get_type_reference( imports: set, source_type: str, typing_compiler: TypingCompiler, + request: "PluginRequestCompiler", unwrap: bool = True, pydantic: bool = False, ) -> str: @@ -72,7 +96,7 @@ def get_type_reference( elif source_type == ".google.protobuf.Timestamp": return "datetime" - source_package, source_type = parse_source_type_name(source_type) + source_package, source_type = parse_source_type_name(source_type, request) current_package: List[str] = package.split(".") if package else [] py_package: List[str] = source_package.split(".") if source_package else [] diff --git a/src/betterproto/plugin/models.py b/src/betterproto/plugin/models.py index e330e6884..826fc7813 100644 --- a/src/betterproto/plugin/models.py +++ b/src/betterproto/plugin/models.py @@ -65,10 +65,7 @@ from betterproto.lib.google.protobuf.compiler import CodeGeneratorRequest from .. import which_one_of -from ..compile.importing import ( - get_type_reference, - parse_source_type_name, -) +from ..compile.importing import get_type_reference from ..compile.naming import ( pythonize_class_name, pythonize_enum_member_name, @@ -205,6 +202,12 @@ def __post_init__(self) -> None: if field_val is PLACEHOLDER: raise ValueError(f"`{field_name}` is a required field.") + def ready(self) -> None: + """ + This function is called after all the compilers are created, but before generating the output code. + """ + pass + @property def output_file(self) -> "OutputTemplate": current = self @@ -214,10 +217,7 @@ def output_file(self) -> "OutputTemplate": @property def request(self) -> "PluginRequestCompiler": - current = self - while not isinstance(current, OutputTemplate): - current = current.parent - return current.parent_request + return self.output_file.parent_request @property def comment(self) -> str: @@ -228,6 +228,10 @@ def comment(self) -> str: proto_file=self.source_file, path=self.path, indent=self.comment_indent ) + @property + def deprecated(self) -> bool: + return self.proto_obj.options.deprecated + @dataclass class PluginRequestCompiler: @@ -244,7 +248,9 @@ def all_messages(self) -> List["MessageCompiler"]: List of all of the messages in this request. """ return [ - msg for output in self.output_packages.values() for msg in output.messages + msg + for output in self.output_packages.values() + for msg in output.messages.values() ] @@ -264,9 +270,9 @@ class OutputTemplate: datetime_imports: Set[str] = field(default_factory=set) pydantic_imports: Set[str] = field(default_factory=set) builtins_import: bool = False - messages: List["MessageCompiler"] = field(default_factory=list) - enums: List["EnumDefinitionCompiler"] = field(default_factory=list) - services: List["ServiceCompiler"] = field(default_factory=list) + messages: Dict[str, "MessageCompiler"] = field(default_factory=dict) + enums: Dict[str, "EnumDefinitionCompiler"] = field(default_factory=dict) + services: Dict[str, "ServiceCompiler"] = field(default_factory=dict) imports_type_checking_only: Set[str] = field(default_factory=set) pydantic_dataclasses: bool = False output: bool = True @@ -299,13 +305,13 @@ def python_module_imports(self) -> Set[str]: imports = set() has_deprecated = False - if any(m.deprecated for m in self.messages): + if any(m.deprecated for m in self.messages.values()): has_deprecated = True - if any(x for x in self.messages if any(x.deprecated_fields)): + if any(x for x in self.messages.values() if any(x.deprecated_fields)): has_deprecated = True if any( any(m.proto_obj.options.deprecated for m in s.methods) - for s in self.services + for s in self.services.values() ): has_deprecated = True @@ -329,17 +335,15 @@ class MessageCompiler(ProtoContentBase): fields: List[Union["FieldCompiler", "MessageCompiler"]] = field( default_factory=list ) - deprecated: bool = field(default=False, init=False) builtins_types: Set[str] = field(default_factory=set) def __post_init__(self) -> None: # Add message to output file if isinstance(self.parent, OutputTemplate): if isinstance(self, EnumDefinitionCompiler): - self.output_file.enums.append(self) + self.output_file.enums[self.proto_name] = self else: - self.output_file.messages.append(self) - self.deprecated = self.proto_obj.options.deprecated + self.output_file.messages[self.proto_name] = self super().__post_init__() @property @@ -417,16 +421,24 @@ def is_oneof(proto_field_obj: FieldDescriptorProto) -> bool: @dataclass -class FieldCompiler(MessageCompiler): +class FieldCompiler(ProtoContentBase): + source_file: FileDescriptorProto + typing_compiler: TypingCompiler + path: List[int] = PLACEHOLDER + builtins_types: Set[str] = field(default_factory=set) + parent: MessageCompiler = PLACEHOLDER proto_obj: FieldDescriptorProto = PLACEHOLDER def __post_init__(self) -> None: # Add field to message - self.parent.fields.append(self) + if isinstance(self.parent, MessageCompiler): + self.parent.fields.append(self) + super().__post_init__() + + def ready(self) -> None: # Check for new imports self.add_imports_to(self.output_file) - super().__post_init__() # call FieldCompiler-> MessageCompiler __post_init__ def get_field_string(self, indent: int = 4) -> str: """Construct string representation of this field as a field.""" @@ -544,6 +556,7 @@ def py_type(self) -> str: imports=self.output_file.imports_end, source_type=self.proto_obj.type_name, typing_compiler=self.typing_compiler, + request=self.request, pydantic=self.output_file.pydantic_dataclasses, ) else: @@ -587,12 +600,22 @@ def pydantic_imports(self) -> Set[str]: @dataclass class MapEntryCompiler(FieldCompiler): - py_k_type: Type = PLACEHOLDER - py_v_type: Type = PLACEHOLDER - proto_k_type: str = PLACEHOLDER - proto_v_type: str = PLACEHOLDER + py_k_type: Optional[Type] = None + py_v_type: Optional[Type] = None + proto_k_type: str = "" + proto_v_type: str = "" - def __post_init__(self) -> None: + def __post_init__(self): + map_entry = f"{self.proto_obj.name.replace('_', '').lower()}entry" + for nested in self.parent.proto_obj.nested_type: + if ( + nested.name.replace("_", "").lower() == map_entry + and nested.options.map_entry + ): + pass + return super().__post_init__() + + def ready(self) -> None: """Explore nested types and set k_type and v_type if unset.""" map_entry = f"{self.proto_obj.name.replace('_', '').lower()}entry" for nested in self.parent.proto_obj.nested_type: @@ -617,7 +640,9 @@ def __post_init__(self) -> None: # Get proto types self.proto_k_type = FieldDescriptorProtoType(nested.field[0].type).name self.proto_v_type = FieldDescriptorProtoType(nested.field[1].type).name - super().__post_init__() # call FieldCompiler-> MessageCompiler __post_init__ + return + + raise ValueError("can't find enum") @property def betterproto_field_args(self) -> List[str]: @@ -678,7 +703,7 @@ class ServiceCompiler(ProtoContentBase): def __post_init__(self) -> None: # Add service to output file - self.output_file.services.append(self) + self.output_file.services[self.proto_name] = self super().__post_init__() # check for unset fields @property @@ -744,6 +769,7 @@ def py_input_message_type(self) -> str: imports=self.output_file.imports_end, source_type=self.proto_obj.input_type, typing_compiler=self.output_file.typing_compiler, + request=self.request, unwrap=False, pydantic=self.output_file.pydantic_dataclasses, ).strip('"') @@ -774,6 +800,7 @@ def py_output_message_type(self) -> str: imports=self.output_file.imports_end, source_type=self.proto_obj.output_type, typing_compiler=self.output_file.typing_compiler, + request=self.request, unwrap=False, pydantic=self.output_file.pydantic_dataclasses, ).strip('"') diff --git a/src/betterproto/plugin/parser.py b/src/betterproto/plugin/parser.py index 5f7b72c40..cf2a8e3eb 100644 --- a/src/betterproto/plugin/parser.py +++ b/src/betterproto/plugin/parser.py @@ -40,7 +40,6 @@ from .typing_compiler import ( DirectImportTypingCompiler, NoTyping310TypingCompiler, - TypingCompiler, TypingImportTypingCompiler, ) @@ -61,7 +60,13 @@ def _traverse( for i, item in enumerate(items): # Adjust the name since we flatten the hierarchy. # Todo: don't change the name, but include full name in returned tuple - item.name = next_prefix = f"{prefix}_{item.name}" + should_rename = ( + not isinstance(item, DescriptorProto) or not item.options.map_entry + ) + + item.name = next_prefix = ( + f"{prefix}.{item.name}" if prefix and should_rename else item.name + ) yield item, [*path, i] if isinstance(item, DescriptorProto): @@ -145,6 +150,21 @@ def generate_code(request: CodeGeneratorRequest) -> CodeGeneratorResponse: for index, service in enumerate(proto_input_file.service): read_protobuf_service(proto_input_file, service, index, output_package) + # All the hierarchy is ready. We can perform pre-computations before generating the output files + for package in request_data.output_packages.values(): + for message in package.messages.values(): + for field in message.fields: + field.ready() + message.ready() + for enum in package.enums.values(): + for variant in enum.fields: + variant.ready() + enum.ready() + for service in package.services.values(): + for method in service.methods: + method.ready() + service.ready() + # Generate output files output_paths: Set[pathlib.Path] = set() for output_package_name, output_package in request_data.output_packages.items(): diff --git a/src/betterproto/templates/header.py.j2 b/src/betterproto/templates/header.py.j2 index b6d0a6c44..f3be1c6c0 100644 --- a/src/betterproto/templates/header.py.j2 +++ b/src/betterproto/templates/header.py.j2 @@ -4,13 +4,13 @@ # This file has been @generated __all__ = ( - {%- for enum in output_file.enums -%} + {% for _, enum in output_file.enums|dictsort(by="key") %} "{{ enum.py_name }}", {%- endfor -%} - {%- for message in output_file.messages -%} + {% for _, message in output_file.messages|dictsort(by="key") %} "{{ message.py_name }}", {%- endfor -%} - {%- for service in output_file.services -%} + {% for _, service in output_file.services|dictsort(by="key") %} "{{ service.py_name }}Stub", "{{ service.py_name }}Base", {%- endfor -%} diff --git a/src/betterproto/templates/template.py.j2 b/src/betterproto/templates/template.py.j2 index 4a252aec6..8230819a9 100644 --- a/src/betterproto/templates/template.py.j2 +++ b/src/betterproto/templates/template.py.j2 @@ -1,4 +1,4 @@ -{% if output_file.enums %}{% for enum in output_file.enums %} +{% if output_file.enums %}{% for _, enum in output_file.enums|dictsort(by="key") %} class {{ enum.py_name }}(betterproto.Enum): {% if enum.comment %} {{ enum.comment }} @@ -22,7 +22,7 @@ class {{ enum.py_name }}(betterproto.Enum): {% endfor %} {% endif %} -{% for message in output_file.messages %} +{% for _, message in output_file.messages|dictsort(by="key") %} {% if output_file.pydantic_dataclasses %} @dataclass(eq=False, repr=False, config={"extra": "forbid"}) {% else %} @@ -63,7 +63,7 @@ class {{ message.py_name }}(betterproto.Message): {% endif %} {% endfor %} -{% for service in output_file.services %} +{% for _, service in output_file.services|dictsort(by="key") %} class {{ service.py_name }}Stub(betterproto.ServiceStub): {% if service.comment %} {{ service.comment }} @@ -147,7 +147,7 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): {{ i }} {% endfor %} -{% for service in output_file.services %} +{% for _, service in output_file.services|dictsort(by="key") %} class {{ service.py_name }}Base(ServiceBase): {% if service.comment %} {{ service.comment }} diff --git a/tests/inputs/config.py b/tests/inputs/config.py index 6da1f887d..4fb1565f8 100644 --- a/tests/inputs/config.py +++ b/tests/inputs/config.py @@ -4,7 +4,6 @@ "namespace_keywords", # 70 "googletypes_struct", # 9 "googletypes_value", # 9 - "import_capitalized_package", "example", # This is the example in the readme. Not a test. } diff --git a/tests/inputs/import_child_scoping_rules/child.proto b/tests/inputs/import_child_scoping_rules/child.proto new file mode 100644 index 000000000..f491e0da9 --- /dev/null +++ b/tests/inputs/import_child_scoping_rules/child.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_child_scoping_rules.aaa.bbb.ccc.ddd; + +message ChildMessage { + +} diff --git a/tests/inputs/import_child_scoping_rules/import_child_scoping_rules.proto b/tests/inputs/import_child_scoping_rules/import_child_scoping_rules.proto new file mode 100644 index 000000000..272852ccd --- /dev/null +++ b/tests/inputs/import_child_scoping_rules/import_child_scoping_rules.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package import_child_scoping_rules; + +import "package.proto"; + +message Test { + aaa.bbb.Msg msg = 1; +} diff --git a/tests/inputs/import_child_scoping_rules/package.proto b/tests/inputs/import_child_scoping_rules/package.proto new file mode 100644 index 000000000..6b51fe567 --- /dev/null +++ b/tests/inputs/import_child_scoping_rules/package.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package import_child_scoping_rules.aaa.bbb; + +import "child.proto"; + +message Msg { + .import_child_scoping_rules.aaa.bbb.ccc.ddd.ChildMessage a = 1; + import_child_scoping_rules.aaa.bbb.ccc.ddd.ChildMessage b = 2; + aaa.bbb.ccc.ddd.ChildMessage c = 3; + bbb.ccc.ddd.ChildMessage d = 4; + ccc.ddd.ChildMessage e = 5; +} diff --git a/tests/inputs/import_nested_child_package_from_root/child.proto b/tests/inputs/import_nested_child_package_from_root/child.proto new file mode 100644 index 000000000..fcd7e2f6c --- /dev/null +++ b/tests/inputs/import_nested_child_package_from_root/child.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package import_nested_child_package_from_root.package.child.otherchild; + +message ChildMessage { + +} diff --git a/tests/inputs/import_nested_child_package_from_root/import_nested_child_package_from_root.proto b/tests/inputs/import_nested_child_package_from_root/import_nested_child_package_from_root.proto new file mode 100644 index 000000000..96da1ace6 --- /dev/null +++ b/tests/inputs/import_nested_child_package_from_root/import_nested_child_package_from_root.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package import_nested_child_package_from_root; + +import "child.proto"; + +message Test { + package.child.otherchild.ChildMessage child = 1; +} diff --git a/tests/test_all_definition.py b/tests/test_all_definition.py index 61abb5f37..01743af77 100644 --- a/tests/test_all_definition.py +++ b/tests/test_all_definition.py @@ -16,4 +16,4 @@ def test_all_definition(): "TestStub", "TestBase", ) - assert enum.__all__ == ("Choice", "ArithmeticOperator", "Test") + assert enum.__all__ == ("ArithmeticOperator", "Choice", "Test") diff --git a/tests/test_get_ref_type.py b/tests/test_get_ref_type.py deleted file mode 100644 index 7b529bd27..000000000 --- a/tests/test_get_ref_type.py +++ /dev/null @@ -1,497 +0,0 @@ -import pytest - -from betterproto.compile.importing import ( - get_type_reference, - parse_source_type_name, -) -from betterproto.plugin.typing_compiler import DirectImportTypingCompiler - - -@pytest.fixture -def typing_compiler() -> DirectImportTypingCompiler: - """ - Generates a simple Direct Import Typing Compiler for testing. - """ - return DirectImportTypingCompiler() - - -@pytest.mark.parametrize( - ["google_type", "expected_name", "expected_import"], - [ - ( - ".google.protobuf.Empty", - '"betterproto_lib_google_protobuf.Empty"', - "import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf", - ), - ( - ".google.protobuf.Struct", - '"betterproto_lib_google_protobuf.Struct"', - "import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf", - ), - ( - ".google.protobuf.ListValue", - '"betterproto_lib_google_protobuf.ListValue"', - "import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf", - ), - ( - ".google.protobuf.Value", - '"betterproto_lib_google_protobuf.Value"', - "import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf", - ), - ], -) -def test_reference_google_wellknown_types_non_wrappers( - google_type: str, - expected_name: str, - expected_import: str, - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type=google_type, - typing_compiler=typing_compiler, - pydantic=False, - ) - - assert name == expected_name - assert imports.__contains__( - expected_import - ), f"{expected_import} not found in {imports}" - - -@pytest.mark.parametrize( - ["google_type", "expected_name", "expected_import"], - [ - ( - ".google.protobuf.Empty", - '"betterproto_lib_pydantic_google_protobuf.Empty"', - "import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf", - ), - ( - ".google.protobuf.Struct", - '"betterproto_lib_pydantic_google_protobuf.Struct"', - "import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf", - ), - ( - ".google.protobuf.ListValue", - '"betterproto_lib_pydantic_google_protobuf.ListValue"', - "import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf", - ), - ( - ".google.protobuf.Value", - '"betterproto_lib_pydantic_google_protobuf.Value"', - "import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf", - ), - ], -) -def test_reference_google_wellknown_types_non_wrappers_pydantic( - google_type: str, - expected_name: str, - expected_import: str, - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type=google_type, - typing_compiler=typing_compiler, - pydantic=True, - ) - - assert name == expected_name - assert imports.__contains__( - expected_import - ), f"{expected_import} not found in {imports}" - - -@pytest.mark.parametrize( - ["google_type", "expected_name"], - [ - (".google.protobuf.DoubleValue", "Optional[float]"), - (".google.protobuf.FloatValue", "Optional[float]"), - (".google.protobuf.Int32Value", "Optional[int]"), - (".google.protobuf.Int64Value", "Optional[int]"), - (".google.protobuf.UInt32Value", "Optional[int]"), - (".google.protobuf.UInt64Value", "Optional[int]"), - (".google.protobuf.BoolValue", "Optional[bool]"), - (".google.protobuf.StringValue", "Optional[str]"), - (".google.protobuf.BytesValue", "Optional[bytes]"), - ], -) -def test_referenceing_google_wrappers_unwraps_them( - google_type: str, expected_name: str, typing_compiler: DirectImportTypingCompiler -): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type=google_type, - typing_compiler=typing_compiler, - ) - - assert name == expected_name - assert imports == set() - - -@pytest.mark.parametrize( - ["google_type", "expected_name"], - [ - ( - ".google.protobuf.DoubleValue", - '"betterproto_lib_google_protobuf.DoubleValue"', - ), - (".google.protobuf.FloatValue", '"betterproto_lib_google_protobuf.FloatValue"'), - (".google.protobuf.Int32Value", '"betterproto_lib_google_protobuf.Int32Value"'), - (".google.protobuf.Int64Value", '"betterproto_lib_google_protobuf.Int64Value"'), - ( - ".google.protobuf.UInt32Value", - '"betterproto_lib_google_protobuf.UInt32Value"', - ), - ( - ".google.protobuf.UInt64Value", - '"betterproto_lib_google_protobuf.UInt64Value"', - ), - (".google.protobuf.BoolValue", '"betterproto_lib_google_protobuf.BoolValue"'), - ( - ".google.protobuf.StringValue", - '"betterproto_lib_google_protobuf.StringValue"', - ), - (".google.protobuf.BytesValue", '"betterproto_lib_google_protobuf.BytesValue"'), - ], -) -def test_referenceing_google_wrappers_without_unwrapping( - google_type: str, expected_name: str, typing_compiler: DirectImportTypingCompiler -): - name = get_type_reference( - package="", - imports=set(), - source_type=google_type, - typing_compiler=typing_compiler, - unwrap=False, - ) - - assert name == expected_name - - -def test_reference_child_package_from_package( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package", - imports=imports, - source_type="package.child.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from . import child"} - assert name == '"child.Message"' - - -def test_reference_child_package_from_root(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type="child.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from . import child"} - assert name == '"child.Message"' - - -def test_reference_camel_cased(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type="child_package.example_message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from . import child_package"} - assert name == '"child_package.ExampleMessage"' - - -def test_reference_nested_child_from_root(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type="nested.child.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .nested import child as nested_child"} - assert name == '"nested_child.Message"' - - -def test_reference_deeply_nested_child_from_root( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type="deeply.nested.child.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .deeply.nested import child as deeply_nested_child"} - assert name == '"deeply_nested_child.Message"' - - -def test_reference_deeply_nested_child_from_package( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package", - imports=imports, - source_type="package.deeply.nested.child.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .deeply.nested import child as deeply_nested_child"} - assert name == '"deeply_nested_child.Message"' - - -def test_reference_root_sibling(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="", - imports=imports, - source_type="Message", - typing_compiler=typing_compiler, - ) - - assert imports == set() - assert name == '"Message"' - - -def test_reference_nested_siblings(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="foo", - imports=imports, - source_type="foo.Message", - typing_compiler=typing_compiler, - ) - - assert imports == set() - assert name == '"Message"' - - -def test_reference_deeply_nested_siblings(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="foo.bar", - imports=imports, - source_type="foo.bar.Message", - typing_compiler=typing_compiler, - ) - - assert imports == set() - assert name == '"Message"' - - -def test_reference_parent_package_from_child( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package.child", - imports=imports, - source_type="package.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ... import package as __package__"} - assert name == '"__package__.Message"' - - -def test_reference_parent_package_from_deeply_nested_child( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package.deeply.nested.child", - imports=imports, - source_type="package.deeply.nested.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ... import nested as __nested__"} - assert name == '"__nested__.Message"' - - -def test_reference_ancestor_package_from_nested_child( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package.ancestor.nested.child", - imports=imports, - source_type="package.ancestor.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .... import ancestor as ___ancestor__"} - assert name == '"___ancestor__.Message"' - - -def test_reference_root_package_from_child(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="package.child", - imports=imports, - source_type="Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ... import Message as __Message__"} - assert name == '"__Message__"' - - -def test_reference_root_package_from_deeply_nested_child( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="package.deeply.nested.child", - imports=imports, - source_type="Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ..... import Message as ____Message__"} - assert name == '"____Message__"' - - -def test_reference_unrelated_package(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="a", - imports=imports, - source_type="p.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .. import p as _p__"} - assert name == '"_p__.Message"' - - -def test_reference_unrelated_nested_package( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="a.b", - imports=imports, - source_type="p.q.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ...p import q as __p_q__"} - assert name == '"__p_q__.Message"' - - -def test_reference_unrelated_deeply_nested_package( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="a.b.c.d", - imports=imports, - source_type="p.q.r.s.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .....p.q.r import s as ____p_q_r_s__"} - assert name == '"____p_q_r_s__.Message"' - - -def test_reference_cousin_package(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="a.x", - imports=imports, - source_type="a.y.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from .. import y as _y__"} - assert name == '"_y__.Message"' - - -def test_reference_cousin_package_different_name( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="test.package1", - imports=imports, - source_type="cousin.package2.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ...cousin import package2 as __cousin_package2__"} - assert name == '"__cousin_package2__.Message"' - - -def test_reference_cousin_package_same_name( - typing_compiler: DirectImportTypingCompiler, -): - imports = set() - name = get_type_reference( - package="test.package", - imports=imports, - source_type="cousin.package.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ...cousin import package as __cousin_package__"} - assert name == '"__cousin_package__.Message"' - - -def test_reference_far_cousin_package(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="a.x.y", - imports=imports, - source_type="a.b.c.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ...b import c as __b_c__"} - assert name == '"__b_c__.Message"' - - -def test_reference_far_far_cousin_package(typing_compiler: DirectImportTypingCompiler): - imports = set() - name = get_type_reference( - package="a.x.y.z", - imports=imports, - source_type="a.b.c.d.Message", - typing_compiler=typing_compiler, - ) - - assert imports == {"from ....b.c import d as ___b_c_d__"} - assert name == '"___b_c_d__.Message"' - - -@pytest.mark.parametrize( - ["full_name", "expected_output"], - [ - ("package.SomeMessage.NestedType", ("package", "SomeMessage.NestedType")), - (".package.SomeMessage.NestedType", ("package", "SomeMessage.NestedType")), - (".service.ExampleRequest", ("service", "ExampleRequest")), - (".package.lower_case_message", ("package", "lower_case_message")), - ], -) -def test_parse_field_type_name(full_name, expected_output): - assert parse_source_type_name(full_name) == expected_output