ormlambda 3.12.2__py3-none-any.whl → 3.34.1__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 (145) 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/databases/__init__.py +0 -1
  11. ormlambda/databases/my_sql/__init__.py +0 -1
  12. ormlambda/databases/my_sql/caster/caster.py +23 -19
  13. ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
  14. ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
  15. ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
  16. ormlambda/databases/my_sql/caster/types/date.py +34 -0
  17. ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
  18. ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
  19. ormlambda/databases/my_sql/caster/types/float.py +7 -7
  20. ormlambda/databases/my_sql/caster/types/int.py +7 -7
  21. ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
  22. ormlambda/databases/my_sql/caster/types/none.py +8 -7
  23. ormlambda/databases/my_sql/caster/types/point.py +4 -4
  24. ormlambda/databases/my_sql/caster/types/string.py +7 -7
  25. ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
  26. ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
  27. ormlambda/databases/my_sql/clauses/__init__.py +4 -10
  28. ormlambda/databases/my_sql/clauses/count.py +5 -15
  29. ormlambda/databases/my_sql/clauses/delete.py +3 -50
  30. ormlambda/databases/my_sql/clauses/group_by.py +3 -16
  31. ormlambda/databases/my_sql/clauses/having.py +2 -6
  32. ormlambda/databases/my_sql/clauses/insert.py +4 -92
  33. ormlambda/databases/my_sql/clauses/joins.py +5 -140
  34. ormlambda/databases/my_sql/clauses/limit.py +4 -15
  35. ormlambda/databases/my_sql/clauses/offset.py +4 -15
  36. ormlambda/databases/my_sql/clauses/order.py +4 -61
  37. ormlambda/databases/my_sql/clauses/update.py +4 -67
  38. ormlambda/databases/my_sql/clauses/upsert.py +3 -66
  39. ormlambda/databases/my_sql/clauses/where.py +4 -42
  40. ormlambda/databases/my_sql/repository.py +217 -0
  41. ormlambda/dialects/__init__.py +39 -0
  42. ormlambda/dialects/default/__init__.py +1 -0
  43. ormlambda/dialects/default/base.py +39 -0
  44. ormlambda/dialects/interface/__init__.py +1 -0
  45. ormlambda/dialects/interface/dialect.py +78 -0
  46. ormlambda/dialects/mysql/__init__.py +8 -0
  47. ormlambda/dialects/mysql/base.py +387 -0
  48. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  49. ormlambda/dialects/mysql/types.py +732 -0
  50. ormlambda/dialects/sqlite/__init__.py +5 -0
  51. ormlambda/dialects/sqlite/base.py +47 -0
  52. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  53. ormlambda/engine/__init__.py +1 -0
  54. ormlambda/engine/base.py +58 -0
  55. ormlambda/engine/create.py +9 -23
  56. ormlambda/engine/url.py +31 -19
  57. ormlambda/env.py +30 -0
  58. ormlambda/errors.py +17 -0
  59. ormlambda/model/base_model.py +7 -9
  60. ormlambda/repository/base_repository.py +36 -5
  61. ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
  62. ormlambda/repository/response.py +134 -0
  63. ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
  64. ormlambda/sql/clause_info/clause_info.py +34 -17
  65. ormlambda/sql/clauses/__init__.py +14 -0
  66. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  67. ormlambda/sql/clauses/count.py +57 -0
  68. ormlambda/sql/clauses/delete.py +71 -0
  69. ormlambda/sql/clauses/group_by.py +30 -0
  70. ormlambda/sql/clauses/having.py +21 -0
  71. ormlambda/sql/clauses/insert.py +104 -0
  72. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  73. ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
  74. ormlambda/sql/clauses/joins.py +159 -0
  75. ormlambda/sql/clauses/limit.py +15 -0
  76. ormlambda/sql/clauses/offset.py +15 -0
  77. ormlambda/sql/clauses/order.py +55 -0
  78. ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
  79. ormlambda/sql/clauses/update.py +84 -0
  80. ormlambda/sql/clauses/upsert.py +77 -0
  81. ormlambda/sql/clauses/where.py +65 -0
  82. ormlambda/sql/column/__init__.py +1 -0
  83. ormlambda/sql/{column.py → column/column.py} +82 -22
  84. ormlambda/sql/comparer.py +51 -37
  85. ormlambda/sql/compiler.py +427 -0
  86. ormlambda/sql/ddl.py +68 -0
  87. ormlambda/sql/elements.py +36 -0
  88. ormlambda/sql/foreign_key.py +43 -39
  89. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  90. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  91. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  92. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  93. ormlambda/sql/sqltypes.py +647 -0
  94. ormlambda/sql/table/__init__.py +1 -1
  95. ormlambda/sql/table/table.py +179 -0
  96. ormlambda/sql/table/table_constructor.py +1 -208
  97. ormlambda/sql/type_api.py +35 -0
  98. ormlambda/sql/types.py +3 -1
  99. ormlambda/sql/visitors.py +74 -0
  100. ormlambda/statements/__init__.py +1 -0
  101. ormlambda/statements/base_statement.py +28 -38
  102. ormlambda/statements/interfaces/IStatements.py +5 -4
  103. ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
  104. ormlambda/{databases/my_sql → statements}/statements.py +50 -60
  105. ormlambda/statements/types.py +2 -2
  106. ormlambda/types/__init__.py +24 -0
  107. ormlambda/types/metadata.py +42 -0
  108. ormlambda/util/__init__.py +88 -0
  109. ormlambda/util/load_module.py +21 -0
  110. ormlambda/util/plugin_loader.py +32 -0
  111. ormlambda/util/typing.py +6 -0
  112. ormlambda-3.34.1.dist-info/AUTHORS +32 -0
  113. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
  114. ormlambda-3.34.1.dist-info/RECORD +157 -0
  115. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
  116. ormlambda/components/__init__.py +0 -4
  117. ormlambda/components/delete/__init__.py +0 -2
  118. ormlambda/components/delete/abstract_delete.py +0 -17
  119. ormlambda/components/insert/__init__.py +0 -2
  120. ormlambda/components/insert/abstract_insert.py +0 -25
  121. ormlambda/components/select/__init__.py +0 -1
  122. ormlambda/components/update/__init__.py +0 -2
  123. ormlambda/components/update/abstract_update.py +0 -29
  124. ormlambda/components/upsert/__init__.py +0 -2
  125. ormlambda/components/upsert/abstract_upsert.py +0 -25
  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/repository/__init__.py +0 -1
  129. ormlambda/databases/my_sql/repository/repository.py +0 -351
  130. ormlambda/engine/template.py +0 -47
  131. ormlambda/sql/dtypes.py +0 -94
  132. ormlambda/utils/__init__.py +0 -1
  133. ormlambda-3.12.2.dist-info/RECORD +0 -125
  134. /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
  135. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  136. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  137. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  138. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  139. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  140. /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
  141. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  142. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  143. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  144. /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
  145. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
  from typing import Any, Callable, Optional, Type, overload, TYPE_CHECKING
3
3
  from enum import Enum
4
- from abc import abstractmethod, ABC
4
+ from abc import abstractmethod
5
5
 
6
6
 
7
7
  if TYPE_CHECKING:
@@ -9,12 +9,11 @@ if TYPE_CHECKING:
9
9
  from ormlambda import Table
10
10
  from ormlambda.sql.clause_info import IAggregate
11
11
  from ormlambda.sql.types import TupleJoinType, ColumnType
12
- from ormlambda.components.join import JoinContext
12
+ from ormlambda.sql.clauses.join import JoinContext
13
13
  from ormlambda.common.enums import JoinType
14
14
  from ormlambda.sql.clause_info import ClauseInfo
15
15
  from ormlambda.sql.types import AliasType
16
16
 
17
-
18
17
  from ..types import (
19
18
  OrderTypes,
20
19
  Tuple,
@@ -31,9 +30,10 @@ from ..types import (
31
30
  WhereTypes,
32
31
  SelectCols,
33
32
  )
33
+ from ormlambda.sql.elements import Element
34
34
 
35
35
 
36
- class IStatements[T: Table](ABC):
36
+ class IStatements[T: Table](Element):
37
37
  @abstractmethod
38
38
  def create_table(self, if_exists: TypeExists = "fail") -> None: ...
39
39
 
@@ -334,6 +334,7 @@ class IStatements_two_generic[T, TPool](IStatements[T]):
334
334
  def repository(self) -> BaseRepository[TPool]: ...
335
335
 
336
336
  @property
337
+ @abstractmethod
337
338
  def query(self) -> str: ...
338
339
 
339
340
  @property
@@ -1,25 +1,29 @@
1
1
  from __future__ import annotations
2
- from typing import Iterable, TypedDict, Optional
2
+ from typing import Iterable, TypedDict, Optional, TYPE_CHECKING
3
3
 
4
4
  from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
5
- from ormlambda.databases.my_sql.clauses.joins import JoinSelector
5
+ from ormlambda.sql.clauses import JoinSelector
6
6
  from ormlambda import ForeignKey
7
7
 
8
8
  from ormlambda.common.interfaces import IQuery
9
9
 
10
10
 
11
11
  from ormlambda.sql.clause_info import ClauseInfo
12
- from .clauses import Limit
13
- from .clauses import Offset
14
- from .clauses import Order
15
- from .clauses import Select
16
12
 
17
- from .clauses import Where
18
- from .clauses import Having
19
- from .clauses import GroupBy
13
+ if TYPE_CHECKING:
14
+ from ..sql.clauses import Limit as Limit
15
+ from ..sql.clauses import Offset as Offset
16
+ from ..sql.clauses import Order as Order
17
+ from ..sql.clauses import Select as Select
20
18
 
19
+ from ..sql.clauses import GroupBy as GroupBy
21
20
 
21
+ from ormlambda.dialects import Dialect
22
+
23
+ from ..sql.clauses import Where as Where
24
+ from ..sql.clauses import Having as Having
22
25
  from ormlambda.common.enums import JoinType
26
+ from ormlambda.sql.elements import ClauseElement
23
27
 
24
28
 
25
29
  class OrderType(TypedDict):
@@ -36,7 +40,8 @@ class OrderType(TypedDict):
36
40
  class QueryBuilder(IQuery):
37
41
  __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
38
42
 
39
- def __init__(self, by: JoinType = JoinType.INNER_JOIN):
43
+ def __init__(self, dialect: Dialect, by: JoinType = JoinType.INNER_JOIN):
44
+ self.dialect = dialect
40
45
  self._context = ClauseInfoContext()
41
46
  self._query_list: OrderType = {}
42
47
  self._by = by
@@ -66,11 +71,11 @@ class QueryBuilder(IQuery):
66
71
  return self._joins
67
72
 
68
73
  @property
69
- def SELECT(self) -> IQuery:
74
+ def SELECT(self) -> ClauseElement:
70
75
  return self._query_list.get("Select", None)
71
76
 
72
77
  @property
73
- def WHERE(self) -> IQuery:
78
+ def WHERE(self) -> ClauseElement:
74
79
  where = self._query_list.get("Where", None)
75
80
  if not isinstance(where, Iterable):
76
81
  if not where:
@@ -79,15 +84,15 @@ class QueryBuilder(IQuery):
79
84
  return where
80
85
 
81
86
  @property
82
- def ORDER(self) -> IQuery:
87
+ def ORDER(self) -> ClauseElement:
83
88
  return self._query_list.get("Order", None)
84
89
 
85
90
  @property
86
- def GROUP_BY(self) -> IQuery:
91
+ def GROUP_BY(self) -> ClauseElement:
87
92
  return self._query_list.get("GroupBy", None)
88
93
 
89
94
  @property
90
- def HAVING(self) -> IQuery:
95
+ def HAVING(self) -> ClauseElement:
91
96
  where = self._query_list.get("Having", None)
92
97
  if not isinstance(where, Iterable):
93
98
  if not where:
@@ -96,15 +101,14 @@ class QueryBuilder(IQuery):
96
101
  return where
97
102
 
98
103
  @property
99
- def LIMIT(self) -> IQuery:
104
+ def LIMIT(self) -> ClauseElement:
100
105
  return self._query_list.get("Limit", None)
101
106
 
102
107
  @property
103
- def OFFSET(self) -> IQuery:
108
+ def OFFSET(self) -> ClauseElement:
104
109
  return self._query_list.get("Offset", None)
105
110
 
106
- @property
107
- def query(self) -> str:
111
+ def query(self, dialect: Dialect, **kwargs) -> str:
108
112
  # COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
109
113
  # This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
110
114
 
@@ -112,14 +116,14 @@ class QueryBuilder(IQuery):
112
116
 
113
117
  JOINS = self.stringify_foreign_key(extract_joins, " ")
114
118
  query_list: tuple[str, ...] = (
115
- self.SELECT.query,
119
+ self.SELECT.compile(dialect).string,
116
120
  JOINS,
117
- Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
118
- self.GROUP_BY.query if self.GROUP_BY else None,
119
- Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
120
- self.ORDER.query if self.ORDER else None,
121
- self.LIMIT.query if self.LIMIT else None,
122
- self.OFFSET.query if self.OFFSET else None,
121
+ Where.join_condition(self.WHERE, True, self._context, dialect=dialect) if self.WHERE else None,
122
+ self.GROUP_BY.compile(dialect).string if self.GROUP_BY else None,
123
+ Having.join_condition(self.HAVING, True, self._context, dialect=dialect) if self.HAVING else None,
124
+ self.ORDER.compile(dialect).string if self.ORDER else None,
125
+ self.LIMIT.compile(dialect).string if self.LIMIT else None,
126
+ self.OFFSET.compile(dialect).string if self.OFFSET else None,
123
127
  )
124
128
  return " ".join([x for x in query_list if x])
125
129
 
@@ -127,7 +131,7 @@ class QueryBuilder(IQuery):
127
131
  if not joins:
128
132
  return None
129
133
  sorted_joins = JoinSelector.sort_join_selectors(joins)
130
- return f"{sep}".join([join.query for join in sorted_joins])
134
+ return f"{sep}".join([join.query(self.dialect) for join in sorted_joins])
131
135
 
132
136
  def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
133
137
  # 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.
@@ -139,14 +143,15 @@ class QueryBuilder(IQuery):
139
143
  # FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
140
144
  for fk in ForeignKey.stored_calls.copy():
141
145
  fk = ForeignKey.stored_calls.pop(fk)
142
- self._context._add_table_alias(fk.tright, fk.alias)
143
- join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
146
+ fk_alias = fk.get_alias(self.dialect)
147
+ self._context._add_table_alias(fk.tright, fk_alias)
148
+ join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk_alias, dialect=self.dialect)
144
149
  joins.add(join)
145
150
 
146
151
  return joins
147
152
 
148
153
  def clear(self) -> None:
149
- self.__init__()
154
+ self.__init__(self.dialect, self.by)
150
155
  return None
151
156
 
152
157
  def update_context(self, clause: ClauseInfo) -> None:
@@ -1,19 +1,16 @@
1
1
  from __future__ import annotations
2
2
  from typing import Concatenate, Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
3
- from mysql.connector import errors, errorcode
4
3
 
5
4
  from ormlambda import ForeignKey
6
5
 
7
- from mysql.connector import MySQLConnection
8
-
9
6
 
10
7
  if TYPE_CHECKING:
8
+ from ormlambda.engine.base import Engine
11
9
  from ormlambda.sql.types import AliasType
12
10
  from ormlambda import Table
13
11
  from ormlambda.statements.types import OrderTypes
14
12
  from ormlambda.sql.types import ColumnType
15
13
  from ormlambda.statements.types import SelectCols
16
- from ormlambda.repository import BaseRepository
17
14
  from ormlambda.statements.interfaces import IStatements_two_generic
18
15
  from ormlambda.statements.types import TypeExists
19
16
  from ormlambda.sql.clause_info import IAggregate
@@ -22,35 +19,22 @@ if TYPE_CHECKING:
22
19
 
23
20
  from ormlambda.sql.clause_info import ClauseInfo
24
21
  from ormlambda.statements import BaseStatement
25
- from .clauses import DeleteQuery
26
- from .clauses import InsertQuery
27
- from .clauses import Limit
28
- from .clauses import Offset
29
- from .clauses import Order
30
- from .clauses import Select
31
-
32
- from .clauses import UpsertQuery
33
- from .clauses import UpdateQuery
34
- from .clauses import Where
35
- from .clauses import Having
36
- from .clauses import Count
37
- from .clauses import GroupBy
38
- from .clauses import Alias
39
-
40
22
 
41
23
  from ormlambda import Table, Column
42
24
  from ormlambda.common.enums import JoinType
43
- from . import functions as func
44
- from ormlambda.components.join import JoinContext, TupleJoinType
25
+ from ormlambda.sql.clauses.join import JoinContext, TupleJoinType
45
26
 
46
27
  from ormlambda.common.global_checker import GlobalChecker
47
28
  from .query_builder import QueryBuilder
48
29
 
30
+ from ormlambda.sql import clauses
31
+ from ormlambda.sql import functions as func
32
+
49
33
 
50
34
  # COMMENT: It's so important to prevent information generated by other tests from being retained in the class.
51
35
  @staticmethod
52
- def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Callable[P, T]:
53
- 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:
54
38
  try:
55
39
  return f(self, *args, **kwargs)
56
40
  except Exception as err:
@@ -62,10 +46,10 @@ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Calla
62
46
  return wrapper
63
47
 
64
48
 
65
- class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
66
- def __init__(self, model: tuple[T, *Ts], repository: BaseRepository) -> None:
67
- super().__init__(model, repository=repository)
68
- self._query_builder = QueryBuilder()
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)
69
53
 
70
54
  @override
71
55
  def create_table(self, if_exists: TypeExists = "fail") -> None:
@@ -75,7 +59,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
75
59
  self._repository.drop_table(name)
76
60
 
77
61
  elif if_exists == "fail":
78
- 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")
79
63
 
80
64
  elif if_exists == "append":
81
65
  counter: int = 0
@@ -84,16 +68,19 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
84
68
  counter += 1
85
69
  char = f"_{counter}"
86
70
  name += char
87
- self._model.__table_name__ = name
88
71
 
89
- 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)
90
77
  self._repository.execute(query)
91
78
  return None
92
79
 
93
80
  @override
94
81
  @clear_list
95
82
  def insert(self, instances: T | list[T]) -> None:
96
- insert = InsertQuery(self._model, self._repository)
83
+ insert = clauses.Insert(self._model, self.repository, self._dialect)
97
84
  insert.insert(instances)
98
85
  insert.execute()
99
86
  return None
@@ -108,7 +95,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
108
95
  # We always going to have a tuple of one element
109
96
  return self.delete(response)
110
97
 
111
- delete = DeleteQuery(self._model, self._repository)
98
+ delete = clauses.Delete(self._model, self._repository, engine=self._engine)
112
99
  delete.delete(instances)
113
100
  delete.execute()
114
101
  # not necessary to call self._query_builder.clear() because select() method already call it
@@ -117,7 +104,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
117
104
  @override
118
105
  @clear_list
119
106
  def upsert(self, instances: T | list[T]) -> None:
120
- upsert = UpsertQuery(self._model, self._repository)
107
+ upsert = clauses.Upsert(self._model, self._repository, engine=self._engine)
121
108
  upsert.upsert(instances)
122
109
  upsert.execute()
123
110
  return None
@@ -125,22 +112,22 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
125
112
  @override
126
113
  @clear_list
127
114
  def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
128
- update = UpdateQuery(self._model, self._repository, self._query_builder.WHERE)
115
+ update = clauses.Update(self._model, self._repository, self._query_builder.WHERE, engine=self._engine)
129
116
  update.update(dicc)
130
117
  update.execute()
131
118
 
132
119
  return None
133
120
 
134
121
  @override
135
- def limit(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
136
- limit = Limit(number)
122
+ def limit(self, number: int) -> IStatements_two_generic[T, TRepo]:
123
+ limit = clauses.Limit(number, dialect=self._dialect)
137
124
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
138
125
  self._query_builder.add_statement(limit)
139
126
  return self
140
127
 
141
128
  @override
142
- def offset(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
143
- offset = Offset(number)
129
+ def offset(self, number: int) -> IStatements_two_generic[T, TRepo]:
130
+ offset = clauses.Offset(number, dialect=self._dialect)
144
131
  self._query_builder.add_statement(offset)
145
132
  return self
146
133
 
@@ -155,40 +142,41 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
155
142
  return self.select_one(self.count(selection, alias, False), flavour=dict)[alias]
156
143
 
157
144
  selection = GlobalChecker.resolved_callback_object(selection, self.models)
158
- return Count(element=selection, alias_clause=alias, context=self._query_builder._context)
145
+ return clauses.Count(
146
+ element=selection,
147
+ alias_clause=alias,
148
+ context=self._query_builder._context,
149
+ dialect=self._dialect,
150
+ )
159
151
 
160
152
  @override
161
- def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
153
+ def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, TRepo]:
162
154
  # FIXME [x]: I've wrapped self._model into tuple to pass it instance attr. Idk if it's correct
163
155
 
164
156
  conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
165
157
  if not isinstance(conditions, Iterable):
166
158
  conditions = (conditions,)
167
- self._query_builder.add_statement(Where(*conditions))
159
+ self._query_builder.add_statement(clauses.Where(*conditions))
168
160
  return self
169
161
 
170
162
  @override
171
- def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
163
+ def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, TRepo]:
172
164
  conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
173
165
  if not isinstance(conditions, Iterable):
174
166
  conditions = (conditions,)
175
- self._query_builder.add_statement(Having(*conditions))
167
+ self._query_builder.add_statement(clauses.Having(*conditions))
176
168
  return self
177
169
 
178
170
  @override
179
- def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
171
+ def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, TRepo]:
180
172
  query = GlobalChecker.resolved_callback_object(columns, self._models)
181
- order = Order(query, order_type)
173
+ order = clauses.Order(query, order_type, dialect=self._dialect)
182
174
  self._query_builder.add_statement(order)
183
175
  return self
184
176
 
185
177
  @override
186
178
  def concat(self, selector: SelectCols[T, str], alias: str = "concat") -> IAggregate:
187
- return func.Concat[T](
188
- values=selector,
189
- alias_clause=alias,
190
- context=self._query_builder._context,
191
- )
179
+ return func.Concat(values=selector, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
192
180
 
193
181
  @override
194
182
  def max[TProp](
@@ -200,7 +188,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
200
188
  column = GlobalChecker.resolved_callback_object(column, self.models)
201
189
  if execute is True:
202
190
  return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
203
- return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
191
+ return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
204
192
 
205
193
  @override
206
194
  def min[TProp](
@@ -212,7 +200,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
212
200
  column = GlobalChecker.resolved_callback_object(column, self.models)
213
201
  if execute is True:
214
202
  return self.select_one(self.min(column, alias, execute=False), flavour=dict)[alias]
215
- return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context)
203
+ return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
216
204
 
217
205
  @override
218
206
  def sum[TProp](
@@ -224,11 +212,11 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
224
212
  column = GlobalChecker.resolved_callback_object(column, self.models)
225
213
  if execute is True:
226
214
  return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
227
- return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
215
+ return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context, dialect=self._dialect)
228
216
 
229
217
  @override
230
218
  def join[LTable: Table, LProp, RTable: Table, RProp](self, joins: tuple[TupleJoinType[LTable, LProp, RTable, RProp]]) -> JoinContext[tuple[*TupleJoinType[LTable, LProp, RTable, RProp]]]:
231
- return JoinContext(self, joins, self._query_builder._context)
219
+ return JoinContext(self, joins, self._query_builder._context, self._dialect)
232
220
 
233
221
  @override
234
222
  @clear_list
@@ -254,15 +242,16 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
254
242
  return result
255
243
  return () if not result else result[0]
256
244
 
257
- select = Select[T, *Ts](
245
+ select = clauses.Select(
258
246
  self._models,
259
247
  columns=select_clause,
248
+ dialect=self.dialect,
260
249
  **kwargs,
261
250
  )
262
251
  self._query_builder.add_statement(select)
263
252
 
264
253
  self._query_builder.by = by
265
- self._query: str = self._query_builder.query
254
+ self._query: str = self._query_builder.query(self._dialect)
266
255
 
267
256
  if flavour:
268
257
  result = self._return_flavour(self.query, flavour, select, **kwargs)
@@ -316,21 +305,22 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
316
305
  )
317
306
 
318
307
  @override
319
- def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T, *Ts], Any]) -> IStatements_two_generic[T]:
308
+ def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T], Any]) -> IStatements_two_generic[T]:
320
309
  column = GlobalChecker.resolved_callback_object(column, self.models)
321
310
 
322
- groupby = GroupBy(column=column, context=self._query_builder._context)
311
+ groupby = clauses.GroupBy(column=column, context=self._query_builder._context, dialect=self.dialect)
323
312
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
324
313
  self._query_builder.add_statement(groupby)
325
314
  return self
326
315
 
327
316
  @override
328
- def alias[TProp](self, column: SelectCols[T, TProp], alias: AliasType[ClauseInfo[T]]) -> ClauseInfo[T]:
317
+ def alias[TProp](self, column: SelectCols[T, TProp], alias: AliasType[ClauseInfo[T]]) -> clauses.Alias[T]:
329
318
  column = GlobalChecker.resolved_callback_object(column, self.models)
330
319
 
331
- return Alias(
320
+ return clauses.Alias(
332
321
  table=column.table,
333
322
  column=column,
334
323
  alias_clause=alias,
335
324
  context=self._query_builder._context,
325
+ dialect=self.dialect,
336
326
  )
@@ -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,88 @@
1
+ from .module_tree import ModuleTree # noqa: F401
2
+ from .load_module import __load_module__ # noqa: F401
3
+ import types
4
+ import inspect
5
+ from typing import Any, Literal, Optional, Sequence, overload, get_origin, TypeGuard, TypeAliasType
6
+ from ormlambda.util.typing import LITERAL_TYPES, _AnnotationScanType
7
+ from .plugin_loader import PluginLoader # noqa: F401
8
+
9
+
10
+ def _inspect_func_args(fn) -> tuple[list[str], bool]:
11
+ try:
12
+ co_varkeywords = inspect.CO_VARKEYWORDS
13
+ except AttributeError:
14
+ # https://docs.python.org/3/library/inspect.html
15
+ # The flags are specific to CPython, and may not be defined in other
16
+ # Python implementations. Furthermore, the flags are an implementation
17
+ # detail, and can be removed or deprecated in future Python releases.
18
+ spec = inspect.getfullargspec(fn)
19
+ return spec[0], bool(spec[2])
20
+ else:
21
+ # use fn.__code__ plus flags to reduce method call overhead
22
+ co = fn.__code__
23
+ nargs = co.co_argcount
24
+ return (
25
+ list(co.co_varnames[:nargs]),
26
+ bool(co.co_flags & co_varkeywords),
27
+ )
28
+
29
+
30
+ @overload
31
+ def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: Literal[True] = ...) -> set[str]: ...
32
+
33
+
34
+ @overload
35
+ def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: Literal[False] = ...) -> Optional[set[str]]: ...
36
+
37
+
38
+ def get_cls_kwargs(cls: type, *, _set: Optional[set[str]] = None, raiseerr: bool = False) -> Optional[set[str]]:
39
+ """
40
+ Get the keyword arguments for a class constructor.
41
+ Args:
42
+ cls: The class to inspect.
43
+ _set: A set to store the keyword arguments.
44
+ raiseerr: Whether to raise an error if the class is not found.
45
+ Returns:
46
+ A set of keyword arguments for the class constructor.
47
+ """
48
+ toplevel = _set is None
49
+ if toplevel:
50
+ _set = set()
51
+ assert _set is not None
52
+
53
+ ctr = cls.__dict__.get("__init__", False)
54
+
55
+ has_init = ctr and isinstance(ctr, types.FunctionType) and isinstance(ctr.__code__, types.CodeType)
56
+
57
+ if has_init:
58
+ names, has_kw = _inspect_func_args(ctr)
59
+ _set.update(names)
60
+
61
+ if not has_kw and not toplevel:
62
+ if raiseerr:
63
+ raise TypeError(f"given cls {cls} doesn't have an __init__ method")
64
+ else:
65
+ return None
66
+ else:
67
+ has_kw = False
68
+
69
+ if not has_init or has_kw:
70
+ for c in cls.__bases__:
71
+ if get_cls_kwargs(c, _set=_set) is None:
72
+ break
73
+
74
+ _set.discard("self")
75
+ return _set
76
+
77
+
78
+ def avoid_sql_injection(name: str):
79
+ if any(char in name for char in [";", "--", "/*", "*/"]):
80
+ raise ValueError("SQL injection detected")
81
+
82
+
83
+ def is_literal(type_: Any) -> bool:
84
+ return get_origin(type) in LITERAL_TYPES
85
+
86
+
87
+ def is_pep695(type_: _AnnotationScanType) -> TypeGuard[TypeAliasType]:
88
+ return isinstance(type_, TypeAliasType)
@@ -0,0 +1,21 @@
1
+ import logging
2
+ import importlib
3
+ from typing import Any, Optional
4
+
5
+ log = logging.getLogger(__name__)
6
+
7
+
8
+ def __load_module__(module_path: str, dialect_name: Optional[str] = None) -> Optional[Any]:
9
+ """
10
+ Load a module by its path and return the module object.
11
+ Returns None if the module cannot be imported
12
+ Args:
13
+ module_path: The dot-separated path to the module
14
+
15
+ Returns:
16
+ The loaded module or None if import fails
17
+ """
18
+ try:
19
+ return importlib.import_module(module_path)
20
+ except ImportError:
21
+ log.error(f"{module_path.split('.')[-1] if not dialect_name else dialect_name} dialect not available")