ormlambda 2.11.2__py3-none-any.whl → 3.7.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 (119) hide show
  1. ormlambda/__init__.py +11 -9
  2. ormlambda/caster/__init__.py +3 -0
  3. ormlambda/caster/base_caster.py +69 -0
  4. ormlambda/caster/caster.py +48 -0
  5. ormlambda/caster/interfaces/ICaster.py +26 -0
  6. ormlambda/caster/interfaces/__init__.py +1 -0
  7. ormlambda/common/__init__.py +1 -1
  8. ormlambda/common/abstract_classes/__init__.py +3 -3
  9. ormlambda/common/abstract_classes/decomposition_query.py +117 -319
  10. ormlambda/common/abstract_classes/non_query_base.py +1 -1
  11. ormlambda/common/enums/condition_types.py +2 -1
  12. ormlambda/common/enums/join_type.py +4 -1
  13. ormlambda/common/errors/__init__.py +15 -2
  14. ormlambda/common/global_checker.py +28 -0
  15. ormlambda/common/interfaces/ICustomAlias.py +4 -1
  16. ormlambda/common/interfaces/IDecompositionQuery.py +9 -34
  17. ormlambda/common/interfaces/IJoinSelector.py +21 -0
  18. ormlambda/common/interfaces/__init__.py +4 -6
  19. ormlambda/components/__init__.py +4 -0
  20. ormlambda/components/insert/abstract_insert.py +1 -1
  21. ormlambda/components/select/ISelect.py +17 -0
  22. ormlambda/components/select/__init__.py +1 -0
  23. ormlambda/components/update/abstract_update.py +4 -4
  24. ormlambda/components/upsert/abstract_upsert.py +1 -1
  25. ormlambda/databases/__init__.py +5 -0
  26. ormlambda/databases/my_sql/__init__.py +3 -1
  27. ormlambda/databases/my_sql/caster/__init__.py +1 -0
  28. ormlambda/databases/my_sql/caster/caster.py +38 -0
  29. ormlambda/databases/my_sql/caster/read.py +39 -0
  30. ormlambda/databases/my_sql/caster/types/__init__.py +8 -0
  31. ormlambda/databases/my_sql/caster/types/bytes.py +31 -0
  32. ormlambda/databases/my_sql/caster/types/datetime.py +34 -0
  33. ormlambda/databases/my_sql/caster/types/float.py +31 -0
  34. ormlambda/databases/my_sql/caster/types/int.py +31 -0
  35. ormlambda/databases/my_sql/caster/types/iterable.py +31 -0
  36. ormlambda/databases/my_sql/caster/types/none.py +30 -0
  37. ormlambda/databases/my_sql/caster/types/point.py +43 -0
  38. ormlambda/databases/my_sql/caster/types/string.py +31 -0
  39. ormlambda/databases/my_sql/caster/write.py +37 -0
  40. ormlambda/databases/my_sql/clauses/ST_AsText.py +36 -0
  41. ormlambda/databases/my_sql/clauses/ST_Contains.py +31 -0
  42. ormlambda/databases/my_sql/clauses/__init__.py +6 -4
  43. ormlambda/databases/my_sql/clauses/alias.py +24 -21
  44. ormlambda/databases/my_sql/clauses/count.py +32 -28
  45. ormlambda/databases/my_sql/clauses/create_database.py +3 -4
  46. ormlambda/databases/my_sql/clauses/delete.py +10 -10
  47. ormlambda/databases/my_sql/clauses/drop_database.py +3 -5
  48. ormlambda/databases/my_sql/clauses/drop_table.py +3 -3
  49. ormlambda/databases/my_sql/clauses/group_by.py +4 -7
  50. ormlambda/databases/my_sql/clauses/insert.py +33 -19
  51. ormlambda/databases/my_sql/clauses/joins.py +66 -59
  52. ormlambda/databases/my_sql/clauses/limit.py +1 -1
  53. ormlambda/databases/my_sql/clauses/offset.py +1 -1
  54. ormlambda/databases/my_sql/clauses/order.py +36 -23
  55. ormlambda/databases/my_sql/clauses/select.py +25 -36
  56. ormlambda/databases/my_sql/clauses/update.py +38 -13
  57. ormlambda/databases/my_sql/clauses/upsert.py +2 -2
  58. ormlambda/databases/my_sql/clauses/where.py +45 -0
  59. ormlambda/databases/my_sql/functions/concat.py +24 -27
  60. ormlambda/databases/my_sql/functions/max.py +32 -28
  61. ormlambda/databases/my_sql/functions/min.py +32 -28
  62. ormlambda/databases/my_sql/functions/sum.py +32 -28
  63. ormlambda/databases/my_sql/join_context.py +75 -0
  64. ormlambda/databases/my_sql/repository/__init__.py +1 -0
  65. ormlambda/databases/my_sql/{repository.py → repository/repository.py} +104 -73
  66. ormlambda/databases/my_sql/statements.py +231 -153
  67. ormlambda/engine/__init__.py +0 -0
  68. ormlambda/engine/template.py +47 -0
  69. ormlambda/model/__init__.py +0 -0
  70. ormlambda/model/base_model.py +37 -0
  71. ormlambda/repository/__init__.py +2 -0
  72. ormlambda/repository/base_repository.py +14 -0
  73. ormlambda/repository/interfaces/IDatabaseConnection.py +12 -0
  74. ormlambda/{common → repository}/interfaces/IRepositoryBase.py +6 -5
  75. ormlambda/repository/interfaces/__init__.py +2 -0
  76. ormlambda/sql/__init__.py +3 -0
  77. ormlambda/sql/clause_info/__init__.py +3 -0
  78. ormlambda/sql/clause_info/clause_info.py +434 -0
  79. ormlambda/sql/clause_info/clause_info_context.py +87 -0
  80. ormlambda/sql/clause_info/interface/IAggregate.py +10 -0
  81. ormlambda/sql/clause_info/interface/__init__.py +1 -0
  82. ormlambda/sql/column.py +126 -0
  83. ormlambda/sql/comparer.py +156 -0
  84. ormlambda/sql/foreign_key.py +115 -0
  85. ormlambda/sql/interfaces/__init__.py +0 -0
  86. ormlambda/sql/table/__init__.py +1 -0
  87. ormlambda/{utils → sql/table}/fields.py +6 -5
  88. ormlambda/{utils → sql/table}/table_constructor.py +43 -91
  89. ormlambda/sql/types.py +25 -0
  90. ormlambda/statements/__init__.py +2 -0
  91. ormlambda/statements/base_statement.py +129 -0
  92. ormlambda/statements/interfaces/IStatements.py +309 -0
  93. ormlambda/statements/interfaces/__init__.py +1 -0
  94. ormlambda/statements/types.py +51 -0
  95. ormlambda/utils/__init__.py +1 -3
  96. ormlambda/utils/module_tree/__init__.py +1 -0
  97. ormlambda/utils/module_tree/dynamic_module.py +20 -14
  98. {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/METADATA +132 -68
  99. ormlambda-3.7.0.dist-info/RECORD +117 -0
  100. ormlambda/common/abstract_classes/abstract_model.py +0 -115
  101. ormlambda/common/interfaces/IAggregate.py +0 -10
  102. ormlambda/common/interfaces/IStatements.py +0 -348
  103. ormlambda/components/where/__init__.py +0 -1
  104. ormlambda/components/where/abstract_where.py +0 -15
  105. ormlambda/databases/my_sql/clauses/where_condition.py +0 -222
  106. ormlambda/model_base.py +0 -36
  107. ormlambda/utils/column.py +0 -105
  108. ormlambda/utils/foreign_key.py +0 -81
  109. ormlambda/utils/lambda_disassembler/__init__.py +0 -4
  110. ormlambda/utils/lambda_disassembler/dis_types.py +0 -133
  111. ormlambda/utils/lambda_disassembler/disassembler.py +0 -69
  112. ormlambda/utils/lambda_disassembler/dtypes.py +0 -103
  113. ormlambda/utils/lambda_disassembler/name_of.py +0 -41
  114. ormlambda/utils/lambda_disassembler/nested_element.py +0 -44
  115. ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -145
  116. ormlambda-2.11.2.dist-info/RECORD +0 -81
  117. /ormlambda/{utils → sql}/dtypes.py +0 -0
  118. {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/LICENSE +0 -0
  119. {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/WHEEL +0 -0
@@ -1,62 +1,188 @@
1
1
  from __future__ import annotations
2
- from typing import Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
3
- import inspect
4
- from mysql.connector import MySQLConnection, errors, errorcode
5
- import functools
2
+ from typing import Concatenate, Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
3
+ from mysql.connector import errors, errorcode
4
+
5
+ from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
6
+ from ormlambda.databases.my_sql.clauses.joins import JoinSelector
7
+ from ormlambda import ForeignKey
8
+
9
+ from ormlambda.common.interfaces import IQuery
10
+ from mysql.connector import MySQLConnection
6
11
 
7
12
 
8
13
  if TYPE_CHECKING:
9
14
  from ormlambda import Table
10
- from ormlambda.components.where.abstract_where import AbstractWhere
11
- from ormlambda.common.interfaces.IStatements import OrderTypes
12
- from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
13
- from ormlambda.common.interfaces.IRepositoryBase import TypeExists
14
- from ormlambda.common.interfaces import IAggregate
15
- from ormlambda.common.interfaces.IStatements import WhereTypes
15
+ from ormlambda.statements.types import OrderTypes
16
+ from ormlambda.sql.types import ColumnType
17
+ from ormlambda.repository.interfaces import IRepositoryBase
18
+ from ormlambda.statements.interfaces import IStatements_two_generic
19
+ from ormlambda.repository.interfaces.IRepositoryBase import TypeExists
20
+ from ormlambda.sql.clause_info import IAggregate
21
+ from ormlambda.statements.types import WhereTypes
16
22
 
17
- from ormlambda.utils.foreign_key import ForeignKey
18
23
 
19
- from ormlambda import AbstractSQLStatements
24
+ from ormlambda.sql.clause_info import ClauseInfo
25
+ from ormlambda.statements import BaseStatement
20
26
  from .clauses import DeleteQuery
21
27
  from .clauses import InsertQuery
22
- from .clauses import JoinSelector
23
- from .clauses import LimitQuery
24
- from .clauses import OffsetQuery
25
- from .clauses import OrderQuery
28
+ from .clauses import Limit
29
+ from .clauses import Offset
30
+ from .clauses import Order
26
31
  from .clauses.select import Select
27
32
 
28
33
  from .clauses import UpsertQuery
29
34
  from .clauses import UpdateQuery
30
- from .clauses import WhereCondition
35
+ from .clauses import Where
31
36
  from .clauses import Count
32
37
  from .clauses import GroupBy
33
38
  from .clauses import Alias
34
39
 
35
40
 
36
- from ormlambda.utils import Table
41
+ from ormlambda import Table, Column
37
42
  from ormlambda.common.enums import JoinType
38
43
  from . import functions as func
44
+ from .join_context import JoinContext, TupleJoinType
45
+ from ormlambda.common.global_checker import GlobalChecker
39
46
 
40
47
 
41
48
  # COMMENT: It's so important to prevent information generated by other tests from being retained in the class.
42
- def clear_list(f: Callable[..., Any]):
43
- @functools.wraps(f)
44
- def wrapper(self: MySQLStatements, *args, **kwargs):
49
+ @staticmethod
50
+ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Callable[P, T]:
51
+ def wrapper(self: MySQLStatements, *args: P.args, **kwargs: P.kwargs) -> T:
45
52
  try:
46
53
  return f(self, *args, **kwargs)
54
+ except Exception as err:
55
+ raise err
47
56
  finally:
48
- self._query_list.clear()
57
+ ForeignKey.stored_calls.clear()
58
+ self._query_builder.clear()
49
59
 
50
60
  return wrapper
51
61
 
52
62
 
53
- class MySQLStatements[T: Table, *Ts](AbstractSQLStatements[T, *Ts, MySQLConnection]):
54
- def __init__(self, model: tuple[T, *Ts], repository: IRepositoryBase[MySQLConnection]) -> None:
63
+ class QueryBuilder(IQuery):
64
+ __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "Order", "GroupBy", "Limit", "Offset")
65
+
66
+ def __init__(self, by: JoinType = JoinType.INNER_JOIN):
67
+ self._context = ClauseInfoContext()
68
+ self._query_list: dict[str, IQuery] = {}
69
+ self._by = by
70
+
71
+ self._joins: Optional[IQuery] = None
72
+ self._select: Optional[IQuery] = None
73
+ self._where: Optional[IQuery] = None
74
+ self._order: Optional[IQuery] = None
75
+ self._group_by: Optional[IQuery] = None
76
+ self._limit: Optional[IQuery] = None
77
+ self._offset: Optional[IQuery] = None
78
+
79
+ def add_statement[T](self, clause: ClauseInfo[T]):
80
+ self.update_context(clause)
81
+ self._query_list[type(clause).__name__] = clause
82
+
83
+ @property
84
+ def by(self) -> JoinType:
85
+ return self._by
86
+
87
+ @by.setter
88
+ def by(self, value: JoinType) -> None:
89
+ self._by = value
90
+
91
+ @property
92
+ def JOINS(self) -> Optional[set[JoinSelector]]:
93
+ return self._joins
94
+
95
+ @property
96
+ def SELECT(self) -> IQuery:
97
+ return self._query_list.get("Select", None)
98
+
99
+ @property
100
+ def WHERE(self) -> IQuery:
101
+ where = self._query_list.get("Where", None)
102
+ if not isinstance(where, Iterable):
103
+ if not where:
104
+ return ()
105
+ return (where,)
106
+ return where
107
+
108
+ @property
109
+ def ORDER(self) -> IQuery:
110
+ return self._query_list.get("Order", None)
111
+
112
+ @property
113
+ def GROUP_BY(self) -> IQuery:
114
+ return self._query_list.get("GroupBy", None)
115
+
116
+ @property
117
+ def LIMIT(self) -> IQuery:
118
+ return self._query_list.get("Limit", None)
119
+
120
+ @property
121
+ def OFFSET(self) -> IQuery:
122
+ return self._query_list.get("Offset", None)
123
+
124
+ @property
125
+ def query(self) -> str:
126
+ # COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
127
+ # This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
128
+
129
+ extract_joins = self.pop_tables_and_create_joins_from_ForeignKey(self._by)
130
+
131
+ JOINS = self.stringify_foreign_key(extract_joins, " ")
132
+ query_list: tuple[str, ...] = (
133
+ self.SELECT.query,
134
+ JOINS,
135
+ Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
136
+ self.ORDER.query if self.ORDER else None,
137
+ self.GROUP_BY.query if self.GROUP_BY else None,
138
+ self.LIMIT.query if self.LIMIT else None,
139
+ self.OFFSET.query if self.OFFSET else None,
140
+ )
141
+ return " ".join([x for x in query_list if x])
142
+
143
+ def stringify_foreign_key(self, joins: set[JoinSelector], sep: str = "\n") -> Optional[str]:
144
+ if not joins:
145
+ return None
146
+ sorted_joins = JoinSelector.sort_join_selectors(joins)
147
+ return f"{sep}".join([join.query for join in sorted_joins])
148
+
149
+ def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
150
+ # When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
151
+ if not ForeignKey.stored_calls:
152
+ return None
153
+
154
+ joins = set()
155
+ # Always it's gonna be a set of two
156
+ # FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
157
+ for _ in range(len(ForeignKey.stored_calls)):
158
+ fk = ForeignKey.stored_calls.pop()
159
+ self._context._add_table_alias(fk.tright, fk.alias)
160
+ join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
161
+ joins.add(join)
162
+
163
+ return joins
164
+
165
+ def clear(self) -> None:
166
+ self.__init__()
167
+ return None
168
+
169
+ def update_context(self, clause: ClauseInfo) -> None:
170
+ if not hasattr(clause, "context"):
171
+ return None
172
+
173
+ if clause.context is not None:
174
+ self._context.update(clause.context)
175
+ clause.context = self._context
176
+
177
+
178
+ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
179
+ def __init__(self, model: tuple[T, *Ts], repository: IRepositoryBase) -> None:
55
180
  super().__init__(model, repository=repository)
181
+ self._query_builder = QueryBuilder()
56
182
 
57
183
  @property
58
184
  @override
59
- def repository(self) -> IRepositoryBase[MySQLConnection]:
185
+ def repository(self) -> IRepositoryBase:
60
186
  return self._repository
61
187
 
62
188
  @override
@@ -107,7 +233,7 @@ class MySQLStatements[T: Table, *Ts](AbstractSQLStatements[T, *Ts, MySQLConnecti
107
233
  delete = DeleteQuery(self._model, self._repository)
108
234
  delete.delete(instances)
109
235
  delete.execute()
110
- # not necessary to call self._query_list.clear() because select() method already call it
236
+ # not necessary to call self._query_builder.clear() because select() method already call it
111
237
  return None
112
238
 
113
239
  @override
@@ -121,7 +247,7 @@ class MySQLStatements[T: Table, *Ts](AbstractSQLStatements[T, *Ts, MySQLConnecti
121
247
  @override
122
248
  @clear_list
123
249
  def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
124
- update = UpdateQuery(self._model, self._repository, self._query_list["where"])
250
+ update = UpdateQuery(self._model, self._repository, self._query_builder.WHERE)
125
251
  update.update(dicc)
126
252
  update.execute()
127
253
 
@@ -129,131 +255,138 @@ class MySQLStatements[T: Table, *Ts](AbstractSQLStatements[T, *Ts, MySQLConnecti
129
255
 
130
256
  @override
131
257
  def limit(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
132
- limit = LimitQuery(number)
258
+ limit = Limit(number)
133
259
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
134
- self._query_list["limit"] = [limit]
260
+ self._query_builder.add_statement(limit)
135
261
  return self
136
262
 
137
263
  @override
138
264
  def offset(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
139
- offset = OffsetQuery(number)
140
- self._query_list["offset"] = [offset]
265
+ offset = Offset(number)
266
+ self._query_builder.add_statement(offset)
141
267
  return self
142
268
 
143
269
  @override
144
270
  def count(
145
271
  self,
146
- selection: Callable[[T], tuple] = lambda x: "*",
147
- alias=True,
148
- alias_name="count",
149
- ) -> IQuery:
150
- return Count[T](self._model, selection, alias=alias, alias_name=alias_name)
272
+ selection: None | Iterable[Table] | Callable[[T], tuple] = lambda x: "*",
273
+ alias_clause="count",
274
+ execute: bool = False,
275
+ ) -> Optional[int]:
276
+ if execute is True:
277
+ return self.select_one(self.count(selection, alias_clause, False), flavour=dict)[alias_clause]
278
+
279
+ if GlobalChecker.is_lambda_function(selection):
280
+ selection = selection(*self.models)
281
+ return Count[T](element=selection, alias_clause=alias_clause, context=self._query_builder._context)
151
282
 
152
283
  @override
153
- def where(self, conditions: WhereTypes = lambda: None, **kwargs) -> IStatements_two_generic[T, MySQLConnection]:
284
+ def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
154
285
  # FIXME [x]: I've wrapped self._model into tuple to pass it instance attr. Idk if it's correct
155
286
 
156
- if isinstance(conditions, Iterable):
157
- for x in conditions:
158
- self._query_list["where"].append(WhereCondition[T](function=x, instances=self._models, **kwargs))
159
- return self
160
-
161
- where_query = WhereCondition[T](function=conditions, instances=self._models, **kwargs)
162
- self._query_list["where"].append(where_query)
287
+ if GlobalChecker.is_lambda_function(conditions):
288
+ conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
289
+ if not isinstance(conditions, Iterable):
290
+ conditions = (conditions,)
291
+ self._query_builder.add_statement(Where(*conditions))
163
292
  return self
164
293
 
165
294
  @override
166
- def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
167
- order = OrderQuery[T](self._model, _lambda_col, order_type)
168
- self._query_list["order"].append(order)
295
+ def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
296
+ query = GlobalChecker.resolved_callback_object(columns, self._models) if GlobalChecker.is_lambda_function(columns) else columns
297
+ order = Order(query, order_type)
298
+ self._query_builder.add_statement(order)
169
299
  return self
170
300
 
171
301
  @override
172
- def concat[*Ts](self, selector: Callable[[T], tuple[*Ts]], alias: bool = True, alias_name: str = "CONCAT") -> IAggregate[T]:
173
- return func.Concat[T](self._model, selector, alias=alias, alias_name=alias_name)
302
+ def concat[*Ts](self, selector: Callable[[T], tuple[*Ts]], alias: bool = True, alias_name: str = "CONCAT") -> IAggregate:
303
+ return func.Concat[T](self._model, selector, alias=alias, alias_name=alias_name, context=self._query_builder._context)
174
304
 
175
305
  @override
176
- def max[TProp](self, column: Callable[[T], TProp], alias: bool = True, alias_name: str = "max") -> TProp:
177
- return func.Max[T](self._model, column=column, alias=alias, alias_name=alias_name)
306
+ def max[TProp](self, column: Callable[[T], TProp], alias: str = "max", execute: bool = False) -> TProp:
307
+ if execute is True:
308
+ return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
309
+ return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
178
310
 
179
311
  @override
180
- def min[TProp](self, column: Callable[[T], TProp], alias: bool = True, alias_name: str = "min") -> TProp:
181
- return func.Min[T](self._model, column=column, alias=alias, alias_name=alias_name)
312
+ def min[TProp](self, column: Callable[[T], TProp], alias: str = "min", execute: bool = False) -> TProp:
313
+ if execute is True:
314
+ return self.select_one(self.min(column, alias, execute=False), flavour=dict)[alias]
315
+ return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context)
182
316
 
183
317
  @override
184
- def sum[TProp](self, column: Callable[[T], TProp], alias: bool = True, alias_name: str = "sum") -> TProp:
185
- return func.Sum[T](self._model, column=column, alias=alias, alias_name=alias_name)
318
+ def sum[TProp](self, column: Callable[[T], TProp], alias: str = "sum", execute: bool = False) -> TProp:
319
+ if execute is True:
320
+ return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
321
+ return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
186
322
 
187
323
  @override
188
- def join[*FKTables](
189
- self,
190
- table: Optional[T] = None,
191
- relation: Optional[WhereCondition[T, *FKTables]] = None,
192
- join_type: Optional[JoinType] = None,
193
- ) -> IStatements_two_generic[T, *FKTables, MySQLConnection]:
194
- if isinstance(table, type) and issubclass(table, Table):
195
- joins = ((table, relation, join_type),)
196
- else:
197
- if any(len(x) != 3 for x in table):
198
- raise ValueError("Each tuple inside of 'join' method must contain the referenced table, relation between columns and join type")
199
- joins = table
200
-
201
- new_tables: list[Type[Table]] = [self._model]
202
-
203
- for table, where, by in joins:
204
- new_tables.append(table)
205
- join_query = JoinSelector[T, type(table)](self._model, table, by=by, where=where)
206
- self._query_list["join"].append(join_query)
207
- self._models = new_tables
208
- return self
324
+ def join[LTable: Table, LProp, RTable: Table, RProp](self, joins: tuple[TupleJoinType[LTable, LProp, RTable, RProp]]) -> JoinContext[tuple[*TupleJoinType[LTable, LProp, RTable, RProp]]]:
325
+ return JoinContext(self, joins, self._query_builder._context)
209
326
 
210
327
  @override
211
- def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T, *Ts], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN, **kwargs):
212
- if len(inspect.signature(selector).parameters) == 0:
328
+ @clear_list
329
+ def select[TValue, TFlavour, *Ts](
330
+ self,
331
+ selector: Optional[tuple[TValue, *Ts]] = None,
332
+ *,
333
+ flavour: Optional[Type[TFlavour]] = None,
334
+ by: JoinType = JoinType.INNER_JOIN,
335
+ **kwargs,
336
+ ):
337
+ select_clause = GlobalChecker.resolved_callback_object(selector, self._models) if GlobalChecker.is_lambda_function(selector) else selector
338
+
339
+ if selector is None:
213
340
  # COMMENT: if we do not specify any lambda function we assumed the user want to retreive only elements of the Model itself avoiding other models
214
- result = self.select(selector=lambda x: (x,), flavour=flavour, by=by)
341
+ result = self.select(selector=lambda x: x, flavour=flavour, by=by)
215
342
  # COMMENT: Always we want to retrieve tuple[tuple[Any]]. That's the reason to return result[0] when we ensure the user want only objects of the first table.
216
343
  # Otherwise, we wil return the result itself
217
344
  if flavour:
218
345
  return result
219
346
  return () if not result else result[0]
220
347
 
221
- joins = self._query_list.pop("join", None)
222
348
  select = Select[T, *Ts](
223
349
  self._models,
224
- lambda_query=selector,
225
- by=by,
226
- alias=False,
227
- joins=joins,
350
+ columns=select_clause,
228
351
  )
229
- self._query_list["select"].append(select)
352
+ self._query_builder.add_statement(select)
230
353
 
231
- self._query: str = self._build()
354
+ self._query_builder.by = by
355
+ self._query: str = self._query_builder.query
232
356
 
357
+ self._query_builder.clear()
233
358
  if flavour:
234
359
  result = self._return_flavour(self.query, flavour, select, **kwargs)
235
- if issubclass(flavour, tuple) and isinstance(selector(*self._models), property):
360
+ if issubclass(flavour, tuple) and isinstance(select_clause, Column | ClauseInfo):
236
361
  return tuple([x[0] for x in result])
237
362
  return result
238
363
  return self._return_model(select, self.query)
239
364
 
240
365
  @override
241
- def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T, *Ts], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN):
366
+ def select_one[TValue, TFlavour, *Ts](self, selector: Optional[tuple[TValue, *Ts]] = None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN):
242
367
  self.limit(1)
243
- if len(inspect.signature(selector).parameters) == 0:
244
- response = self.select(selector=lambda x: (x,), flavour=flavour, by=by)
245
- else:
246
- response = self.select(selector=selector, flavour=flavour, by=by)
247
368
 
369
+ response = self.select(selector=selector, flavour=flavour, by=by)
370
+
371
+ if not isinstance(response, Iterable):
372
+ return response
248
373
  if flavour:
249
374
  return response[0] if response else None
250
375
 
251
376
  # response var could be return more than one element when we work with models an we
252
377
  # select columns from different tables using a join query
253
- if len(response) == 1 and len(response[0]) == 1:
254
- return response[0][0]
378
+ # FIXME [x]: before it was if len(response) == 1 and len(response[0]) == 1: return response[0][0]
379
+ if len(response) == 1:
380
+ if isinstance(response[0], Iterable) and len(response[0]) == 1:
381
+ return response[0][0]
382
+ else:
383
+ return response[0]
255
384
  return tuple([res[0] for res in response])
256
385
 
386
+ @override
387
+ def first[TValue, *Ts](self, selector: Optional[tuple[TValue, *Ts]] = None):
388
+ return self.select_one(selector)
389
+
257
390
  @override
258
391
  def group_by(self, column: str | Callable[[T, *Ts], Any]):
259
392
  if isinstance(column, str):
@@ -261,65 +394,10 @@ class MySQLStatements[T: Table, *Ts](AbstractSQLStatements[T, *Ts, MySQLConnecti
261
394
  else:
262
395
  groupby = GroupBy[T, tuple[*Ts]](self._models, column)
263
396
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
264
- self._query_list["group by"].append(groupby)
397
+ self._query_builder.add_statement(groupby)
265
398
  return self
266
399
 
267
400
  @override
268
- def alias(self, column: Callable[[T, *Ts], Any], alias: str) -> IStatements_two_generic[T, *Ts, MySQLConnection]:
269
- return Alias[T, *Ts](self.models, column, alias_name=alias)
270
-
271
- @override
272
- @clear_list
273
- def _build(self) -> str:
274
- query_list: list[str] = []
275
- for x in self.__order__:
276
- if len(self._query_list) == 0:
277
- break
278
-
279
- sub_query: Optional[list[IQuery]] = self._query_list.pop(x, None)
280
- if sub_query is None:
281
- continue
282
-
283
- if isinstance(sub_query[0], WhereCondition):
284
- query_ = self.__build_where_clause(sub_query)
285
-
286
- elif isinstance((select := sub_query[0]), Select):
287
- query_: str = ""
288
- where_joins = self.__create_necessary_inner_join()
289
- if where_joins:
290
- select._joins.update(where_joins)
291
- query_ = select.query
292
-
293
- else:
294
- query_ = "\n".join([x.query for x in sub_query])
295
-
296
- query_list.append(query_)
297
- return "\n".join(query_list)
298
-
299
- def __build_where_clause(self, where_condition: list[AbstractWhere]) -> str:
300
- query: str = where_condition[0].query
301
-
302
- for where in where_condition[1:]:
303
- q = where.query.replace(where.WHERE, "AND")
304
- and_, clause = q.split(" ", maxsplit=1)
305
- query += f" {and_} ({clause})"
306
- return query
307
-
308
- def __create_necessary_inner_join(self) -> Optional[set[JoinSelector]]:
309
- # When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
310
- if "where" not in self._query_list:
311
- return None
312
-
313
- for where in self._query_list["where"]:
314
- where: AbstractWhere
315
-
316
- tables = where.get_involved_tables()
317
-
318
- if tables:
319
- # FIXME [ ]: Refactor to avoid copy and paste the same code of the '_add_fk_relationship' method
320
- joins = []
321
- for ltable, rtable in tables:
322
- lambda_relationship = ForeignKey.MAPPED[ltable.__table_name__].referenced_tables[rtable.__table_name__].relationship
323
- joins.append(JoinSelector(ltable, rtable, JoinType.INNER_JOIN, where=lambda_relationship))
324
- return set(joins)
325
- return None
401
+ def alias[TProp](self, column: ColumnType[TProp], alias: str) -> Alias[T, TProp]:
402
+ ci = ClauseInfo(table=column.table, column=column, alias_clause=alias, context=self._query_builder._context)
403
+ return Alias(ci, alias_clause=alias)
File without changes
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Optional, Type
4
+
5
+ if TYPE_CHECKING:
6
+ from ormlambda.repository import IRepositoryBase
7
+ from ormlambda.statements.interfaces import IStatements
8
+ from ormlambda.caster import ICaster
9
+
10
+
11
+ class Template[TCnx]:
12
+ repository: Type[IRepositoryBase]
13
+ caster: Type[ICaster]
14
+ statement: Type[IStatements]
15
+
16
+
17
+ class RepositoryTemplateDict[TRepo]:
18
+ _instance: Optional[RepositoryTemplateDict[TRepo]] = None
19
+
20
+ def __new__[T: Template](cls):
21
+ if cls._instance is not None:
22
+ return cls._instance
23
+
24
+ cls._instance = super().__new__(cls)
25
+
26
+ from ..databases.my_sql import (
27
+ MySQLCaster,
28
+ MySQLRepository,
29
+ MySQLStatements,
30
+ )
31
+
32
+ class MySQLTemplate[TCnx](Template[TCnx]):
33
+ repository = MySQLRepository
34
+ caster = MySQLCaster
35
+ statement = MySQLStatements
36
+
37
+ # FIXME [ ]: should return T instead of Template
38
+ cls._data: dict[IRepositoryBase, Template] = {
39
+ MySQLRepository: MySQLTemplate,
40
+ }
41
+
42
+ return cls._instance
43
+
44
+ # FIXME [ ]: should return T instead of Template
45
+ def get[T: Template](self, key: IRepositoryBase) -> Template:
46
+ key = key if isinstance(key, type) else type(key)
47
+ return self._instance._data[key]
File without changes
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ from typing import TYPE_CHECKING, Type
5
+
6
+ from ormlambda.engine.template import RepositoryTemplateDict
7
+
8
+ if TYPE_CHECKING:
9
+ from ormlambda.statements.interfaces import IStatements_two_generic
10
+ from ormlambda.repository import BaseRepository
11
+
12
+
13
+ # endregion
14
+
15
+
16
+ class BaseModel[T]:
17
+ """
18
+ Class to select the correct BaseStatement class depends on the repository.
19
+
20
+ Contiene los metodos necesarios para hacer consultas a una tabla
21
+ """
22
+
23
+ # region Constructor
24
+
25
+ def __new__[TPool](cls, model: Type[T], repository: BaseRepository[TPool]) -> IStatements_two_generic[T, TPool]:
26
+ if repository is None:
27
+ raise ValueError("`None` cannot be passed to the `repository` attribute when calling the `BaseModel` class")
28
+
29
+ new_cls = RepositoryTemplateDict().get(repository).statement
30
+
31
+ if not new_cls:
32
+ raise Exception(f"The selected repository '{repository}' does not exist.")
33
+
34
+ return new_cls(model, repository)
35
+
36
+
37
+ ORM = BaseModel
@@ -0,0 +1,2 @@
1
+ from .interfaces import IRepositoryBase # noqa: F401
2
+ from .base_repository import BaseRepository # noqa: F401
@@ -0,0 +1,14 @@
1
+ import contextlib
2
+ from typing import Generator, Type
3
+ from ormlambda.repository import IRepositoryBase
4
+ import abc
5
+
6
+
7
+ class BaseRepository[TPool](IRepositoryBase):
8
+ def __init__(self, pool: Type[TPool], **kwargs: str) -> None:
9
+ self._data_config: dict[str, str] = kwargs
10
+ self._pool: TPool = pool(**kwargs)
11
+
12
+ @contextlib.contextmanager
13
+ @abc.abstractmethod
14
+ def get_connection[TCnx](self) -> Generator[TCnx, None, None]: ...
@@ -0,0 +1,12 @@
1
+ import abc
2
+
3
+
4
+ class IDatabaseConnection[TCnx](abc.ABC):
5
+ @abc.abstractmethod
6
+ def cursor(self, **kwargs): ...
7
+ @abc.abstractmethod
8
+ def commit(self): ...
9
+ @abc.abstractmethod
10
+ def rollback(self): ...
11
+ @abc.abstractmethod
12
+ def get_connection(self) -> TCnx: ...
@@ -1,15 +1,16 @@
1
+ from __future__ import annotations
1
2
  from abc import ABC, abstractmethod
2
- from typing import Literal, Optional, Type
3
+ from typing import Optional, Type, Iterable, TYPE_CHECKING
3
4
 
4
- TypeExists = Literal["fail", "replace", "append"]
5
+ if TYPE_CHECKING:
6
+ from ormlambda.statements.types import TypeExists
5
7
 
6
-
7
- class IRepositoryBase[T](ABC):
8
+ class IRepositoryBase(ABC):
8
9
  def __repr__(self) -> str:
9
10
  return f"{IRepositoryBase.__name__}: {self.__class__.__name__}"
10
11
 
11
12
  @abstractmethod
12
- def read_sql[TFlavour](self, cnx: T, query: str, flavour: Optional[Type[TFlavour]], **kwargs) -> tuple[TFlavour]: ...
13
+ def read_sql[TFlavour: Iterable](self, query: str, flavour: Optional[Type[TFlavour]], **kwargs) -> tuple[TFlavour]: ...
13
14
 
14
15
  @abstractmethod
15
16
  def executemany_with_values(self, query: str, values) -> None: ...
@@ -0,0 +1,2 @@
1
+ from .IRepositoryBase import IRepositoryBase # noqa: F401
2
+ from .IDatabaseConnection import IDatabaseConnection # noqa: F401
@@ -0,0 +1,3 @@
1
+ from .column import Column as Column
2
+ from .foreign_key import ForeignKey as ForeignKey
3
+ from .table import Table as Table
@@ -0,0 +1,3 @@
1
+ from .interface import IAggregate # noqa: F401
2
+ from .clause_info import ClauseInfo, AggregateFunctionBase # noqa: F401
3
+ from .clause_info_context import ClauseContextType, ClauseInfoContext # noqa: F401