sera-2 1.6.1__py3-none-any.whl → 1.6.2__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.
sera/libs/base_orm.py CHANGED
@@ -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):
sera/libs/base_service.py CHANGED
@@ -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
 
sera/make/make_app.py CHANGED
@@ -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
sera/misc/_formatter.py CHANGED
@@ -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(
sera/models/_module.py CHANGED
@@ -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
sera/typing.py CHANGED
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sera-2
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary:
5
5
  Author: Binh Vu
6
6
  Author-email: bvu687@gmail.com
@@ -2,17 +2,17 @@ sera/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sera/constants.py,sha256=mzAaMyIx8TJK0-RYYJ5I24C4s0Uvj26OLMJmBo0pxHI,123
3
3
  sera/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  sera/libs/api_helper.py,sha256=hUEy0INHM18lxTQ348tgbXNceOHcjiAnqmuL_8CRpLQ,2509
5
- sera/libs/base_orm.py,sha256=Fte-NOq62-4ulpjKmdO4V5pQKy9JfaL3ryBqpRRKkNQ,2913
6
- sera/libs/base_service.py,sha256=CNAdOPxgXfvdANj75qd4GW5EC0WYW-L5Utm0ozfBbio,4075
5
+ sera/libs/base_orm.py,sha256=sTiHvbvLALSFygCITKFTXK1w-8UtxXGT_Te-1PEAiCI,3094
6
+ sera/libs/base_service.py,sha256=l5D4IjxIiz8LBRranUYddb8J0Y6SwSyetKYTLrCUdQA,4098
7
7
  sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  sera/make/__main__.py,sha256=G5O7s6135-708honwqMFn2yPTs06WbGQTHpupID0eZ4,1417
9
- sera/make/make_app.py,sha256=3Crq-Y-VzeHKKAaxfrEqguqTZEtdNPOuGojBQpC9QHw,5950
9
+ sera/make/make_app.py,sha256=n9NtW73O3s_5Q31VHIRmnd-jEIcpDO7ksAsOdovde2s,5999
10
10
  sera/make/make_python_api.py,sha256=RuJUm9z-4plBEtjobeOPr12o27OT-0tSeXI4ZlM3IY0,29433
11
- sera/make/make_python_model.py,sha256=8U3fmIK_ahpxyMsCypQvISBVoncRUspYEppWX-_MLtU,35402
11
+ sera/make/make_python_model.py,sha256=xf4revAwVWEnF6QhxbbqPyUGgXOOB--Gu3jPxsESg0Y,36593
12
12
  sera/make/make_python_services.py,sha256=RsinYZdfkrTlTn9CT50VgqGs9w6IZawsJx-KEmqfnEY,2062
13
13
  sera/make/make_typescript_model.py,sha256=dT1ZHpsDvegX07jxDRncd_iu4FAiaV_7xyioWBJdbes,36999
14
14
  sera/misc/__init__.py,sha256=Dh4uDq0D4N53h3zhvmwfa5a0TPVRSUvLzb0hkFuPirk,411
15
- sera/misc/_formatter.py,sha256=_m1lK2__wD0I0xnZ7JpjQeaxyf6ygYOZdrYzaYJd4s4,1625
15
+ sera/misc/_formatter.py,sha256=aCGYL08l8f3aLODHxSocxBBwkRYEo3K1QzCDEn3suj0,1685
16
16
  sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
17
17
  sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
18
18
  sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
@@ -21,12 +21,12 @@ sera/models/_constraints.py,sha256=lZmCh6Py0UVMdhTR7zUOPPzGqJGbmDCzf7xH7yITcbQ,1
21
21
  sera/models/_datatype.py,sha256=uMxK_8wBLowaIMIAYCb3V17YmkzJrKKc5orjImzqWbA,5818
22
22
  sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
23
23
  sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
24
- sera/models/_module.py,sha256=CAk8CCuo3hs_mBkmfXDV-BsMQhv4Wgtp0tcY78JLjUE,4922
24
+ sera/models/_module.py,sha256=8QRSCubZmdDP9rL58rGAS6X5VCrkc1ZHvuMu1I1KrWk,5043
25
25
  sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
26
26
  sera/models/_parse.py,sha256=sJYfQtwek96ltpgxExG4xUbiLnU3qvNYhTP1CeyXGjs,9746
27
27
  sera/models/_property.py,sha256=CmEmgOShtSyNFq05YW3tGupwCIVRzPMKudXWld8utPk,5530
28
28
  sera/models/_schema.py,sha256=r-Gqg9Lb_wR3UrbNvfXXgt_qs5bts0t2Ve7aquuF_OI,1155
29
- sera/typing.py,sha256=Ak0Su0EhjFirWOOwbIW8u4YpEwP8apfpIH5Tv9foTRo,430
30
- sera_2-1.6.1.dist-info/METADATA,sha256=GuYpBmAl_5gq9go1LZ7AcjVsXi3Nx5a21dDYIpEokzg,856
31
- sera_2-1.6.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
32
- sera_2-1.6.1.dist-info/RECORD,,
29
+ sera/typing.py,sha256=Q4QMfbtfrCjC9tFfsZPhsAnbNX4lm4NHQ9lmjNXYdV0,772
30
+ sera_2-1.6.2.dist-info/METADATA,sha256=B_Brp3KBIu2C0iqX-5cjwdWAfen1DttoeUyj0ms_7-4,856
31
+ sera_2-1.6.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
32
+ sera_2-1.6.2.dist-info/RECORD,,
File without changes