protoc-gen-sqlmodel 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,7 @@
1
+ from protobuf.plugin import run
2
+
3
+ from .plugin import generate
4
+
5
+
6
+ def main():
7
+ run("protoc-gen-sqlmodel", "0.1.0", generate)
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This plugin parses protobuf files into SQLModel classes.
4
+ The plugin system exposes the following structure, used as a base for functional parsing:
5
+
6
+ Schema
7
+ └── DescFile
8
+ ├── extensions Sequence[DescExtension]
9
+ ├── dependencies Sequence[DescFile]
10
+ ├── enums Sequence[DescEnum]
11
+ │ └── values Sequence[DescEnumValue]
12
+ ├── messages Sequence[DescMessage]
13
+ │ ├── fields Sequence[DescField]
14
+ │ ├── nested_enums Sequence[DescEnum]
15
+ │ ├── nested_messages Sequence[DescMessage]
16
+ │ └── oneofs Sequence[DescOneof]
17
+ └── Sequence[DescService]
18
+ └── ...
19
+
20
+ Services are skipped at the moment.
21
+ """
22
+
23
+ from logging import getLogger
24
+
25
+ from protobuf import (
26
+ DescEnum,
27
+ DescExtension,
28
+ DescField,
29
+ DescFieldValueEnum,
30
+ DescFieldValueList,
31
+ DescFieldValueMap,
32
+ DescFieldValueMessage,
33
+ DescFieldValueScalar,
34
+ DescMessage,
35
+ DescOneof,
36
+ ScalarType,
37
+ )
38
+ from protobuf._descriptors import SupportedFieldPresence
39
+ from protobuf.plugin import File, Module, Schema
40
+
41
+ ENUM = Module("enum").ident("Enum")
42
+ FIELD = Module("sqlmodel").ident("Field")
43
+ SQLMODEL = Module("sqlmodel").ident("SQLModel")
44
+ ANY = Module("typing").ident("Any")
45
+ FINAL = Module("typing").ident("Final")
46
+ EXTENSION = Module("protobuf").ident("Extension")
47
+ FIELD_OPT = Module("protobuf.wkt").ident("FieldOptions")
48
+ MESSAGE_OPT = Module("protobuf.wkt").ident("MessageOptions")
49
+
50
+
51
+ logger = getLogger()
52
+
53
+
54
+ def get_presence(presence: SupportedFieldPresence) -> str:
55
+ match presence:
56
+ case SupportedFieldPresence.EXPLICIT:
57
+ return " | None "
58
+ case SupportedFieldPresence.IMPLICIT:
59
+ return ""
60
+ case _:
61
+ return ""
62
+
63
+
64
+ def get_python_scalar_type(scalar: ScalarType):
65
+ match scalar:
66
+ case ScalarType.BOOL:
67
+ return "bool"
68
+ case ScalarType.BYTES:
69
+ return "bytes"
70
+ case (
71
+ ScalarType.DOUBLE
72
+ | ScalarType.FIXED32
73
+ | ScalarType.FIXED64
74
+ | ScalarType.FLOAT
75
+ | ScalarType.SFIXED32
76
+ | ScalarType.SFIXED64
77
+ ):
78
+ return "float"
79
+ case (
80
+ ScalarType.INT32
81
+ | ScalarType.INT64
82
+ | ScalarType.UINT32
83
+ | ScalarType.UINT64
84
+ | ScalarType.SINT32
85
+ | ScalarType.SINT64
86
+ ):
87
+ return "int"
88
+ case ScalarType.STRING:
89
+ return "str"
90
+
91
+
92
+ def handle_leaf_enum(desc: DescEnum, f: File):
93
+ with f.scope(f"class {desc.name}(", ENUM, ", int):"):
94
+ for v in desc.values:
95
+ f.print(f"{v.local_name} = {v.number}")
96
+ f.print()
97
+
98
+
99
+ def handle_leaf_oneof(desc: DescOneof, f: File):
100
+ f.print(f"# oneof {desc.local_name}")
101
+ distinguisher = f"which_{desc.local_name}: "
102
+ f.print(
103
+ distinguisher,
104
+ Module("typing").ident("Literal"),
105
+ "['",
106
+ "', '".join([field.local_name for field in desc.fields]),
107
+ "']",
108
+ )
109
+ for field in desc.fields:
110
+ handle_leaf_field(field, f)
111
+ f.print()
112
+
113
+
114
+ def handle_import(element: DescEnum | DescMessage):
115
+ if element.file.proto.package == "google.protobuf": # well known types
116
+ match element.name:
117
+ case "Timestamp":
118
+ return Module("datetime").ident("datetime")
119
+ case "Any":
120
+ return Module("typing").ident("Any")
121
+ return Module.for_desc(element.file, "_sqlmodel").ident(element.name)
122
+
123
+
124
+ def handle_leaf_field(desc: DescField, f: File):
125
+ field_name, field_presence = desc.name, get_presence(desc.presence)
126
+ field_preamble, field_presence_preamble = f"{field_name}: ", f"{field_presence}= "
127
+ print_components = [field_preamble]
128
+
129
+ match desc.value:
130
+ case DescFieldValueScalar(scalar, default_value, _):
131
+ print_components.append(get_python_scalar_type(scalar))
132
+ if default_value:
133
+ print_components.extend(
134
+ [field_presence_preamble, FIELD, f"(default={default_value})"]
135
+ )
136
+ else:
137
+ print_components.append(field_presence)
138
+ case DescFieldValueMessage(message, _, _):
139
+ print_components.extend([handle_import(message), field_presence])
140
+ case DescFieldValueEnum(enum, default_value, _):
141
+ print_components.append(handle_import(enum))
142
+ if default_value is not None: # can be 0
143
+ print_components.extend(
144
+ [field_presence_preamble, FIELD, f"(default={default_value})"]
145
+ )
146
+ else:
147
+ print_components.append(field_presence)
148
+ case DescFieldValueMap(key, value):
149
+ print_components.append(f"dict[{get_python_scalar_type(key)}, ")
150
+ match value:
151
+ case ScalarType():
152
+ print_components.append(f"{get_python_scalar_type(value)}]")
153
+ case DescMessage() | DescEnum():
154
+ print_components.extend([handle_import(value), "]"])
155
+ print_components.append(field_presence)
156
+ case DescFieldValueList(element, _, _):
157
+ print_components.append("list[")
158
+ match element:
159
+ case ScalarType():
160
+ print_components.append(f"{get_python_scalar_type(element)}]")
161
+ case DescEnum() | DescMessage():
162
+ print_components.extend(
163
+ [
164
+ handle_import(element),
165
+ "]",
166
+ field_presence,
167
+ ] # is this a bug? shouldn't field_presence be added to the scalar case too?
168
+ )
169
+ # if opts := desc.proto.options:
170
+ # from .sqlmodel_extensions_pb import ext_sqlmodel_type
171
+
172
+ # if ext_sqlmodel_type in opts:
173
+ # print_components.extend(
174
+ # [
175
+ # field_presence_preamble,
176
+ # FIELD,
177
+ # f"(sa_column={handle_import(opts[ext_sqlmodel_type])})",
178
+ # ]
179
+ # )
180
+ f.print(*print_components)
181
+
182
+
183
+ def handle_message(desc: DescMessage, f: File, root: bool = True):
184
+ class_suffix = "):"
185
+ if opts := desc.proto.options:
186
+ from .proto.sqlmodel_extensions_pb import ext_table
187
+
188
+ class_suffix = ", table=True):" if ext_table in opts else "):"
189
+ with f.scope(f"class {desc.name}(", SQLMODEL, class_suffix):
190
+ for nm in desc.nested_enums:
191
+ handle_leaf_enum(nm, f)
192
+ for nm in desc.nested_messages:
193
+ handle_message(nm, f, False)
194
+ for field in desc.fields:
195
+ handle_leaf_field(field, f)
196
+ for oneof in desc.oneofs:
197
+ handle_leaf_oneof(oneof, f)
198
+ f.print()
199
+ if root:
200
+ f.print()
201
+
202
+
203
+ def handle_extension(desc: DescExtension, f: File):
204
+ match desc.extendee.type_name:
205
+ case "google.protobuf.MessageOptions":
206
+ ext = MESSAGE_OPT
207
+ case "google.protobuf.FieldOptions":
208
+ ext = FIELD_OPT
209
+ match desc.value:
210
+ case DescFieldValueScalar(scalar, _, _):
211
+ ext_type = get_python_scalar_type(scalar)
212
+
213
+ f.print(
214
+ f"ext_{desc.name}: ",
215
+ FINAL,
216
+ "[",
217
+ EXTENSION,
218
+ "[",
219
+ ext,
220
+ ", ",
221
+ ext_type,
222
+ "]] = ",
223
+ EXTENSION,
224
+ "()",
225
+ )
226
+
227
+
228
+ def generate(schema: Schema) -> None:
229
+ for desc in schema.files_to_generate:
230
+ f = schema.generate_file(desc, "_sqlmodel.py")
231
+ f.preamble(desc)
232
+ for ext in desc.extensions:
233
+ handle_extension(ext, f)
234
+ for e in desc.enums:
235
+ handle_leaf_enum(e, f)
236
+ for m in desc.messages:
237
+ handle_message(m, f)
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,15 @@
1
+ syntax = "proto3";
2
+
3
+ package example.sqlmodel;
4
+
5
+ import "google/protobuf/descriptor.proto";
6
+
7
+ // Marks a protobuf message as a SQLModel table.
8
+ extend google.protobuf.MessageOptions {
9
+ bool table = 50001;
10
+ }
11
+
12
+ // Overrides the default protobuf-to-SQLModel field conversion.
13
+ extend google.protobuf.FieldOptions {
14
+ string sqlmodel_type = 50002;
15
+ }
@@ -0,0 +1,48 @@
1
+ # Generated from sqlmodel_extensions.proto. DO NOT EDIT.
2
+ # Generated by protoc-gen-py v0.4.0 with parameter "".
3
+ # ruff: noqa: PGH004
4
+ # ruff: noqa
5
+ # fmt: off
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Final, TYPE_CHECKING
10
+
11
+ from protobuf import Extension
12
+ from protobuf._codegen import file_desc
13
+ from protobuf.wkt import FieldOptions, MessageOptions, descriptor_pb
14
+
15
+ if TYPE_CHECKING:
16
+ from protobuf import DescFile
17
+
18
+
19
+ ext_table: Final[Extension[MessageOptions, bool]] = Extension()
20
+ """
21
+ ```proto
22
+ extend google.protobuf.MessageOptions { optional bool table = 50001 [json_name = "[example.sqlmodel.table]"] }
23
+ ```
24
+ """
25
+
26
+ ext_sqlmodel_type: Final[Extension[FieldOptions, str]] = Extension()
27
+ """
28
+ ```proto
29
+ extend google.protobuf.FieldOptions { optional string sqlmodel_type = 50002 [json_name = "[example.sqlmodel.sqlmodel_type]"] }
30
+ ```
31
+ """
32
+
33
+
34
+ _DESC = file_desc(
35
+ b'\n\x19sqlmodel_extensions.proto\x12\x10example.sqlmodel\x1a google/protobuf/descriptor.proto:7\n\x05table\x18\xd1\x86\x03 \x01(\x08\x12\x1f.google.protobuf.MessageOptionsR\x05table:D\n\rsqlmodel_type\x18\xd2\x86\x03 \x01(\t\x12\x1d.google.protobuf.FieldOptionsR\x0csqlmodelTypeb\x06proto3',
36
+ [
37
+ descriptor_pb.desc(),
38
+ ],
39
+ {
40
+ "table": ext_table,
41
+ "sqlmodel_type": ext_sqlmodel_type,
42
+ },
43
+ )
44
+
45
+
46
+ def desc() -> DescFile:
47
+ """Returns the descriptor for the file `sqlmodel_extensions.proto`."""
48
+ return _DESC
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.3
2
+ Name: protoc-gen-sqlmodel
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author: river-studio-main
6
+ Author-email: river-studio-main <62842368+river-studio-main@users.noreply.github.com>
7
+ Requires-Dist: logging>=0.4.9.6
8
+ Requires-Dist: protobuf-py>=0.4.0
9
+ Requires-Dist: protoc-gen-py>=0.4.0
10
+ Requires-Dist: sqlmodel>=0.0.42
11
+ Requires-Python: >=3.14
12
+ Description-Content-Type: text/markdown
13
+
14
+ # protoc-gen-sqlmodel
15
+ A protoc plugin for producing sqlmodel classes
16
+ Run with:
17
+ ```shell
18
+ uv run protoc input.proto --plugin=protoc-gen-custom-plugin=./plugin.py --custom-plugin_out=.
19
+ ```
@@ -0,0 +1,9 @@
1
+ protoc_gen_sqlmodel/__init__.py,sha256=2mk05i4auYnmvdAXeUM2g9rhQrnngiH81GzxvQbs-90,126
2
+ protoc_gen_sqlmodel/plugin.py,sha256=QN5wjuxx7LlhuwSUp5F0v09lRRlHRwgSrua8MdsHODg,7716
3
+ protoc_gen_sqlmodel/proto/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
+ protoc_gen_sqlmodel/proto/sqlmodel_extensions.proto,sha256=u_7NfuoG3mMEimRGWWB1veWnP8HvUXObPHvKhkjcGVA,341
5
+ protoc_gen_sqlmodel/proto/sqlmodel_extensions_pb.py,sha256=lNXhthzIyPkhPn8g2Q-Oc1FPKSvFO762aZ-CWPZv9ds,1410
6
+ protoc_gen_sqlmodel-0.1.0.dist-info/WHEEL,sha256=mru_b36sH6joUMnwf7IlFCun3RoDjrNg9RcfBmEcqsE,81
7
+ protoc_gen_sqlmodel-0.1.0.dist-info/entry_points.txt,sha256=Ylw6qZzYikYLO18g7bvP3fF1VZhSWWIcJWcVfBk--Rc,66
8
+ protoc_gen_sqlmodel-0.1.0.dist-info/METADATA,sha256=7FuhaBdxZCaqwPOlgZ91xwdk_pcpwMF3pI6dTxxLO9g,596
9
+ protoc_gen_sqlmodel-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.11
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ protoc-gen-sqlmodel = protoc_gen_sqlmodel:main
3
+