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.
Files changed (154) hide show
  1. ormlambda/__init__.py +3 -1
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +65 -0
  5. ormlambda/common/abstract_classes/decomposition_query.py +27 -68
  6. ormlambda/common/abstract_classes/non_query_base.py +10 -8
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/ICustomAlias.py +1 -1
  10. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  11. ormlambda/dialects/__init__.py +39 -0
  12. ormlambda/dialects/default/__init__.py +1 -0
  13. ormlambda/dialects/default/base.py +39 -0
  14. ormlambda/dialects/interface/__init__.py +1 -0
  15. ormlambda/dialects/interface/dialect.py +78 -0
  16. ormlambda/dialects/mysql/__init__.py +38 -0
  17. ormlambda/dialects/mysql/base.py +388 -0
  18. ormlambda/dialects/mysql/caster/caster.py +39 -0
  19. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +1 -0
  20. ormlambda/dialects/mysql/caster/types/boolean.py +35 -0
  21. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +7 -7
  22. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +7 -7
  23. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +7 -7
  24. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +7 -7
  25. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +7 -7
  26. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +8 -7
  27. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +4 -4
  28. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +7 -7
  29. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_AsText.py +8 -7
  30. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_Contains.py +10 -5
  31. ormlambda/dialects/mysql/clauses/__init__.py +13 -0
  32. ormlambda/dialects/mysql/clauses/count.py +33 -0
  33. ormlambda/dialects/mysql/clauses/delete.py +9 -0
  34. ormlambda/dialects/mysql/clauses/group_by.py +17 -0
  35. ormlambda/dialects/mysql/clauses/having.py +12 -0
  36. ormlambda/dialects/mysql/clauses/insert.py +9 -0
  37. ormlambda/dialects/mysql/clauses/joins.py +14 -0
  38. ormlambda/dialects/mysql/clauses/limit.py +6 -0
  39. ormlambda/dialects/mysql/clauses/offset.py +6 -0
  40. ormlambda/dialects/mysql/clauses/order.py +8 -0
  41. ormlambda/dialects/mysql/clauses/update.py +8 -0
  42. ormlambda/dialects/mysql/clauses/upsert.py +9 -0
  43. ormlambda/dialects/mysql/clauses/where.py +7 -0
  44. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  45. ormlambda/dialects/mysql/repository/__init__.py +1 -0
  46. ormlambda/dialects/mysql/repository/repository.py +212 -0
  47. ormlambda/dialects/mysql/types.py +732 -0
  48. ormlambda/dialects/sqlite/__init__.py +5 -0
  49. ormlambda/dialects/sqlite/base.py +47 -0
  50. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  51. ormlambda/engine/__init__.py +1 -0
  52. ormlambda/engine/base.py +77 -0
  53. ormlambda/engine/create.py +9 -23
  54. ormlambda/engine/url.py +34 -19
  55. ormlambda/env.py +30 -0
  56. ormlambda/errors.py +17 -0
  57. ormlambda/model/base_model.py +7 -9
  58. ormlambda/repository/base_repository.py +36 -5
  59. ormlambda/repository/interfaces/IRepositoryBase.py +119 -12
  60. ormlambda/repository/response.py +134 -0
  61. ormlambda/sql/clause_info/__init__.py +2 -1
  62. ormlambda/sql/clause_info/aggregate_function_base.py +96 -0
  63. ormlambda/sql/clause_info/clause_info.py +35 -115
  64. ormlambda/sql/clause_info/interface/IClauseInfo.py +37 -0
  65. ormlambda/sql/clause_info/interface/__init__.py +1 -0
  66. ormlambda/sql/clauses/__init__.py +14 -0
  67. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  68. ormlambda/{databases/my_sql → sql}/clauses/count.py +15 -1
  69. ormlambda/{databases/my_sql → sql}/clauses/delete.py +22 -7
  70. ormlambda/sql/clauses/group_by.py +30 -0
  71. ormlambda/{databases/my_sql → sql}/clauses/having.py +7 -2
  72. ormlambda/{databases/my_sql → sql}/clauses/insert.py +16 -9
  73. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  74. ormlambda/sql/clauses/join/__init__.py +1 -0
  75. ormlambda/{databases/my_sql → sql/clauses/join}/join_context.py +15 -7
  76. ormlambda/{databases/my_sql → sql}/clauses/joins.py +29 -19
  77. ormlambda/sql/clauses/limit.py +15 -0
  78. ormlambda/sql/clauses/offset.py +15 -0
  79. ormlambda/{databases/my_sql → sql}/clauses/order.py +14 -24
  80. ormlambda/{databases/my_sql → sql}/clauses/select.py +14 -13
  81. ormlambda/{databases/my_sql → sql}/clauses/update.py +24 -11
  82. ormlambda/{databases/my_sql → sql}/clauses/upsert.py +19 -10
  83. ormlambda/{databases/my_sql → sql}/clauses/where.py +28 -8
  84. ormlambda/sql/column/__init__.py +1 -0
  85. ormlambda/sql/{column.py → column/column.py} +85 -22
  86. ormlambda/sql/comparer.py +51 -37
  87. ormlambda/sql/compiler.py +668 -0
  88. ormlambda/sql/ddl.py +82 -0
  89. ormlambda/sql/elements.py +36 -0
  90. ormlambda/sql/foreign_key.py +61 -39
  91. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  92. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  93. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  94. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  95. ormlambda/sql/sqltypes.py +647 -0
  96. ormlambda/sql/table/__init__.py +1 -1
  97. ormlambda/sql/table/table.py +175 -0
  98. ormlambda/sql/table/table_constructor.py +1 -208
  99. ormlambda/sql/type_api.py +35 -0
  100. ormlambda/sql/types.py +3 -1
  101. ormlambda/sql/visitors.py +74 -0
  102. ormlambda/statements/__init__.py +1 -0
  103. ormlambda/statements/base_statement.py +34 -40
  104. ormlambda/statements/interfaces/IStatements.py +28 -21
  105. ormlambda/statements/query_builder.py +163 -0
  106. ormlambda/{databases/my_sql → statements}/statements.py +68 -210
  107. ormlambda/statements/types.py +2 -2
  108. ormlambda/types/__init__.py +24 -0
  109. ormlambda/types/metadata.py +42 -0
  110. ormlambda/util/__init__.py +87 -0
  111. ormlambda/{utils → util}/module_tree/dynamic_module.py +4 -3
  112. ormlambda/util/plugin_loader.py +32 -0
  113. ormlambda/util/typing.py +6 -0
  114. ormlambda-3.34.0.dist-info/AUTHORS +32 -0
  115. {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/METADATA +56 -10
  116. ormlambda-3.34.0.dist-info/RECORD +152 -0
  117. ormlambda/components/__init__.py +0 -4
  118. ormlambda/components/delete/__init__.py +0 -2
  119. ormlambda/components/delete/abstract_delete.py +0 -17
  120. ormlambda/components/insert/__init__.py +0 -2
  121. ormlambda/components/insert/abstract_insert.py +0 -25
  122. ormlambda/components/select/__init__.py +0 -1
  123. ormlambda/components/update/__init__.py +0 -2
  124. ormlambda/components/update/abstract_update.py +0 -29
  125. ormlambda/components/upsert/__init__.py +0 -2
  126. ormlambda/components/upsert/abstract_upsert.py +0 -25
  127. ormlambda/databases/__init__.py +0 -5
  128. ormlambda/databases/my_sql/__init__.py +0 -4
  129. ormlambda/databases/my_sql/caster/caster.py +0 -39
  130. ormlambda/databases/my_sql/clauses/__init__.py +0 -20
  131. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  132. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  133. ormlambda/databases/my_sql/clauses/drop_table.py +0 -23
  134. ormlambda/databases/my_sql/clauses/group_by.py +0 -31
  135. ormlambda/databases/my_sql/clauses/limit.py +0 -17
  136. ormlambda/databases/my_sql/clauses/offset.py +0 -17
  137. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  138. ormlambda/databases/my_sql/repository/repository.py +0 -351
  139. ormlambda/engine/template.py +0 -47
  140. ormlambda/sql/dtypes.py +0 -94
  141. ormlambda/utils/__init__.py +0 -1
  142. ormlambda-3.11.2.dist-info/RECORD +0 -120
  143. /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
  144. /ormlambda/{databases/my_sql/types.py → dialects/mysql/repository/pool_types.py} +0 -0
  145. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  146. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  147. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  148. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  149. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  150. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  151. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  152. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  153. {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/LICENSE +0 -0
  154. {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, TypedDict, override, Type, TYPE_CHECKING, Any, Callable, Optional
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.repository.interfaces.IRepositoryBase import TypeExists
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 functions as func
47
- from .join_context import JoinContext, TupleJoinType
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[MySQLStatements, P], T]) -> Callable[P, T]:
54
- def wrapper(self: MySQLStatements, *args: P.args, **kwargs: P.kwargs) -> T:
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 OrderType(TypedDict):
67
- Select: Select
68
- JoinSelector: JoinSelector
69
- Where: Where
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._repository.drop_table(name)
59
+ self.drop_table()
218
60
 
219
61
  elif if_exists == "fail":
220
- raise errors.ProgrammingError(msg=f"Table '{self._model.__table_name__}' already exists", errno=errorcode.ER_TABLE_EXISTS_ERROR)
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
- query = self._model.create_table_query()
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 table_exists(self) -> bool:
237
- return self._repository.table_exists(self._model.__table_name__)
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 = InsertQuery(self._model, self._repository)
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 = DeleteQuery(self._model, self._repository)
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 = UpsertQuery(self._model, self._repository)
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 = UpdateQuery(self._model, self._repository, self._query_builder.WHERE)
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, MySQLConnection]:
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, MySQLConnection]:
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(element=selection, alias_clause=alias, context=self._query_builder._context)
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, MySQLConnection]:
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, MySQLConnection]:
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, MySQLConnection]:
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[T](
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[T, *Ts](
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, *Ts], Any]):
462
- groupby = GroupBy(column=column, context=self._query_builder._context)
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: ColumnType[TProp], alias: AliasType[ClauseInfo[T]]) -> ClauseInfo[T]:
469
- return Alias(
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
  )
@@ -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[LTable, LProp, RTable, RProp],
52
- tuple[Comparer[LTable, LProp, RTable, RProp], ...],
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
- from collections import defaultdict
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.create_table_query())
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