ormlambda 3.12.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 (150) hide show
  1. ormlambda/__init__.py +2 -0
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
  5. ormlambda/common/abstract_classes/decomposition_query.py +17 -2
  6. ormlambda/common/abstract_classes/non_query_base.py +9 -7
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  10. ormlambda/dialects/__init__.py +39 -0
  11. ormlambda/dialects/default/__init__.py +1 -0
  12. ormlambda/dialects/default/base.py +39 -0
  13. ormlambda/dialects/interface/__init__.py +1 -0
  14. ormlambda/dialects/interface/dialect.py +78 -0
  15. ormlambda/dialects/mysql/__init__.py +38 -0
  16. ormlambda/dialects/mysql/base.py +388 -0
  17. ormlambda/dialects/mysql/caster/caster.py +39 -0
  18. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +1 -0
  19. ormlambda/dialects/mysql/caster/types/boolean.py +35 -0
  20. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +7 -7
  21. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +7 -7
  22. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +7 -7
  23. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +7 -7
  24. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +7 -7
  25. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +8 -7
  26. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +4 -4
  27. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +7 -7
  28. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_AsText.py +8 -7
  29. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_Contains.py +10 -5
  30. ormlambda/dialects/mysql/clauses/__init__.py +13 -0
  31. ormlambda/dialects/mysql/clauses/count.py +33 -0
  32. ormlambda/dialects/mysql/clauses/delete.py +9 -0
  33. ormlambda/dialects/mysql/clauses/group_by.py +17 -0
  34. ormlambda/dialects/mysql/clauses/having.py +12 -0
  35. ormlambda/dialects/mysql/clauses/insert.py +9 -0
  36. ormlambda/dialects/mysql/clauses/joins.py +14 -0
  37. ormlambda/dialects/mysql/clauses/limit.py +6 -0
  38. ormlambda/dialects/mysql/clauses/offset.py +6 -0
  39. ormlambda/dialects/mysql/clauses/order.py +8 -0
  40. ormlambda/dialects/mysql/clauses/update.py +8 -0
  41. ormlambda/dialects/mysql/clauses/upsert.py +9 -0
  42. ormlambda/dialects/mysql/clauses/where.py +7 -0
  43. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  44. ormlambda/dialects/mysql/repository/__init__.py +1 -0
  45. ormlambda/dialects/mysql/repository/repository.py +212 -0
  46. ormlambda/dialects/mysql/types.py +732 -0
  47. ormlambda/dialects/sqlite/__init__.py +5 -0
  48. ormlambda/dialects/sqlite/base.py +47 -0
  49. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  50. ormlambda/engine/__init__.py +1 -0
  51. ormlambda/engine/base.py +77 -0
  52. ormlambda/engine/create.py +9 -23
  53. ormlambda/engine/url.py +31 -19
  54. ormlambda/env.py +30 -0
  55. ormlambda/errors.py +17 -0
  56. ormlambda/model/base_model.py +7 -9
  57. ormlambda/repository/base_repository.py +36 -5
  58. ormlambda/repository/interfaces/IRepositoryBase.py +119 -12
  59. ormlambda/repository/response.py +134 -0
  60. ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
  61. ormlambda/sql/clause_info/clause_info.py +34 -17
  62. ormlambda/sql/clauses/__init__.py +14 -0
  63. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  64. ormlambda/{databases/my_sql → sql}/clauses/count.py +15 -1
  65. ormlambda/{databases/my_sql → sql}/clauses/delete.py +22 -7
  66. ormlambda/sql/clauses/group_by.py +30 -0
  67. ormlambda/{databases/my_sql → sql}/clauses/having.py +7 -2
  68. ormlambda/{databases/my_sql → sql}/clauses/insert.py +16 -9
  69. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  70. ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
  71. ormlambda/{databases/my_sql → sql}/clauses/joins.py +29 -19
  72. ormlambda/sql/clauses/limit.py +15 -0
  73. ormlambda/sql/clauses/offset.py +15 -0
  74. ormlambda/{databases/my_sql → sql}/clauses/order.py +14 -24
  75. ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
  76. ormlambda/{databases/my_sql → sql}/clauses/update.py +24 -11
  77. ormlambda/{databases/my_sql → sql}/clauses/upsert.py +17 -12
  78. ormlambda/{databases/my_sql → sql}/clauses/where.py +28 -8
  79. ormlambda/sql/column/__init__.py +1 -0
  80. ormlambda/sql/{column.py → column/column.py} +82 -22
  81. ormlambda/sql/comparer.py +51 -37
  82. ormlambda/sql/compiler.py +668 -0
  83. ormlambda/sql/ddl.py +82 -0
  84. ormlambda/sql/elements.py +36 -0
  85. ormlambda/sql/foreign_key.py +61 -39
  86. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  87. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  88. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  89. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  90. ormlambda/sql/sqltypes.py +647 -0
  91. ormlambda/sql/table/__init__.py +1 -1
  92. ormlambda/sql/table/table.py +175 -0
  93. ormlambda/sql/table/table_constructor.py +1 -208
  94. ormlambda/sql/type_api.py +35 -0
  95. ormlambda/sql/types.py +3 -1
  96. ormlambda/sql/visitors.py +74 -0
  97. ormlambda/statements/__init__.py +1 -0
  98. ormlambda/statements/base_statement.py +28 -38
  99. ormlambda/statements/interfaces/IStatements.py +8 -4
  100. ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
  101. ormlambda/{databases/my_sql → statements}/statements.py +57 -61
  102. ormlambda/statements/types.py +2 -2
  103. ormlambda/types/__init__.py +24 -0
  104. ormlambda/types/metadata.py +42 -0
  105. ormlambda/util/__init__.py +87 -0
  106. ormlambda/{utils → util}/module_tree/dynamic_module.py +1 -1
  107. ormlambda/util/plugin_loader.py +32 -0
  108. ormlambda/util/typing.py +6 -0
  109. ormlambda-3.34.0.dist-info/AUTHORS +32 -0
  110. {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/METADATA +1 -1
  111. ormlambda-3.34.0.dist-info/RECORD +152 -0
  112. ormlambda/components/__init__.py +0 -4
  113. ormlambda/components/delete/__init__.py +0 -2
  114. ormlambda/components/delete/abstract_delete.py +0 -17
  115. ormlambda/components/insert/__init__.py +0 -2
  116. ormlambda/components/insert/abstract_insert.py +0 -25
  117. ormlambda/components/select/__init__.py +0 -1
  118. ormlambda/components/update/__init__.py +0 -2
  119. ormlambda/components/update/abstract_update.py +0 -29
  120. ormlambda/components/upsert/__init__.py +0 -2
  121. ormlambda/components/upsert/abstract_upsert.py +0 -25
  122. ormlambda/databases/__init__.py +0 -5
  123. ormlambda/databases/my_sql/__init__.py +0 -4
  124. ormlambda/databases/my_sql/caster/caster.py +0 -39
  125. ormlambda/databases/my_sql/clauses/__init__.py +0 -20
  126. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  127. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  128. ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
  129. ormlambda/databases/my_sql/clauses/group_by.py +0 -30
  130. ormlambda/databases/my_sql/clauses/limit.py +0 -17
  131. ormlambda/databases/my_sql/clauses/offset.py +0 -17
  132. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  133. ormlambda/databases/my_sql/repository/repository.py +0 -351
  134. ormlambda/engine/template.py +0 -47
  135. ormlambda/sql/dtypes.py +0 -94
  136. ormlambda/utils/__init__.py +0 -1
  137. ormlambda-3.12.2.dist-info/RECORD +0 -125
  138. /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
  139. /ormlambda/{databases/my_sql/types.py → dialects/mysql/repository/pool_types.py} +0 -0
  140. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  141. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  142. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  143. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  144. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  145. /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
  146. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  147. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  148. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  149. {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/LICENSE +0 -0
  150. {ormlambda-3.12.2.dist-info → ormlambda-3.34.0.dist-info}/WHEEL +0 -0
@@ -3,20 +3,24 @@ from collections import defaultdict
3
3
  from typing import override, Optional, TYPE_CHECKING, Type
4
4
 
5
5
 
6
- from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
6
+ from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
7
7
  from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
8
8
  from ormlambda.common.interfaces.IQueryCommand import IQuery
9
9
  from ormlambda import JoinType
10
10
  from ormlambda.sql.clause_info import ClauseInfo
11
11
  from ormlambda.sql.comparer import Comparer
12
12
  from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
13
+ from ormlambda.sql.elements import ClauseElement
14
+
13
15
 
14
16
  # TODOL [x]: Try to import Table module without circular import Error
15
17
  if TYPE_CHECKING:
16
18
  from ormlambda import Table
19
+ from ormlambda.dialects import Dialect
17
20
 
18
21
 
19
- class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
22
+ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], ClauseElement):
23
+ __visit_name__ = "join"
20
24
  __slots__: tuple = (
21
25
  "_comparer",
22
26
  "_orig_table",
@@ -37,26 +41,31 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
37
41
 
38
42
  def __init__[LProp, RProp](
39
43
  self,
40
- where: Comparer[TLeft, LProp, TRight, RProp],
44
+ where: Comparer,
41
45
  by: JoinType,
42
46
  alias: Optional[str] = "{table}",
43
47
  context: ClauseContextType = None,
48
+ *,
49
+ dialect: Dialect,
50
+ **kw,
44
51
  ) -> None:
45
- self._comparer: Comparer[TLeft, LProp, TRight, RProp] = where
46
- self._orig_table: TLeft = where.left_condition.table
47
- self._right_table: TRight = where.right_condition.table
52
+ lcon = where.left_condition(dialect)
53
+ rcon = where.right_condition(dialect)
54
+ self._comparer: Comparer = where
55
+ self._orig_table: TLeft = lcon.table
56
+ self._right_table: TRight = rcon.table
48
57
  self._by: JoinType = by
49
- self._left_col: str = where.left_condition._column.column_name
50
- self._right_col: str = where.right_condition._column.column_name
58
+ self._left_col: str = lcon._column.column_name
59
+ self._right_col: str = rcon._column.column_name
51
60
  self._compareop = where._compare
52
61
  self._context: ClauseContextType = context if context else ClauseInfoContext()
53
62
 
54
63
  # COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
55
64
  self._alias: Optional[str] = alias
56
65
 
57
- self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context)
58
- self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context())
59
- self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context())
66
+ self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context, dialect=dialect, **kw)
67
+ self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
68
+ self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
60
69
 
61
70
  def __eq__(self, __value: JoinSelector) -> bool:
62
71
  return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
@@ -82,20 +91,18 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
82
91
  return ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
83
92
 
84
93
  @classmethod
85
- def join_selectors(cls, *args: JoinSelector) -> str:
86
- return "\n".join([x.query for x in args])
94
+ def join_selectors(cls, dialect: Dialect, *args: JoinSelector) -> str:
95
+ return "\n".join([x.query(dialect) for x in args])
87
96
 
88
- @property
89
- @override
90
- def query(self) -> str:
97
+ def query(self, dialect: Dialect, **kwargs) -> str:
91
98
  self._context = ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
92
99
  list_ = [
93
100
  self._by.value, # inner join
94
- self._from_clause.query,
101
+ self._from_clause.query(dialect, **kwargs),
95
102
  "ON",
96
- self._left_table_clause.query,
103
+ self._left_table_clause.query(dialect, **kwargs),
97
104
  self._compareop, # =
98
- self._right_table_clause.query,
105
+ self._right_table_clause.query(dialect, **kwargs),
99
106
  ]
100
107
  return " ".join([x for x in list_ if x is not None])
101
108
 
@@ -147,3 +154,6 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
147
154
  continue
148
155
  res.extend(tables)
149
156
  return res
157
+
158
+
159
+ __all__ = ["JoinSelector"]
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+ from ormlambda.sql.elements import ClauseElement
3
+
4
+
5
+ class Limit(ClauseElement):
6
+ __visit_name__ = "limit"
7
+ LIMIT = "LIMIT"
8
+
9
+ def __init__(self, number: int, **kwargs) -> None:
10
+ if not isinstance(number, int):
11
+ raise ValueError
12
+ self._number: int = number
13
+
14
+
15
+ __all__ = ["Limit"]
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+ from ormlambda.sql.elements import ClauseElement
3
+
4
+
5
+ class Offset(ClauseElement):
6
+ __visit_name__ = "offset"
7
+ OFFSET = "OFFSET"
8
+
9
+ def __init__(self, number: int, **kwargs) -> None:
10
+ if not isinstance(number, int):
11
+ raise ValueError
12
+ self._number: int = number
13
+
14
+
15
+ __all__ = ["Offset"]
@@ -1,14 +1,20 @@
1
1
  from __future__ import annotations
2
2
  import typing as tp
3
3
 
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
4
+ from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
5
5
  from ormlambda.sql.types import ColumnType
6
6
  from ormlambda.sql.clause_info import AggregateFunctionBase
7
7
 
8
8
  from ormlambda.statements import OrderType
9
+ from ormlambda.sql.elements import ClauseElement
9
10
 
11
+ if tp.TYPE_CHECKING:
12
+ from ormlambda.dialects import Dialect
13
+
14
+
15
+ class Order(AggregateFunctionBase, ClauseElement):
16
+ __visit_name__ = "order"
10
17
 
11
- class Order(AggregateFunctionBase):
12
18
  @staticmethod
13
19
  def FUNCTION_NAME() -> str:
14
20
  return "ORDER BY"
@@ -18,11 +24,16 @@ class Order(AggregateFunctionBase):
18
24
  column: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
19
25
  order_type: tp.Iterable[OrderType],
20
26
  context: ClauseContextType = None,
27
+ *,
28
+ dialect: Dialect,
29
+ **kw,
21
30
  ):
22
31
  super().__init__(
23
32
  table=None,
24
33
  column=column,
25
34
  context=context,
35
+ dialect=dialect,
36
+ **kw,
26
37
  )
27
38
 
28
39
  if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
@@ -41,25 +52,4 @@ class Order(AggregateFunctionBase):
41
52
  pass
42
53
  raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
43
54
 
44
- @tp.override
45
- @property
46
- def query(self) -> str:
47
- string_columns: list[str] = []
48
- columns = self.unresolved_column
49
-
50
- # if this attr is not iterable means that we only pass one column without wrapped in a list or tuple
51
- if isinstance(columns, str):
52
- string_columns = f"{columns} {str(self._order_type[0])}"
53
- return f"{self.FUNCTION_NAME()} {string_columns}"
54
-
55
- if not isinstance(columns, tp.Iterable):
56
- columns = (columns,)
57
-
58
- assert len(columns) == len(self._order_type)
59
-
60
- context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
61
- for index, clause in enumerate(self._convert_into_clauseInfo(columns, context)):
62
- clause.alias_clause = None
63
- string_columns.append(f"{clause.query} {str(self._order_type[index])}")
64
-
65
- return f"{self.FUNCTION_NAME()} {', '.join(string_columns)}"
55
+ __all__ = ["Order"]
@@ -1,17 +1,20 @@
1
1
  from __future__ import annotations
2
- from typing import Optional, override, Type, Callable, TYPE_CHECKING
2
+ from ormlambda.sql.elements import ClauseElement
3
+ from typing import Optional, Type, Callable, TYPE_CHECKING
3
4
 
4
5
  from ormlambda.sql.clause_info import ClauseInfo
5
6
  from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
6
7
  from ormlambda.sql.types import AliasType
7
8
  from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
8
- from ormlambda.components import ISelect
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from ormlambda import Table
12
+ from ormlambda.dialects import Dialect
12
13
 
13
14
 
14
- class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
15
+ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ClauseElement):
16
+ __visit_name__ = "select"
17
+
15
18
  CLAUSE: str = "SELECT"
16
19
 
17
20
  def __init__(
@@ -21,12 +24,14 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
21
24
  *,
22
25
  alias_table: AliasType[ClauseInfo] = "{table}",
23
26
  context: Optional[ClauseInfoContext] = None,
27
+ dialect: Dialect,
24
28
  **kwargs,
25
29
  ) -> None:
26
30
  super().__init__(
27
31
  tables,
28
32
  columns,
29
33
  context=context,
34
+ dialect=dialect,
30
35
  **kwargs,
31
36
  )
32
37
  self._alias_table = alias_table
@@ -35,18 +40,12 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
35
40
 
36
41
  @property
37
42
  def FROM(self) -> ClauseInfo[T]:
38
- return ClauseInfo(self.table, None, alias_table=self._alias_table, context=self._context)
43
+ return ClauseInfo(self.table, None, alias_table=self._alias_table, context=self._context, dialect=self._dialect, **self.kwargs)
39
44
 
40
45
  @property
41
46
  def COLUMNS(self) -> str:
42
- return ClauseInfo.join_clauses(self._all_clauses, ",", self.context)
47
+ dialect = self.kwargs.pop("dialect", self._dialect)
48
+ return ClauseInfo.join_clauses(self._all_clauses, ",", self.context, dialect=dialect)
43
49
 
44
- # TODOL: see who to deal when we will have to add more mysql methods
45
- @override
46
- @property
47
- def query(self) -> str:
48
- # COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
49
- # This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
50
- FROM = self.FROM
51
50
 
52
- return f"{self.CLAUSE} {self.COLUMNS} FROM {FROM.query}"
51
+ __all__ = ["Select"]
@@ -1,11 +1,18 @@
1
- from typing import Type, override, Any
1
+ from __future__ import annotations
2
+ from typing import Type, override, Any, TYPE_CHECKING, Optional
2
3
 
3
- from ormlambda.components.update import UpdateQueryBase
4
4
  from ormlambda import Table, Column
5
- from ormlambda.repository import IRepositoryBase
6
5
  from ormlambda.caster.caster import Caster
7
6
  from .where import Where
8
- from ormlambda.sql.types import ColumnType
7
+ from ormlambda.common.abstract_classes import NonQueryBase
8
+ from .interfaces import IUpdate
9
+
10
+ if TYPE_CHECKING:
11
+ from ormlambda.sql.types import ColumnType
12
+ from .where import Where
13
+ from ormlambda.engine import Engine
14
+
15
+ from ormlambda.sql.elements import ClauseElement
9
16
 
10
17
 
11
18
  class UpdateKeyError(KeyError):
@@ -20,9 +27,12 @@ class UpdateKeyError(KeyError):
20
27
  return f"The column '{self._key}' does not belong to the table '{self._table.__table_name__}'. Please check the columns in the query."
21
28
 
22
29
 
23
- class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
24
- def __init__(self, model: T, repository: Any, where: list[Where]) -> None:
25
- super().__init__(model, repository, where)
30
+ class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseElement):
31
+ __visit_name__ = "update"
32
+
33
+ def __init__(self, model: T, repository: Any, where: list[Where], engine: Engine) -> None:
34
+ super().__init__(model, repository, engine=engine)
35
+ self._where: Optional[list[Where]] = where
26
36
 
27
37
  @override
28
38
  @property
@@ -33,11 +43,11 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
33
43
  def execute(self) -> None:
34
44
  if self._where:
35
45
  for where in self._where:
36
- query_with_table = where.query
46
+ query_with_table = where.query(self._engine.dialect)
37
47
  for x in where._comparer:
38
48
  # TODOH []: Refactor this part. We need to get only the columns withouth __table_name__ preffix
39
- self._query += " " + query_with_table.replace(x.left_condition.table.__table_name__ + ".", "")
40
- return self._repository.execute_with_values(self._query, self._values)
49
+ self._query += " " + query_with_table.replace(x.left_condition(self._dialect).table.__table_name__ + ".", "")
50
+ return self._engine.repository.execute_with_values(self._query, self._values)
41
51
 
42
52
  @override
43
53
  def update[TProp](self, dicc: dict[str | ColumnType[TProp], Any]) -> None:
@@ -45,7 +55,7 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
45
55
  raise TypeError
46
56
 
47
57
  col_names: list[Column] = []
48
- CASTER = Caster(self._repository)
58
+ CASTER = self._engine.dialect.caster()
49
59
  for col, value in dicc.items():
50
60
  if isinstance(col, str):
51
61
  if not hasattr(self._model, col):
@@ -69,3 +79,6 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
69
79
  if self._model is not col.table:
70
80
  raise UpdateKeyError(self._model, col)
71
81
  return not col.is_auto_generated
82
+
83
+
84
+ __all__ = ["Update"]
@@ -1,20 +1,22 @@
1
1
  from __future__ import annotations
2
2
  from typing import override, TYPE_CHECKING
3
3
 
4
- from ormlambda import Table
5
- from ormlambda.components.upsert import UpsertQueryBase
6
- from ormlambda.repository import IRepositoryBase, BaseRepository
4
+ if TYPE_CHECKING:
5
+ from ormlambda.engine import Engine
7
6
 
7
+ from ormlambda import Table
8
+ from ormlambda.repository import BaseRepository
9
+ from ormlambda.sql.clauses.interfaces import IUpsert
10
+ from ormlambda.common.abstract_classes import NonQueryBase
11
+ from .insert import Insert
12
+ from ormlambda.sql.elements import ClauseElement
8
13
 
9
- from .insert import InsertQuery
10
14
 
11
- if TYPE_CHECKING:
12
- from mysql.connector import MySQLConnection
15
+ class Upsert[T: Table, TRepo](NonQueryBase[T, TRepo], IUpsert[T], ClauseElement):
16
+ __visit_name__ = "upsert"
13
17
 
14
-
15
- class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
16
- def __init__(self, model: T, repository: BaseRepository[MySQLConnection]) -> None:
17
- super().__init__(model, repository)
18
+ def __init__(self, model: T, repository: BaseRepository[TRepo], engine: Engine) -> None:
19
+ super().__init__(model, repository, engine=engine)
18
20
 
19
21
  @override
20
22
  @property
@@ -23,7 +25,7 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
23
25
 
24
26
  @override
25
27
  def execute(self) -> None:
26
- return self._repository.executemany_with_values(self._query, self._values)
28
+ return self._engine.repository.executemany_with_values(self._query, self._values)
27
29
 
28
30
  @override
29
31
  def upsert(self, instances: T | list[T]) -> None:
@@ -54,7 +56,7 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
54
56
  COL2 = _val.COL2;
55
57
 
56
58
  """
57
- insert = InsertQuery[T](self._model, self._repository)
59
+ insert = Insert(self._model, self._engine.repository, self._engine.dialect)
58
60
  insert.insert(instances)
59
61
 
60
62
  if isinstance(instances, Table):
@@ -70,3 +72,6 @@ class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
70
72
  self._query = query
71
73
  self._values = insert.values
72
74
  return None
75
+
76
+
77
+ __all__ = ["Upsert"]
@@ -3,14 +3,25 @@ import typing as tp
3
3
  from ormlambda.sql.comparer import Comparer
4
4
  from ormlambda.sql.clause_info import AggregateFunctionBase
5
5
  from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
6
+ from ormlambda.sql.elements import ClauseElement
6
7
 
8
+ if tp.TYPE_CHECKING:
9
+ from ormlambda.dialects import Dialect
7
10
 
8
- class Where(AggregateFunctionBase):
11
+
12
+ class Where(AggregateFunctionBase, ClauseElement):
9
13
  """
10
14
  The purpose of this class is to create 'WHERE' condition queries properly.
11
15
  """
12
16
 
13
- def __init__(self, *comparer: Comparer, restrictive: bool = True, context: ClauseContextType = None) -> None:
17
+ __visit_name__ = "where"
18
+
19
+ def __init__(
20
+ self,
21
+ *comparer: Comparer,
22
+ restrictive: bool = True,
23
+ context: ClauseContextType = None,
24
+ ) -> None:
14
25
  self._comparer: tuple[Comparer] = comparer
15
26
  self._restrictive: bool = restrictive
16
27
  self._context: ClauseContextType = context if context else ClauseInfoContext()
@@ -19,11 +30,16 @@ class Where(AggregateFunctionBase):
19
30
  def FUNCTION_NAME() -> str:
20
31
  return "WHERE"
21
32
 
22
- @property
23
- def query(self) -> str:
33
+ def query(self, dialect: Dialect, **kwargs) -> str:
24
34
  if isinstance(self._comparer, tp.Iterable):
25
35
  context = ClauseInfoContext(table_context=self._context._table_context)
26
- comparer = Comparer.join_comparers(self._comparer, restrictive=self._restrictive, context=context)
36
+ comparer = Comparer.join_comparers(
37
+ self._comparer,
38
+ restrictive=self._restrictive,
39
+ context=context,
40
+ dialect=dialect,
41
+ **kwargs,
42
+ )
27
43
  else:
28
44
  comparer = self._comparer
29
45
  return f"{self.FUNCTION_NAME()} {comparer}"
@@ -33,13 +49,17 @@ class Where(AggregateFunctionBase):
33
49
  return None
34
50
 
35
51
  @classmethod
36
- def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
52
+ def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext, dialect: Dialect = None) -> str:
37
53
  if not isinstance(wheres, tp.Iterable):
38
54
  wheres = (wheres,)
39
55
 
40
56
  comparers: list[Comparer] = []
41
57
  for where in wheres:
42
58
  for c in where._comparer:
43
- c.set_context(context)
59
+ if not c._context:
60
+ c._context = context
44
61
  comparers.append(c)
45
- return cls(*comparers, restrictive=restrictive, context=context).query
62
+ return cls(*comparers, restrictive=restrictive, context=context).query(dialect=dialect)
63
+
64
+
65
+ __all__ = ["Where"]
@@ -0,0 +1 @@
1
+ from .column import Column # noqa: F401
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
- from typing import Iterable, Type, Optional, TYPE_CHECKING, overload
3
2
  import abc
3
+ from typing import Annotated, Any, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args
4
4
  from ormlambda.sql.types import TableType, ComparerType, ColumnType
5
5
  from ormlambda import ConditionType
6
6
 
@@ -8,6 +8,19 @@ if TYPE_CHECKING:
8
8
  import re
9
9
  from ormlambda import Table
10
10
  from ormlambda.sql.comparer import Comparer, Regex, Like
11
+ from ormlambda.sql.type_api import TypeEngine
12
+
13
+
14
+ from ormlambda.types import (
15
+ # metadata
16
+ PrimaryKey,
17
+ AutoGenerated,
18
+ AutoIncrement,
19
+ Unique,
20
+ CheckTypes,
21
+ Default,
22
+ NotNull,
23
+ )
11
24
 
12
25
 
13
26
  class Column[TProp]:
@@ -21,6 +34,9 @@ class Column[TProp]:
21
34
  "is_auto_generated",
22
35
  "is_auto_increment",
23
36
  "is_unique",
37
+ "is_not_null",
38
+ "default_value",
39
+ "sql_type",
24
40
  "__private_name",
25
41
  "_check",
26
42
  )
@@ -37,7 +53,9 @@ class Column[TProp]:
37
53
  is_auto_generated: bool = False,
38
54
  is_auto_increment: bool = False,
39
55
  is_unique: bool = False,
56
+ is_not_null: bool = False,
40
57
  check_types: bool = True,
58
+ default: Optional[Any] = None,
41
59
  ) -> None: ...
42
60
 
43
61
  def __init__[T: Table](
@@ -48,8 +66,10 @@ class Column[TProp]:
48
66
  is_auto_generated: bool = False,
49
67
  is_auto_increment: bool = False,
50
68
  is_unique: bool = False,
69
+ is_not_null: bool = False,
51
70
  check_types: bool = True,
52
71
  column_name: Optional[str] = None,
72
+ default: Optional[Any] = None,
53
73
  ) -> None:
54
74
  if dtype is None and column_name is None:
55
75
  raise AttributeError("You must specify either the 'dtype' or 'column_name' attribute.")
@@ -62,14 +82,17 @@ class Column[TProp]:
62
82
  self.column_name: Optional[str] = column_name
63
83
  self.__private_name: Optional[str] = None
64
84
  self._check = check_types
85
+ self.default_value: TProp = default
86
+ self.sql_type: TypeEngine[TProp] = None
65
87
 
66
88
  self.is_primary_key: bool = is_primary_key
67
89
  self.is_auto_generated: bool = is_auto_generated
68
90
  self.is_auto_increment: bool = is_auto_increment
69
91
  self.is_unique: bool = is_unique
92
+ self.is_not_null: bool = is_not_null
70
93
 
71
94
  def __repr__(self) -> str:
72
- return f"{type(self).__name__}[{self.dtype.__name__}] => {self.column_name}"
95
+ return f"{type(self).__name__}[{self.dtype.__class__.__name__}] => {self.column_name}"
73
96
 
74
97
  def __str__(self) -> str:
75
98
  return self.table.__table_name__ + "." + self.column_name
@@ -79,6 +102,8 @@ class Column[TProp]:
79
102
  self.column_name = name
80
103
  self.__private_name = self.PRIVATE_CHAR + name
81
104
 
105
+ self._fill_from_annotations(owner, name)
106
+
82
107
  def __get__(self, obj, objtype=None) -> ColumnType[TProp]:
83
108
  if not obj:
84
109
  return self
@@ -100,43 +125,78 @@ class Column[TProp]:
100
125
  self.is_auto_generated,
101
126
  self.is_auto_increment,
102
127
  self.is_unique,
128
+ self.is_not_null,
103
129
  )
104
130
  )
105
131
 
132
+ def _fill_from_annotations[T: Table](self, obj: Type[T], name: str) -> None:
133
+ """Read the metada when using Annotated typing class, and set the attributes accordingly"""
134
+
135
+ from ormlambda.sql.type_api import TypeEngine
136
+
137
+ annotations = get_type_hints(obj, include_extras=True)
138
+ if name in annotations:
139
+ annotation = annotations[name]
140
+ if get_origin(annotation) is Annotated:
141
+ dtype, *metadata = get_args(annotation)
142
+
143
+ if not self.dtype:
144
+ self.dtype = dtype
145
+
146
+ for meta in metadata:
147
+ if isinstance(meta, TypeEngine):
148
+ self.sql_type = meta
149
+ elif isinstance(meta, PrimaryKey):
150
+ self.is_primary_key = True
151
+ elif isinstance(meta, AutoGenerated):
152
+ self.is_auto_generated = True
153
+ elif isinstance(meta, AutoIncrement):
154
+ self.is_auto_increment = True
155
+ elif isinstance(meta, Unique):
156
+ self.is_unique = True
157
+ elif isinstance(meta, CheckTypes):
158
+ self._check = True
159
+ elif isinstance(meta, Default):
160
+ self.default_value = meta.value
161
+ self.is_auto_generated = True
162
+ elif isinstance(meta, NotNull):
163
+ self.is_not_null = True
164
+ return None
165
+
106
166
  @abc.abstractmethod
107
- def __comparer_creator[LTable: Table, OTherTable: Table, OTherType](self, other: ColumnType[OTherType], compare: ComparerType, *args) -> Comparer:
167
+ def __comparer_creator(self, other: ColumnType, compare: ComparerType) -> Comparer:
108
168
  from ormlambda.sql.comparer import Comparer
109
169
 
110
- return Comparer[LTable, TProp, OTherTable, OTherType](self, other, compare, *args)
170
+ return Comparer(self, other, compare)
111
171
 
112
- def __eq__[LTable, OTherTable, OTherProp](self, other: ColumnType[OTherProp], *args) -> Comparer[LTable, TProp, OTherTable, OTherProp]:
113
- return self.__comparer_creator(other, ConditionType.EQUAL.value, *args)
172
+ def __eq__(self, other: ColumnType) -> Comparer:
173
+ return self.__comparer_creator(other, ConditionType.EQUAL.value)
114
174
 
115
- def __ne__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
116
- return self.__comparer_creator(other, ConditionType.NOT_EQUAL.value, *args)
175
+ def __ne__(self, other: ColumnType) -> Comparer:
176
+ return self.__comparer_creator(other, ConditionType.NOT_EQUAL.value)
117
177
 
118
- def __lt__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
119
- return self.__comparer_creator(other, ConditionType.LESS_THAN.value, *args)
178
+ def __lt__(self, other: ColumnType) -> Comparer:
179
+ return self.__comparer_creator(other, ConditionType.LESS_THAN.value)
120
180
 
121
- def __le__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
122
- return self.__comparer_creator(other, ConditionType.LESS_THAN_OR_EQUAL.value, *args)
181
+ def __le__(self, other: ColumnType) -> Comparer:
182
+ return self.__comparer_creator(other, ConditionType.LESS_THAN_OR_EQUAL.value)
123
183
 
124
- def __gt__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
125
- return self.__comparer_creator(other, ConditionType.GREATER_THAN.value, *args)
184
+ def __gt__(self, other: ColumnType) -> Comparer:
185
+ return self.__comparer_creator(other, ConditionType.GREATER_THAN.value)
126
186
 
127
- def __ge__[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
128
- return self.__comparer_creator(other, ConditionType.GREATER_THAN_OR_EQUAL.value, *args)
187
+ def __ge__(self, other: ColumnType) -> Comparer:
188
+ return self.__comparer_creator(other, ConditionType.GREATER_THAN_OR_EQUAL.value)
129
189
 
130
- def contains[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
190
+ def contains(self, other: ColumnType) -> Comparer:
131
191
  if not isinstance(other, tuple) and isinstance(other, Iterable):
132
192
  other = tuple(other)
133
193
 
134
- return self.__comparer_creator(other, ConditionType.IN.value, *args)
194
+ return self.__comparer_creator(other, ConditionType.IN.value)
135
195
 
136
- def not_contains[LTable, OTherTable, OtherProp](self, other: ColumnType[OtherProp], *args) -> Comparer[LTable, TProp, OTherTable, OtherProp]:
137
- return self.__comparer_creator(other, ConditionType.NOT_IN.value, *args)
196
+ def not_contains(self, other: ColumnType) -> Comparer:
197
+ return self.__comparer_creator(other, ConditionType.NOT_IN.value)
138
198
 
139
- def regex[LProp, RProp](self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex[LProp, RProp]:
199
+ def regex(self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex:
140
200
  from ormlambda.sql.comparer import Regex
141
201
 
142
202
  if not isinstance(flags, Iterable):
@@ -148,7 +208,7 @@ class Column[TProp]:
148
208
  flags=flags,
149
209
  )
150
210
 
151
- def like[LProp, RProp](self, pattern: str) -> Like[LProp, RProp]:
211
+ def like(self, pattern: str) -> Like:
152
212
  from ormlambda.sql.comparer import Like
153
213
 
154
214
  return Like(self, pattern)