ormlambda 3.35.2__py3-none-any.whl → 4.0.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 (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 +12 -7
  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 +32 -49
  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 +51 -84
  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 +120 -110
  72. ormlambda/statements/types.py +5 -25
  73. ormlambda/util/__init__.py +7 -86
  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.2.dist-info → ormlambda-4.0.0.dist-info}/METADATA +29 -31
  79. ormlambda-4.0.0.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.2.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.2.dist-info → ormlambda-4.0.0.dist-info}/AUTHORS +0 -0
  129. {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.dist-info}/LICENSE +0 -0
  130. {ormlambda-3.35.2.dist-info → ormlambda-4.0.0.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,14 +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
5
+ from ormlambda.sql.ddl import CreateTable, DropTable
6
+ from ormlambda import util
9
7
 
10
8
  if TYPE_CHECKING:
11
- from ormlambda.statements import BaseStatement
9
+ from ormlambda.sql import Column
12
10
  from ormlambda.dialects import Dialect
11
+ from ormlambda import ForeignKey
13
12
 
14
13
  from .table_constructor import __init_constructor__
15
14
 
@@ -33,6 +32,7 @@ class TableMeta(type):
33
32
  raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
34
33
 
35
34
  self = __init_constructor__(cls_object)
35
+
36
36
  return self
37
37
 
38
38
  def __repr__(cls: "Table") -> str:
@@ -84,7 +84,7 @@ class Table(metaclass=TableMeta):
84
84
 
85
85
  dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
86
86
  equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
87
- return f'{self.__class__.__name__}({", ".join(equal_loop)})'
87
+ return f"{self.__class__.__name__}({', '.join(equal_loop)})"
88
88
 
89
89
  def __getitem__[TProp](self, value: str | Column[TProp]) -> Optional[TProp]:
90
90
  name = value if isinstance(value, str) else value.column_name
@@ -93,25 +93,40 @@ class Table(metaclass=TableMeta):
93
93
  return None
94
94
 
95
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
+
96
109
  dicc: dict[str, Any] = {}
97
110
  for x in self.__annotations__:
98
111
  value = getattr(self, x)
99
- if isinstance(value, dict):
100
- value = tuple(sorted(value.items()))
101
- if isinstance(value, list | set):
102
- value = tuple(value)
103
- dicc[x] = value
112
+ dicc[x] = make_hashable(value)
104
113
  return dicc
105
114
 
115
+ @util.preload_module("ormlambda.sql")
106
116
  @classmethod
107
117
  def get_pk(cls) -> Optional[Column]:
118
+ Column = util.preloaded.sql_column.Column
119
+ ColumnProxy = util.preloaded.sql_column.ColumnProxy
108
120
  for obj in cls.__dict__.values():
109
- if isinstance(obj, Column) and obj.is_primary_key:
121
+ if isinstance(obj, Column | ColumnProxy) and obj.is_primary_key:
110
122
  return obj
111
123
  return None
112
124
 
125
+ @util.preload_module("ormlambda.sql")
113
126
  @classmethod
114
127
  def get_columns(cls) -> tuple[Column, ...]:
128
+ Column = util.preloaded.sql_column.Column
129
+
115
130
  return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
116
131
 
117
132
  @classmethod
@@ -120,48 +135,13 @@ class Table(metaclass=TableMeta):
120
135
  if name == key:
121
136
  return value
122
137
 
123
- @classmethod
124
- def create_table_query(cls, statement: BaseStatement) -> str:
125
- """It's classmethod because of it does not matter the columns values to create the table"""
126
- from ormlambda.sql.schema_generator import SchemaGeneratorFactory
127
-
128
- return SchemaGeneratorFactory.get_generator(statement._dialect).create_table(cls)
129
-
130
138
  @classmethod
131
139
  def create_table(cls, dialect: Dialect) -> str:
132
140
  return CreateTable(cls).compile(dialect).string
133
141
 
134
142
  @classmethod
135
- def find_dependent_tables(cls) -> tuple["Table", ...]:
136
- """Work in progress"""
137
- return
138
-
139
- # TODOL: Dive into new way to return dependent tables
140
- def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
141
- """
142
- Create a graph to be ordered
143
- """
144
- table = ForeignKey[Table, Table].MAPPED[table_name]
145
- for x in table.referenced_tables:
146
- if data := ForeignKey.MAPPED.get(x, None):
147
- get_involved_tables(graph, data.table_object.__table_name__)
148
-
149
- graph[table.table_object.__table_name__] = list(table.referenced_tables)
150
- return None
151
-
152
- graph: dict[Table, list[Table]] = {}
153
- dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
154
- if dependent is None:
155
- return tuple([])
156
-
157
- graph[cls.__table_name__] = list(dependent.referenced_tables)
158
- get_involved_tables(graph, cls.__table_name__)
159
-
160
- dfs = DFSTraversal.sort(graph)
161
-
162
- order_table = dfs[: dfs.index(cls.__table_name__)]
163
-
164
- 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
165
145
 
166
146
  def __eq__(self, __value: Any) -> bool:
167
147
  return hash(self) == hash(__value)
@@ -175,6 +155,9 @@ class Table(metaclass=TableMeta):
175
155
  return f"`{cls.__table_name__}_{column}`"
176
156
  return cls.__table_name__
177
157
 
158
+ @util.preload_module("ormlambda.sql")
178
159
  @classmethod
179
160
  def foreign_keys(cls) -> dict[str, ForeignKey]:
161
+ ForeignKey = util.preloaded.sql_foreign_key.ForeignKey
162
+
180
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,90 @@
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
7
+ from ormlambda import util
9
8
 
10
9
  from ormlambda.common.errors import AggregateFunctionError
10
+ from ormlambda.sql.clause_info import IAggregate
11
+
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from ormlambda.engine.base import Engine
14
- from ormlambda.dialects.interface.dialect import Dialect
15
15
  from ormlambda.sql.clauses import Select
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,)
28
-
29
- repository = engine.repository
30
- self.__valid_repository(repository)
31
- self._repository: BaseRepository[TRepo] = repository
32
-
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")
37
-
38
- @property
39
- def dialect(self) -> Dialect:
40
- return self._dialect
41
-
42
- @override
43
- def table_exists(self) -> bool:
44
- return self._repository.table_exists(self._model.__table_name__)
45
-
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
51
-
52
- def __repr__(self):
53
- return f"<Model: {self.__class__.__name__}>"
54
-
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)
57
-
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()
62
-
63
- return response_sql
64
-
65
- @property
66
- def query(self) -> str:
67
- return self._query
68
-
69
- @property
70
- def model(self) -> Type[T]:
71
- return self._model
21
+ type ResponseType = Iterable[dict[str, Any]]
72
22
 
73
- # TODOL: add *Ts when wil be possible
74
- @property
75
- def models(self) -> tuple:
76
- return self._models
77
23
 
78
- @property
79
- def repository(self) -> BaseRepository:
80
- return self._repository
81
-
82
-
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
24
+ class ClusterResponse[T, TFlavour]:
25
+ def __init__(self, select: Select[T], engine: Engine, flavour: TFlavour, query: str) -> None:
86
26
  self._select: Select[T] = select
87
- self._response_sql: tuple[dict[str, Any]] = response_sql
88
- self._caster = dialect.caster
27
+ self.engine = engine
28
+ self.flavour = flavour
29
+ self.query = query
89
30
 
90
- def cluster(self) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
91
- tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self._create_cluster()
31
+ def cluster(self, response_sql: ResponseType) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
32
+ tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self._create_cluster(response_sql)
92
33
 
93
- response = {}
34
+ first_table = list(tbl_dicc)[0]
94
35
  tuple_response = []
95
36
  # it not depend of flavour attr
96
- for table, attribute_list in tbl_dicc.items():
37
+ n_attrs = len(tbl_dicc[first_table])
38
+ for i in range(n_attrs):
97
39
  new_instance = []
98
- for attrs in attribute_list:
40
+ for table in tbl_dicc:
41
+ attrs = tbl_dicc[table][i]
99
42
  new_instance.append(table(**attrs))
100
- response[table] = tuple(new_instance)
101
43
  tuple_response.append(tuple(new_instance))
102
44
  return tuple(tuple_response)
103
45
 
104
- def _create_cluster(self) -> dict[Type[Table], list[dict[str, Any]]]:
46
+ def _create_cluster(self, response_sql: ResponseType) -> dict[Type[Table], list[dict[str, Any]]]:
105
47
  # 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)
48
+ row_count = len(response_sql)
107
49
 
108
50
  def make_list_of_dicts() -> list[dict[str, Any]]:
109
51
  return [{} for _ in range(row_count)]
110
52
 
111
53
  table_attr_dict = defaultdict(make_list_of_dicts)
112
54
 
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):
55
+ for i, dicc_cols in enumerate(response_sql):
56
+ for clause in self._select.columns:
57
+ # if col is None or not hasattr(table, col):
58
+ if isinstance(clause, IAggregate):
119
59
  raise AggregateFunctionError(clause)
120
60
 
121
- table_attr_dict[table][i][col] = dicc_cols[clause.alias_clause]
61
+ table = clause.table
62
+
63
+ table_attr_dict[table][i][clause.column_name] = dicc_cols[clause.alias]
122
64
  # Convert back to a normal dict if you like (defaultdict is a dict subclass).
123
65
  return dict(table_attr_dict)
66
+
67
+ @util.preload_module("ormlambda.sql.column")
68
+ def response(self, **kwargs) -> TFlavour[T, ...]:
69
+ ColumnProxy = util.preloaded.sql_column.ColumnProxy
70
+
71
+ if not self.flavour:
72
+ return self._return_model()
73
+
74
+ result = self._return_flavour(self.flavour, self._select, **kwargs)
75
+ if issubclass(self.flavour, tuple) and len(self._select.used_columns()) == 1 and isinstance(self._select.used_columns()[0], ColumnProxy):
76
+ return tuple([x[0] for x in result])
77
+ return result
78
+
79
+ def _return_flavour[TValue](self, flavour: Type[TValue], select, **kwargs) -> tuple[TValue]:
80
+ return self.engine.repository.read_sql(self.query, flavour=flavour, select=select, **kwargs)
81
+
82
+ def _return_model(self) -> tuple[tuple[T]]:
83
+ response_sql = self.engine.repository.read_sql(self.query, flavour=dict, select=self._select) # store all columns of the SQL query
84
+ if response_sql and isinstance(response_sql, Iterable):
85
+ response = self.cluster(response_sql)
86
+ if response and len(response[0]) == 1:
87
+ return tuple([x[0] for x in response])
88
+ return response
89
+
90
+ return response_sql