sera-2 1.6.1__tar.gz → 1.6.3__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 (32) hide show
  1. {sera_2-1.6.1 → sera_2-1.6.3}/PKG-INFO +1 -1
  2. {sera_2-1.6.1 → sera_2-1.6.3}/pyproject.toml +1 -1
  3. {sera_2-1.6.1 → sera_2-1.6.3}/sera/libs/base_orm.py +9 -1
  4. {sera_2-1.6.1 → sera_2-1.6.3}/sera/libs/base_service.py +1 -1
  5. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/make_app.py +2 -2
  6. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/make_python_model.py +30 -5
  7. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/make_typescript_model.py +19 -6
  8. {sera_2-1.6.1 → sera_2-1.6.3}/sera/misc/_formatter.py +4 -2
  9. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_module.py +5 -1
  10. {sera_2-1.6.1 → sera_2-1.6.3}/sera/typing.py +13 -1
  11. {sera_2-1.6.1 → sera_2-1.6.3}/README.md +0 -0
  12. {sera_2-1.6.1 → sera_2-1.6.3}/sera/__init__.py +0 -0
  13. {sera_2-1.6.1 → sera_2-1.6.3}/sera/constants.py +0 -0
  14. {sera_2-1.6.1 → sera_2-1.6.3}/sera/libs/__init__.py +0 -0
  15. {sera_2-1.6.1 → sera_2-1.6.3}/sera/libs/api_helper.py +0 -0
  16. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/__init__.py +0 -0
  17. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/__main__.py +0 -0
  18. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/make_python_api.py +0 -0
  19. {sera_2-1.6.1 → sera_2-1.6.3}/sera/make/make_python_services.py +0 -0
  20. {sera_2-1.6.1 → sera_2-1.6.3}/sera/misc/__init__.py +0 -0
  21. {sera_2-1.6.1 → sera_2-1.6.3}/sera/misc/_utils.py +0 -0
  22. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/__init__.py +0 -0
  23. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_class.py +0 -0
  24. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_collection.py +0 -0
  25. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_constraints.py +0 -0
  26. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_datatype.py +0 -0
  27. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_default.py +0 -0
  28. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_enum.py +0 -0
  29. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_multi_lingual_string.py +0 -0
  30. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_parse.py +0 -0
  31. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_property.py +0 -0
  32. {sera_2-1.6.1 → sera_2-1.6.3}/sera/models/_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.6.1
3
+ Version: 1.6.3
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sera-2"
3
- version = "1.6.1"
3
+ version = "1.6.3"
4
4
  description = ""
5
5
  authors = ["Binh Vu <bvu687@gmail.com>"]
6
6
  readme = "README.md"
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import Optional
4
4
 
5
5
  import orjson
6
+ from sera.typing import UNSET
6
7
  from sqlalchemy import LargeBinary, TypeDecorator
7
8
  from sqlalchemy import create_engine as sqlalchemy_create_engine
8
9
  from sqlalchemy import update
@@ -15,6 +16,8 @@ class BaseORM:
15
16
  args = {}
16
17
  for col in self.__table__.columns: # type: ignore
17
18
  val = getattr(self, col.name)
19
+ if val is UNSET:
20
+ continue
18
21
  if col.primary_key:
19
22
  q = q.where(getattr(self.__class__, col.name) == val)
20
23
  args[col.name] = val
@@ -22,7 +25,12 @@ class BaseORM:
22
25
  return q.values(**args)
23
26
 
24
27
  def get_update_args(self):
25
- return {col.name: getattr(self, col.name) for col in self.__table__.columns} # type: ignore
28
+ table = self.__table__ # type: ignore
29
+ return {
30
+ col.name: val
31
+ for col in table.columns
32
+ if (val := getattr(self, col.name)) is not UNSET
33
+ }
26
34
 
27
35
  @classmethod
28
36
  def from_dict(cls, data: dict):
@@ -114,7 +114,7 @@ class BaseService(Generic[ID, R]):
114
114
 
115
115
  def update(self, record: R, session: Session) -> R:
116
116
  """Update an existing record."""
117
- session.add(record)
117
+ session.execute(record.get_update_query())
118
118
  session.commit()
119
119
  return record
120
120
 
@@ -7,7 +7,6 @@ from typing import Annotated
7
7
 
8
8
  from codegen.models import DeferredVar, PredefinedFn, Program, expr, stmt
9
9
  from loguru import logger
10
-
11
10
  from sera.make.make_python_api import make_python_api
12
11
  from sera.make.make_python_model import (
13
12
  make_python_data_model,
@@ -83,6 +82,7 @@ def make_config(app: App):
83
82
  expr.ExprFuncCall(
84
83
  expr.ExprIdent("parse_schema"),
85
84
  [
85
+ expr.ExprConstant(app.name),
86
86
  PredefinedFn.list(
87
87
  [
88
88
  expr.ExprDivision(
@@ -93,7 +93,7 @@ def make_config(app: App):
93
93
  )
94
94
  for path in app.schema_files
95
95
  ]
96
- )
96
+ ),
97
97
  ],
98
98
  ),
99
99
  ),
@@ -159,11 +159,24 @@ def make_python_data_model(
159
159
  else:
160
160
  value = expr.ExprMethodCall(value, "to_db", [])
161
161
  elif isinstance(prop, DataProperty) and prop.is_diff_data_model_datatype():
162
- # convert the value to the python type used in db.
163
- value = get_data_conversion(
162
+ # convert the value to the python type used in db
163
+ converted_value = get_data_conversion(
164
164
  prop.get_data_model_datatype().get_python_type().type,
165
165
  prop.datatype.get_python_type().type,
166
166
  )(value)
167
+
168
+ if prop.data.is_private:
169
+ # if the property is private and it's UNSET, we cannot transform it to the database type
170
+ # and has to use the UNSET value (the update query will ignore this field)
171
+ program.import_("sera.typing.UNSET", True)
172
+ program.import_("sera.typing.is_set", True)
173
+ value = expr.ExprTernary(
174
+ expr.ExprFuncCall(expr.ExprIdent("is_set"), [value]),
175
+ converted_value,
176
+ expr.ExprIdent("UNSET"),
177
+ )
178
+ else:
179
+ value = converted_value
167
180
  return value
168
181
 
169
182
  def make_upsert(program: Program, cls: Class):
@@ -177,11 +190,13 @@ def make_python_data_model(
177
190
  alias=f"{cls.name}DB",
178
191
  )
179
192
  cls_ast = program.root.class_(
180
- "Upsert" + cls.name, [expr.ExprIdent("msgspec.Struct")]
193
+ "Upsert" + cls.name,
194
+ [expr.ExprIdent("msgspec.Struct"), expr.ExprIdent("kw_only=True")],
181
195
  )
182
196
  for prop in cls.properties.values():
183
197
  # this is a create object, so users can create private field
184
- # hence, we do not check for prop.is_private
198
+ # hence, we do not check for prop.is_private -- however, this field can be omitted
199
+ # during update, so we need to mark it as optional
185
200
  # if prop.data.is_private:
186
201
  # continue
187
202
 
@@ -189,7 +204,14 @@ def make_python_data_model(
189
204
  pytype = prop.get_data_model_datatype().get_python_type()
190
205
  if pytype.dep is not None:
191
206
  program.import_(pytype.dep, True)
207
+
192
208
  pytype_type = pytype.type
209
+ if prop.data.is_private:
210
+ program.import_("typing.Union", True)
211
+ program.import_("sera.typing.UnsetType", True)
212
+ program.import_("sera.typing.UNSET", True)
213
+ pytype_type = f"Union[{pytype_type}, UnsetType]"
214
+
193
215
  if len(prop.data.constraints) > 0:
194
216
  # if the property has constraints, we need to figure out
195
217
  program.import_("typing.Annotated", True)
@@ -202,7 +224,9 @@ def make_python_data_model(
202
224
  raise NotImplementedError(prop.data.constraints)
203
225
 
204
226
  prop_default_value = None
205
- if prop.default_value is not None:
227
+ if prop.data.is_private:
228
+ prop_default_value = expr.ExprIdent("UNSET")
229
+ elif prop.default_value is not None:
206
230
  prop_default_value = expr.ExprConstant(prop.default_value)
207
231
  elif prop.default_factory is not None:
208
232
  program.import_(prop.default_factory.pyfunc, True)
@@ -215,6 +239,7 @@ def make_python_data_model(
215
239
  )
216
240
  ],
217
241
  )
242
+
218
243
  cls_ast(
219
244
  stmt.DefClassVarStatement(
220
245
  prop.name, pytype_type, prop_default_value
@@ -5,7 +5,6 @@ from typing import Any, Callable
5
5
  from codegen.models import AST, PredefinedFn, Program, expr, stmt
6
6
  from codegen.models.var import DeferredVar
7
7
  from loguru import logger
8
-
9
8
  from sera.misc import (
10
9
  assert_isinstance,
11
10
  assert_not_null,
@@ -723,9 +722,10 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
723
722
  prop_defs: list[tuple[expr.Expr, expr.Expr]] = []
724
723
 
725
724
  for prop in cls.properties.values():
726
- if prop.data.is_private:
727
- # skip private fields as this is for APIs exchange
728
- continue
725
+ # we must include private properties that are needed during upsert for our forms.
726
+ # if prop.data.is_private:
727
+ # # skip private fields as this is for APIs exchange
728
+ # continue
729
729
  propname = to_camel_case(prop.name)
730
730
  tsprop = {}
731
731
 
@@ -821,11 +821,24 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
821
821
  )
822
822
 
823
823
  program.import_("sera-db.Schema", True)
824
- program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
824
+ program.import_(
825
+ f"@.models.{pkg.dir.name}.Draft{cls.name}.Draft{cls.name}", True
826
+ )
825
827
  program.root(
826
828
  stmt.LineBreak(),
827
829
  stmt.TypescriptStatement(
828
- f"export const {cls.name}Schema: Schema<{cls.name}> = "
830
+ f"export type {cls.name}Properties = "
831
+ + " | ".join(
832
+ [
833
+ expr.ExprConstant(to_camel_case(prop.name)).to_typescript()
834
+ for prop in cls.properties.values()
835
+ ]
836
+ )
837
+ + ";"
838
+ ),
839
+ stmt.LineBreak(),
840
+ stmt.TypescriptStatement(
841
+ f"export const {cls.name}Schema: Schema<Draft{cls.name}, {cls.name}Properties> = "
829
842
  + PredefinedFn.dict(
830
843
  [
831
844
  (expr.ExprIdent("properties"), PredefinedFn.dict(prop_defs)),
@@ -5,9 +5,8 @@ from concurrent.futures import ThreadPoolExecutor
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
 
8
- from tqdm import tqdm
9
-
10
8
  from sera.typing import Language
9
+ from tqdm import tqdm
11
10
 
12
11
 
13
12
  @dataclass
@@ -49,6 +48,9 @@ class Formatter:
49
48
  f"Formatting not implemented for {file.language}"
50
49
  )
51
50
 
51
+ if len(self.pending_files) == 0:
52
+ return
53
+
52
54
  with ThreadPoolExecutor() as executor:
53
55
  list(
54
56
  tqdm(
@@ -10,7 +10,6 @@ import black.mode
10
10
  import isort
11
11
  from codegen.models import Program
12
12
  from loguru import logger
13
-
14
13
  from sera.misc import File, Formatter
15
14
  from sera.typing import Language
16
15
 
@@ -169,3 +168,8 @@ class App:
169
168
  self.schema_files = schema_files
170
169
 
171
170
  self.language = language
171
+
172
+ @property
173
+ def name(self) -> str:
174
+ """Get the name of the application"""
175
+ return self.root.dir.name
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import Enum
4
- from typing import Annotated, TypeVar
4
+ from typing import Annotated, Any, TypeGuard, TypeVar, Union
5
+
6
+ import msgspec
5
7
 
6
8
 
7
9
  class doc(str):
@@ -18,3 +20,13 @@ ObjectPath = Annotated[
18
20
  class Language(str, Enum):
19
21
  Python = "python"
20
22
  Typescript = "typescript"
23
+
24
+
25
+ # re-export msgspec.UnsetType & msgspec.UNSET, so that we are consistent with ORM & data modules
26
+ UnsetType = msgspec.UnsetType
27
+ UNSET: Any = msgspec.UNSET
28
+
29
+
30
+ def is_set(value: Union[T, UnsetType]) -> TypeGuard[T]:
31
+ """Typeguard to check if a value is set (not UNSET)"""
32
+ return value is not UNSET
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes