betterproto2-compiler 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. betterproto2_compiler-0.0.1/LICENSE.md +22 -0
  2. betterproto2_compiler-0.0.1/PKG-INFO +35 -0
  3. betterproto2_compiler-0.0.1/README.md +11 -0
  4. betterproto2_compiler-0.0.1/pyproject.toml +109 -0
  5. betterproto2_compiler-0.0.1/src/betterproto2_compiler/__init__.py +0 -0
  6. betterproto2_compiler-0.0.1/src/betterproto2_compiler/_types.py +13 -0
  7. betterproto2_compiler-0.0.1/src/betterproto2_compiler/casing.py +140 -0
  8. betterproto2_compiler-0.0.1/src/betterproto2_compiler/compile/__init__.py +0 -0
  9. betterproto2_compiler-0.0.1/src/betterproto2_compiler/compile/importing.py +193 -0
  10. betterproto2_compiler-0.0.1/src/betterproto2_compiler/compile/naming.py +21 -0
  11. betterproto2_compiler-0.0.1/src/betterproto2_compiler/enum.py +180 -0
  12. betterproto2_compiler-0.0.1/src/betterproto2_compiler/grpc/__init__.py +0 -0
  13. betterproto2_compiler-0.0.1/src/betterproto2_compiler/grpc/grpclib_client.py +172 -0
  14. betterproto2_compiler-0.0.1/src/betterproto2_compiler/grpc/grpclib_server.py +32 -0
  15. betterproto2_compiler-0.0.1/src/betterproto2_compiler/grpc/util/__init__.py +0 -0
  16. betterproto2_compiler-0.0.1/src/betterproto2_compiler/grpc/util/async_channel.py +190 -0
  17. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/__init__.py +0 -0
  18. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/google/__init__.py +0 -0
  19. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/google/protobuf/__init__.py +1 -0
  20. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/google/protobuf/compiler/__init__.py +1 -0
  21. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/pydantic/__init__.py +0 -0
  22. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/pydantic/google/__init__.py +0 -0
  23. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/pydantic/google/protobuf/__init__.py +2690 -0
  24. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/pydantic/google/protobuf/compiler/__init__.py +209 -0
  25. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/std/__init__.py +0 -0
  26. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/std/google/__init__.py +0 -0
  27. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/std/google/protobuf/__init__.py +2517 -0
  28. betterproto2_compiler-0.0.1/src/betterproto2_compiler/lib/std/google/protobuf/compiler/__init__.py +197 -0
  29. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/__init__.py +3 -0
  30. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/__main__.py +3 -0
  31. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/compiler.py +59 -0
  32. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/main.py +52 -0
  33. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/models.py +709 -0
  34. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/module_validation.py +161 -0
  35. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/parser.py +263 -0
  36. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/plugin.bat +2 -0
  37. betterproto2_compiler-0.0.1/src/betterproto2_compiler/plugin/typing_compiler.py +167 -0
  38. betterproto2_compiler-0.0.1/src/betterproto2_compiler/py.typed +0 -0
  39. betterproto2_compiler-0.0.1/src/betterproto2_compiler/templates/header.py.j2 +50 -0
  40. betterproto2_compiler-0.0.1/src/betterproto2_compiler/templates/template.py.j2 +243 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Daniel G. Taylor
4
+ Copyright (c) 2024 The betterproto contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.1
2
+ Name: betterproto2_compiler
3
+ Version: 0.0.1
4
+ Summary: Compiler for betterproto2
5
+ Home-page: https://github.com/betterproto/python-betterproto2-compiler
6
+ License: MIT
7
+ Keywords: protobuf,gRPC,compiler
8
+ Author: Adrien Vannson
9
+ Author-email: adrien.vannson@protonmail.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: grpclib (>=0.4.1,<0.5.0)
18
+ Requires-Dist: jinja2 (>=3.0.3)
19
+ Requires-Dist: ruff (>=0.7.4,<0.8.0)
20
+ Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
21
+ Project-URL: Repository, https://github.com/betterproto/python-betterproto2-compiler
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Betterproto2 compiler
25
+
26
+ ![](https://github.com/betterproto/python-betterproto2-compiler/actions/workflows/ci.yml/badge.svg)
27
+
28
+
29
+
30
+ ## License
31
+
32
+ Copyright © 2019 Daniel G. Taylor
33
+
34
+ Copyright © 2024 The betterproto contributors
35
+
@@ -0,0 +1,11 @@
1
+ # Betterproto2 compiler
2
+
3
+ ![](https://github.com/betterproto/python-betterproto2-compiler/actions/workflows/ci.yml/badge.svg)
4
+
5
+
6
+
7
+ ## License
8
+
9
+ Copyright © 2019 Daniel G. Taylor
10
+
11
+ Copyright © 2024 The betterproto contributors
@@ -0,0 +1,109 @@
1
+ [tool.poetry]
2
+ name = "betterproto2_compiler"
3
+ version = "0.0.1"
4
+ description = "Compiler for betterproto2"
5
+ authors = ["Adrien Vannson <adrien.vannson@protonmail.com>", "Daniel G. Taylor <danielgtaylor@gmail.com>"]
6
+ readme = "README.md"
7
+ repository = "https://github.com/betterproto/python-betterproto2-compiler"
8
+ keywords = ["protobuf", "gRPC", "compiler"]
9
+ license = "MIT"
10
+ packages = [
11
+ { include = "betterproto2_compiler", from = "src" }
12
+ ]
13
+
14
+ [tool.poetry.dependencies]
15
+ python = "^3.10"
16
+ # betterproto = { git = "https://github.com/betterproto/python-betterproto2", rev = "881bd6e09a809dc61add4ae4ed6a3c70c1fca00a" }
17
+ # The Ruff version is pinned. To update it, also update it in .pre-commit-config.yaml
18
+ ruff = "~0.7.4"
19
+ grpclib = "^0.4.1"
20
+ jinja2 = ">=3.0.3"
21
+ typing-extensions = "^4.7.1"
22
+
23
+ [tool.poetry.group.dev.dependencies]
24
+ jinja2 = ">=3.0.3"
25
+ mypy = "^1.11.2"
26
+ pre-commit = "^2.17.0"
27
+ grpcio-tools = "^1.54.2"
28
+ mkdocs-material = {version = "^9.5.49", python = ">=3.10"}
29
+ mkdocstrings = {version = "^0.27.0", python = ">=3.10", extras = ["python"]}
30
+
31
+ [tool.poetry.group.test.dependencies]
32
+ poethepoet = ">=0.9.0"
33
+ pytest = "^6.2.5"
34
+ protobuf = "^4"
35
+
36
+ [tool.poetry.scripts]
37
+ protoc-gen-python_betterproto = "betterproto2_compiler.plugin:main"
38
+
39
+ [tool.ruff]
40
+ extend-exclude = ["tests/output_*"]
41
+ target-version = "py310"
42
+ line-length = 120
43
+
44
+ [tool.ruff.lint]
45
+ select = [
46
+ "F401", # Unused imports
47
+ "F841", # Unused local variables
48
+ "F821", # Undefined names
49
+ "E501", # Line length violations
50
+
51
+ "SIM101", # Simplify unnecessary if-else blocks
52
+ "SIM102", # Simplify return or yield statements
53
+ "SIM103", # Simplify list/set/dict comprehensions
54
+
55
+ "I",
56
+ ]
57
+
58
+
59
+ [tool.ruff.lint.isort]
60
+ combine-as-imports = true
61
+
62
+ # Dev workflow tasks
63
+
64
+ [tool.poe.tasks.generate]
65
+ script = "tests.generate:main"
66
+ help = "Generate test cases"
67
+
68
+ [tool.poe.tasks.types]
69
+ cmd = "mypy src --ignore-missing-imports"
70
+ help = "Check types with mypy"
71
+
72
+ [tool.poe.tasks.format]
73
+ sequence = ["_format", "_sort-imports"]
74
+ help = "Format the source code, and sort the imports"
75
+
76
+ [tool.poe.tasks.check]
77
+ sequence = ["_check-format", "_check-imports"]
78
+ help = "Check that the source code is formatted and the imports sorted"
79
+
80
+ [tool.poe.tasks._format]
81
+ cmd = "ruff format src tests"
82
+ help = "Format the source code without sorting the imports"
83
+
84
+ [tool.poe.tasks._sort-imports]
85
+ cmd = "ruff check --select I --fix src tests"
86
+ help = "Sort the imports"
87
+
88
+ [tool.poe.tasks._check-format]
89
+ cmd = "ruff format --diff src tests"
90
+ help = "Check that the source code is formatted"
91
+
92
+ [tool.poe.tasks._check-imports]
93
+ cmd = "ruff check --select I src tests"
94
+ help = "Check that the imports are sorted"
95
+
96
+ [tool.poe.tasks.generate_lib]
97
+ cmd = """
98
+ protoc
99
+ --plugin=protoc-gen-custom=src/betterproto2_compiler/plugin/main.py
100
+ --custom_opt=INCLUDE_GOOGLE
101
+ --custom_out=src/betterproto/lib/std
102
+ -I /usr/local/include/
103
+ /usr/local/include/google/protobuf/**/*.proto
104
+ """
105
+ help = "Regenerate the types in betterproto.lib.std.google"
106
+
107
+ [build-system]
108
+ requires = ["poetry-core>=1.0.0,<2"]
109
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,13 @@
1
+ from typing import (
2
+ TYPE_CHECKING,
3
+ TypeVar,
4
+ )
5
+
6
+ if TYPE_CHECKING:
7
+ from grpclib._typing import IProtoMessage
8
+
9
+ from . import Message
10
+
11
+ # Bound type variable to allow methods to return `self` of subclasses
12
+ T = TypeVar("T", bound="Message")
13
+ ST = TypeVar("ST", bound="IProtoMessage")
@@ -0,0 +1,140 @@
1
+ import keyword
2
+ import re
3
+
4
+ # Word delimiters and symbols that will not be preserved when re-casing.
5
+ # language=PythonRegExp
6
+ SYMBOLS = "[^a-zA-Z0-9]*"
7
+
8
+ # Optionally capitalized word.
9
+ # language=PythonRegExp
10
+ WORD = "[A-Z]*[a-z]*[0-9]*"
11
+
12
+ # Uppercase word, not followed by lowercase letters.
13
+ # language=PythonRegExp
14
+ WORD_UPPER = "[A-Z]+(?![a-z])[0-9]*"
15
+
16
+
17
+ def safe_snake_case(value: str) -> str:
18
+ """Snake case a value taking into account Python keywords."""
19
+ value = snake_case(value)
20
+ value = sanitize_name(value)
21
+ return value
22
+
23
+
24
+ def snake_case(value: str, strict: bool = True) -> str:
25
+ """
26
+ Join words with an underscore into lowercase and remove symbols.
27
+
28
+ Parameters
29
+ -----------
30
+ value: :class:`str`
31
+ The value to convert.
32
+ strict: :class:`bool`
33
+ Whether or not to force single underscores.
34
+
35
+ Returns
36
+ --------
37
+ :class:`str`
38
+ The value in snake_case.
39
+ """
40
+
41
+ def substitute_word(symbols: str, word: str, is_start: bool) -> str:
42
+ if not word:
43
+ return ""
44
+ if strict:
45
+ delimiter_count = 0 if is_start else 1 # Single underscore if strict.
46
+ elif is_start:
47
+ delimiter_count = len(symbols)
48
+ elif word.isupper() or word.islower():
49
+ delimiter_count = max(1, len(symbols)) # Preserve all delimiters if not strict.
50
+ else:
51
+ delimiter_count = len(symbols) + 1 # Extra underscore for leading capital.
52
+
53
+ return ("_" * delimiter_count) + word.lower()
54
+
55
+ snake = re.sub(
56
+ f"(^)?({SYMBOLS})({WORD_UPPER}|{WORD})",
57
+ lambda groups: substitute_word(groups[2], groups[3], groups[1] is not None),
58
+ value,
59
+ )
60
+ return snake
61
+
62
+
63
+ def pascal_case(value: str, strict: bool = True) -> str:
64
+ """
65
+ Capitalize each word and remove symbols.
66
+
67
+ Parameters
68
+ -----------
69
+ value: :class:`str`
70
+ The value to convert.
71
+ strict: :class:`bool`
72
+ Whether or not to output only alphanumeric characters.
73
+
74
+ Returns
75
+ --------
76
+ :class:`str`
77
+ The value in PascalCase.
78
+ """
79
+
80
+ def substitute_word(symbols, word):
81
+ if strict:
82
+ return word.capitalize() # Remove all delimiters
83
+
84
+ if word.islower():
85
+ delimiter_length = len(symbols[:-1]) # Lose one delimiter
86
+ else:
87
+ delimiter_length = len(symbols) # Preserve all delimiters
88
+
89
+ return ("_" * delimiter_length) + word.capitalize()
90
+
91
+ return re.sub(
92
+ f"({SYMBOLS})({WORD_UPPER}|{WORD})",
93
+ lambda groups: substitute_word(groups[1], groups[2]),
94
+ value,
95
+ )
96
+
97
+
98
+ def camel_case(value: str, strict: bool = True) -> str:
99
+ """
100
+ Capitalize all words except first and remove symbols.
101
+
102
+ Parameters
103
+ -----------
104
+ value: :class:`str`
105
+ The value to convert.
106
+ strict: :class:`bool`
107
+ Whether or not to output only alphanumeric characters.
108
+
109
+ Returns
110
+ --------
111
+ :class:`str`
112
+ The value in camelCase.
113
+ """
114
+ return lowercase_first(pascal_case(value, strict=strict))
115
+
116
+
117
+ def lowercase_first(value: str) -> str:
118
+ """
119
+ Lower cases the first character of the value.
120
+
121
+ Parameters
122
+ ----------
123
+ value: :class:`str`
124
+ The value to lower case.
125
+
126
+ Returns
127
+ -------
128
+ :class:`str`
129
+ The lower cased string.
130
+ """
131
+ return value[0:1].lower() + value[1:]
132
+
133
+
134
+ def sanitize_name(value: str) -> str:
135
+ # https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
136
+ if keyword.iskeyword(value):
137
+ return f"{value}_"
138
+ if not value.isidentifier():
139
+ return f"_{value}"
140
+ return value
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Dict,
7
+ List,
8
+ Set,
9
+ Tuple,
10
+ Type,
11
+ )
12
+
13
+ from ..casing import safe_snake_case
14
+ from ..lib.google import protobuf as google_protobuf
15
+ from .naming import pythonize_class_name
16
+
17
+ if TYPE_CHECKING:
18
+ from ..plugin.models import PluginRequestCompiler
19
+ from ..plugin.typing_compiler import TypingCompiler
20
+
21
+ WRAPPER_TYPES: Dict[str, Type] = {
22
+ ".google.protobuf.DoubleValue": google_protobuf.DoubleValue,
23
+ ".google.protobuf.FloatValue": google_protobuf.FloatValue,
24
+ ".google.protobuf.Int32Value": google_protobuf.Int32Value,
25
+ ".google.protobuf.Int64Value": google_protobuf.Int64Value,
26
+ ".google.protobuf.UInt32Value": google_protobuf.UInt32Value,
27
+ ".google.protobuf.UInt64Value": google_protobuf.UInt64Value,
28
+ ".google.protobuf.BoolValue": google_protobuf.BoolValue,
29
+ ".google.protobuf.StringValue": google_protobuf.StringValue,
30
+ ".google.protobuf.BytesValue": google_protobuf.BytesValue,
31
+ }
32
+
33
+
34
+ def parse_source_type_name(field_type_name: str, request: "PluginRequestCompiler") -> Tuple[str, str]:
35
+ """
36
+ Split full source type name into package and type name.
37
+ E.g. 'root.package.Message' -> ('root.package', 'Message')
38
+ 'root.Message.SomeEnum' -> ('root', 'Message.SomeEnum')
39
+
40
+ The function goes through the symbols that have been defined (names, enums,
41
+ packages) to find the actual package and name of the object that is referenced.
42
+ """
43
+ if field_type_name[0] != ".":
44
+ raise RuntimeError("relative names are not supported")
45
+ field_type_name = field_type_name[1:]
46
+ parts = field_type_name.split(".")
47
+
48
+ answer = None
49
+
50
+ # a.b.c:
51
+ # i=0: "", "a.b.c"
52
+ # i=1: "a", "b.c"
53
+ # i=2: "a.b", "c"
54
+ for i in range(len(parts)):
55
+ package_name, object_name = ".".join(parts[:i]), ".".join(parts[i:])
56
+
57
+ package = request.output_packages.get(package_name)
58
+
59
+ if not package:
60
+ continue
61
+
62
+ if object_name in package.messages or object_name in package.enums:
63
+ if answer:
64
+ # This should have already been handeled by protoc
65
+ raise ValueError(f"ambiguous definition: {field_type_name}")
66
+ answer = package_name, object_name
67
+
68
+ if answer:
69
+ return answer
70
+
71
+ raise ValueError(f"can't find type name: {field_type_name}")
72
+
73
+
74
+ def get_type_reference(
75
+ *,
76
+ package: str,
77
+ imports: set,
78
+ source_type: str,
79
+ typing_compiler: TypingCompiler,
80
+ request: "PluginRequestCompiler",
81
+ unwrap: bool = True,
82
+ pydantic: bool = False,
83
+ ) -> str:
84
+ """
85
+ Return a Python type name for a proto type reference. Adds the import if
86
+ necessary. Unwraps well known type if required.
87
+ """
88
+ if unwrap:
89
+ if source_type in WRAPPER_TYPES:
90
+ wrapped_type = type(WRAPPER_TYPES[source_type]().value)
91
+ return typing_compiler.optional(wrapped_type.__name__)
92
+
93
+ if source_type == ".google.protobuf.Duration":
94
+ return "datetime.timedelta"
95
+
96
+ elif source_type == ".google.protobuf.Timestamp":
97
+ return "datetime.datetime"
98
+
99
+ source_package, source_type = parse_source_type_name(source_type, request)
100
+
101
+ current_package: List[str] = package.split(".") if package else []
102
+ py_package: List[str] = source_package.split(".") if source_package else []
103
+ py_type: str = pythonize_class_name(source_type)
104
+
105
+ compiling_google_protobuf = current_package == ["google", "protobuf"]
106
+ importing_google_protobuf = py_package == ["google", "protobuf"]
107
+ if importing_google_protobuf and not compiling_google_protobuf:
108
+ py_package = ["betterproto", "lib"] + (["pydantic"] if pydantic else []) + py_package
109
+
110
+ if py_package[:1] == ["betterproto"]:
111
+ return reference_absolute(imports, py_package, py_type)
112
+
113
+ if py_package == current_package:
114
+ return reference_sibling(py_type)
115
+
116
+ if py_package[: len(current_package)] == current_package:
117
+ return reference_descendent(current_package, imports, py_package, py_type)
118
+
119
+ if current_package[: len(py_package)] == py_package:
120
+ return reference_ancestor(current_package, imports, py_package, py_type)
121
+
122
+ return reference_cousin(current_package, imports, py_package, py_type)
123
+
124
+
125
+ def reference_absolute(imports: Set[str], py_package: List[str], py_type: str) -> str:
126
+ """
127
+ Returns a reference to a python type located in the root, i.e. sys.path.
128
+ """
129
+ string_import = ".".join(py_package)
130
+ string_alias = safe_snake_case(string_import)
131
+ imports.add(f"import {string_import} as {string_alias}")
132
+ return f"{string_alias}.{py_type}"
133
+
134
+
135
+ def reference_sibling(py_type: str) -> str:
136
+ """
137
+ Returns a reference to a python type within the same package as the current package.
138
+ """
139
+ return f"{py_type}"
140
+
141
+
142
+ def reference_descendent(current_package: List[str], imports: Set[str], py_package: List[str], py_type: str) -> str:
143
+ """
144
+ Returns a reference to a python type in a package that is a descendent of the
145
+ current package, and adds the required import that is aliased to avoid name
146
+ conflicts.
147
+ """
148
+ importing_descendent = py_package[len(current_package) :]
149
+ string_from = ".".join(importing_descendent[:-1])
150
+ string_import = importing_descendent[-1]
151
+ if string_from:
152
+ string_alias = "_".join(importing_descendent)
153
+ imports.add(f"from .{string_from} import {string_import} as {string_alias}")
154
+ return f"{string_alias}.{py_type}"
155
+ else:
156
+ imports.add(f"from . import {string_import}")
157
+ return f"{string_import}.{py_type}"
158
+
159
+
160
+ def reference_ancestor(current_package: List[str], imports: Set[str], py_package: List[str], py_type: str) -> str:
161
+ """
162
+ Returns a reference to a python type in a package which is an ancestor to the
163
+ current package, and adds the required import that is aliased (if possible) to avoid
164
+ name conflicts.
165
+
166
+ Adds trailing __ to avoid name mangling (python.org/dev/peps/pep-0008/#id34).
167
+ """
168
+ distance_up = len(current_package) - len(py_package)
169
+ if py_package:
170
+ string_import = py_package[-1]
171
+ string_alias = f"_{'_' * distance_up}{string_import}__"
172
+ string_from = f"..{'.' * distance_up}"
173
+ imports.add(f"from {string_from} import {string_import} as {string_alias}")
174
+ return f"{string_alias}.{py_type}"
175
+ else:
176
+ string_alias = f"{'_' * distance_up}{py_type}__"
177
+ imports.add(f"from .{'.' * distance_up} import {py_type} as {string_alias}")
178
+ return string_alias
179
+
180
+
181
+ def reference_cousin(current_package: List[str], imports: Set[str], py_package: List[str], py_type: str) -> str:
182
+ """
183
+ Returns a reference to a python type in a package that is not descendent, ancestor
184
+ or sibling, and adds the required import that is aliased to avoid name conflicts.
185
+ """
186
+ shared_ancestry = os.path.commonprefix([current_package, py_package]) # type: ignore
187
+ distance_up = len(current_package) - len(shared_ancestry)
188
+ string_from = f".{'.' * distance_up}" + ".".join(py_package[len(shared_ancestry) : -1])
189
+ string_import = py_package[-1]
190
+ # Add trailing __ to avoid name mangling (python.org/dev/peps/pep-0008/#id34)
191
+ string_alias = f"{'_' * distance_up}" + safe_snake_case(".".join(py_package[len(shared_ancestry) :])) + "__"
192
+ imports.add(f"from {string_from} import {string_import} as {string_alias}")
193
+ return f"{string_alias}.{py_type}"
@@ -0,0 +1,21 @@
1
+ from betterproto2_compiler import casing
2
+
3
+
4
+ def pythonize_class_name(name: str) -> str:
5
+ return casing.pascal_case(name)
6
+
7
+
8
+ def pythonize_field_name(name: str) -> str:
9
+ return casing.safe_snake_case(name)
10
+
11
+
12
+ def pythonize_method_name(name: str) -> str:
13
+ return casing.safe_snake_case(name)
14
+
15
+
16
+ def pythonize_enum_member_name(name: str, enum_name: str) -> str:
17
+ enum_name = casing.snake_case(enum_name).upper()
18
+ find = name.find(enum_name)
19
+ if find != -1:
20
+ name = name[find + len(enum_name) :].strip("_")
21
+ return casing.sanitize_name(name)