ormlambda 3.35.3__py3-none-any.whl → 4.0.4__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 (130) hide show
  1. ormlambda/__init__.py +79 -51
  2. ormlambda/caster/caster.py +6 -1
  3. ormlambda/common/abstract_classes/__init__.py +0 -2
  4. ormlambda/common/enums/__init__.py +1 -0
  5. ormlambda/common/enums/order_type.py +9 -0
  6. ormlambda/common/errors/__init__.py +13 -3
  7. ormlambda/common/global_checker.py +86 -8
  8. ormlambda/common/interfaces/IQueryCommand.py +2 -2
  9. ormlambda/common/interfaces/__init__.py +0 -2
  10. ormlambda/dialects/__init__.py +75 -3
  11. ormlambda/dialects/default/base.py +1 -1
  12. ormlambda/dialects/mysql/__init__.py +35 -78
  13. ormlambda/dialects/mysql/base.py +226 -40
  14. ormlambda/dialects/mysql/clauses/ST_AsText.py +26 -0
  15. ormlambda/dialects/mysql/clauses/ST_Contains.py +30 -0
  16. ormlambda/dialects/mysql/clauses/__init__.py +1 -0
  17. ormlambda/dialects/mysql/repository/__init__.py +1 -0
  18. ormlambda/{databases/my_sql → dialects/mysql/repository}/repository.py +0 -5
  19. ormlambda/dialects/mysql/types.py +6 -0
  20. ormlambda/engine/base.py +26 -4
  21. ormlambda/errors.py +9 -0
  22. ormlambda/model/base_model.py +3 -10
  23. ormlambda/repository/base_repository.py +1 -1
  24. ormlambda/repository/interfaces/IRepositoryBase.py +0 -7
  25. ormlambda/repository/response.py +21 -8
  26. ormlambda/sql/__init__.py +12 -3
  27. ormlambda/sql/clause_info/__init__.py +0 -2
  28. ormlambda/sql/clause_info/clause_info.py +94 -76
  29. ormlambda/sql/clause_info/interface/IAggregate.py +14 -4
  30. ormlambda/sql/clause_info/interface/IClauseInfo.py +6 -11
  31. ormlambda/sql/clauses/alias.py +6 -37
  32. ormlambda/sql/clauses/count.py +21 -36
  33. ormlambda/sql/clauses/group_by.py +13 -19
  34. ormlambda/sql/clauses/having.py +2 -6
  35. ormlambda/sql/clauses/insert.py +3 -3
  36. ormlambda/sql/clauses/interfaces/__init__.py +0 -1
  37. ormlambda/sql/clauses/join/join_context.py +5 -12
  38. ormlambda/sql/clauses/joins.py +34 -52
  39. ormlambda/sql/clauses/limit.py +1 -2
  40. ormlambda/sql/clauses/offset.py +1 -2
  41. ormlambda/sql/clauses/order.py +17 -21
  42. ormlambda/sql/clauses/select.py +56 -28
  43. ormlambda/sql/clauses/update.py +13 -10
  44. ormlambda/sql/clauses/where.py +20 -39
  45. ormlambda/sql/column/__init__.py +1 -0
  46. ormlambda/sql/column/column.py +19 -12
  47. ormlambda/sql/column/column_proxy.py +117 -0
  48. ormlambda/sql/column_table_proxy.py +23 -0
  49. ormlambda/sql/comparer.py +31 -65
  50. ormlambda/sql/compiler.py +248 -58
  51. ormlambda/sql/context/__init__.py +304 -0
  52. ormlambda/sql/ddl.py +19 -5
  53. ormlambda/sql/elements.py +3 -0
  54. ormlambda/sql/foreign_key.py +42 -64
  55. ormlambda/sql/functions/__init__.py +0 -1
  56. ormlambda/sql/functions/concat.py +35 -38
  57. ormlambda/sql/functions/max.py +12 -36
  58. ormlambda/sql/functions/min.py +13 -28
  59. ormlambda/sql/functions/sum.py +17 -33
  60. ormlambda/sql/sqltypes.py +2 -0
  61. ormlambda/sql/table/__init__.py +1 -0
  62. ormlambda/sql/table/table.py +31 -45
  63. ormlambda/sql/table/table_proxy.py +88 -0
  64. ormlambda/sql/type_api.py +4 -1
  65. ormlambda/sql/types.py +15 -12
  66. ormlambda/statements/__init__.py +0 -2
  67. ormlambda/statements/base_statement.py +53 -91
  68. ormlambda/statements/interfaces/IStatements.py +77 -123
  69. ormlambda/statements/interfaces/__init__.py +1 -1
  70. ormlambda/statements/query_builder.py +296 -128
  71. ormlambda/statements/statements.py +122 -115
  72. ormlambda/statements/types.py +5 -25
  73. ormlambda/util/__init__.py +7 -100
  74. ormlambda/util/langhelpers.py +102 -0
  75. ormlambda/util/module_tree/dynamic_module.py +1 -1
  76. ormlambda/util/preloaded.py +80 -0
  77. ormlambda/util/typing.py +12 -3
  78. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/METADATA +56 -79
  79. ormlambda-4.0.4.dist-info/RECORD +139 -0
  80. ormlambda/common/abstract_classes/clause_info_converter.py +0 -65
  81. ormlambda/common/abstract_classes/decomposition_query.py +0 -141
  82. ormlambda/common/abstract_classes/query_base.py +0 -15
  83. ormlambda/common/interfaces/ICustomAlias.py +0 -7
  84. ormlambda/common/interfaces/IDecompositionQuery.py +0 -33
  85. ormlambda/databases/__init__.py +0 -4
  86. ormlambda/databases/my_sql/__init__.py +0 -3
  87. ormlambda/databases/my_sql/clauses/ST_AsText.py +0 -37
  88. ormlambda/databases/my_sql/clauses/ST_Contains.py +0 -36
  89. ormlambda/databases/my_sql/clauses/__init__.py +0 -14
  90. ormlambda/databases/my_sql/clauses/count.py +0 -33
  91. ormlambda/databases/my_sql/clauses/delete.py +0 -9
  92. ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
  93. ormlambda/databases/my_sql/clauses/group_by.py +0 -17
  94. ormlambda/databases/my_sql/clauses/having.py +0 -12
  95. ormlambda/databases/my_sql/clauses/insert.py +0 -9
  96. ormlambda/databases/my_sql/clauses/joins.py +0 -14
  97. ormlambda/databases/my_sql/clauses/limit.py +0 -6
  98. ormlambda/databases/my_sql/clauses/offset.py +0 -6
  99. ormlambda/databases/my_sql/clauses/order.py +0 -8
  100. ormlambda/databases/my_sql/clauses/update.py +0 -8
  101. ormlambda/databases/my_sql/clauses/upsert.py +0 -9
  102. ormlambda/databases/my_sql/clauses/where.py +0 -7
  103. ormlambda/dialects/interface/__init__.py +0 -1
  104. ormlambda/dialects/interface/dialect.py +0 -78
  105. ormlambda/sql/clause_info/aggregate_function_base.py +0 -96
  106. ormlambda/sql/clause_info/clause_info_context.py +0 -87
  107. ormlambda/sql/clauses/interfaces/ISelect.py +0 -17
  108. ormlambda/sql/clauses/new_join.py +0 -119
  109. ormlambda/util/load_module.py +0 -21
  110. ormlambda/util/plugin_loader.py +0 -32
  111. ormlambda-3.35.3.dist-info/RECORD +0 -159
  112. /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
  113. /ormlambda/{databases/my_sql → dialects/mysql}/caster/caster.py +0 -0
  114. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +0 -0
  115. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/boolean.py +0 -0
  116. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +0 -0
  117. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/date.py +0 -0
  118. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +0 -0
  119. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/decimal.py +0 -0
  120. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +0 -0
  121. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +0 -0
  122. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +0 -0
  123. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/json.py +0 -0
  124. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +0 -0
  125. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +0 -0
  126. /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +0 -0
  127. /ormlambda/{databases/my_sql → dialects/mysql/repository}/pool_types.py +0 -0
  128. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/AUTHORS +0 -0
  129. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/LICENSE +0 -0
  130. {ormlambda-3.35.3.dist-info → ormlambda-4.0.4.dist-info}/WHEEL +0 -0
@@ -1,39 +1,24 @@
1
1
  from __future__ import annotations
2
- import typing as tp
3
2
 
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
5
- from ormlambda.sql.clause_info import ClauseInfo
3
+ from ormlambda.sql.elements import ClauseElement
6
4
  from ormlambda.sql.types import ColumnType, AliasType
7
- from ormlambda.sql.clause_info import AggregateFunctionBase
5
+ from ormlambda.sql.clause_info import IAggregate
8
6
 
9
- if tp.TYPE_CHECKING:
10
- from ormlambda.dialects import Dialect
11
7
 
12
-
13
- class Min(AggregateFunctionBase):
14
- @staticmethod
15
- def FUNCTION_NAME() -> str:
16
- return "MIN"
8
+ class Min(ClauseElement, IAggregate):
9
+ __visit_name__ = "min"
17
10
 
18
11
  def __init__[TProp](
19
12
  self,
20
- elements: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
21
- alias_clause: AliasType[ColumnType[TProp]] = "min",
22
- context: ClauseContextType = None,
23
- *,
24
- dialect: Dialect,
13
+ elements: ColumnType[TProp],
14
+ alias: AliasType[ColumnType[TProp]] = "min",
25
15
  ):
26
- super().__init__(table=None, column=elements, alias_table=None, alias_clause=alias_clause, context=context, keep_asterisk=False, preserve_context=False, dialect=dialect)
27
-
28
- @tp.override
29
- def query(self, dialect: Dialect, **kwargs) -> str:
30
- columns: list[str] = []
16
+ self.column = elements
17
+ self.alias = alias
31
18
 
32
- context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
33
- for clause in self._convert_into_clauseInfo(self.unresolved_column, context, dialect=self._dialect):
34
- new_clause = clause
35
- new_clause.alias_clause = None
36
- columns.append(new_clause)
19
+ def used_columns(self):
20
+ return [self.column]
37
21
 
38
- method_string = f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns,dialect=self._dialect)})"
39
- return self._concat_alias_and_column(method_string, self._alias_aggregate)
22
+ @property
23
+ def dtype(self) -> int:
24
+ return int
@@ -1,41 +1,25 @@
1
1
  from __future__ import annotations
2
- import typing as tp
3
2
 
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
5
- from ormlambda.sql.clause_info import ClauseInfo
6
- from ormlambda.sql.types import ColumnType, AliasType
7
- from ormlambda.sql.clause_info import AggregateFunctionBase
8
-
9
- if tp.TYPE_CHECKING:
10
- from ormlambda.dialects import Dialect
3
+ from ormlambda.sql.elements import ClauseElement
11
4
 
5
+ from ormlambda.sql.types import ColumnType, AliasType
6
+ from ormlambda.sql.clause_info import IAggregate
12
7
 
13
- class Sum(AggregateFunctionBase):
14
- @staticmethod
15
- def FUNCTION_NAME() -> str:
16
- return "SUM"
17
8
 
18
- def __init__[TProp](self, elements: tuple[ColumnType[TProp], ...] | ColumnType[TProp], alias_clause: AliasType[ColumnType[TProp]] = "sum", context: ClauseContextType = None, *, dialect: Dialect):
19
- super().__init__(
20
- table=None,
21
- column=elements,
22
- alias_table=None,
23
- alias_clause=alias_clause,
24
- context=context,
25
- keep_asterisk=False,
26
- preserve_context=False,
27
- dialect=dialect,
28
- )
9
+ class Sum(ClauseElement, IAggregate):
10
+ __visit_name__ = "sum"
29
11
 
30
- @tp.override
31
- def query(self, dialect: Dialect, **kwargs) -> str:
32
- columns: list[str] = []
12
+ def __init__[TProp](
13
+ self,
14
+ elements: ColumnType[TProp],
15
+ alias: AliasType[ColumnType[TProp]] = "sum",
16
+ ):
17
+ self.column = elements
18
+ self.alias = alias
33
19
 
34
- context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
35
- for clause in self._convert_into_clauseInfo(self.unresolved_column, context, self._dialect):
36
- new_clause = clause
37
- new_clause.alias_clause = None
38
- columns.append(new_clause)
20
+ def used_columns(self):
21
+ return [self.column]
39
22
 
40
- method_string = f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns,dialect=self._dialect)})"
41
- return self._concat_alias_and_column(method_string, self._alias_aggregate)
23
+ @property
24
+ def dtype(self) -> int:
25
+ return int
ormlambda/sql/sqltypes.py CHANGED
@@ -651,6 +651,8 @@ _type_dicc: dict[Any, TypeEngine[Any]] = {
651
651
 
652
652
 
653
653
  def resolve_primitive_types[T](value: T) -> TypeEngine[T]:
654
+ if issubclass(value, TypeEngine):
655
+ return value()
654
656
  if inspect.isclass(value):
655
657
  type_ = _type_dicc.get(value, None)
656
658
 
@@ -1 +1,2 @@
1
1
  from .table import Table, TableMeta # noqa: F401
2
+ from .table_proxy import TableProxy # noqa: F401
@@ -2,15 +2,13 @@ from __future__ import annotations
2
2
  from typing import Any, Optional, Type, dataclass_transform, TYPE_CHECKING
3
3
  import json
4
4
 
5
- from ormlambda.sql import Column
6
- from ormlambda.sql import ForeignKey
7
- from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
8
- from ormlambda.sql.ddl import CreateTable
9
- from ormlambda.util import make_hashable
5
+ from ormlambda.sql.ddl import CreateTable, DropTable
6
+ from ormlambda import util
10
7
 
11
8
  if TYPE_CHECKING:
12
- from ormlambda.statements import BaseStatement
9
+ from ormlambda.sql import Column
13
10
  from ormlambda.dialects import Dialect
11
+ from ormlambda import ForeignKey
14
12
 
15
13
  from .table_constructor import __init_constructor__
16
14
 
@@ -34,6 +32,7 @@ class TableMeta(type):
34
32
  raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
35
33
 
36
34
  self = __init_constructor__(cls_object)
35
+
37
36
  return self
38
37
 
39
38
  def __repr__(cls: "Table") -> str:
@@ -85,7 +84,7 @@ class Table(metaclass=TableMeta):
85
84
 
86
85
  dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
87
86
  equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
88
- return f'{self.__class__.__name__}({", ".join(equal_loop)})'
87
+ return f"{self.__class__.__name__}({', '.join(equal_loop)})"
89
88
 
90
89
  def __getitem__[TProp](self, value: str | Column[TProp]) -> Optional[TProp]:
91
90
  name = value if isinstance(value, str) else value.column_name
@@ -94,21 +93,40 @@ class Table(metaclass=TableMeta):
94
93
  return None
95
94
 
96
95
  def to_dict(self) -> dict[str, str | int]:
96
+ def make_hashable(item: Any) -> Any:
97
+ if isinstance(item, dict):
98
+ return tuple(sorted((k, make_hashable(x)) for k, x in item.items()))
99
+
100
+ if isinstance(item, (list | set)):
101
+ return tuple(make_hashable(x) for x in item)
102
+ if hasattr(item, "__iter__") and not isinstance(item, str | bytes):
103
+ try:
104
+ return tuple(make_hashable(x) for x in item)
105
+ except TypeError:
106
+ return item # if it fails, it's already hashable
107
+ return item
108
+
97
109
  dicc: dict[str, Any] = {}
98
110
  for x in self.__annotations__:
99
111
  value = getattr(self, x)
100
112
  dicc[x] = make_hashable(value)
101
113
  return dicc
102
114
 
115
+ @util.preload_module("ormlambda.sql")
103
116
  @classmethod
104
117
  def get_pk(cls) -> Optional[Column]:
118
+ Column = util.preloaded.sql_column.Column
119
+ ColumnProxy = util.preloaded.sql_column.ColumnProxy
105
120
  for obj in cls.__dict__.values():
106
- if isinstance(obj, Column) and obj.is_primary_key:
121
+ if isinstance(obj, Column | ColumnProxy) and obj.is_primary_key:
107
122
  return obj
108
123
  return None
109
124
 
125
+ @util.preload_module("ormlambda.sql")
110
126
  @classmethod
111
127
  def get_columns(cls) -> tuple[Column, ...]:
128
+ Column = util.preloaded.sql_column.Column
129
+
112
130
  return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
113
131
 
114
132
  @classmethod
@@ -117,48 +135,13 @@ class Table(metaclass=TableMeta):
117
135
  if name == key:
118
136
  return value
119
137
 
120
- @classmethod
121
- def create_table_query(cls, statement: BaseStatement) -> str:
122
- """It's classmethod because of it does not matter the columns values to create the table"""
123
- from ormlambda.sql.schema_generator import SchemaGeneratorFactory
124
-
125
- return SchemaGeneratorFactory.get_generator(statement._dialect).create_table(cls)
126
-
127
138
  @classmethod
128
139
  def create_table(cls, dialect: Dialect) -> str:
129
140
  return CreateTable(cls).compile(dialect).string
130
141
 
131
142
  @classmethod
132
- def find_dependent_tables(cls) -> tuple["Table", ...]:
133
- """Work in progress"""
134
- return
135
-
136
- # TODOL: Dive into new way to return dependent tables
137
- def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
138
- """
139
- Create a graph to be ordered
140
- """
141
- table = ForeignKey[Table, Table].MAPPED[table_name]
142
- for x in table.referenced_tables:
143
- if data := ForeignKey.MAPPED.get(x, None):
144
- get_involved_tables(graph, data.table_object.__table_name__)
145
-
146
- graph[table.table_object.__table_name__] = list(table.referenced_tables)
147
- return None
148
-
149
- graph: dict[Table, list[Table]] = {}
150
- dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
151
- if dependent is None:
152
- return tuple([])
153
-
154
- graph[cls.__table_name__] = list(dependent.referenced_tables)
155
- get_involved_tables(graph, cls.__table_name__)
156
-
157
- dfs = DFSTraversal.sort(graph)
158
-
159
- order_table = dfs[: dfs.index(cls.__table_name__)]
160
-
161
- return [ForeignKey.MAPPED[x].table_object for x in order_table]
143
+ def drop_table(cls, dialect: Dialect) -> str:
144
+ return DropTable(cls).compile(dialect).string
162
145
 
163
146
  def __eq__(self, __value: Any) -> bool:
164
147
  return hash(self) == hash(__value)
@@ -172,6 +155,9 @@ class Table(metaclass=TableMeta):
172
155
  return f"`{cls.__table_name__}_{column}`"
173
156
  return cls.__table_name__
174
157
 
158
+ @util.preload_module("ormlambda.sql")
175
159
  @classmethod
176
160
  def foreign_keys(cls) -> dict[str, ForeignKey]:
161
+ ForeignKey = util.preloaded.sql_foreign_key.ForeignKey
162
+
177
163
  return {key: value for key, value in cls.__dict__.items() if isinstance(value, ForeignKey)}
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Optional, Any, Type
3
+
4
+ from ormlambda.sql.column_table_proxy import ColumnTableProxy
5
+ from ormlambda.sql.elements import ClauseElement
6
+ from ormlambda.sql.context import FKChain
7
+
8
+ from ormlambda import util
9
+
10
+ if TYPE_CHECKING:
11
+ from ormlambda import Table
12
+ from ormlambda import ForeignKey
13
+
14
+
15
+ type CurrentPathType = Optional[FKChain]
16
+ type ForeignKeyRegistryType = dict[str, list[ForeignKey]]
17
+ type PathAliasesType = dict[str, str]
18
+ type QueryMetadataType = dict[str, Any]
19
+
20
+
21
+ class TableProxy[T: Table](ColumnTableProxy, ClauseElement):
22
+ __visit_name__ = "table_proxy"
23
+ _table_class: Type[Table]
24
+ _path: FKChain
25
+
26
+ def __init__(self, table_class: Type[T], path: Optional[FKChain] = None):
27
+ if not path:
28
+ path = FKChain(table_class, [])
29
+
30
+ self._table_class = table_class
31
+ super().__init__(path.copy())
32
+
33
+ def __repr__(self) -> str:
34
+ return f"{TableProxy.__name__}({self._table_class.__table_name__}) Path={self._path.get_path_key()})"
35
+
36
+ @util.preload_module(
37
+ "ormlambda.sql.foreign_key",
38
+ "ormlambda.sql.column",
39
+ )
40
+ def __getattr__(self, name: str):
41
+ """Intercept attribute access to handle foreign keys and columns"""
42
+
43
+ ColumnProxy = util.preloaded.sql_column.ColumnProxy
44
+ Column = util.preloaded.sql_column.Column
45
+ ForeignKey = util.preloaded.sql_foreign_key.ForeignKey
46
+
47
+ # Get the actual attribute from the table class
48
+ try:
49
+ attr = getattr(self._table_class, name)
50
+ except AttributeError:
51
+ # If column doesn't exist is because we're dealing with aliases like
52
+ # `lambda x: x.count` where 'count' is actually an alias not a column name
53
+ # we don't want use table name
54
+ attr = Column(dtype=str)
55
+ attr.column_name = name
56
+ return ColumnProxy(attr, path=FKChain(None, []))
57
+
58
+ if isinstance(attr, ForeignKey):
59
+ new_path = self._path.copy()
60
+ new_path.add_step(attr)
61
+ return TableProxy(attr.tright, new_path)
62
+
63
+ elif isinstance(attr, Column):
64
+ # Accessing a column - return column reference with path info
65
+
66
+ column = ColumnProxy(attr, self._path.copy())
67
+ self._path.clear()
68
+ return column
69
+
70
+ else:
71
+ return attr
72
+
73
+ def get_alias(self) -> str:
74
+ """Get the alias for this table based on its path"""
75
+ return self._path.get_alias()
76
+
77
+ def get_table_chain(self):
78
+ return self.get_alias()
79
+
80
+ @util.preload_module("ormlambda.sql.column")
81
+ def get_columns(self) -> tuple[ColumnTableProxy]:
82
+ ColumnProxy = util.preloaded.sql_column.ColumnProxy
83
+
84
+ result = []
85
+ for column in self._table_class.get_columns():
86
+ col_proxy = ColumnProxy(column, self._path)
87
+ result.append(col_proxy)
88
+ return result
ormlambda/sql/type_api.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
  from typing import Any, ClassVar
3
3
  from ormlambda.sql.visitors import Element
4
+ from ormlambda import util
4
5
  import abc
5
6
 
6
7
 
@@ -26,8 +27,10 @@ class TypeEngine[T: Any](Element, abc.ABC):
26
27
  def _resolve_for_literal_value(self, value: T) -> TypeEngine[T]:
27
28
  return self
28
29
 
30
+ @util.preload_module("ormlambda.sql.sqltypes")
29
31
  def coerce_compared_value[TType](self, value: TType) -> TypeEngine[TType]:
30
- from .sqltypes import resolve_primitive_types, NULLTYPE
32
+ resolve_primitive_types = util.preloaded.sql_types.resolve_primitive_types
33
+ NULLTYPE = util.preloaded.sql_types.NULLTYPE
31
34
 
32
35
  _coerced_type = resolve_primitive_types(value)
33
36
  if _coerced_type is NULLTYPE:
ormlambda/sql/types.py CHANGED
@@ -1,27 +1,30 @@
1
- import typing as tp
1
+ from typing import TYPE_CHECKING, Literal, Callable, Type
2
2
 
3
3
 
4
- if tp.TYPE_CHECKING:
5
- from ormlambda import Table, Column, ForeignKey
4
+ if TYPE_CHECKING:
5
+ from ormlambda.sql.clause_info import IAggregate
6
+ from ormlambda import Table
6
7
  from ormlambda.sql.comparer import Comparer
7
8
  from ormlambda import ConditionType as ConditionEnum
8
- from ormlambda.common.enums.join_type import JoinType
9
+ from ormlambda.common.enums.join_type import JoinType as JoinType
10
+ from ormlambda import ColumnProxy, TableProxy
9
11
 
10
12
 
11
- type AsteriskType = str
12
- type TableType[T: Table] = tp.Type[T] | ForeignKey[T]
13
- type ColumnType[TProp] = TProp | Column[TProp] | AsteriskType | tuple[Column]
14
- type AliasType[T] = tp.Optional[str | tp.Callable[[T], str]]
13
+ type TableType[T: Table] = Type[T] | TableProxy[T]
14
+ type ColumnType[TProp] = TProp | ColumnProxy[TProp]
15
+ type AliasType[TProp] = str | Callable[[ColumnProxy[TProp]], str]
15
16
 
16
17
  # region Comparer Types
17
- type ComparerType = tp.Literal["=", "!=", "<", "<=", ">", ">=", "in"]
18
+ type ComparerType = Literal["=", "!=", "<", "<=", ">", ">=", "in"]
18
19
  type ConditionType[TProp] = Comparer | ColumnType[TProp]
19
- type UnionType = tp.Literal["AND", "OR", ""]
20
+ type UnionType = Literal["AND", "OR", ""]
20
21
  type ComparerTypes = ComparerType | UnionType | ConditionEnum
22
+ type SelectCol = ColumnProxy | IAggregate | Comparer
21
23
  # endregion
22
24
 
23
25
  type TupleJoinType[T] = tuple[Comparer]
24
26
 
25
- ASTERISK: AsteriskType = "*"
27
+ ASTERISK = "*"
26
28
 
27
- from .compiler import *
29
+ # TODOL []: Look if we can avoid this *
30
+ from .compiler import * # noqa: F403, E402
@@ -1,3 +1 @@
1
- from .base_statement import BaseStatement # noqa: F401
2
- from .types import OrderType # noqa: F401
3
1
  from .statements import Statements # noqa: F401
@@ -1,123 +1,85 @@
1
1
  from __future__ import annotations
2
- from typing import Any, Type, override, Iterable, Literal, TYPE_CHECKING, Optional
2
+ from typing import Any, Type, Iterable, Literal, TYPE_CHECKING
3
3
  from collections import defaultdict
4
4
 
5
5
 
6
- from ormlambda.repository import BaseRepository
7
- from ormlambda.statements.interfaces import IStatements_two_generic
8
6
  from ormlambda import Table
9
7
 
10
8
  from ormlambda.common.errors import AggregateFunctionError
9
+ from ormlambda.sql.clause_info import IAggregate
10
+
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from ormlambda.engine.base import Engine
14
- from ormlambda.dialects.interface.dialect import Dialect
15
14
  from ormlambda.sql.clauses import Select
15
+ from ormlambda import ColumnProxy
16
16
 
17
17
 
18
18
  ORDER_QUERIES = Literal["select", "join", "where", "order", "with", "group by", "limit", "offset"]
19
19
 
20
20
 
21
- class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
22
- def __init__(self, model: tuple[T, ...], engine: Engine) -> None:
23
- self._engine = engine
24
- self._dialect = engine.dialect
25
- self._query: Optional[str] = None
26
- self._model: T = model[0] if isinstance(model, Iterable) else model
27
- self._models: tuple[T] = self._model if isinstance(model, Iterable) else (model,)
21
+ type ResponseType = Iterable[dict[str, Any]]
28
22
 
29
- repository = engine.repository
30
- self.__valid_repository(repository)
31
- self._repository: BaseRepository[TRepo] = repository
32
23
 
33
- if not issubclass(self._model, Table):
34
- # Deben heredar de Table ya que es la forma que tenemos para identificar si estamos pasando una instancia del tipo que corresponde o no cuando llamamos a insert o upsert.
35
- # Si no heredase de Table no sabriamos identificar el tipo de dato del que se trata porque al llamar a isinstance, obtendriamos el nombre de la clase que mapea a la tabla, Encargo, Edificio, Presupuesto y no podriamos crear una clase generica
36
- raise Exception(f"'{model}' class does not inherit from Table class")
24
+ class ClusterResponse[T, TFlavour]:
25
+ def __init__(self, select: Select[T], engine: Engine, flavour: TFlavour, query: str) -> None:
26
+ self._select: Select[T] = select
27
+ self.engine = engine
28
+ self.flavour = flavour
29
+ self.query = query
37
30
 
38
- @property
39
- def dialect(self) -> Dialect:
40
- return self._dialect
31
+ def cluster(self, response_sql: ResponseType) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
32
+ # We'll create a default list of dicts *once* we know how many rows are in _response_sql
41
33
 
42
- @override
43
- def table_exists(self) -> bool:
44
- return self._repository.table_exists(self._model.__table_name__)
34
+ tables: dict[Table, list[ColumnProxy]] = defaultdict(list)
35
+ for clause in self._select.columns:
36
+ if isinstance(clause, IAggregate):
37
+ raise AggregateFunctionError(clause)
45
38
 
46
- @staticmethod
47
- def __valid_repository(repository: Any) -> bool:
48
- if not isinstance(repository, BaseRepository):
49
- raise ValueError(f"'repository' attribute does not instance of '{BaseRepository.__name__}'")
50
- return True
39
+ tables[clause.table].append(clause)
51
40
 
52
- def __repr__(self):
53
- return f"<Model: {self.__class__.__name__}>"
41
+ res = []
42
+ for dicc_cols in response_sql:
43
+ converted_row = []
44
+ for table, columns in tables.items():
45
+ dicc = {}
46
+ for col in columns:
47
+ if not hasattr(col, "column_name"):
48
+ pass
49
+ dicc[col.column_name] = dicc_cols[col.alias]
50
+ converted_row.append(table(**dicc))
51
+ res.append(tuple(converted_row))
54
52
 
55
- def _return_flavour[TValue](self, query, flavour: Type[TValue], select, **kwargs) -> tuple[TValue]:
56
- return self._repository.read_sql(query, flavour=flavour, select=select, **kwargs)
53
+ tuple_response = tuple(res)
57
54
 
58
- def _return_model(self, select, query: str) -> tuple[tuple[T]]:
59
- response_sql = self._repository.read_sql(query, flavour=dict, select=select) # store all columns of the SQL query
60
- if response_sql and isinstance(response_sql, Iterable):
61
- return ClusterResponse(self._dialect, select, response_sql).cluster()
55
+ if not tuple_response:
56
+ return tuple_response
62
57
 
63
- return response_sql
58
+ if len(tuple_response) == 1:
59
+ return tuple_response[0]
64
60
 
65
- @property
66
- def query(self) -> str:
67
- return self._query
61
+ if len(tuple_response[0]) == 1:
62
+ return tuple([x[0] for x in tuple_response])
63
+ return tuple_response
68
64
 
69
- @property
70
- def model(self) -> Type[T]:
71
- return self._model
65
+ def cluster_data(self, **kwargs) -> TFlavour[T, ...]:
66
+ if not self.flavour:
67
+ return self._return_model()
72
68
 
73
- # TODOL: add *Ts when wil be possible
74
- @property
75
- def models(self) -> tuple:
76
- return self._models
69
+ return self._return_flavour(self.flavour, **kwargs)
77
70
 
78
- @property
79
- def repository(self) -> BaseRepository:
80
- return self._repository
71
+ def _return_flavour[TValue](self, flavour: Type[TValue], **kwargs) -> tuple[TValue]:
72
+ return self.engine.repository.read_sql(
73
+ query=self.query,
74
+ flavour=flavour,
75
+ select=self._select,
76
+ **kwargs,
77
+ )
81
78
 
79
+ def _return_model(self) -> tuple[tuple[T]]:
80
+ response_sql = self._return_flavour(flavour=dict)
82
81
 
83
- class ClusterResponse[T]:
84
- def __init__(self, dialect: Dialect, select: Select[T], response_sql: tuple[dict[str, Any]]) -> None:
85
- self._dialect: Dialect = dialect
86
- self._select: Select[T] = select
87
- self._response_sql: tuple[dict[str, Any]] = response_sql
88
- self._caster = dialect.caster
89
-
90
- def cluster(self) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
91
- tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self._create_cluster()
92
-
93
- response = {}
94
- tuple_response = []
95
- # it not depend of flavour attr
96
- for table, attribute_list in tbl_dicc.items():
97
- new_instance = []
98
- for attrs in attribute_list:
99
- new_instance.append(table(**attrs))
100
- response[table] = tuple(new_instance)
101
- tuple_response.append(tuple(new_instance))
102
- return tuple(tuple_response)
103
-
104
- def _create_cluster(self) -> dict[Type[Table], list[dict[str, Any]]]:
105
- # We'll create a default list of dicts *once* we know how many rows are in _response_sql
106
- row_count = len(self._response_sql)
107
-
108
- def make_list_of_dicts() -> list[dict[str, Any]]:
109
- return [{} for _ in range(row_count)]
110
-
111
- table_attr_dict = defaultdict(make_list_of_dicts)
112
-
113
- for i, dicc_cols in enumerate(self._response_sql):
114
- for clause in self._select.all_clauses:
115
- table = clause.table
116
- col = clause.column
117
-
118
- if col is None or not hasattr(table, col):
119
- raise AggregateFunctionError(clause)
82
+ if response_sql and isinstance(response_sql, Iterable):
83
+ return self.cluster(response_sql)
120
84
 
121
- table_attr_dict[table][i][col] = dicc_cols[clause.alias_clause]
122
- # Convert back to a normal dict if you like (defaultdict is a dict subclass).
123
- return dict(table_attr_dict)
85
+ return response_sql