protoc-gen-sqlmodel 0.1.0__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.
- protoc_gen_sqlmodel-0.1.0/PKG-INFO +19 -0
- protoc_gen_sqlmodel-0.1.0/README.md +6 -0
- protoc_gen_sqlmodel-0.1.0/pyproject.toml +23 -0
- protoc_gen_sqlmodel-0.1.0/pyproject.toml.orig +22 -0
- protoc_gen_sqlmodel-0.1.0/src/protoc_gen_sqlmodel/__init__.py +7 -0
- protoc_gen_sqlmodel-0.1.0/src/protoc_gen_sqlmodel/plugin.py +237 -0
- protoc_gen_sqlmodel-0.1.0/src/protoc_gen_sqlmodel/proto/__init__.py +1 -0
- protoc_gen_sqlmodel-0.1.0/src/protoc_gen_sqlmodel/proto/sqlmodel_extensions.proto +15 -0
- protoc_gen_sqlmodel-0.1.0/src/protoc_gen_sqlmodel/proto/sqlmodel_extensions_pb.py +48 -0
|
@@ -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,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "protoc-gen-sqlmodel"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"logging>=0.4.9.6",
|
|
9
|
+
"protobuf-py>=0.4.0",
|
|
10
|
+
"protoc-gen-py>=0.4.0",
|
|
11
|
+
"sqlmodel>=0.0.42",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[project.authors]]
|
|
15
|
+
name = "river-studio-main"
|
|
16
|
+
email = "62842368+river-studio-main@users.noreply.github.com"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
protoc-gen-sqlmodel = "protoc_gen_sqlmodel:main"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "protoc-gen-sqlmodel"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "river-studio-main", email = "62842368+river-studio-main@users.noreply.github.com" },
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"logging>=0.4.9.6",
|
|
12
|
+
"protobuf-py>=0.4.0",
|
|
13
|
+
"protoc-gen-py>=0.4.0",
|
|
14
|
+
"sqlmodel>=0.0.42",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
protoc-gen-sqlmodel = "protoc_gen_sqlmodel:main"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
@@ -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
|