ormlambda 3.11.2__py3-none-any.whl → 3.34.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 +3 -1
- ormlambda/caster/__init__.py +1 -1
- ormlambda/caster/caster.py +29 -12
- ormlambda/common/abstract_classes/clause_info_converter.py +65 -0
- ormlambda/common/abstract_classes/decomposition_query.py +27 -68
- ormlambda/common/abstract_classes/non_query_base.py +10 -8
- ormlambda/common/abstract_classes/query_base.py +3 -1
- ormlambda/common/errors/__init__.py +29 -0
- ormlambda/common/interfaces/ICustomAlias.py +1 -1
- ormlambda/common/interfaces/IQueryCommand.py +6 -2
- ormlambda/dialects/__init__.py +39 -0
- ormlambda/dialects/default/__init__.py +1 -0
- ormlambda/dialects/default/base.py +39 -0
- ormlambda/dialects/interface/__init__.py +1 -0
- ormlambda/dialects/interface/dialect.py +78 -0
- ormlambda/dialects/mysql/__init__.py +38 -0
- ormlambda/dialects/mysql/base.py +388 -0
- ormlambda/dialects/mysql/caster/caster.py +39 -0
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +1 -0
- ormlambda/dialects/mysql/caster/types/boolean.py +35 -0
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +8 -7
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +4 -4
- ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +7 -7
- ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_AsText.py +8 -7
- ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_Contains.py +10 -5
- ormlambda/dialects/mysql/clauses/__init__.py +13 -0
- ormlambda/dialects/mysql/clauses/count.py +33 -0
- ormlambda/dialects/mysql/clauses/delete.py +9 -0
- ormlambda/dialects/mysql/clauses/group_by.py +17 -0
- ormlambda/dialects/mysql/clauses/having.py +12 -0
- ormlambda/dialects/mysql/clauses/insert.py +9 -0
- ormlambda/dialects/mysql/clauses/joins.py +14 -0
- ormlambda/dialects/mysql/clauses/limit.py +6 -0
- ormlambda/dialects/mysql/clauses/offset.py +6 -0
- ormlambda/dialects/mysql/clauses/order.py +8 -0
- ormlambda/dialects/mysql/clauses/update.py +8 -0
- ormlambda/dialects/mysql/clauses/upsert.py +9 -0
- ormlambda/dialects/mysql/clauses/where.py +7 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -0
- ormlambda/dialects/mysql/repository/__init__.py +1 -0
- ormlambda/dialects/mysql/repository/repository.py +212 -0
- ormlambda/dialects/mysql/types.py +732 -0
- ormlambda/dialects/sqlite/__init__.py +5 -0
- ormlambda/dialects/sqlite/base.py +47 -0
- ormlambda/dialects/sqlite/pysqlite.py +32 -0
- ormlambda/engine/__init__.py +1 -0
- ormlambda/engine/base.py +77 -0
- ormlambda/engine/create.py +9 -23
- ormlambda/engine/url.py +34 -19
- ormlambda/env.py +30 -0
- ormlambda/errors.py +17 -0
- ormlambda/model/base_model.py +7 -9
- ormlambda/repository/base_repository.py +36 -5
- ormlambda/repository/interfaces/IRepositoryBase.py +119 -12
- ormlambda/repository/response.py +134 -0
- ormlambda/sql/clause_info/__init__.py +2 -1
- ormlambda/sql/clause_info/aggregate_function_base.py +96 -0
- ormlambda/sql/clause_info/clause_info.py +35 -115
- ormlambda/sql/clause_info/interface/IClauseInfo.py +37 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/clauses/__init__.py +14 -0
- ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
- ormlambda/{databases/my_sql → sql}/clauses/count.py +15 -1
- ormlambda/{databases/my_sql → sql}/clauses/delete.py +22 -7
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/{databases/my_sql → sql}/clauses/having.py +7 -2
- ormlambda/{databases/my_sql → sql}/clauses/insert.py +16 -9
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/sql/clauses/join/__init__.py +1 -0
- ormlambda/{databases/my_sql → sql/clauses/join}/join_context.py +15 -7
- ormlambda/{databases/my_sql → sql}/clauses/joins.py +29 -19
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/{databases/my_sql → sql}/clauses/order.py +14 -24
- ormlambda/{databases/my_sql → sql}/clauses/select.py +14 -13
- ormlambda/{databases/my_sql → sql}/clauses/update.py +24 -11
- ormlambda/{databases/my_sql → sql}/clauses/upsert.py +19 -10
- ormlambda/{databases/my_sql → sql}/clauses/where.py +28 -8
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/{column.py → column/column.py} +85 -22
- ormlambda/sql/comparer.py +51 -37
- ormlambda/sql/compiler.py +668 -0
- ormlambda/sql/ddl.py +82 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +61 -39
- ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
- ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
- ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
- ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
- ormlambda/sql/sqltypes.py +647 -0
- ormlambda/sql/table/__init__.py +1 -1
- ormlambda/sql/table/table.py +175 -0
- ormlambda/sql/table/table_constructor.py +1 -208
- ormlambda/sql/type_api.py +35 -0
- ormlambda/sql/types.py +3 -1
- ormlambda/sql/visitors.py +74 -0
- ormlambda/statements/__init__.py +1 -0
- ormlambda/statements/base_statement.py +34 -40
- ormlambda/statements/interfaces/IStatements.py +28 -21
- ormlambda/statements/query_builder.py +163 -0
- ormlambda/{databases/my_sql → statements}/statements.py +68 -210
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +87 -0
- ormlambda/{utils → util}/module_tree/dynamic_module.py +4 -3
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.0.dist-info/AUTHORS +32 -0
- {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/METADATA +56 -10
- ormlambda-3.34.0.dist-info/RECORD +152 -0
- ormlambda/components/__init__.py +0 -4
- ormlambda/components/delete/__init__.py +0 -2
- ormlambda/components/delete/abstract_delete.py +0 -17
- ormlambda/components/insert/__init__.py +0 -2
- ormlambda/components/insert/abstract_insert.py +0 -25
- ormlambda/components/select/__init__.py +0 -1
- ormlambda/components/update/__init__.py +0 -2
- ormlambda/components/update/abstract_update.py +0 -29
- ormlambda/components/upsert/__init__.py +0 -2
- ormlambda/components/upsert/abstract_upsert.py +0 -25
- ormlambda/databases/__init__.py +0 -5
- ormlambda/databases/my_sql/__init__.py +0 -4
- ormlambda/databases/my_sql/caster/caster.py +0 -39
- ormlambda/databases/my_sql/clauses/__init__.py +0 -20
- ormlambda/databases/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
- ormlambda/databases/my_sql/clauses/drop_table.py +0 -23
- ormlambda/databases/my_sql/clauses/group_by.py +0 -31
- ormlambda/databases/my_sql/clauses/limit.py +0 -17
- ormlambda/databases/my_sql/clauses/offset.py +0 -17
- ormlambda/databases/my_sql/repository/__init__.py +0 -1
- ormlambda/databases/my_sql/repository/repository.py +0 -351
- ormlambda/engine/template.py +0 -47
- ormlambda/sql/dtypes.py +0 -94
- ormlambda/utils/__init__.py +0 -1
- ormlambda-3.11.2.dist-info/RECORD +0 -120
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
- /ormlambda/{databases/my_sql/types.py → dialects/mysql/repository/pool_types.py} +0 -0
- /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
- /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
- /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
- /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
- /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
- /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
- {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/WHEEL +0 -0
@@ -1,57 +1,40 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Concatenate, Iterable,
|
3
|
-
from mysql.connector import errors, errorcode
|
2
|
+
from typing import Concatenate, Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
|
4
3
|
|
5
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
6
|
-
from ormlambda.databases.my_sql.clauses.joins import JoinSelector
|
7
4
|
from ormlambda import ForeignKey
|
8
5
|
|
9
|
-
from ormlambda.common.interfaces import IQuery
|
10
|
-
from mysql.connector import MySQLConnection
|
11
|
-
|
12
6
|
|
13
7
|
if TYPE_CHECKING:
|
8
|
+
from ormlambda.engine.base import Engine
|
14
9
|
from ormlambda.sql.types import AliasType
|
15
10
|
from ormlambda import Table
|
16
11
|
from ormlambda.statements.types import OrderTypes
|
17
12
|
from ormlambda.sql.types import ColumnType
|
18
13
|
from ormlambda.statements.types import SelectCols
|
19
|
-
from ormlambda.repository.interfaces import IRepositoryBase
|
20
14
|
from ormlambda.statements.interfaces import IStatements_two_generic
|
21
|
-
from ormlambda.
|
15
|
+
from ormlambda.statements.types import TypeExists
|
22
16
|
from ormlambda.sql.clause_info import IAggregate
|
23
17
|
from ormlambda.statements.types import WhereTypes
|
24
18
|
|
25
19
|
|
26
20
|
from ormlambda.sql.clause_info import ClauseInfo
|
27
21
|
from ormlambda.statements import BaseStatement
|
28
|
-
from .clauses import DeleteQuery
|
29
|
-
from .clauses import InsertQuery
|
30
|
-
from .clauses import Limit
|
31
|
-
from .clauses import Offset
|
32
|
-
from .clauses import Order
|
33
|
-
from .clauses import Select
|
34
|
-
|
35
|
-
from .clauses import UpsertQuery
|
36
|
-
from .clauses import UpdateQuery
|
37
|
-
from .clauses import Where
|
38
|
-
from .clauses import Having
|
39
|
-
from .clauses import Count
|
40
|
-
from .clauses import GroupBy
|
41
|
-
from .clauses import Alias
|
42
|
-
|
43
22
|
|
44
23
|
from ormlambda import Table, Column
|
45
24
|
from ormlambda.common.enums import JoinType
|
46
|
-
from . import
|
47
|
-
|
25
|
+
from ormlambda.sql.clauses.join import JoinContext, TupleJoinType
|
26
|
+
|
48
27
|
from ormlambda.common.global_checker import GlobalChecker
|
28
|
+
from .query_builder import QueryBuilder
|
29
|
+
|
30
|
+
from ormlambda.sql import clauses
|
31
|
+
from ormlambda.sql import functions as func
|
49
32
|
|
50
33
|
|
51
34
|
# COMMENT: It's so important to prevent information generated by other tests from being retained in the class.
|
52
35
|
@staticmethod
|
53
|
-
def clear_list[T, **P](f: Callable[Concatenate[
|
54
|
-
def wrapper(self:
|
36
|
+
def clear_list[T, **P](f: Callable[Concatenate[Statements, P], T]) -> Callable[P, T]:
|
37
|
+
def wrapper(self: Statements, *args: P.args, **kwargs: P.kwargs) -> T:
|
55
38
|
try:
|
56
39
|
return f(self, *args, **kwargs)
|
57
40
|
except Exception as err:
|
@@ -63,161 +46,20 @@ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Calla
|
|
63
46
|
return wrapper
|
64
47
|
|
65
48
|
|
66
|
-
class
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
Order: Order
|
71
|
-
GroupBy: GroupBy
|
72
|
-
Having: Having
|
73
|
-
Limit: Limit
|
74
|
-
Offset: Offset
|
75
|
-
|
76
|
-
|
77
|
-
class QueryBuilder(IQuery):
|
78
|
-
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
|
79
|
-
|
80
|
-
def __init__(self, by: JoinType = JoinType.INNER_JOIN):
|
81
|
-
self._context = ClauseInfoContext()
|
82
|
-
self._query_list: OrderType = {}
|
83
|
-
self._by = by
|
84
|
-
|
85
|
-
self._joins: Optional[IQuery] = None
|
86
|
-
self._select: Optional[IQuery] = None
|
87
|
-
self._where: Optional[IQuery] = None
|
88
|
-
self._order: Optional[IQuery] = None
|
89
|
-
self._group_by: Optional[IQuery] = None
|
90
|
-
self._limit: Optional[IQuery] = None
|
91
|
-
self._offset: Optional[IQuery] = None
|
92
|
-
|
93
|
-
def add_statement[T](self, clause: ClauseInfo[T]):
|
94
|
-
self.update_context(clause)
|
95
|
-
self._query_list[type(clause).__name__] = clause
|
96
|
-
|
97
|
-
@property
|
98
|
-
def by(self) -> JoinType:
|
99
|
-
return self._by
|
100
|
-
|
101
|
-
@by.setter
|
102
|
-
def by(self, value: JoinType) -> None:
|
103
|
-
self._by = value
|
104
|
-
|
105
|
-
@property
|
106
|
-
def JOINS(self) -> Optional[set[JoinSelector]]:
|
107
|
-
return self._joins
|
108
|
-
|
109
|
-
@property
|
110
|
-
def SELECT(self) -> IQuery:
|
111
|
-
return self._query_list.get("Select", None)
|
112
|
-
|
113
|
-
@property
|
114
|
-
def WHERE(self) -> IQuery:
|
115
|
-
where = self._query_list.get("Where", None)
|
116
|
-
if not isinstance(where, Iterable):
|
117
|
-
if not where:
|
118
|
-
return ()
|
119
|
-
return (where,)
|
120
|
-
return where
|
121
|
-
|
122
|
-
@property
|
123
|
-
def ORDER(self) -> IQuery:
|
124
|
-
return self._query_list.get("Order", None)
|
125
|
-
|
126
|
-
@property
|
127
|
-
def GROUP_BY(self) -> IQuery:
|
128
|
-
return self._query_list.get("GroupBy", None)
|
129
|
-
|
130
|
-
@property
|
131
|
-
def HAVING(self) -> IQuery:
|
132
|
-
where = self._query_list.get("Having", None)
|
133
|
-
if not isinstance(where, Iterable):
|
134
|
-
if not where:
|
135
|
-
return ()
|
136
|
-
return (where,)
|
137
|
-
return where
|
138
|
-
|
139
|
-
@property
|
140
|
-
def LIMIT(self) -> IQuery:
|
141
|
-
return self._query_list.get("Limit", None)
|
142
|
-
|
143
|
-
@property
|
144
|
-
def OFFSET(self) -> IQuery:
|
145
|
-
return self._query_list.get("Offset", None)
|
146
|
-
|
147
|
-
@property
|
148
|
-
def query(self) -> str:
|
149
|
-
# COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
|
150
|
-
# This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
|
151
|
-
|
152
|
-
extract_joins = self.pop_tables_and_create_joins_from_ForeignKey(self._by)
|
153
|
-
|
154
|
-
JOINS = self.stringify_foreign_key(extract_joins, " ")
|
155
|
-
query_list: tuple[str, ...] = (
|
156
|
-
self.SELECT.query,
|
157
|
-
JOINS,
|
158
|
-
Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
|
159
|
-
self.GROUP_BY.query if self.GROUP_BY else None,
|
160
|
-
Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
|
161
|
-
self.ORDER.query if self.ORDER else None,
|
162
|
-
self.LIMIT.query if self.LIMIT else None,
|
163
|
-
self.OFFSET.query if self.OFFSET else None,
|
164
|
-
)
|
165
|
-
return " ".join([x for x in query_list if x])
|
166
|
-
|
167
|
-
def stringify_foreign_key(self, joins: set[JoinSelector], sep: str = "\n") -> Optional[str]:
|
168
|
-
if not joins:
|
169
|
-
return None
|
170
|
-
sorted_joins = JoinSelector.sort_join_selectors(joins)
|
171
|
-
return f"{sep}".join([join.query for join in sorted_joins])
|
172
|
-
|
173
|
-
def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
|
174
|
-
# 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.
|
175
|
-
if not ForeignKey.stored_calls:
|
176
|
-
return None
|
177
|
-
|
178
|
-
joins = set()
|
179
|
-
# Always it's gonna be a set of two
|
180
|
-
# FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
|
181
|
-
for fk in ForeignKey.stored_calls.copy():
|
182
|
-
fk = ForeignKey.stored_calls.pop(fk)
|
183
|
-
self._context._add_table_alias(fk.tright, fk.alias)
|
184
|
-
join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
|
185
|
-
joins.add(join)
|
186
|
-
|
187
|
-
return joins
|
188
|
-
|
189
|
-
def clear(self) -> None:
|
190
|
-
self.__init__()
|
191
|
-
return None
|
192
|
-
|
193
|
-
def update_context(self, clause: ClauseInfo) -> None:
|
194
|
-
if not hasattr(clause, "context"):
|
195
|
-
return None
|
196
|
-
|
197
|
-
if clause.context is not None:
|
198
|
-
self._context.update(clause.context)
|
199
|
-
clause.context = self._context
|
200
|
-
|
201
|
-
|
202
|
-
class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
203
|
-
def __init__(self, model: tuple[T, *Ts], repository: IRepositoryBase) -> None:
|
204
|
-
super().__init__(model, repository=repository)
|
205
|
-
self._query_builder = QueryBuilder()
|
206
|
-
|
207
|
-
@property
|
208
|
-
@override
|
209
|
-
def repository(self) -> IRepositoryBase:
|
210
|
-
return self._repository
|
49
|
+
class Statements[T: Table, TRepo](BaseStatement[T, None]):
|
50
|
+
def __init__(self, model: T, engine: Engine) -> None:
|
51
|
+
super().__init__(model, engine)
|
52
|
+
self._query_builder = QueryBuilder(self.dialect)
|
211
53
|
|
212
54
|
@override
|
213
55
|
def create_table(self, if_exists: TypeExists = "fail") -> None:
|
214
56
|
name: str = self._model.__table_name__
|
215
57
|
if self._repository.table_exists(name):
|
216
58
|
if if_exists == "replace":
|
217
|
-
self.
|
59
|
+
self.drop_table()
|
218
60
|
|
219
61
|
elif if_exists == "fail":
|
220
|
-
raise
|
62
|
+
raise ValueError(f"Table '{self._model.__table_name__}' already exists")
|
221
63
|
|
222
64
|
elif if_exists == "append":
|
223
65
|
counter: int = 0
|
@@ -226,20 +68,25 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
226
68
|
counter += 1
|
227
69
|
char = f"_{counter}"
|
228
70
|
name += char
|
229
|
-
self._model.__table_name__ = name
|
230
71
|
|
231
|
-
|
72
|
+
new_model = self._model
|
73
|
+
new_model.__table_name__ = name
|
74
|
+
return new_model.create_table(self.dialect)
|
75
|
+
|
76
|
+
query = self.model.create_table(self.dialect)
|
232
77
|
self._repository.execute(query)
|
233
78
|
return None
|
234
79
|
|
235
80
|
@override
|
236
|
-
def
|
237
|
-
|
81
|
+
def drop_table(self)->None:
|
82
|
+
q = self.model.drop_table(self.dialect)
|
83
|
+
self._repository.execute(q)
|
84
|
+
return None
|
238
85
|
|
239
86
|
@override
|
240
87
|
@clear_list
|
241
88
|
def insert(self, instances: T | list[T]) -> None:
|
242
|
-
insert =
|
89
|
+
insert = clauses.Insert(self._model, self.repository, self._dialect)
|
243
90
|
insert.insert(instances)
|
244
91
|
insert.execute()
|
245
92
|
return None
|
@@ -254,7 +101,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
254
101
|
# We always going to have a tuple of one element
|
255
102
|
return self.delete(response)
|
256
103
|
|
257
|
-
delete =
|
104
|
+
delete = clauses.Delete(self._model, self._repository, engine=self._engine)
|
258
105
|
delete.delete(instances)
|
259
106
|
delete.execute()
|
260
107
|
# not necessary to call self._query_builder.clear() because select() method already call it
|
@@ -263,7 +110,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
263
110
|
@override
|
264
111
|
@clear_list
|
265
112
|
def upsert(self, instances: T | list[T]) -> None:
|
266
|
-
upsert =
|
113
|
+
upsert = clauses.Upsert(self._model, self._repository, engine=self._engine)
|
267
114
|
upsert.upsert(instances)
|
268
115
|
upsert.execute()
|
269
116
|
return None
|
@@ -271,29 +118,29 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
271
118
|
@override
|
272
119
|
@clear_list
|
273
120
|
def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
|
274
|
-
update =
|
121
|
+
update = clauses.Update(self._model, self._repository, self._query_builder.WHERE, engine=self._engine)
|
275
122
|
update.update(dicc)
|
276
123
|
update.execute()
|
277
124
|
|
278
125
|
return None
|
279
126
|
|
280
127
|
@override
|
281
|
-
def limit(self, number: int) -> IStatements_two_generic[T,
|
282
|
-
limit = Limit(number)
|
128
|
+
def limit(self, number: int) -> IStatements_two_generic[T, TRepo]:
|
129
|
+
limit = clauses.Limit(number, dialect=self._dialect)
|
283
130
|
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
284
131
|
self._query_builder.add_statement(limit)
|
285
132
|
return self
|
286
133
|
|
287
134
|
@override
|
288
|
-
def offset(self, number: int) -> IStatements_two_generic[T,
|
289
|
-
offset = Offset(number)
|
135
|
+
def offset(self, number: int) -> IStatements_two_generic[T, TRepo]:
|
136
|
+
offset = clauses.Offset(number, dialect=self._dialect)
|
290
137
|
self._query_builder.add_statement(offset)
|
291
138
|
return self
|
292
139
|
|
293
140
|
@override
|
294
141
|
def count[TProp](
|
295
142
|
self,
|
296
|
-
selection: None | SelectCols[T,TProp] = lambda x: "*",
|
143
|
+
selection: None | SelectCols[T, TProp] = lambda x: "*",
|
297
144
|
alias="count",
|
298
145
|
execute: bool = False,
|
299
146
|
) -> Optional[int]:
|
@@ -301,40 +148,41 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
301
148
|
return self.select_one(self.count(selection, alias, False), flavour=dict)[alias]
|
302
149
|
|
303
150
|
selection = GlobalChecker.resolved_callback_object(selection, self.models)
|
304
|
-
return Count(
|
151
|
+
return clauses.Count(
|
152
|
+
element=selection,
|
153
|
+
alias_clause=alias,
|
154
|
+
context=self._query_builder._context,
|
155
|
+
dialect=self._dialect,
|
156
|
+
)
|
305
157
|
|
306
158
|
@override
|
307
|
-
def where(self, conditions: WhereTypes) -> IStatements_two_generic[T,
|
159
|
+
def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, TRepo]:
|
308
160
|
# FIXME [x]: I've wrapped self._model into tuple to pass it instance attr. Idk if it's correct
|
309
161
|
|
310
162
|
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
311
163
|
if not isinstance(conditions, Iterable):
|
312
164
|
conditions = (conditions,)
|
313
|
-
self._query_builder.add_statement(Where(*conditions))
|
165
|
+
self._query_builder.add_statement(clauses.Where(*conditions))
|
314
166
|
return self
|
315
167
|
|
316
168
|
@override
|
317
|
-
def having(self, conditions: WhereTypes) -> IStatements_two_generic[T,
|
169
|
+
def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, TRepo]:
|
318
170
|
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
319
171
|
if not isinstance(conditions, Iterable):
|
320
172
|
conditions = (conditions,)
|
321
|
-
self._query_builder.add_statement(Having(*conditions))
|
173
|
+
self._query_builder.add_statement(clauses.Having(*conditions))
|
322
174
|
return self
|
323
175
|
|
324
176
|
@override
|
325
|
-
def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T,
|
177
|
+
def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, TRepo]:
|
326
178
|
query = GlobalChecker.resolved_callback_object(columns, self._models)
|
327
|
-
order = Order(query, order_type)
|
179
|
+
order = clauses.Order(query, order_type, dialect=self._dialect)
|
328
180
|
self._query_builder.add_statement(order)
|
329
181
|
return self
|
330
182
|
|
331
183
|
@override
|
332
184
|
def concat(self, selector: SelectCols[T, str], alias: str = "concat") -> IAggregate:
|
333
|
-
return func.Concat
|
334
|
-
values=selector,
|
335
|
-
alias_clause=alias,
|
336
|
-
context=self._query_builder._context,
|
337
|
-
)
|
185
|
+
return func.Concat(values=selector, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
|
338
186
|
|
339
187
|
@override
|
340
188
|
def max[TProp](
|
@@ -346,7 +194,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
346
194
|
column = GlobalChecker.resolved_callback_object(column, self.models)
|
347
195
|
if execute is True:
|
348
196
|
return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
|
349
|
-
return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
|
197
|
+
return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
|
350
198
|
|
351
199
|
@override
|
352
200
|
def min[TProp](
|
@@ -358,7 +206,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
358
206
|
column = GlobalChecker.resolved_callback_object(column, self.models)
|
359
207
|
if execute is True:
|
360
208
|
return self.select_one(self.min(column, alias, execute=False), flavour=dict)[alias]
|
361
|
-
return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context)
|
209
|
+
return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
|
362
210
|
|
363
211
|
@override
|
364
212
|
def sum[TProp](
|
@@ -370,11 +218,11 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
370
218
|
column = GlobalChecker.resolved_callback_object(column, self.models)
|
371
219
|
if execute is True:
|
372
220
|
return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
|
373
|
-
return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
|
221
|
+
return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
|
374
222
|
|
375
223
|
@override
|
376
224
|
def join[LTable: Table, LProp, RTable: Table, RProp](self, joins: tuple[TupleJoinType[LTable, LProp, RTable, RProp]]) -> JoinContext[tuple[*TupleJoinType[LTable, LProp, RTable, RProp]]]:
|
377
|
-
return JoinContext(self, joins, self._query_builder._context)
|
225
|
+
return JoinContext(self, joins, self._query_builder._context, self._dialect)
|
378
226
|
|
379
227
|
@override
|
380
228
|
@clear_list
|
@@ -386,6 +234,9 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
386
234
|
by: JoinType = JoinType.INNER_JOIN,
|
387
235
|
**kwargs,
|
388
236
|
):
|
237
|
+
if "alias" in kwargs:
|
238
|
+
alias = kwargs.pop("alias")
|
239
|
+
kwargs["alias_clause"] = alias
|
389
240
|
select_clause = GlobalChecker.resolved_callback_object(selector, self._models)
|
390
241
|
|
391
242
|
if selector is None:
|
@@ -397,14 +248,16 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
397
248
|
return result
|
398
249
|
return () if not result else result[0]
|
399
250
|
|
400
|
-
select = Select
|
251
|
+
select = clauses.Select(
|
401
252
|
self._models,
|
402
253
|
columns=select_clause,
|
254
|
+
dialect=self.dialect,
|
255
|
+
**kwargs,
|
403
256
|
)
|
404
257
|
self._query_builder.add_statement(select)
|
405
258
|
|
406
259
|
self._query_builder.by = by
|
407
|
-
self._query: str = self._query_builder.query
|
260
|
+
self._query: str = self._query_builder.query(self._dialect)
|
408
261
|
|
409
262
|
if flavour:
|
410
263
|
result = self._return_flavour(self.query, flavour, select, **kwargs)
|
@@ -458,17 +311,22 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
458
311
|
)
|
459
312
|
|
460
313
|
@override
|
461
|
-
def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T
|
462
|
-
|
314
|
+
def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T], Any]) -> IStatements_two_generic[T]:
|
315
|
+
column = GlobalChecker.resolved_callback_object(column, self.models)
|
316
|
+
|
317
|
+
groupby = clauses.GroupBy(column=column, context=self._query_builder._context, dialect=self.dialect)
|
463
318
|
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
464
319
|
self._query_builder.add_statement(groupby)
|
465
320
|
return self
|
466
321
|
|
467
322
|
@override
|
468
|
-
def alias[TProp](self, column:
|
469
|
-
|
323
|
+
def alias[TProp](self, column: SelectCols[T, TProp], alias: AliasType[ClauseInfo[T]]) -> clauses.Alias[T]:
|
324
|
+
column = GlobalChecker.resolved_callback_object(column, self.models)
|
325
|
+
|
326
|
+
return clauses.Alias(
|
470
327
|
table=column.table,
|
471
328
|
column=column,
|
472
329
|
alias_clause=alias,
|
473
330
|
context=self._query_builder._context,
|
331
|
+
dialect=self.dialect,
|
474
332
|
)
|
ormlambda/statements/types.py
CHANGED
@@ -48,8 +48,8 @@ type TypeExists = Literal["fail", "replace", "append"]
|
|
48
48
|
|
49
49
|
type WhereTypes[LTable, LProp, RTable, RProp] = Union[
|
50
50
|
bool,
|
51
|
-
Comparer
|
52
|
-
tuple[Comparer
|
51
|
+
Comparer,
|
52
|
+
tuple[Comparer],
|
53
53
|
Callable[[LTable], WhereTypes[LTable, LProp, RTable, RProp]],
|
54
54
|
]
|
55
55
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
|
4
|
+
from .metadata import (
|
5
|
+
PrimaryKey,
|
6
|
+
AutoGenerated,
|
7
|
+
AutoIncrement,
|
8
|
+
Unique,
|
9
|
+
CheckTypes,
|
10
|
+
Default,
|
11
|
+
NotNull,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"PrimaryKey",
|
17
|
+
"AutoGenerated",
|
18
|
+
"AutoIncrement",
|
19
|
+
"Unique",
|
20
|
+
"CheckTypes",
|
21
|
+
"Default",
|
22
|
+
"NotNull",
|
23
|
+
"Binary",
|
24
|
+
]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class PrimaryKey:
|
2
|
+
"""Marks a column as a primary key"""
|
3
|
+
|
4
|
+
...
|
5
|
+
|
6
|
+
|
7
|
+
class AutoGenerated:
|
8
|
+
"""Marks a column as auto-generated"""
|
9
|
+
|
10
|
+
...
|
11
|
+
|
12
|
+
|
13
|
+
class AutoIncrement:
|
14
|
+
"""Marks a column as auto-increment"""
|
15
|
+
|
16
|
+
...
|
17
|
+
|
18
|
+
|
19
|
+
class Unique:
|
20
|
+
"""Marks a column as unique"""
|
21
|
+
|
22
|
+
...
|
23
|
+
|
24
|
+
|
25
|
+
class CheckTypes:
|
26
|
+
"""Controls type checking for this column"""
|
27
|
+
|
28
|
+
def __init__(self, enabled: bool = True):
|
29
|
+
self.enabled: bool = enabled
|
30
|
+
|
31
|
+
|
32
|
+
class Default[T]:
|
33
|
+
"""Sets a default value for a column"""
|
34
|
+
|
35
|
+
def __init__(self, value: T):
|
36
|
+
self.value: T = value
|
37
|
+
|
38
|
+
|
39
|
+
class NotNull:
|
40
|
+
"""Marks a column as not-null"""
|
41
|
+
|
42
|
+
...
|
@@ -0,0 +1,87 @@
|
|
1
|
+
from .module_tree import ModuleTree # noqa: F401
|
2
|
+
import types
|
3
|
+
import inspect
|
4
|
+
from typing import Any, Literal, Optional, overload, get_origin, TypeGuard, TypeAliasType
|
5
|
+
from ormlambda.util.typing import LITERAL_TYPES, _AnnotationScanType
|
6
|
+
from .plugin_loader import PluginLoader # noqa: F401
|
7
|
+
|
8
|
+
|
9
|
+
def _inspect_func_args(fn) -> tuple[list[str], bool]:
|
10
|
+
try:
|
11
|
+
co_varkeywords = inspect.CO_VARKEYWORDS
|
12
|
+
except AttributeError:
|
13
|
+
# https://docs.python.org/3/library/inspect.html
|
14
|
+
# The flags are specific to CPython, and may not be defined in other
|
15
|
+
# Python implementations. Furthermore, the flags are an implementation
|
16
|
+
# detail, and can be removed or deprecated in future Python releases.
|
17
|
+
spec = inspect.getfullargspec(fn)
|
18
|
+
return spec[0], bool(spec[2])
|
19
|
+
else:
|
20
|
+
# use fn.__code__ plus flags to reduce method call overhead
|
21
|
+
co = fn.__code__
|
22
|
+
nargs = co.co_argcount
|
23
|
+
return (
|
24
|
+
list(co.co_varnames[:nargs]),
|
25
|
+
bool(co.co_flags & co_varkeywords),
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
@overload
|
30
|
+
def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: Literal[True] = ...) -> set[str]: ...
|
31
|
+
|
32
|
+
|
33
|
+
@overload
|
34
|
+
def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: Literal[False] = ...) -> Optional[set[str]]: ...
|
35
|
+
|
36
|
+
|
37
|
+
def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: bool = False) -> Optional[set[str]]:
|
38
|
+
"""
|
39
|
+
Get the keyword arguments for a class constructor.
|
40
|
+
Args:
|
41
|
+
cls: The class to inspect.
|
42
|
+
_set: A set to store the keyword arguments.
|
43
|
+
raiseerr: Whether to raise an error if the class is not found.
|
44
|
+
Returns:
|
45
|
+
A set of keyword arguments for the class constructor.
|
46
|
+
"""
|
47
|
+
toplevel = _set is None
|
48
|
+
if toplevel:
|
49
|
+
_set = set()
|
50
|
+
assert _set is not None
|
51
|
+
|
52
|
+
ctr = cls.__dict__.get("__init__", False)
|
53
|
+
|
54
|
+
has_init = ctr and isinstance(ctr, types.FunctionType) and isinstance(ctr.__code__, types.CodeType)
|
55
|
+
|
56
|
+
if has_init:
|
57
|
+
names, has_kw = _inspect_func_args(ctr)
|
58
|
+
_set.update(names)
|
59
|
+
|
60
|
+
if not has_kw and not toplevel:
|
61
|
+
if raiseerr:
|
62
|
+
raise TypeError(f"given cls {cls} doesn't have an __init__ method")
|
63
|
+
else:
|
64
|
+
return None
|
65
|
+
else:
|
66
|
+
has_kw = False
|
67
|
+
|
68
|
+
if not has_init or has_kw:
|
69
|
+
for c in cls.__bases__:
|
70
|
+
if get_cls_kwargs(c, _set=_set) is None:
|
71
|
+
break
|
72
|
+
|
73
|
+
_set.discard("self")
|
74
|
+
return _set
|
75
|
+
|
76
|
+
|
77
|
+
def avoid_sql_injection(name: str):
|
78
|
+
if any(char in name for char in [";", "--", "/*", "*/"]):
|
79
|
+
raise ValueError("SQL injection detected")
|
80
|
+
|
81
|
+
|
82
|
+
def is_literal(type_: Any) -> bool:
|
83
|
+
return get_origin(type) in LITERAL_TYPES
|
84
|
+
|
85
|
+
|
86
|
+
def is_pep695(type_: _AnnotationScanType) -> TypeGuard[TypeAliasType]:
|
87
|
+
return isinstance(type_, TypeAliasType)
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
2
2
|
import sys
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Optional, Type, TYPE_CHECKING
|
5
|
-
|
5
|
+
|
6
|
+
# from collections import defaultdict
|
6
7
|
import importlib.util
|
7
8
|
import inspect
|
8
9
|
import re
|
@@ -12,7 +13,7 @@ if TYPE_CHECKING:
|
|
12
13
|
|
13
14
|
|
14
15
|
# from ormlambda import ForeignKey
|
15
|
-
from .dfs_traversal import DFSTraversal
|
16
|
+
# from .dfs_traversal import DFSTraversal
|
16
17
|
|
17
18
|
|
18
19
|
class Node:
|
@@ -235,7 +236,7 @@ class ModuleTree:
|
|
235
236
|
# we need to ensure that the object we going to add in table_list is the same
|
236
237
|
for name, obj in table_class:
|
237
238
|
if name == node.class_name:
|
238
|
-
table_list.append(obj.
|
239
|
+
table_list.append(obj.create_table())
|
239
240
|
return tuple(table_list)
|
240
241
|
|
241
242
|
@staticmethod
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from typing import Callable, Optional
|
2
|
+
from types import ModuleType
|
3
|
+
from ormlambda import errors
|
4
|
+
|
5
|
+
|
6
|
+
class PluginLoader:
|
7
|
+
def __init__(self, group: str, auto_fn: Optional[Callable[..., ModuleType]] = None):
|
8
|
+
self.group = group
|
9
|
+
self.impls: dict[str, ModuleType] = {}
|
10
|
+
self.auto_fn = auto_fn
|
11
|
+
|
12
|
+
def clear(self):
|
13
|
+
self.impls.clear()
|
14
|
+
|
15
|
+
def load(self, name: str) -> Optional[ModuleType]:
|
16
|
+
if name in self.impls:
|
17
|
+
return self.impls[name]()
|
18
|
+
if self.auto_fn:
|
19
|
+
loader = self.auto_fn(name)
|
20
|
+
if loader:
|
21
|
+
self.impls[name] = loader
|
22
|
+
return loader()
|
23
|
+
raise errors.NoSuchModuleError(f"Can't load plugin: {self.group}:{name}")
|
24
|
+
|
25
|
+
def register(self, name: str, modulepath: str, objname: str) -> None:
|
26
|
+
def load():
|
27
|
+
mod = __import__(modulepath)
|
28
|
+
for token in modulepath.split(".")[1:]:
|
29
|
+
mod = getattr(mod, token)
|
30
|
+
return getattr(mod, objname)
|
31
|
+
|
32
|
+
self.impls[name] = load
|