sera-2 1.21.2__py3-none-any.whl → 1.24.1__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,369 @@
1
+ from __future__ import annotations
2
+
3
+ from codegen.models import ImportHelper, PredefinedFn, Program, expr, stmt
4
+
5
+ from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
6
+ from sera.misc import assert_not_null, to_camel_case, to_pascal_case
7
+ from sera.models import (
8
+ Class,
9
+ DataProperty,
10
+ Enum,
11
+ ObjectProperty,
12
+ Package,
13
+ Schema,
14
+ TsTypeWithDep,
15
+ )
16
+ from sera.typing import is_set
17
+
18
+
19
+ def make_class_schema(schema: Schema, cls: Class, pkg: Package):
20
+ """Make schema definition for the class in frontend so that components can use this information select
21
+ appropriate components to display or edit the data.
22
+
23
+ Args:
24
+ schema: The overall schema of the application, which contains all classes & enums
25
+ cls: The class that we want to generate the schema
26
+ pkg: The output package (directory) for the class in the `@.models` package. For example, if the
27
+ class is `User`, then the package would be `src/models/user`.
28
+
29
+ Returns:
30
+ This function do not return anything as it writes the schema directly to a file.
31
+ """
32
+ if not cls.is_public:
33
+ # skip classes that are not public
34
+ return
35
+
36
+ program = Program()
37
+ prop_defs: list[tuple[DataProperty | ObjectProperty, expr.Expr, expr.Expr]] = []
38
+ prop_normalizers: list[tuple[expr.Expr, expr.Expr]] = []
39
+
40
+ import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
41
+
42
+ for prop in cls.properties.values():
43
+ # we must include private properties that are needed during upsert for our forms.
44
+ # if prop.data.is_private:
45
+ # # skip private fields as this is for APIs exchange
46
+ # continue
47
+ tspropname = to_camel_case(prop.name)
48
+ pypropname = prop.name
49
+ if isinstance(prop, ObjectProperty) and prop.target.db is not None:
50
+ # this is a database object, we append id to the property name
51
+ tspropname = tspropname + "Id"
52
+ pypropname = prop.name + "_id"
53
+
54
+ tsprop = {}
55
+
56
+ if isinstance(prop, DataProperty):
57
+ tstype = prop.get_data_model_datatype().get_typescript_type()
58
+ # for schema definition, we need to use the original type, not the type alias
59
+ # if prop.name == idprop.name:
60
+ # # use id type alias
61
+ # tstype = TsTypeWithDep(f"{cls.name}Id")
62
+ for dep in tstype.deps:
63
+ program.import_(dep, True)
64
+ tsprop = [
65
+ (
66
+ expr.ExprIdent("datatype"),
67
+ (
68
+ expr.ExprConstant(tstype.spectype)
69
+ if tstype.type not in schema.enums
70
+ else expr.ExprConstant("enum")
71
+ ),
72
+ ),
73
+ *(
74
+ [
75
+ (
76
+ expr.ExprIdent("enumType"),
77
+ export_enum_info(program, schema.enums[tstype.type]),
78
+ )
79
+ ]
80
+ if tstype.type in schema.enums
81
+ else []
82
+ ),
83
+ *(
84
+ [
85
+ (
86
+ expr.ExprIdent("foreignKeyTarget"),
87
+ expr.ExprConstant(prop.db.foreign_key.name),
88
+ )
89
+ ]
90
+ if prop.db is not None
91
+ and prop.db.is_primary_key
92
+ and prop.db.foreign_key is not None
93
+ else []
94
+ ),
95
+ (
96
+ expr.ExprIdent("isRequired"),
97
+ expr.ExprConstant(
98
+ not prop.is_optional
99
+ and prop.default_value is None
100
+ and prop.default_factory is None
101
+ ),
102
+ ),
103
+ ]
104
+
105
+ norm_func = get_normalizer(tstype, import_helper)
106
+ if norm_func is not None:
107
+ # we have a normalizer for this type
108
+ prop_normalizers.append((expr.ExprIdent(tspropname), norm_func))
109
+ else:
110
+ assert isinstance(prop, ObjectProperty)
111
+ if prop.target.db is not None:
112
+ # this class is stored in the database, we store the id instead
113
+ tstype = (
114
+ assert_not_null(prop.target.get_id_property())
115
+ .get_data_model_datatype()
116
+ .get_typescript_type()
117
+ )
118
+ else:
119
+ # we are going to store the whole object
120
+ tstype = TsTypeWithDep(
121
+ type=prop.target.name,
122
+ spectype=prop.target.name,
123
+ deps=[
124
+ f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}"
125
+ ],
126
+ )
127
+
128
+ # we don't store the type itself, but just the name of the type
129
+ # so not need to import the dependency
130
+ # if tstype.dep is not None:
131
+ # program.import_(
132
+ # tstype.dep,
133
+ # True,
134
+ # )
135
+
136
+ tsprop = [
137
+ (
138
+ expr.ExprIdent("targetClass"),
139
+ expr.ExprConstant(prop.target.name),
140
+ ),
141
+ (
142
+ expr.ExprIdent("datatype"),
143
+ expr.ExprConstant(
144
+ tstype.spectype if prop.target.db is not None else "undefined"
145
+ ),
146
+ ),
147
+ (
148
+ expr.ExprIdent("cardinality"),
149
+ expr.ExprConstant(prop.cardinality.value),
150
+ ),
151
+ (
152
+ expr.ExprIdent("isEmbedded"),
153
+ expr.ExprConstant(prop.target.db is None),
154
+ ),
155
+ (
156
+ expr.ExprIdent("isRequired"),
157
+ expr.ExprConstant(not prop.is_optional),
158
+ ),
159
+ ]
160
+
161
+ prop_defs.append(
162
+ (
163
+ prop,
164
+ expr.ExprIdent(tspropname),
165
+ PredefinedFn.dict(
166
+ [
167
+ (expr.ExprIdent("name"), expr.ExprConstant(pypropname)),
168
+ (expr.ExprIdent("tsName"), expr.ExprConstant(tspropname)),
169
+ (
170
+ expr.ExprIdent("updateFuncName"),
171
+ expr.ExprConstant(f"update{to_pascal_case(prop.name)}"),
172
+ ),
173
+ (
174
+ expr.ExprIdent("label"),
175
+ expr.ExprConstant(prop.label.to_dict()),
176
+ ),
177
+ (
178
+ expr.ExprIdent("description"),
179
+ (
180
+ expr.ExprConstant(prop.description.to_dict())
181
+ if not prop.description.is_empty()
182
+ else expr.ExprConstant("undefined")
183
+ ),
184
+ ),
185
+ (
186
+ expr.ExprIdent("constraints"),
187
+ PredefinedFn.list(
188
+ [
189
+ expr.ExprConstant(
190
+ constraint.get_typescript_constraint()
191
+ )
192
+ for constraint in prop.data.constraints
193
+ ]
194
+ ),
195
+ ),
196
+ ]
197
+ + tsprop
198
+ ),
199
+ )
200
+ )
201
+
202
+ for type in ["ObjectProperty", "DataProperty"]:
203
+ program.import_(f"sera-db.{type}", True)
204
+ if cls.db is not None:
205
+ program.import_(f"sera-db.Schema", True)
206
+ else:
207
+ program.import_(f"sera-db.EmbeddedSchema", True)
208
+
209
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
210
+ program.import_(f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True)
211
+ program.import_(
212
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.draft{cls.name}Validators", True
213
+ )
214
+ if cls.db is not None:
215
+ program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id", True)
216
+
217
+ program.root(
218
+ stmt.LineBreak(),
219
+ stmt.TypescriptStatement(
220
+ f"export type {cls.name}SchemaType = "
221
+ + PredefinedFn.dict(
222
+ (
223
+ [
224
+ (expr.ExprIdent("id"), expr.ExprIdent(f"{cls.name}Id")),
225
+ ]
226
+ if cls.db is not None
227
+ else []
228
+ )
229
+ + [
230
+ (
231
+ expr.ExprIdent("publicProperties"),
232
+ expr.ExprIdent(
233
+ " | ".join(
234
+ [
235
+ expr.ExprConstant(
236
+ to_camel_case(prop.name) + "Id"
237
+ if isinstance(prop, ObjectProperty)
238
+ and prop.target.db is not None
239
+ else to_camel_case(prop.name)
240
+ ).to_typescript()
241
+ for prop in cls.properties.values()
242
+ if not prop.data.is_private
243
+ ]
244
+ )
245
+ ),
246
+ ),
247
+ (
248
+ expr.ExprIdent("allProperties"),
249
+ expr.ExprIdent(
250
+ f"{cls.name}SchemaType['publicProperties']"
251
+ + (
252
+ " | "
253
+ + " | ".join(
254
+ [
255
+ expr.ExprConstant(
256
+ to_camel_case(prop.name)
257
+ ).to_typescript()
258
+ for prop in cls.properties.values()
259
+ if prop.data.is_private
260
+ ]
261
+ )
262
+ if any(
263
+ prop.data.is_private
264
+ for prop in cls.properties.values()
265
+ )
266
+ else ""
267
+ )
268
+ ),
269
+ ),
270
+ (
271
+ expr.ExprIdent("cls"),
272
+ expr.ExprIdent(cls.name),
273
+ ),
274
+ (
275
+ expr.ExprIdent("draftCls"),
276
+ expr.ExprIdent(f"Draft{cls.name}"),
277
+ ),
278
+ ]
279
+ ).to_typescript()
280
+ + ";",
281
+ ),
282
+ stmt.LineBreak(),
283
+ stmt.TypescriptStatement(
284
+ f"const publicProperties: Record<{cls.name}SchemaType['publicProperties'], DataProperty | ObjectProperty> = "
285
+ + PredefinedFn.dict(
286
+ [
287
+ (prop_name, prop_def)
288
+ for prop, prop_name, prop_def in prop_defs
289
+ if not prop.data.is_private
290
+ ]
291
+ ).to_typescript()
292
+ + ";"
293
+ ),
294
+ stmt.LineBreak(),
295
+ stmt.TypescriptStatement(
296
+ (
297
+ f"export const {cls.name}Schema: Schema<{cls.name}SchemaType['id'], {cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties'], {cls.name}SchemaType> = "
298
+ if cls.db is not None
299
+ else f"export const {cls.name}Schema: EmbeddedSchema<{cls.name}SchemaType['cls'], {cls.name}SchemaType['draftCls'], {cls.name}SchemaType['publicProperties'], {cls.name}SchemaType['allProperties']> = "
300
+ )
301
+ + PredefinedFn.dict(
302
+ [
303
+ (
304
+ expr.ExprIdent("publicProperties"),
305
+ expr.ExprIdent("publicProperties"),
306
+ ),
307
+ (
308
+ expr.ExprIdent("allProperties"),
309
+ expr.ExprIdent(
310
+ "{ ...publicProperties, "
311
+ + ", ".join(
312
+ [
313
+ f"{prop_name.to_typescript()}: {prop_def.to_typescript()}"
314
+ for prop, prop_name, prop_def in prop_defs
315
+ if prop.data.is_private
316
+ ]
317
+ )
318
+ + "}"
319
+ ),
320
+ ),
321
+ (
322
+ expr.ExprIdent("validators"),
323
+ expr.ExprIdent(f"draft{cls.name}Validators"),
324
+ ),
325
+ (
326
+ expr.ExprIdent("normalizers"),
327
+ PredefinedFn.dict(prop_normalizers),
328
+ ),
329
+ ]
330
+ + (
331
+ [
332
+ (
333
+ expr.ExprIdent("primaryKey"),
334
+ expr.ExprConstant(
335
+ assert_not_null(cls.get_id_property()).name
336
+ ),
337
+ )
338
+ ]
339
+ if cls.db is not None
340
+ else []
341
+ )
342
+ ).to_typescript()
343
+ + ";"
344
+ ),
345
+ )
346
+ pkg.module(cls.name + "Schema").write(program)
347
+
348
+
349
+ def export_enum_info(program: Program, enum: Enum) -> expr.Expr:
350
+ """Export enum information to
351
+
352
+ ```
353
+ {
354
+ type: <EnumType>,
355
+ label: { [value]: MultiLingualString },
356
+ description: { [value]: MultiLingualString }
357
+ }
358
+ ```
359
+ """
360
+ for key in ["Label", "Description"]:
361
+ program.import_(f"@.models.enums.{enum.name}{key}", True)
362
+
363
+ return PredefinedFn.dict(
364
+ [
365
+ (expr.ExprIdent("type"), expr.ExprIdent(enum.name)),
366
+ (expr.ExprIdent("label"), expr.ExprIdent(enum.name + "Label")),
367
+ (expr.ExprIdent("description"), expr.ExprIdent(enum.name + "Description")),
368
+ ]
369
+ )
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Any, Callable
5
+
6
+ from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
7
+ from codegen.models.var import DeferredVar
8
+ from loguru import logger
9
+
10
+ from sera.make.ts_frontend.make_class_schema import make_class_schema
11
+ from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
12
+ from sera.misc import (
13
+ assert_isinstance,
14
+ assert_not_null,
15
+ identity,
16
+ to_camel_case,
17
+ to_pascal_case,
18
+ to_snake_case,
19
+ )
20
+ from sera.models import (
21
+ Class,
22
+ DataProperty,
23
+ Enum,
24
+ ObjectProperty,
25
+ Package,
26
+ Schema,
27
+ TsTypeWithDep,
28
+ )
29
+ from sera.typing import is_set
30
+
31
+
32
+ def make_typescript_enums(schema: Schema, target_pkg: Package):
33
+ """Make typescript enum for the schema"""
34
+ enum_pkg = target_pkg.pkg("enums")
35
+
36
+ for enum in schema.enums.values():
37
+ make_enum(enum, enum_pkg)
38
+
39
+ program = Program()
40
+ for enum in schema.enums.values():
41
+ program.import_(f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}", True)
42
+ program.import_(
43
+ f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Label", True
44
+ )
45
+ program.import_(
46
+ f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}Description", True
47
+ )
48
+
49
+ program.root(
50
+ stmt.LineBreak(),
51
+ stmt.TypescriptStatement(
52
+ "export { "
53
+ + ", ".join([enum.name for enum in schema.enums.values()])
54
+ + ","
55
+ + ", ".join([enum.name + "Label" for enum in schema.enums.values()])
56
+ + ","
57
+ + ", ".join([enum.name + "Description" for enum in schema.enums.values()])
58
+ + "};"
59
+ ),
60
+ )
61
+ enum_pkg.module("index").write(program)
62
+
63
+
64
+ def make_enum(enum: Enum, pkg: Package):
65
+ program = Program()
66
+ program.root(
67
+ stmt.LineBreak(),
68
+ lambda ast: ast.class_like("enum", enum.name)(
69
+ *[
70
+ stmt.DefEnumValueStatement(
71
+ name=value.name,
72
+ value=expr.ExprConstant(value.value),
73
+ )
74
+ for value in enum.values.values()
75
+ ]
76
+ ),
77
+ stmt.LineBreak(),
78
+ stmt.TypescriptStatement(
79
+ f"export const {enum.name}Label = "
80
+ + PredefinedFn.dict(
81
+ [
82
+ (
83
+ expr.ExprConstant(value.value),
84
+ expr.ExprConstant(value.label.to_dict()),
85
+ )
86
+ for value in enum.values.values()
87
+ ]
88
+ ).to_typescript()
89
+ ),
90
+ stmt.LineBreak(),
91
+ stmt.TypescriptStatement(
92
+ f"export const {enum.name}Description = "
93
+ + PredefinedFn.dict(
94
+ [
95
+ (
96
+ expr.ExprConstant(value.value),
97
+ expr.ExprConstant(value.description.to_dict()),
98
+ )
99
+ for value in enum.values.values()
100
+ ]
101
+ ).to_typescript()
102
+ ),
103
+ )
104
+ pkg.module(enum.get_tsmodule_name()).write(program)
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
6
+
7
+ from sera.models import (
8
+ Class,
9
+ DataProperty,
10
+ Enum,
11
+ ObjectProperty,
12
+ Package,
13
+ Schema,
14
+ TsTypeWithDep,
15
+ )
16
+
17
+ TS_GLOBAL_IDENTS = {
18
+ "normalizers.normalizeNumber": "sera-db.normalizers",
19
+ "normalizers.normalizeOptionalNumber": "sera-db.normalizers",
20
+ "normalizers.normalizeDate": "sera-db.normalizers",
21
+ "normalizers.normalizeOptionalDate": "sera-db.normalizers",
22
+ }
23
+
24
+
25
+ def get_normalizer(
26
+ tstype: TsTypeWithDep, import_helper: ImportHelper
27
+ ) -> Optional[expr.ExprIdent]:
28
+ if tstype.type == "number":
29
+ return import_helper.use("normalizers.normalizeNumber")
30
+ if tstype.type == "number | undefined":
31
+ return import_helper.use("normalizers.normalizeOptionalNumber")
32
+ if tstype.type == "Date":
33
+ return import_helper.use("normalizers.normalizeDate")
34
+ if tstype.type == "Date | undefined":
35
+ return import_helper.use("normalizers.normalizeOptionalDate")
36
+
37
+ assert "number" not in tstype.type, tstype.type
38
+ return None
sera/misc/__init__.py CHANGED
@@ -12,6 +12,7 @@ from sera.misc._utils import (
12
12
  load_data_from_dir,
13
13
  replay_events,
14
14
  to_camel_case,
15
+ to_kebab_case,
15
16
  to_pascal_case,
16
17
  to_snake_case,
17
18
  )
@@ -34,4 +35,5 @@ __all__ = [
34
35
  "load_data_from_dir",
35
36
  "replay_events",
36
37
  "auto_import",
38
+ "to_kebab_case",
37
39
  ]
sera/misc/_utils.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import inspect
4
4
  import re
5
5
  from collections import defaultdict
6
+ from curses.ascii import isupper
7
+ from functools import lru_cache
6
8
  from importlib import import_module
7
9
  from pathlib import Path
8
10
  from typing import (
@@ -82,13 +84,16 @@ def import_attr(attr_ident: str):
82
84
  return getattr(module, cls)
83
85
 
84
86
 
85
- def to_snake_case(camelcase: str) -> str:
87
+ @lru_cache(maxsize=1280)
88
+ def to_snake_case(name: str) -> str:
86
89
  """Convert camelCase to snake_case."""
87
- snake = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", camelcase)
90
+ snake = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name)
88
91
  snake = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", snake)
92
+ snake = snake.replace("-", "_")
89
93
  return snake.lower()
90
94
 
91
95
 
96
+ @lru_cache(maxsize=1280)
92
97
  def to_camel_case(snake: str) -> str:
93
98
  """Convert snake_case to camelCase."""
94
99
  components = snake.split("_")
@@ -99,6 +104,7 @@ def to_camel_case(snake: str) -> str:
99
104
  return out
100
105
 
101
106
 
107
+ @lru_cache(maxsize=1280)
102
108
  def to_pascal_case(snake: str) -> str:
103
109
  """Convert snake_case to PascalCase."""
104
110
  components = snake.split("_")
@@ -109,6 +115,15 @@ def to_pascal_case(snake: str) -> str:
109
115
  return out
110
116
 
111
117
 
118
+ @lru_cache(maxsize=1280)
119
+ def to_kebab_case(name: str) -> str:
120
+ """Convert a name to kebab-case."""
121
+ kebab = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1-\2", name)
122
+ kebab = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", kebab)
123
+ kebab = kebab.replace("_", "-")
124
+ return kebab.lower()
125
+
126
+
112
127
  def assert_isinstance(x: Any, cls: type[T]) -> T:
113
128
  if not isinstance(x, cls):
114
129
  raise Exception(f"{type(x)} doesn't match with {type(cls)}")
sera/models/_class.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Optional
5
5
 
6
- from sera.misc import to_snake_case
6
+ from sera.misc import to_kebab_case, to_snake_case
7
7
  from sera.models._multi_lingual_string import MultiLingualString
8
8
  from sera.models._property import DataProperty, ObjectProperty
9
9
 
@@ -80,4 +80,4 @@ class Class:
80
80
 
81
81
  def get_tsmodule_name(self) -> str:
82
82
  """Get the typescript module name of this class as if there is a typescript module created to store this class only."""
83
- return self.name[0].lower() + self.name[1:]
83
+ return to_kebab_case(self.name)
@@ -23,7 +23,7 @@ class DataCollection:
23
23
  """Get the python module name of this collection as if there is a python module created to store this collection only."""
24
24
  return self.cls.get_pymodule_name()
25
25
 
26
- def get_queryable_fields(self) -> list[tuple[str, tuple[str, str]]]:
26
+ def get_queryable_fields(self) -> list[str]:
27
27
  """Get the fields of this collection that can be used in a queries."""
28
28
  output = []
29
29
  for prop in self.cls.properties.values():
@@ -48,17 +48,21 @@ class DataCollection:
48
48
  # This property is a data property or an object property not stored in the database, so we use its name
49
49
  propname = prop.name
50
50
 
51
- if isinstance(prop, DataProperty):
52
- convert_func = prop.datatype.pytype.get_string_conversion_func()
53
- else:
54
- assert isinstance(prop, ObjectProperty) and prop.target.db is not None
55
- target_idprop = prop.target.get_id_property()
56
- assert target_idprop is not None
57
- convert_func = (
58
- target_idprop.datatype.pytype.get_string_conversion_func()
59
- )
51
+ output.append(propname)
52
+ return output
53
+
54
+ def get_join_queryable_fields(self) -> dict[str, list[str]]:
55
+ """Get the fields of this collection that can be used in join queries."""
56
+ output = {}
57
+ for prop in self.cls.properties.values():
58
+ if isinstance(prop, DataProperty) and prop.db.foreign_key is not None:
59
+ # This property is a foreign key, so we add it to the output
60
+ output[prop.name] = DataCollection(
61
+ prop.db.foreign_key
62
+ ).get_queryable_fields()
63
+ elif isinstance(prop, ObjectProperty) and prop.target.db is not None:
64
+ output[prop.name] = DataCollection(prop.target).get_queryable_fields()
60
65
 
61
- output.append((propname, convert_func))
62
66
  return output
63
67
 
64
68
  def get_service_name(self):
@@ -25,7 +25,7 @@ class Constraint:
25
25
  # the UI will ensure to submit it in E.164 format
26
26
  return r"msgspec.Meta(pattern=r'^\+[1-9]\d{1,14}$')"
27
27
  elif self.name == "email":
28
- return r"msgspec.Meta(min_length=3, max_length=254, pattern=r'^[^@]+@[^@]+\.[^@]+$')"
28
+ return r"msgspec.Meta(min_length=3, max_length=254, pattern=r'^[^@]+@[^@]+\.[a-zA-Z\.]+$')"
29
29
  elif self.name == "not_empty":
30
30
  return "msgspec.Meta(min_length=1)"
31
31
  elif self.name == "username":