ormlambda 3.7.1__py3-none-any.whl → 3.11.0__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.
Files changed (40) hide show
  1. ormlambda/__init__.py +2 -0
  2. ormlambda/caster/base_caster.py +3 -3
  3. ormlambda/common/global_checker.py +1 -1
  4. ormlambda/components/select/ISelect.py +4 -4
  5. ormlambda/components/select/__init__.py +1 -1
  6. ormlambda/databases/my_sql/caster/caster.py +1 -0
  7. ormlambda/databases/my_sql/caster/types/bytes.py +3 -3
  8. ormlambda/databases/my_sql/caster/types/datetime.py +3 -3
  9. ormlambda/databases/my_sql/caster/types/float.py +3 -3
  10. ormlambda/databases/my_sql/caster/types/int.py +3 -3
  11. ormlambda/databases/my_sql/caster/types/iterable.py +3 -3
  12. ormlambda/databases/my_sql/caster/types/none.py +3 -3
  13. ormlambda/databases/my_sql/caster/types/string.py +3 -3
  14. ormlambda/databases/my_sql/clauses/__init__.py +1 -0
  15. ormlambda/databases/my_sql/clauses/alias.py +15 -21
  16. ormlambda/databases/my_sql/clauses/group_by.py +19 -20
  17. ormlambda/databases/my_sql/clauses/having.py +16 -0
  18. ormlambda/databases/my_sql/clauses/order.py +6 -1
  19. ormlambda/databases/my_sql/clauses/update.py +1 -1
  20. ormlambda/databases/my_sql/clauses/where.py +3 -3
  21. ormlambda/databases/my_sql/functions/concat.py +8 -6
  22. ormlambda/databases/my_sql/join_context.py +3 -3
  23. ormlambda/databases/my_sql/repository/repository.py +60 -13
  24. ormlambda/databases/my_sql/statements.py +73 -22
  25. ormlambda/databases/my_sql/types.py +73 -0
  26. ormlambda/engine/__init__.py +2 -0
  27. ormlambda/engine/create.py +35 -0
  28. ormlambda/engine/url.py +744 -0
  29. ormlambda/engine/utils.py +17 -0
  30. ormlambda/repository/base_repository.py +2 -3
  31. ormlambda/repository/interfaces/IRepositoryBase.py +1 -0
  32. ormlambda/sql/column.py +27 -2
  33. ormlambda/sql/foreign_key.py +36 -4
  34. ormlambda/sql/table/table_constructor.py +2 -2
  35. ormlambda/statements/interfaces/IStatements.py +37 -25
  36. ormlambda/statements/types.py +4 -1
  37. {ormlambda-3.7.1.dist-info → ormlambda-3.11.0.dist-info}/METADATA +107 -8
  38. {ormlambda-3.7.1.dist-info → ormlambda-3.11.0.dist-info}/RECORD +40 -35
  39. {ormlambda-3.7.1.dist-info → ormlambda-3.11.0.dist-info}/LICENSE +0 -0
  40. {ormlambda-3.7.1.dist-info → ormlambda-3.11.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,17 @@
1
+ from typing import Optional, Any, TypeGuard, Iterable
2
+ import collections.abc as collections_abc
3
+
4
+
5
+ def is_non_string_iterable(obj: Any) -> TypeGuard[Iterable[Any]]:
6
+ return isinstance(obj, collections_abc.Iterable) and not isinstance(obj, (str, bytes))
7
+
8
+
9
+ def to_list(x: Any, default: Optional[list[Any]] = None) -> list[Any]:
10
+ if x is None:
11
+ return default # type: ignore
12
+ if not is_non_string_iterable(x):
13
+ return [x]
14
+ elif isinstance(x, list):
15
+ return x
16
+ else:
17
+ return list(x)
@@ -1,12 +1,11 @@
1
1
  import contextlib
2
- from typing import Generator, Type
2
+ from typing import Generator, Type, Unpack
3
3
  from ormlambda.repository import IRepositoryBase
4
4
  import abc
5
5
 
6
6
 
7
7
  class BaseRepository[TPool](IRepositoryBase):
8
- def __init__(self, pool: Type[TPool], **kwargs: str) -> None:
9
- self._data_config: dict[str, str] = kwargs
8
+ def __init__[TArgs](self, pool: Type[TPool], **kwargs: Unpack[TArgs]) -> None:
10
9
  self._pool: TPool = pool(**kwargs)
11
10
 
12
11
  @contextlib.contextmanager
@@ -5,6 +5,7 @@ from typing import Optional, Type, Iterable, TYPE_CHECKING
5
5
  if TYPE_CHECKING:
6
6
  from ormlambda.statements.types import TypeExists
7
7
 
8
+
8
9
  class IRepositoryBase(ABC):
9
10
  def __repr__(self) -> str:
10
11
  return f"{IRepositoryBase.__name__}: {self.__class__.__name__}"
ormlambda/sql/column.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Iterable, Type, Optional, TYPE_CHECKING
2
+ from typing import Iterable, Type, Optional, TYPE_CHECKING, overload
3
3
  import abc
4
4
  from ormlambda.sql.types import TableType, ComparerType, ColumnType
5
5
  from ormlambda import ConditionType
@@ -25,18 +25,41 @@ class Column[TProp]:
25
25
  "_check",
26
26
  )
27
27
 
28
+ @overload
29
+ def __init__(self, *, column_name: str): ...
30
+
31
+ @overload
28
32
  def __init__[T: Table](
29
33
  self,
30
34
  dtype: Type[TProp],
35
+ *,
31
36
  is_primary_key: bool = False,
32
37
  is_auto_generated: bool = False,
33
38
  is_auto_increment: bool = False,
34
39
  is_unique: bool = False,
35
40
  check_types: bool = True,
41
+ ) -> None: ...
42
+
43
+ def __init__[T: Table](
44
+ self,
45
+ dtype: Optional[Type[TProp]] = None,
46
+ *,
47
+ is_primary_key: bool = False,
48
+ is_auto_generated: bool = False,
49
+ is_auto_increment: bool = False,
50
+ is_unique: bool = False,
51
+ check_types: bool = True,
52
+ column_name: Optional[str] = None,
36
53
  ) -> None:
54
+ if dtype is None and column_name is None:
55
+ raise AttributeError("You must specify either the 'dtype' or 'column_name' attribute.")
56
+
57
+ if column_name is not None:
58
+ dtype = type(column_name)
59
+
37
60
  self.dtype: Type[TProp] = dtype
38
61
  self.table: Optional[TableType[T]] = None
39
- self.column_name: Optional[str] = None
62
+ self.column_name: Optional[str] = column_name
40
63
  self.__private_name: Optional[str] = None
41
64
  self._check = check_types
42
65
 
@@ -70,7 +93,9 @@ class Column[TProp]:
70
93
  def __hash__(self) -> int:
71
94
  return hash(
72
95
  (
96
+ self.dtype,
73
97
  self.column_name,
98
+ self.table,
74
99
  self.is_primary_key,
75
100
  self.is_auto_generated,
76
101
  self.is_auto_increment,
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, TYPE_CHECKING, Optional, Any, Type, overload
2
+ from typing import Callable, TYPE_CHECKING, Optional, Any, Type, cast, overload
3
3
 
4
4
  from ormlambda.common.interfaces.IQueryCommand import IQuery
5
5
 
@@ -10,15 +10,45 @@ if TYPE_CHECKING:
10
10
  from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
11
11
 
12
12
 
13
+ class ForeignKeyContext(set["ForeignKey"]):
14
+ def clear(self):
15
+ to_remove = {x for x in self if not cast(ForeignKey, x)._keep_alive}
16
+ for el in to_remove:
17
+ self.remove(el)
18
+
19
+ def remove(self, element):
20
+ return super().remove(element)
21
+
22
+ def pop(self, item):
23
+ for el in self:
24
+ if el != item:
25
+ continue
26
+
27
+ if not cast(ForeignKey, el)._keep_alive:
28
+ super().remove(el)
29
+ return el
30
+
31
+ def add(self, element):
32
+ return super().add(element)
33
+
34
+
13
35
  class ForeignKey[TLeft: Table, TRight: Table](IQuery):
14
- stored_calls: set[ForeignKey] = set()
36
+ stored_calls: ForeignKeyContext = ForeignKeyContext()
15
37
 
16
38
  @overload
17
39
  def __new__[LProp, RProp](self, comparer: Comparer[LProp, RProp], clause_name: str) -> None: ...
18
40
  @overload
19
- def __new__[LProp, TRight, RProp](cls, tright: Type[TRight], relationship: Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]) -> TRight: ...
41
+ def __new__[LProp, TRight, RProp](cls, tright: Type[TRight], relationship: Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]], keep_alive: bool = ...) -> TRight: ...
20
42
 
21
- def __new__[LProp, TRight, RProp](cls, tright: Optional[TRight] = None, relationship: Optional[Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]] = None, *, comparer: Optional[Comparer] = None, clause_name: Optional[str] = None) -> TRight:
43
+ def __new__[LProp, TRight, RProp](
44
+ cls,
45
+ tright: Optional[TRight] = None,
46
+ relationship: Optional[Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]] = None,
47
+ *,
48
+ comparer: Optional[Comparer] = None,
49
+ clause_name: Optional[str] = None,
50
+ keep_alive: bool = False,
51
+ ) -> TRight:
22
52
  return super().__new__(cls)
23
53
 
24
54
  def __init__[LProp, RProp](
@@ -28,7 +58,9 @@ class ForeignKey[TLeft: Table, TRight: Table](IQuery):
28
58
  *,
29
59
  comparer: Optional[Comparer] = None,
30
60
  clause_name: Optional[str] = None,
61
+ keep_alive: bool = False,
31
62
  ) -> None:
63
+ self._keep_alive = keep_alive
32
64
  if comparer is not None and clause_name is not None:
33
65
  self.__init__with_comparer(comparer, clause_name)
34
66
  else:
@@ -170,8 +170,8 @@ class Table(metaclass=TableMeta):
170
170
  return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
171
171
 
172
172
  @classmethod
173
- def get_column[TProp](cls,name:str) -> Column[TProp]:
174
- for key,value in cls.__annotations__.items():
173
+ def get_column[TProp](cls, name: str) -> Column[TProp]:
174
+ for key, value in cls.__annotations__.items():
175
175
  if name == key:
176
176
  return value
177
177
 
@@ -29,6 +29,8 @@ from ..types import (
29
29
  WhereTypes,
30
30
  )
31
31
 
32
+ type SelectCols[T, TProp] = Callable[[T], ColumnType[TProp]] | ColumnType[TProp]
33
+
32
34
 
33
35
  class IStatements[T: Table](ABC):
34
36
  @abstractmethod
@@ -132,7 +134,7 @@ class IStatements[T: Table](ABC):
132
134
  @abstractmethod
133
135
  def count(
134
136
  self,
135
- selection: Callable[[T], tuple] = lambda x: "*",
137
+ selection: Callable[[T], tuple] = ...,
136
138
  alias_clause="count",
137
139
  execute: bool = False,
138
140
  ) -> Optional[IStatements[T]]: ...
@@ -160,6 +162,16 @@ class IStatements[T: Table](ABC):
160
162
  @abstractmethod
161
163
  def where[LProp, RTable, RProp](self, conditions: WhereTypes[T, LProp, RTable, RProp] = None) -> IStatements[T]: ...
162
164
 
165
+ # endregion
166
+
167
+ # region having
168
+
169
+ @overload
170
+ def having[LProp, RTable, RProp](self, conditions: Callable[[T], WhereTypes[T, LProp, RTable, RProp]]) -> IStatements[T]: ...
171
+
172
+ @abstractmethod
173
+ def having[LProp, RTable, RProp](self, conditions: WhereTypes[T, LProp, RTable, RProp] = None) -> IStatements[T]: ...
174
+
163
175
  # endregion
164
176
  # region order
165
177
  @overload
@@ -172,14 +184,14 @@ class IStatements[T: Table](ABC):
172
184
  # endregion
173
185
  # region concat
174
186
  @overload
175
- def concat[P](self, selector: Callable[[T], tuple[P]]) -> IAggregate: ...
187
+ def concat(self, selector: Callable[[T], str], alias: str = "CONCAT") -> IAggregate: ...
176
188
 
177
189
  # endregion
178
190
  # region max
179
191
  @overload
180
192
  def max[TProp](
181
193
  self,
182
- column: Callable[[T], TProp],
194
+ column: SelectCols[T, TProp],
183
195
  alias: Optional[str] = ...,
184
196
  execute: bool = ...,
185
197
  ) -> TProp: ...
@@ -188,7 +200,7 @@ class IStatements[T: Table](ABC):
188
200
  @overload
189
201
  def min[TProp](
190
202
  self,
191
- column: Callable[[T], TProp],
203
+ column: SelectCols[T, TProp],
192
204
  alias: Optional[str] = ...,
193
205
  execute: bool = ...,
194
206
  ) -> TProp: ...
@@ -197,7 +209,7 @@ class IStatements[T: Table](ABC):
197
209
  @overload
198
210
  def sum[TProp](
199
211
  self,
200
- column: Callable[[T], TProp],
212
+ column: SelectCols[T, TProp],
201
213
  alias: Optional[str] = ...,
202
214
  execute: bool = ...,
203
215
  ) -> TProp: ...
@@ -208,6 +220,8 @@ class IStatements[T: Table](ABC):
208
220
  # endregion
209
221
  # region select
210
222
  type SelectorType[TOri, *T] = Callable[[TOri], tuple[*T]] | tuple[*T]
223
+ type SelectorFlavourType[T, TResponse] = Optional[Callable[[T], TResponse]] | TResponse
224
+ type SelectorOneType[T, TResponse] = Callable[[T, TResponse]] | TResponse
211
225
 
212
226
  @overload
213
227
  def select[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](self, selector: SelectorType[T, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], *, by: Optional[Enum] = ...) -> Select10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]: ...
@@ -234,8 +248,6 @@ class IStatements[T: Table](ABC):
234
248
  @overload
235
249
  def select(self) -> Tuple[T]: ...
236
250
 
237
- type SelectorFlavourType[T, TResponse] = Optional[Callable[[T], TResponse]]
238
-
239
251
  # @overload
240
252
  # def select[TFlavour](self, selector: Optional[Callable[[T], tuple]] = ..., *, cast_to_tuple: bool = ..., flavour: Type[TFlavour], by: Optional[Enum] = ..., **kwargs) -> TFlavour: ...
241
253
  @overload
@@ -243,7 +255,7 @@ class IStatements[T: Table](ABC):
243
255
  @overload
244
256
  def select[*TRes](self, selector: SelectorFlavourType[T, tuple[*TRes]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ..., **kwargs) -> tuple[tuple[*TRes]]: ...
245
257
  @overload
246
- def select[TFlavour](self, selector: SelectorFlavourType[T, tuple] = ..., *, flavour: Type[TFlavour], by: Optional[Enum] = ..., **kwargs) -> tuple[TFlavour]: ...
258
+ def select[TFlavour](self, selector: SelectorFlavourType[T, tuple] = ..., *, flavour: Type[TFlavour], by: Optional[Enum] = ..., **kwargs) -> tuple[TFlavour, ...]: ...
247
259
 
248
260
  @abstractmethod
249
261
  def select[TValue, TFlavour, P](self, selector: SelectorFlavourType[T, tuple[TValue, P]] = ..., *, cast_to_tuple: bool = ..., flavour: Type[TFlavour] = ..., by: JoinType = ..., **kwargs): ...
@@ -255,21 +267,21 @@ class IStatements[T: Table](ABC):
255
267
  @overload
256
268
  def select_one[TFlavour](self, *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
257
269
  @overload
258
- def select_one[T1](self, selector: Callable[[T], T1 | tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
270
+ def select_one[T1](self, selector: SelectorOneType[T, T1 | tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
259
271
  @overload
260
- def select_one[*TRes](self, selector: Callable[[T], tuple[*TRes]], *, by: Optional[Enum] = ...) -> tuple[*TRes]: ...
272
+ def select_one[*TRes](self, selector: SelectorOneType[T, tuple[*TRes]], *, by: Optional[Enum] = ...) -> tuple[*TRes]: ...
261
273
  @overload
262
- def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type, **kwargs) -> T1: ...
274
+ def select_one[T1](self, selector: SelectorOneType[T, tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type, **kwargs) -> T1: ...
263
275
  @overload
264
- def select_one[T1, TFlavour](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
276
+ def select_one[T1, TFlavour](self, selector: SelectorOneType[T, T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
265
277
  @overload
266
- def select_one[*TRest](self, selector: Callable[[T], tuple[*TRest]], *, by: Optional[Enum] = ..., flavour: Type[tuple], **kwargs) -> tuple[*TRest]: ...
278
+ def select_one[*TRest](self, selector: SelectorOneType[T, tuple[*TRest]], *, by: Optional[Enum] = ..., flavour: Type[tuple], **kwargs) -> tuple[*TRest]: ...
267
279
  @overload
268
- def select_one[TFlavour](self, selector: Callable[[T], tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
280
+ def select_one[TFlavour](self, selector: SelectorOneType[T, tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
269
281
  @abstractmethod
270
282
  def select_one[TValue, TFlavour, *TRest](
271
283
  self,
272
- selector: Optional[Callable[[T], tuple[TValue, *TRest]]] = lambda: None,
284
+ selector: Optional[SelectorOneType[T, tuple[TValue, *TRest]]] = lambda: None,
273
285
  *,
274
286
  flavour: Type[TFlavour] = ...,
275
287
  by: Optional[Enum] = ...,
@@ -281,23 +293,23 @@ class IStatements[T: Table](ABC):
281
293
  @overload
282
294
  def first(self) -> T: ...
283
295
  @overload
284
- def first[TFlavour](self, *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
296
+ def first[T1](self, selector: SelectorOneType[T, T1 | tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
285
297
  @overload
286
- def first[T1](self, selector: Callable[[T], T1 | tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
298
+ def first[*TRes](self, selector: SelectorOneType[T, tuple[*TRes]], *, by: Optional[Enum] = ...) -> tuple[*TRes]: ...
287
299
  @overload
288
- def first[*TRes](self, selector: Callable[[T], tuple[*TRes]], *, by: Optional[Enum] = ...) -> tuple[*TRes]: ...
300
+ def first[TFlavour](self, *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
289
301
  @overload
290
- def first[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type, **kwargs) -> T1: ...
302
+ def first[T1](self, selector: SelectorOneType[T, tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type, **kwargs) -> T1: ...
291
303
  @overload
292
- def first[T1, TFlavour](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
304
+ def first[T1, TFlavour](self, selector: SelectorOneType[T, T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
293
305
  @overload
294
- def first[*TRest](self, selector: Callable[[T], tuple[*TRest]], *, by: Optional[Enum] = ..., flavour: Type[tuple], **kwargs) -> tuple[*TRest]: ...
306
+ def first[*TRest](self, selector: SelectorOneType[T, tuple[*TRest]], *, by: Optional[Enum] = ..., flavour: Type[tuple], **kwargs) -> tuple[*TRest]: ...
295
307
  @overload
296
- def first[TFlavour](self, selector: Callable[[T], tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
308
+ def first[TFlavour](self, selector: SelectorOneType[T, tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour], **kwargs) -> TFlavour: ...
297
309
  @abstractmethod
298
310
  def first[TValue, TFlavour, *TRest](
299
311
  self,
300
- selector: Optional[Callable[[T], tuple[TValue, *TRest]]] = lambda: None,
312
+ selector: Optional[SelectorOneType[T, tuple[TValue, *TRest]]] = lambda: None,
301
313
  *,
302
314
  flavour: Type[TFlavour] = ...,
303
315
  by: Optional[Enum] = ...,
@@ -305,9 +317,9 @@ class IStatements[T: Table](ABC):
305
317
 
306
318
  # endregion
307
319
 
308
- # region group_by
320
+ # region groupby
309
321
  @abstractmethod
310
- def group_by[TRepo](self, column: Callable[[T], TRepo]) -> IStatements[T]: ...
322
+ def groupby[TRepo](self, column: Callable[[T], TRepo]) -> IStatements[T]: ...
311
323
 
312
324
  # endregion
313
325
 
@@ -18,7 +18,10 @@ if TYPE_CHECKING:
18
18
  type OrderTypes = Literal["ASC", "DESC"] | OrderType | Iterable[OrderType]
19
19
 
20
20
 
21
- class OrderType(enum.Enum):
21
+ class OrderType(str, enum.Enum):
22
+ def __str__(self):
23
+ return super().__str__()
24
+
22
25
  ASC = "ASC"
23
26
  DESC = "DESC"
24
27
 
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ormlambda
3
- Version: 3.7.1
3
+ Version: 3.11.0
4
4
  Summary: ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions
5
5
  Author: p-hzamora
6
6
  Author-email: p.hzamora@icloud.com
7
7
  Requires-Python: >=3.12,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
- Requires-Dist: fluent-validation (==4.3.1)
11
10
  Requires-Dist: mysql-connector-python (>=9.0.0,<10.0.0)
12
- Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
13
11
  Requires-Dist: shapely (>=2.0.6,<3.0.0)
14
12
  Description-Content-Type: text/markdown
15
13
 
@@ -17,7 +15,7 @@ Description-Content-Type: text/markdown
17
15
  ![downloads](https://img.shields.io/pypi/dm/ormlambda?label=downloads)
18
16
  ![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)
19
17
 
20
- # ormMySQL
18
+ # ormlambda Documentation
21
19
  This ORM is designed to connect with a MySQL server, facilitating the management of various database queries. Built with flexibility and efficiency in mind, this ORM empowers developers to interact with the database using lambda functions, allowing for concise and expressive query construction.
22
20
 
23
21
  # Creating your first lambda query
@@ -38,19 +36,34 @@ database = MySQLRepository(user=USERNAME, password=PASSWORD, database="sakila",
38
36
  ## Select all columns
39
37
  ```python
40
38
  from ormlambda import ORM
41
- from ormlambda.databases.my_sql import MySQLRepository
39
+ from ormlambda import create_engine
42
40
 
43
41
  from models.address import Address
44
- from config import config_dict
42
+ from test.config import config_dict
45
43
 
46
- db = MySQLRepository(**config_dict)
44
+ db = create_engine('mysql://root:1234@localhost:3306/sakila')
47
45
 
48
- AddressModel = ORM(Address,db)
46
+ AddressModel = ORM(Address, db)
49
47
 
50
48
  result = AddressModel.select()
51
49
  ```
52
50
  The `result` var will be of type `tuple[Address, ...]`
53
51
 
52
+ ## Improving Typing
53
+ For those cases where you need to pass the database configuration from a `dict`, you can use `MySQLArgs` TypedDict object to improve type annotations.
54
+
55
+ ```python
56
+ from ormlambda.databases.my_sql.types import MySQLArgs
57
+
58
+ config_dict: MySQLArgs = {
59
+ "user": DB_USERNAME,
60
+ "password": DB_PASSWORD,
61
+ "host": DB_HOST,
62
+ "database": DB_DATABASE,
63
+ }
64
+ db = MySQLRepository(**config_dict)
65
+ ```
66
+
54
67
  ## Select multiples tables
55
68
  Once the `AddressModel` class is created, we will not only be able to access all the information in that table, but also all the information in all the tables that have foreign keys related to it."
56
69
 
@@ -296,6 +309,92 @@ res = AddressModel.sum(Address.address_id, execute=True)
296
309
  res = AddressModel.count(Address.address_id, execute=True)
297
310
  ```
298
311
 
312
+ ## 1. Concat
313
+
314
+ The `concat` method allows you to concatenate multiple columns or values into a single string. This is particularly useful for creating derived fields in your queries.
315
+
316
+ #### Usage
317
+
318
+ ```python
319
+ response = ORM(Address, db).where(Address.City.Country.country.regex(r"^Spain")).first(
320
+ (
321
+ Address.address,
322
+ Address.City.city,
323
+ self.tmodel.concat(
324
+ (
325
+ "Address: ",
326
+ Address.address,
327
+ " - city: ",
328
+ Address.City.city,
329
+ " - country: ",
330
+ Address.City.Country.country,
331
+ )
332
+ ),
333
+ ),
334
+ flavour=dict,
335
+ )
336
+
337
+ {
338
+ "address_address": "939 Probolinggo Loop",
339
+ "city_city": "A Coruña (La Coruña)",
340
+ "CONCAT": "Address: 939 Probolinggo Loop - city: A Coruña (La Coruña) - country: Spain",
341
+ }
342
+ ```
343
+ As you can see in the response, the result is a dictionary where the keys are a combination of the table name and the column name. This is done to avoid collisions with columns from other tables that might have the same name.
344
+
345
+ Another elegant approach to adjust the response and obtain an object is by using the `flavour` attribute. You can pass a callable object, which will be used to instantiate it with the returned data.
346
+
347
+ ## Using BaseModel for Custom Responses (Pydantic)
348
+
349
+ You can utilize `BaseModel` from Pydantic to create structured response models. This allows you to define the expected structure of your data, ensuring type safety and validation.
350
+
351
+ ### Example: Creating a Custom Response Model
352
+
353
+ You can create a custom response model by subclassing `BaseModel`. In this model, you define the fields that you expect in your response, along with their types.
354
+
355
+ ```python
356
+ class AddressCombine(BaseModel):
357
+ address: str
358
+ city: str
359
+ country: str
360
+
361
+ model_config: ConfigDict = {"extra": "forbid"}
362
+
363
+ ddbb = MySQLRepository(**config_dict)
364
+ model = ORM(Address, ddbb)
365
+ select = (
366
+ model.order(lambda x: x.City.Country.country, "DESC")
367
+ .limit(10)
368
+ .where(Address.City.Country.country == "Spain")
369
+ .first(
370
+ lambda x: (
371
+ x.address,
372
+ x.City.city,
373
+ x.City.Country.country,
374
+ ),
375
+ flavour=AddressCombine,
376
+ )
377
+ )
378
+ ```
379
+
380
+ Once you execute the query, the result will be an instance of your custom model. You can access the fields directly, ensuring that the data adheres to the structure you defined.
381
+
382
+ ```python
383
+
384
+
385
+ print(select.address)
386
+ print(select.city)
387
+ print(select.country)
388
+ ```
389
+
390
+
391
+ <!-- ### 2. Having
392
+
393
+ The `having` method is used to filter results based on aggregate functions. It is typically used in conjunction with `group by` clauses.
394
+
395
+ #### Usage -->
396
+
397
+
299
398
  ## Combine aggregation method
300
399
  As shown in the previous examples, setting the `execute` attribute to `True` allows us to perform the corresponding query in a single line. However, if you're looking to improve efficiency, you can combine all of them into one query.
301
400
  ```python