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.
- ormlambda/__init__.py +11 -9
- ormlambda/caster/__init__.py +3 -0
- ormlambda/caster/base_caster.py +69 -0
- ormlambda/caster/caster.py +48 -0
- ormlambda/caster/interfaces/ICaster.py +26 -0
- ormlambda/caster/interfaces/__init__.py +1 -0
- ormlambda/common/__init__.py +1 -1
- ormlambda/common/abstract_classes/__init__.py +3 -3
- ormlambda/common/abstract_classes/decomposition_query.py +117 -319
- ormlambda/common/abstract_classes/non_query_base.py +1 -1
- ormlambda/common/enums/condition_types.py +2 -1
- ormlambda/common/enums/join_type.py +4 -1
- ormlambda/common/errors/__init__.py +15 -2
- ormlambda/common/global_checker.py +28 -0
- ormlambda/common/interfaces/ICustomAlias.py +4 -1
- ormlambda/common/interfaces/IDecompositionQuery.py +9 -34
- ormlambda/common/interfaces/IJoinSelector.py +21 -0
- ormlambda/common/interfaces/__init__.py +4 -6
- ormlambda/components/__init__.py +4 -0
- ormlambda/components/insert/abstract_insert.py +1 -1
- ormlambda/components/select/ISelect.py +17 -0
- ormlambda/components/select/__init__.py +1 -0
- ormlambda/components/update/abstract_update.py +4 -4
- ormlambda/components/upsert/abstract_upsert.py +1 -1
- ormlambda/databases/__init__.py +5 -0
- ormlambda/databases/my_sql/__init__.py +3 -1
- ormlambda/databases/my_sql/caster/__init__.py +1 -0
- ormlambda/databases/my_sql/caster/caster.py +38 -0
- ormlambda/databases/my_sql/caster/read.py +39 -0
- ormlambda/databases/my_sql/caster/types/__init__.py +8 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +31 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +34 -0
- ormlambda/databases/my_sql/caster/types/float.py +31 -0
- ormlambda/databases/my_sql/caster/types/int.py +31 -0
- ormlambda/databases/my_sql/caster/types/iterable.py +31 -0
- ormlambda/databases/my_sql/caster/types/none.py +30 -0
- ormlambda/databases/my_sql/caster/types/point.py +43 -0
- ormlambda/databases/my_sql/caster/types/string.py +31 -0
- ormlambda/databases/my_sql/caster/write.py +37 -0
- ormlambda/databases/my_sql/clauses/ST_AsText.py +36 -0
- ormlambda/databases/my_sql/clauses/ST_Contains.py +31 -0
- ormlambda/databases/my_sql/clauses/__init__.py +6 -4
- ormlambda/databases/my_sql/clauses/alias.py +24 -21
- ormlambda/databases/my_sql/clauses/count.py +32 -28
- ormlambda/databases/my_sql/clauses/create_database.py +3 -4
- ormlambda/databases/my_sql/clauses/delete.py +10 -10
- ormlambda/databases/my_sql/clauses/drop_database.py +3 -5
- ormlambda/databases/my_sql/clauses/drop_table.py +3 -3
- ormlambda/databases/my_sql/clauses/group_by.py +4 -7
- ormlambda/databases/my_sql/clauses/insert.py +33 -19
- ormlambda/databases/my_sql/clauses/joins.py +66 -59
- ormlambda/databases/my_sql/clauses/limit.py +1 -1
- ormlambda/databases/my_sql/clauses/offset.py +1 -1
- ormlambda/databases/my_sql/clauses/order.py +36 -23
- ormlambda/databases/my_sql/clauses/select.py +25 -36
- ormlambda/databases/my_sql/clauses/update.py +38 -13
- ormlambda/databases/my_sql/clauses/upsert.py +2 -2
- ormlambda/databases/my_sql/clauses/where.py +45 -0
- ormlambda/databases/my_sql/functions/concat.py +24 -27
- ormlambda/databases/my_sql/functions/max.py +32 -28
- ormlambda/databases/my_sql/functions/min.py +32 -28
- ormlambda/databases/my_sql/functions/sum.py +32 -28
- ormlambda/databases/my_sql/join_context.py +75 -0
- ormlambda/databases/my_sql/repository/__init__.py +1 -0
- ormlambda/databases/my_sql/{repository.py → repository/repository.py} +104 -73
- ormlambda/databases/my_sql/statements.py +231 -153
- ormlambda/engine/__init__.py +0 -0
- ormlambda/engine/template.py +47 -0
- ormlambda/model/__init__.py +0 -0
- ormlambda/model/base_model.py +37 -0
- ormlambda/repository/__init__.py +2 -0
- ormlambda/repository/base_repository.py +14 -0
- ormlambda/repository/interfaces/IDatabaseConnection.py +12 -0
- ormlambda/{common → repository}/interfaces/IRepositoryBase.py +6 -5
- ormlambda/repository/interfaces/__init__.py +2 -0
- ormlambda/sql/__init__.py +3 -0
- ormlambda/sql/clause_info/__init__.py +3 -0
- ormlambda/sql/clause_info/clause_info.py +434 -0
- ormlambda/sql/clause_info/clause_info_context.py +87 -0
- ormlambda/sql/clause_info/interface/IAggregate.py +10 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/column.py +126 -0
- ormlambda/sql/comparer.py +156 -0
- ormlambda/sql/foreign_key.py +115 -0
- ormlambda/sql/interfaces/__init__.py +0 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/{utils → sql/table}/fields.py +6 -5
- ormlambda/{utils → sql/table}/table_constructor.py +43 -91
- ormlambda/sql/types.py +25 -0
- ormlambda/statements/__init__.py +2 -0
- ormlambda/statements/base_statement.py +129 -0
- ormlambda/statements/interfaces/IStatements.py +309 -0
- ormlambda/statements/interfaces/__init__.py +1 -0
- ormlambda/statements/types.py +51 -0
- ormlambda/utils/__init__.py +1 -3
- ormlambda/utils/module_tree/__init__.py +1 -0
- ormlambda/utils/module_tree/dynamic_module.py +20 -14
- {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/METADATA +132 -68
- ormlambda-3.7.0.dist-info/RECORD +117 -0
- ormlambda/common/abstract_classes/abstract_model.py +0 -115
- ormlambda/common/interfaces/IAggregate.py +0 -10
- ormlambda/common/interfaces/IStatements.py +0 -348
- ormlambda/components/where/__init__.py +0 -1
- ormlambda/components/where/abstract_where.py +0 -15
- ormlambda/databases/my_sql/clauses/where_condition.py +0 -222
- ormlambda/model_base.py +0 -36
- ormlambda/utils/column.py +0 -105
- ormlambda/utils/foreign_key.py +0 -81
- ormlambda/utils/lambda_disassembler/__init__.py +0 -4
- ormlambda/utils/lambda_disassembler/dis_types.py +0 -133
- ormlambda/utils/lambda_disassembler/disassembler.py +0 -69
- ormlambda/utils/lambda_disassembler/dtypes.py +0 -103
- ormlambda/utils/lambda_disassembler/name_of.py +0 -41
- ormlambda/utils/lambda_disassembler/nested_element.py +0 -44
- ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -145
- ormlambda-2.11.2.dist-info/RECORD +0 -81
- /ormlambda/{utils → sql}/dtypes.py +0 -0
- {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/LICENSE +0 -0
- {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
|
4
|
-
|
5
|
-
import
|
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.
|
11
|
-
from ormlambda.
|
12
|
-
from ormlambda.
|
13
|
-
from ormlambda.
|
14
|
-
from ormlambda.
|
15
|
-
from ormlambda.
|
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
|
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
|
23
|
-
from .clauses import
|
24
|
-
from .clauses import
|
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
|
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
|
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
|
-
|
43
|
-
|
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
|
-
|
57
|
+
ForeignKey.stored_calls.clear()
|
58
|
+
self._query_builder.clear()
|
49
59
|
|
50
60
|
return wrapper
|
51
61
|
|
52
62
|
|
53
|
-
class
|
54
|
-
|
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
|
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.
|
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.
|
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 =
|
258
|
+
limit = Limit(number)
|
133
259
|
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
134
|
-
self.
|
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 =
|
140
|
-
self.
|
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
|
-
|
148
|
-
|
149
|
-
) ->
|
150
|
-
|
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
|
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
|
157
|
-
|
158
|
-
|
159
|
-
|
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,
|
167
|
-
|
168
|
-
|
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
|
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:
|
177
|
-
|
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:
|
181
|
-
|
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:
|
185
|
-
|
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[*
|
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
|
-
|
212
|
-
|
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:
|
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
|
-
|
225
|
-
by=by,
|
226
|
-
alias=False,
|
227
|
-
joins=joins,
|
350
|
+
columns=select_clause,
|
228
351
|
)
|
229
|
-
self.
|
352
|
+
self._query_builder.add_statement(select)
|
230
353
|
|
231
|
-
self.
|
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(
|
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[
|
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
|
-
|
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.
|
397
|
+
self._query_builder.add_statement(groupby)
|
265
398
|
return self
|
266
399
|
|
267
400
|
@override
|
268
|
-
def alias(self, column:
|
269
|
-
|
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,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
|
3
|
+
from typing import Optional, Type, Iterable, TYPE_CHECKING
|
3
4
|
|
4
|
-
|
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,
|
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: ...
|