reydb 1.1.51__py3-none-any.whl → 1.1.53__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.
reydb/rorm.py CHANGED
@@ -9,13 +9,18 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Self, Any, Type, TypeVar, Generic, Final
12
+ from typing import Self, Any, Type, TypeVar, Generic, Final, overload
13
+ from collections.abc import Callable
13
14
  from functools import wraps as functools_wraps
15
+ from pydantic import ConfigDict, field_validator as pydantic_field_validator, model_validator as pydantic_model_validator
14
16
  from sqlalchemy.orm import SessionTransaction
17
+ from sqlalchemy.sql import sqltypes
18
+ from sqlalchemy.sql.sqltypes import TypeEngine
15
19
  from sqlalchemy.sql.dml import Insert, Update, Delete
16
- from sqlmodel import SQLModel, Session, Field as sqlmodel_Field
20
+ from sqlmodel import SQLModel, Session, Table
21
+ from sqlmodel.main import SQLModelMetaclass, FieldInfo
17
22
  from sqlmodel.sql._expression_select_cls import SelectOfScalar as Select
18
- from reykit.rbase import CallableT, is_instance
23
+ from reykit.rbase import CallableT, Null, is_instance
19
24
 
20
25
  from .rbase import DatabaseBase
21
26
  from .rdb import Database
@@ -23,7 +28,9 @@ from .rdb import Database
23
28
 
24
29
  __all__ = (
25
30
  'DatabaseORMBase',
31
+ 'DatabaseORMModelMeta',
26
32
  'DatabaseORMModel',
33
+ 'DatabaseORMModelField',
27
34
  'DatabaseORM',
28
35
  'DatabaseORMSession',
29
36
  'DatabaseORMStatement',
@@ -34,18 +41,99 @@ __all__ = (
34
41
  )
35
42
 
36
43
 
44
+ ModelT = TypeVar('ModelT', bound='DatabaseORMModel')
45
+
46
+
37
47
  class DatabaseORMBase(DatabaseBase):
38
48
  """
39
49
  Database ORM base type.
40
50
  """
41
51
 
42
52
 
43
- class DatabaseORMModel(DatabaseORMBase, SQLModel):
53
+ class DatabaseORMModelMeta(DatabaseORMBase, SQLModelMetaclass):
54
+ """
55
+ Database ORM base meta type.
56
+ """
57
+
58
+
59
+ def __new__(
60
+ cls,
61
+ name: str,
62
+ bases: tuple[Type],
63
+ attrs: dict[str, Any],
64
+ **kwargs: Any
65
+ ) -> Type:
66
+ """
67
+ Create type.
68
+
69
+ Parameters
70
+ ----------
71
+ name : Type name.
72
+ bases : Type base types.
73
+ attrs : Type attributes and methods dictionary.
74
+ kwargs : Type other key arguments.
75
+ """
76
+
77
+ # Handle parameter.
78
+ if attrs['__module__'] == '__main__':
79
+ table_args = attrs.setdefault('__table_args__', {})
80
+ table_args['quote'] = True
81
+ if '__comment__' in attrs:
82
+ table_args['comment'] = attrs.pop('__comment__')
83
+
84
+ ## Field.
85
+ for __name__ in attrs['__annotations__']:
86
+ field = attrs.get(__name__)
87
+ if field is None:
88
+ field = attrs[__name__] = DatabaseORMModelField()
89
+ sa_column_kwargs: dict = field.sa_column_kwargs
90
+ sa_column_kwargs.setdefault('name', __name__)
91
+
92
+ # Base.
93
+ new_cls = super().__new__(cls, name, bases, attrs, **kwargs)
94
+
95
+ return new_cls
96
+
97
+
98
+ model_metaclass: SQLModelMetaclass = DatabaseORMModelMeta
99
+
100
+
101
+ class DatabaseORMModel(DatabaseORMBase, SQLModel, metaclass=model_metaclass):
44
102
  """
45
103
  Database ORM model type.
104
+
105
+ Examples
106
+ --------
107
+ >>> class Foo(DatabaseORMModel, table=True):
108
+ ... __comment__ = 'Table comment.'
109
+ ... ...
46
110
  """
47
111
 
48
112
 
113
+ def update(self, data: 'DatabaseORMModel | dict[dict, Any]') -> None:
114
+ """
115
+ Update attributes.
116
+
117
+ Parameters
118
+ ----------
119
+ data : `DatabaseORMModel` or `dict`.
120
+ """
121
+
122
+ # Update.
123
+ self.sqlmodel_update(data)
124
+
125
+
126
+ def validate(self) -> Self:
127
+ """
128
+ Validate all attributes, and copy self instance to new instance.
129
+ """
130
+
131
+ # Validate.
132
+ model = self.model_validate(self)
133
+
134
+ return model
135
+
136
+
49
137
  def copy(self) -> Self:
50
138
  """
51
139
  Copy self instance to new instance.
@@ -56,13 +144,211 @@ class DatabaseORMModel(DatabaseORMBase, SQLModel):
56
144
  """
57
145
 
58
146
  # Copy.
59
- data = self.model_dump()
147
+ data = self.data
60
148
  instance = self.__class__(**data)
61
149
 
62
150
  return instance
63
151
 
64
152
 
65
- ModelT = TypeVar('ModelT', bound=DatabaseORMModel)
153
+ @property
154
+ def data(self) -> dict[str, Any]:
155
+ """
156
+ All attributes data.
157
+
158
+ Returns
159
+ -------
160
+ data.
161
+ """
162
+
163
+ # Get.
164
+ data = self.model_dump()
165
+
166
+ return data
167
+
168
+
169
+ @classmethod
170
+ def table(cls_or_self) -> Table | None:
171
+ """
172
+ Mapping `Table` instance.
173
+
174
+ Returns
175
+ -------
176
+ Instance or null.
177
+ """
178
+
179
+ # Get.
180
+ table: Table | None = getattr(cls_or_self, '__table__', None)
181
+
182
+ return table
183
+
184
+
185
+ @classmethod
186
+ def comment(cls_or_self, comment: str) -> None:
187
+ """
188
+ Set table comment.
189
+
190
+ Parameters
191
+ ----------
192
+ comment : Comment.
193
+ """
194
+
195
+ # Set.
196
+ table = cls_or_self.table()
197
+ table.comment = comment
198
+
199
+
200
+ class DatabaseORMModelField(DatabaseBase, FieldInfo):
201
+ """
202
+ Database ORM model filed type.
203
+
204
+ Examples
205
+ --------
206
+ >>> class Foo(DatabaseORMModel, table=True):
207
+ ... key: int = DatabaseORMModelField(key=True, commment='Field commment.')
208
+ """
209
+
210
+
211
+ @overload
212
+ def __init__(
213
+ self,
214
+ arg_default: Any | Callable[[], Any] | Null = Null,
215
+ *,
216
+ arg_name: str | None = None,
217
+ field_default: str | None = None,
218
+ filed_name: str | None = None,
219
+ field_type: TypeEngine | None = None,
220
+ key: bool = False,
221
+ key_auto: bool = False,
222
+ non_null: bool = False,
223
+ index_n: bool = False,
224
+ index_u: bool = False,
225
+ comment: str | None = None,
226
+ unique: bool = False,
227
+ re: str | None = None,
228
+ len_min: int | None = None,
229
+ len_max: int | None = None,
230
+ num_gt: float | None = None,
231
+ num_ge: float | None = None,
232
+ num_lt: float | None = None,
233
+ num_le: float | None = None,
234
+ num_multiple: float | None = None,
235
+ num_places: int | None = None,
236
+ num_places_dec: int | None = None,
237
+ **kwargs: Any
238
+ ) -> None: ...
239
+
240
+ def __init__(
241
+ self,
242
+ arg_default: Any | Callable[[], Any] | Null = Null,
243
+ **kwargs: Any
244
+ ) -> None:
245
+ """
246
+ Build instance attributes.
247
+
248
+ Parameters
249
+ ----------
250
+ arg_default : Call argument default value.
251
+ arg_name : Call argument name.
252
+ - `None`: Same as attribute name.
253
+ field_default : Database field defualt value.
254
+ filed_name : Database field name.
255
+ - `None`: Same as attribute name.
256
+ field_type : Database field type.
257
+ - `None`: Based type annotation automatic judgment.
258
+ key : Whether the field is primary key.
259
+ key_auto : Whether the field is automatic increment primary key.
260
+ non_null : Whether the field is non null constraint.
261
+ index_n : Whether the field add normal index.
262
+ index_u : Whether the field add unique index.
263
+ comment : Field commment.
264
+ unique : Require the sequence element if is all unique.
265
+ re : Require the partial string if is match regular expression.
266
+ len_min : Require the sequence or string minimum length.
267
+ len_max : Require the sequence or string maximum length.
268
+ num_gt : Require the number greater than this value. (i.e. `number > num_gt`)
269
+ num_lt : Require the number less than this value. (i.e. `number < num_lt`)
270
+ num_ge : Require the number greater than and equal to this value. (i.e. `number >= num_ge`)
271
+ num_le : Require the number less than and equal to this value. (i.e. `number <= num_le`)
272
+ num_multiple : Require the number to be multiple of this value. (i.e. `number % num_multiple == 0`)
273
+ num_places : Require the number digit places maximum length.
274
+ num_places_dec : Require the number decimal places maximum length.
275
+ **kwargs : Other key arguments.
276
+ """
277
+
278
+ # Handle parameter.
279
+ kwargs = {
280
+ key: value
281
+ for key, value in kwargs.items()
282
+ if value not in (None, False)
283
+ }
284
+ kwargs.setdefault('sa_column_kwargs', {})
285
+ kwargs['sa_column_kwargs']['quote'] = True
286
+
287
+ ## Convert argument name.
288
+ mapping_keys = {
289
+ 'arg_name': 'alias',
290
+ 'key': 'primary_key',
291
+ 'index_n': 'index',
292
+ 'index_u': 'unique',
293
+ 're': 'pattern',
294
+ 'len_min': ('min_length', 'min_items'),
295
+ 'len_max': ('max_length', 'max_items'),
296
+ 'num_gt': 'gt',
297
+ 'num_ge': 'ge',
298
+ 'num_lt': 'lt',
299
+ 'num_le': 'le',
300
+ 'num_multiple': 'multiple_of',
301
+ 'num_places': 'max_digits',
302
+ 'num_places_dec': 'decimal_places'
303
+ }
304
+
305
+ for key_old, key_new in mapping_keys.items():
306
+ if type(key_new) != tuple:
307
+ key_new = (key_new,)
308
+ if key_old in kwargs:
309
+ value = kwargs.pop(key_old)
310
+ for key in key_new:
311
+ kwargs[key] = value
312
+
313
+ ## Argument default.
314
+ if (
315
+ arg_default != Null
316
+ and callable(arg_default)
317
+ ):
318
+ kwargs['default_factory'] = arg_default
319
+ else:
320
+ kwargs['default'] = arg_default
321
+
322
+ ## Field default.
323
+ if 'field_default' in kwargs:
324
+ kwargs['sa_column_kwargs']['server_default'] = kwargs.pop('field_default')
325
+
326
+ ## Field name.
327
+ if 'filed_name' in kwargs:
328
+ kwargs['sa_column_kwargs']['name'] = kwargs.pop('filed_name')
329
+
330
+ ## Field type.
331
+ if 'filed_name' in kwargs:
332
+ kwargs['sa_column_kwargs']['type_'] = kwargs.pop('filed_type')
333
+
334
+ ## Key auto.
335
+ if 'key_auto' in kwargs:
336
+ kwargs['sa_column_kwargs']['autoincrement'] = True
337
+ else:
338
+ kwargs['sa_column_kwargs']['autoincrement'] = False
339
+
340
+ ## Non null.
341
+ if 'non_null' in kwargs:
342
+ kwargs['nullable'] = not kwargs.pop('non_null')
343
+ else:
344
+ kwargs['nullable'] = True
345
+
346
+ ## Comment.
347
+ if 'comment' in kwargs:
348
+ kwargs['sa_column_kwargs']['comment'] = kwargs.pop('comment')
349
+
350
+ # Base.
351
+ super().__init__(**kwargs)
66
352
 
67
353
 
68
354
  class DatabaseORM(DatabaseORMBase):
@@ -76,7 +362,11 @@ class DatabaseORM(DatabaseORMBase):
76
362
  """
77
363
 
78
364
  Model = DatabaseORMModel
79
- Field = sqlmodel_Field
365
+ Field = DatabaseORMModelField
366
+ Config = ConfigDict
367
+ tyeps = sqltypes
368
+ wrap_validate_filed = pydantic_field_validator
369
+ wrap_validate_model = pydantic_model_validator
80
370
 
81
371
 
82
372
  def __init__(self, db: Database) -> None:
@@ -90,27 +380,80 @@ class DatabaseORM(DatabaseORMBase):
90
380
 
91
381
  # Build.
92
382
  self.db = db
383
+ self._session = self.session(True)
93
384
 
94
- ## Avoid descriptor error.
95
- self.Field = sqlmodel_Field
385
+ ## Method.
386
+ self.get = self._session.get
387
+ self.gets = self._session.gets
388
+ self.all = self._session.all
389
+ self.add = self._session.add
96
390
 
97
391
 
98
- def session(self):
392
+ def session(self, autocommit: bool = False):
99
393
  """
100
394
  Build `DataBaseORMSession` instance.
101
395
 
396
+ Parameters
397
+ ----------
398
+ autocommit: Whether automatic commit execute.
399
+
102
400
  Returns
103
401
  -------
104
402
  Instance.
105
403
  """
106
404
 
107
405
  # Build.
108
- sess = DataBaseORMSession(self)
406
+ sess = DataBaseORMSession(self, autocommit)
109
407
 
110
408
  return sess
111
409
 
112
410
 
113
- __call__ = session
411
+ def create(
412
+ self,
413
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
414
+ skip: bool = False
415
+ ) -> None:
416
+ """
417
+ Create table.
418
+
419
+ Parameters
420
+ ----------
421
+ models : ORM model instances.
422
+ check : Skip existing table and not mapping model.
423
+ """
424
+
425
+ # Create.
426
+ for model in models:
427
+ table = model.table()
428
+ if (
429
+ not skip
430
+ or table is not None
431
+ ):
432
+ table.create(self.db.engine, checkfirst=skip)
433
+
434
+
435
+ def drop(
436
+ self,
437
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
438
+ skip: bool = False
439
+ ) -> None:
440
+ """
441
+ Delete table.
442
+
443
+ Parameters
444
+ ----------
445
+ models : ORM model instances.
446
+ check : Skip not exist table and not mapping model.
447
+ """
448
+
449
+ # Create.
450
+ for model in models:
451
+ table = model.table()
452
+ if (
453
+ not skip
454
+ or table is not None
455
+ ):
456
+ table.drop(self.db.engine, checkfirst=skip)
114
457
 
115
458
 
116
459
  class DataBaseORMSession(DatabaseORMBase):
@@ -119,18 +462,24 @@ class DataBaseORMSession(DatabaseORMBase):
119
462
  """
120
463
 
121
464
 
122
- def __init__(self, orm: DatabaseORM) -> None:
465
+ def __init__(
466
+ self,
467
+ orm: 'DatabaseORM',
468
+ autocommit: bool = False
469
+ ) -> None:
123
470
  """
124
471
  Build instance attributes.
125
472
 
126
473
  Parameters
127
474
  ----------
128
475
  orm : `DatabaseORM` instance.
476
+ autocommit: Whether automatic commit execute.
129
477
  """
130
478
 
131
479
  # Build.
132
480
  self.orm = orm
133
- self.session = Session(orm.db.engine)
481
+ self.autocommit = autocommit
482
+ self.session: Session | None = None
134
483
  self.begin: SessionTransaction | None = None
135
484
 
136
485
 
@@ -162,7 +511,9 @@ class DataBaseORMSession(DatabaseORMBase):
162
511
  """
163
512
 
164
513
  # Close.
165
- self.session.close()
514
+ if self.session is not None:
515
+ self.session.close()
516
+ self.session = None
166
517
 
167
518
 
168
519
  def __enter__(self) -> Self:
@@ -202,9 +553,9 @@ class DataBaseORMSession(DatabaseORMBase):
202
553
  __del__ = close
203
554
 
204
555
 
205
- def wrap_begin(method: CallableT) -> CallableT:
556
+ def wrap_transact(method: CallableT) -> CallableT:
206
557
  """
207
- Decorator, create and store `SessionTransaction` instance.
558
+ Decorator, automated transaction.
208
559
 
209
560
  Parameters
210
561
  ----------
@@ -218,25 +569,76 @@ class DataBaseORMSession(DatabaseORMBase):
218
569
 
219
570
  # Define.
220
571
  @functools_wraps(method)
221
- def wrap(self, *args, **kwargs):
572
+ def wrap(self: 'DataBaseORMSession', *args, **kwargs):
573
+
574
+ # Session.
575
+ if self.session is None:
576
+ self.session = Session(self.orm.db.engine)
222
577
 
223
- # Create.
578
+ # Begin.
224
579
  if self.begin is None:
225
580
  self.begin = self.session.begin()
226
581
 
227
582
  # Execute.
228
583
  result = method(self, *args, **kwargs)
229
584
 
585
+ # Autucommit.
586
+ if self.autocommit:
587
+ self.commit()
588
+ self.close()
589
+
230
590
  return result
231
591
 
232
592
 
233
593
  return wrap
234
594
 
235
595
 
236
- @wrap_begin
596
+ @wrap_transact
597
+ def create(
598
+ self,
599
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
600
+ skip: bool = False
601
+ ) -> None:
602
+ """
603
+ Create table.
604
+
605
+ Parameters
606
+ ----------
607
+ models : ORM model instances.
608
+ check : Skip existing table.
609
+ """
610
+
611
+ # Create.
612
+ for model in models:
613
+ table = model.table()
614
+ table.create(self.session.connection(), checkfirst=skip)
615
+
616
+
617
+ @wrap_transact
618
+ def drop(
619
+ self,
620
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
621
+ skip: bool = False
622
+ ) -> None:
623
+ """
624
+ Delete table.
625
+
626
+ Parameters
627
+ ----------
628
+ models : ORM model instances.
629
+ check : Skip not exist table.
630
+ """
631
+
632
+ # Create.
633
+ for model in models:
634
+ table = model.table()
635
+ table.drop(self.session.connection(), checkfirst=skip)
636
+
637
+
638
+ @wrap_transact
237
639
  def get(self, model: Type[ModelT] | ModelT, key: Any | tuple[Any]) -> ModelT | None:
238
640
  """
239
- select records by primary key.
641
+ Select records by primary key.
240
642
 
241
643
  Parameters
242
644
  ----------
@@ -257,10 +659,17 @@ class DataBaseORMSession(DatabaseORMBase):
257
659
  # Get.
258
660
  result = self.session.get(model, key)
259
661
 
662
+ # Autucommit.
663
+ if (
664
+ self.autocommit
665
+ and result is not None
666
+ ):
667
+ self.session.expunge(result)
668
+
260
669
  return result
261
670
 
262
671
 
263
- @wrap_begin
672
+ @wrap_transact
264
673
  def gets(self, model: Type[ModelT] | ModelT, *keys: Any | tuple[Any]) -> list[ModelT]:
265
674
  """
266
675
  Select records by primary key sequence.
@@ -291,7 +700,7 @@ class DataBaseORMSession(DatabaseORMBase):
291
700
  return results
292
701
 
293
702
 
294
- @wrap_begin
703
+ @wrap_transact
295
704
  def all(self, model: Type[ModelT] | ModelT) -> list[ModelT]:
296
705
  """
297
706
  Select all records.
@@ -305,13 +714,19 @@ class DataBaseORMSession(DatabaseORMBase):
305
714
  With records ORM model instance list.
306
715
  """
307
716
 
717
+ # Handle parameter.
718
+ if is_instance(model):
719
+ model = type(model)
720
+
308
721
  # Get.
309
- models = self.select(model).execute()
722
+ select = Select(model)
723
+ models = self.session.exec(select)
724
+ models = list(models)
310
725
 
311
726
  return models
312
727
 
313
728
 
314
- @wrap_begin
729
+ @wrap_transact
315
730
  def add(self, *models: DatabaseORMModel) -> None:
316
731
  """
317
732
  Insert records.
@@ -325,7 +740,7 @@ class DataBaseORMSession(DatabaseORMBase):
325
740
  self.session.add_all(models)
326
741
 
327
742
 
328
- @wrap_begin
743
+ @wrap_transact
329
744
  def rm(self, *models: DatabaseORMModel) -> None:
330
745
  """
331
746
  Delete records.
@@ -340,7 +755,7 @@ class DataBaseORMSession(DatabaseORMBase):
340
755
  self.session.delete(model)
341
756
 
342
757
 
343
- @wrap_begin
758
+ @wrap_transact
344
759
  def refresh(self, *models: DatabaseORMModel) -> None:
345
760
  """
346
761
  Refresh records.
@@ -355,7 +770,7 @@ class DataBaseORMSession(DatabaseORMBase):
355
770
  self.session.refresh(model)
356
771
 
357
772
 
358
- @wrap_begin
773
+ @wrap_transact
359
774
  def expire(self, *models: DatabaseORMModel) -> None:
360
775
  """
361
776
  Mark records to expire, refresh on next call.
@@ -370,7 +785,7 @@ class DataBaseORMSession(DatabaseORMBase):
370
785
  self.session.expire(model)
371
786
 
372
787
 
373
- @wrap_begin
788
+ @wrap_transact
374
789
  def select(self, model: Type[ModelT] | ModelT):
375
790
  """
376
791
  Build `DatabaseORMSelect` instance.
@@ -394,7 +809,7 @@ class DataBaseORMSession(DatabaseORMBase):
394
809
  return select
395
810
 
396
811
 
397
- @wrap_begin
812
+ @wrap_transact
398
813
  def insert(self, model: Type[ModelT] | ModelT):
399
814
  """
400
815
  Build `DatabaseORMInsert` instance.
@@ -418,7 +833,7 @@ class DataBaseORMSession(DatabaseORMBase):
418
833
  return select
419
834
 
420
835
 
421
- @wrap_begin
836
+ @wrap_transact
422
837
  def update(self, model: Type[ModelT] | ModelT):
423
838
  """
424
839
  Build `DatabaseORMUpdate` instance.
@@ -442,7 +857,7 @@ class DataBaseORMSession(DatabaseORMBase):
442
857
  return select
443
858
 
444
859
 
445
- @wrap_begin
860
+ @wrap_transact
446
861
  def delete(self, model: Type[ModelT] | ModelT):
447
862
  """
448
863
  Build `DatabaseORMDelete` instance.
@@ -486,13 +901,13 @@ class DatabaseORMStatement(DatabaseORMBase):
486
901
  model : ORM model instance.
487
902
  """
488
903
 
904
+ # Base.
905
+ super().__init__(self.model)
906
+
489
907
  # Build.
490
908
  self.sess = sess
491
909
  self.model = model
492
910
 
493
- # Init.
494
- super().__init__(self.model)
495
-
496
911
 
497
912
  def execute(self) -> None:
498
913
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reydb
3
- Version: 1.1.51
3
+ Version: 1.1.53
4
4
  Summary: Database method set.
5
5
  Project-URL: homepage, https://github.com/reyxbo/reydb/
6
6
  Author-email: Rey <reyxbo@163.com>