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
@@ -0,0 +1,304 @@
1
+ from __future__ import annotations
2
+ import threading
3
+ from contextlib import contextmanager
4
+ from typing import TYPE_CHECKING, Generator, Literal, Optional, Any, TypedDict, Protocol
5
+
6
+
7
+ if TYPE_CHECKING:
8
+ from ormlambda import Table
9
+ from ormlambda.sql import ForeignKey
10
+ from ormlambda.sql.context import FKChain
11
+ from ormlambda.sql.clauses import JoinSelector
12
+
13
+ type CurrentPathType = FKChain
14
+ type ForeignKeyRegistryType = dict[str, ForeignKey]
15
+ type QueryMetadataType = dict[str, Any]
16
+ type TableAliasType = dict[str, str] # table_name -> alias
17
+ type ClauseAliasType = dict[str, str] # clause_key -> alias
18
+ type JoinSelectorType = dict[str, str] # clause_key -> alias
19
+
20
+ # Separate persistent and transient data types
21
+ type PersistentDataKeys = Literal["foreign_key_registry"]
22
+ type ContextDictKeys = Literal[
23
+ "current_path",
24
+ "foreign_key_registry",
25
+ "query_metadata",
26
+ "table_aliases",
27
+ "join_selector",
28
+ ]
29
+
30
+
31
+ class ContextDict(TypedDict):
32
+ current_path: CurrentPathType
33
+ foreign_key_registry: ForeignKeyRegistryType # PERSISTENT: Survives across queries
34
+ query_metadata: QueryMetadataType # TRANSIENT: Reset per query
35
+ table_aliases: TableAliasType # TRANSIENT: Reset per query
36
+ join_selector: JoinSelectorType # TRANSIENT: Reset per query
37
+
38
+
39
+ class LocalContext(Protocol):
40
+ context: ContextDict
41
+
42
+
43
+ class PathContext:
44
+ _local: LocalContext
45
+
46
+ def __init__(self):
47
+ self._local = threading.local()
48
+
49
+ def __repr__(self):
50
+ return f"{PathContext.__name__}"
51
+
52
+ @staticmethod
53
+ def _initialize_context() -> ContextDict:
54
+ return {
55
+ "current_path": NO_CURRENT_PATH,
56
+ "foreign_key_registry": {},
57
+ "query_metadata": {},
58
+ "table_aliases": {},
59
+ "join_selector": {},
60
+ }
61
+
62
+ def _get_context(self, key: Optional[ContextDictKeys] = None) -> ContextDict:
63
+ """
64
+ we Always need to return the context variable itself to has a references to the context variable.
65
+ An approach like return self._initialize_context() won't work as expected
66
+ """
67
+ if not self.has_context():
68
+ self._set_context(self._initialize_context())
69
+
70
+ return self._local.context if not key else self._local.context.get(key, None)
71
+
72
+ def _set_context(self, context: ContextDict) -> None:
73
+ self._local.context = context
74
+ return None
75
+
76
+ def get_current_path(self) -> Optional[FKChain]:
77
+ return self._get_context("current_path")
78
+
79
+ def set_current_path(self, path: FKChain) -> None:
80
+ context = self._get_context()
81
+ context["current_path"] = path
82
+ return None
83
+
84
+ def add_foreign_key_access(self, fk: ForeignKey, path: FKChain) -> None:
85
+ context = self._get_context()
86
+
87
+ # Store in registry
88
+ if fk not in context["foreign_key_registry"]:
89
+ context["foreign_key_registry"][f"{fk.tleft.__table_name__}.{fk.clause_name}"] = fk
90
+
91
+ return None
92
+
93
+ def get_all_foreign_key_accesses(self) -> ForeignKeyRegistryType:
94
+ return self._get_context("foreign_key_registry").copy()
95
+
96
+ def get_foreign_key_from_registry(self, key: str) -> Optional[ForeignKey]:
97
+ """Get a specific ForeignKey from the registry by key"""
98
+ return self._get_context("foreign_key_registry").get(key, None)
99
+
100
+ def remove_foreign_key_from_registry(self, key: str) -> bool:
101
+ """Remove a ForeignKey from the registry. Returns True if removed, False if not found"""
102
+ context = self._get_context()
103
+ if key in context["foreign_key_registry"]:
104
+ del context["foreign_key_registry"][key]
105
+ return True
106
+ return False
107
+
108
+ def clear_foreign_key_registry(self) -> None:
109
+ """Clear only the ForeignKey registry while preserving other context"""
110
+ context = self._get_context()
111
+ context["foreign_key_registry"].clear()
112
+ return None
113
+
114
+ def get_foreign_key_registry_size(self) -> int:
115
+ """Get the number of ForeignKeys in the registry"""
116
+ return len(self._get_context("foreign_key_registry"))
117
+
118
+ def clear_context(self):
119
+ """Clear all context data (legacy method - use clear_all_context for clarity)"""
120
+ self.clear_all_context()
121
+
122
+ def clear_all_context(self) -> None:
123
+ """Clear all context data including persistent ForeignKey registry"""
124
+ if self.has_context():
125
+ delattr(self._local, "context")
126
+
127
+ def clear_transient_context(self) -> None:
128
+ """Clear only transient context data, preserving ForeignKey registry"""
129
+ if not self.has_context():
130
+ return None
131
+
132
+ context = self._get_context()
133
+ # Preserve foreign_key_registry, clear everything else
134
+ preserved_registry = context["foreign_key_registry"].copy()
135
+
136
+ context["current_path"] = NO_CURRENT_PATH
137
+ context["query_metadata"].clear()
138
+ context["table_aliases"].clear()
139
+ context["join_selector"].clear()
140
+ # Restore preserved registry
141
+ context["foreign_key_registry"] = preserved_registry
142
+
143
+ return None
144
+
145
+ def reset_query_context(self) -> None:
146
+ """Reset context for new query while preserving persistent data"""
147
+ self.clear_transient_context()
148
+
149
+ def has_context(self) -> bool:
150
+ return hasattr(self._local, "context")
151
+
152
+ @contextmanager
153
+ def query_context(self, table: Optional[Table] = None) -> Generator[PathContext, None, None]:
154
+ """Context manager for query execution with proper cleanup"""
155
+ # Store the old context if it exists
156
+ old_context = None
157
+ if self.has_context():
158
+ old_context = self._get_context()
159
+
160
+ try:
161
+ self.initialize_context_with_table(table)
162
+ yield self
163
+
164
+ finally:
165
+ # Restore old context or use selective clearing to preserve FK registry
166
+ if old_context is not None:
167
+ self._set_context(old_context)
168
+ else:
169
+ # Use selective clearing to preserve foreign_key_registry
170
+ self.reset_query_context()
171
+
172
+ def initialize_context_with_table(self, table: Optional[Table] = None) -> None:
173
+ # Preserve foreign_key_registry if context already exists
174
+ preserved_registry = {}
175
+ if self.has_context():
176
+ preserved_registry = self._get_context("foreign_key_registry").copy()
177
+
178
+ self._set_context(self._initialize_context())
179
+
180
+ # Restore preserved registry
181
+ if preserved_registry:
182
+ context = self._get_context()
183
+ context["foreign_key_registry"] = preserved_registry
184
+
185
+ if not table:
186
+ return None
187
+
188
+ initial_path = FKChain(table, [])
189
+ self.set_current_path(initial_path)
190
+ return None
191
+
192
+ def reset_current_path(self, table):
193
+ initial_path = FKChain(table, [])
194
+ self.set_current_path(initial_path)
195
+ return None
196
+
197
+ # FIXME [x]: Alias Management Methods (replacing ClauseInfoContext functionality)
198
+ def add_table_alias(self, table: Table, alias: str) -> None:
199
+ """Add a table alias to the context"""
200
+ if not table or not alias:
201
+ return None
202
+
203
+ context = self._get_context()
204
+ table_key = table.__table_name__ if hasattr(table, "__table_name__") else str(table)
205
+ context["table_aliases"][table_key] = alias
206
+ return None
207
+
208
+ def get_table_alias(self, table: Table) -> Optional[str]:
209
+ """Get a table alias from the context"""
210
+ if not table:
211
+ return None
212
+
213
+ context = self._get_context()
214
+ table_key = table.__table_name__ if hasattr(table, "__table_name__") else str(table)
215
+ return context["table_aliases"].get(table_key, None)
216
+
217
+ def add_join(self, join: JoinSelector, alias: str) -> None:
218
+ """Add a table alias to the context"""
219
+ if not join or not alias:
220
+ return None
221
+
222
+ context = self._get_context()
223
+ context["join_selector"][alias] = join
224
+ return None
225
+
226
+ def get_join(self, alias: str) -> Optional[str]:
227
+ """Get a table alias from the context"""
228
+ if not alias:
229
+ return None
230
+
231
+ context = self._get_context("join_selector")
232
+ return context.get(alias, None)
233
+
234
+
235
+ class FKChain:
236
+ base: Optional[Table]
237
+ steps: list[ForeignKey]
238
+
239
+ def __init__(
240
+ self,
241
+ base: Optional[Table] = None,
242
+ steps: Optional[list[Table]] = None,
243
+ ):
244
+ self.base = base if base else None
245
+ self.steps = steps if steps else []
246
+
247
+ def __repr__(self):
248
+ return f"{FKChain.__name__}: {self.get_path_key()}"
249
+
250
+ @property
251
+ def parent(self) -> FKChain:
252
+ # FIXME [ ]: what if we reach the top parent? we need to return None in some point
253
+ if len(self.steps) <= 1:
254
+ steps = []
255
+ else:
256
+ steps = self.steps[:-1].copy()
257
+
258
+ return FKChain(self.base, steps)
259
+
260
+ def add_step(self, step: Table | ForeignKey):
261
+ """Add a step to the path"""
262
+ if not self.base:
263
+ self.base = step
264
+ return None
265
+
266
+ self.steps.append(step)
267
+ return None
268
+
269
+ def copy(self) -> FKChain:
270
+ return FKChain(base=self.base, steps=self.steps.copy())
271
+
272
+ def get_path_key(self) -> str:
273
+ return self._generate_chain(".")
274
+
275
+ def get_alias(self) -> str:
276
+ """Generate table alias from the path"""
277
+ if not self.base:
278
+ return ""
279
+ return self._generate_chain("_") if self.steps else self.base.__table_name__
280
+
281
+ def _generate_chain(self, char: str) -> str:
282
+ if not self.base:
283
+ return ""
284
+ result: list[Table] = [self.base.__table_name__]
285
+ for step in self.steps:
286
+ # For foreign keys, use their clause name or a descriptive name
287
+ data = getattr(step, "clause_name", f"fk_{step.__class__.__name__}")
288
+ result.append(data)
289
+ return char.join(result)
290
+
291
+ def get_depth(self) -> int:
292
+ """Get the depth of this path (number of foreign key steps)"""
293
+ return len(self.steps)
294
+
295
+ def clear(self) -> None:
296
+ self.steps.clear()
297
+
298
+ def __getitem__(self, number: int):
299
+ return FKChain(self.base, self.steps[number])
300
+
301
+
302
+ NO_CURRENT_PATH = FKChain(None, [])
303
+
304
+ PATH_CONTEXT = PathContext()
ormlambda/sql/ddl.py CHANGED
@@ -3,7 +3,8 @@ from typing import TYPE_CHECKING
3
3
  from .elements import ClauseElement
4
4
 
5
5
  if TYPE_CHECKING:
6
- from ormlambda.dialects.interface.dialect import Dialect
6
+ from ormlambda import URL
7
+ from ormlambda.dialects import Dialect
7
8
  from ormlambda import Column
8
9
  from ormlambda import Table
9
10
 
@@ -22,16 +23,22 @@ class BaseDDLElement(ClauseElement):
22
23
  return dialect.ddl_compiler(dialect, self, **kw)
23
24
 
24
25
 
25
- class CreateTable(BaseDDLElement):
26
+ class CreateDropTable:
27
+ def __init__(self, element: Table):
28
+ self.element = element
29
+ self.columns = [CreateColumn(c) for c in element.get_columns()]
30
+
31
+
32
+ class CreateTable(CreateDropTable, BaseDDLElement):
26
33
  """
27
34
  Class representing a CREATE TABLE statement.
28
35
  """
29
36
 
30
37
  __visit_name__ = "create_table"
31
38
 
32
- def __init__(self, element: Table):
33
- self.element = element
34
- self.columns = [CreateColumn(c) for c in element.get_columns()]
39
+
40
+ class DropTable(CreateDropTable, BaseDDLElement):
41
+ __visit_name__ = "drop_table"
35
42
 
36
43
 
37
44
  class CreateColumn[T](BaseDDLElement):
@@ -66,3 +73,10 @@ class SchemaExists(BaseDDLElement):
66
73
 
67
74
  def __init__(self, schema: str):
68
75
  self.schema = schema
76
+
77
+
78
+ class CreateBackup(BaseDDLElement):
79
+ __visit_name__ = "create_backup"
80
+
81
+ def __init__(self, url: URL):
82
+ self.url = url
ormlambda/sql/elements.py CHANGED
@@ -34,3 +34,6 @@ class ClauseElement(CompilerElement):
34
34
  """
35
35
 
36
36
  __visit_name__ = "clause_element"
37
+
38
+ def __repr__(self):
39
+ return f"{ClauseElement.__name__}: {type(self).__name__}"
@@ -1,42 +1,28 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, TYPE_CHECKING, Optional, Any, Type, cast, overload, ClassVar
2
+ import logging
3
+ from typing import Callable, TYPE_CHECKING, Optional, Any, Type, overload
3
4
 
4
- from ormlambda.common.interfaces.IQueryCommand import IQuery
5
- from ormlambda.sql.elements import Element
5
+ from ormlambda.sql.ddl import BaseDDLElement
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from ormlambda.sql.comparer import Comparer
9
9
  from ormlambda import Table
10
- from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
11
10
  from ormlambda.dialects import Dialect
12
11
 
12
+ from ormlambda.util import preloaded as _preloaded
13
13
 
14
- class ForeignKeyContext(set["ForeignKey"]):
15
- def clear(self):
16
- to_remove = {x for x in self if not cast(ForeignKey, x)._keep_alive}
17
- for el in to_remove:
18
- self.remove(el)
14
+ log = logging.getLogger(__name__)
19
15
 
20
- def remove(self, element):
21
- return super().remove(element)
22
16
 
23
- def pop(self, item):
24
- for el in self:
25
- if el != item:
26
- continue
27
-
28
- if not cast(ForeignKey, el)._keep_alive:
29
- super().remove(el)
30
- return el
31
-
32
- def add(self, element):
33
- return super().add(element)
34
-
35
-
36
- class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
17
+ class ForeignKey[TLeft: Table, TRight: Table](BaseDDLElement):
37
18
  __visit_name__ = "foreign_key"
38
19
 
39
- stored_calls: ClassVar[ForeignKeyContext] = ForeignKeyContext()
20
+ __slots__ = (
21
+ "_tright",
22
+ "_relationship",
23
+ "_comparer",
24
+ "_clause_name",
25
+ )
40
26
 
41
27
  @overload
42
28
  def __new__(self, comparer: Comparer, clause_name: str) -> None: ...
@@ -59,11 +45,9 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
59
45
  *,
60
46
  comparer: Optional[Comparer] = None,
61
47
  clause_name: Optional[str] = None,
62
- keep_alive: bool = False,
63
48
  **kwargs: Any,
64
49
  ) -> None:
65
50
  self.kwargs = kwargs
66
- self._keep_alive = keep_alive
67
51
  if comparer is not None and clause_name is not None:
68
52
  self.__init__with_comparer(comparer, clause_name, **kwargs)
69
53
  else:
@@ -71,8 +55,8 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
71
55
 
72
56
  def __init__with_comparer(self, comparer: Comparer, clause_name: str, **kwargs) -> None:
73
57
  self._relationship = None
74
- self._tleft: TLeft = comparer.left_condition(**kwargs).table
75
- self._tright: TRight = comparer.right_condition(**kwargs).table
58
+ self._tleft: TLeft = comparer.left_condition.table
59
+ self._tright: TRight = comparer.right_condition.table
76
60
  self._clause_name: str = clause_name
77
61
  self._comparer: Comparer = comparer
78
62
 
@@ -88,10 +72,9 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
88
72
  self._clause_name: str = name
89
73
 
90
74
  def __get__(self, obj: Optional[TRight], objtype=None) -> ForeignKey[TLeft, TRight] | TRight:
91
- if not obj:
92
- ForeignKey.stored_calls.add(self)
93
- return self
94
- return self._tright
75
+ if obj:
76
+ return self.tright
77
+ return self
95
78
 
96
79
  def __set__(self, obj, value):
97
80
  raise AttributeError(f"The {ForeignKey.__name__} '{self._clause_name}' in the '{self._tleft.__table_name__}' table cannot be overwritten.")
@@ -102,50 +85,45 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
102
85
  return getattr(self._tright, name)
103
86
 
104
87
  def __repr__(self) -> str:
105
- return f"{self.__class__.__name__}(" f"left={self._tleft.__name__ if self._tleft else 'None'}, " f"right={self._tright.__name__ if self._tright else 'None'}, " f"name={self._clause_name})"
88
+ return f"{self.__class__.__name__}(left={self._tleft.__name__ if self._tleft else 'None'}, right={self._tright.__name__ if self._tright else 'None'}, name={self._clause_name})"
106
89
 
107
90
  @property
108
- def tleft(self) -> TLeft:
91
+ def tleft(self) -> Table:
109
92
  return self._tleft
110
93
 
111
94
  @property
112
- def tright(self) -> TRight:
95
+ def tright(self) -> Table:
113
96
  return self._tright
114
97
 
115
98
  @property
116
99
  def clause_name(self) -> str:
117
100
  return self._clause_name
118
101
 
119
- def query(self, dialect: Dialect, **kwargs) -> str:
120
- compare = self.resolved_function(dialect)
121
- left_col = compare.left_condition(dialect).column
122
- rcon = alias if (alias := compare.right_condition(dialect).alias_table) else compare.right_condition(dialect).table.__table_name__
123
- return f"FOREIGN KEY ({left_col}) REFERENCES {rcon}({compare.right_condition(dialect).column})"
124
-
125
102
  def get_alias(self, dialect: Dialect) -> str:
126
- self._comparer = self.resolved_function(dialect)
127
- self._comparer._dialect = dialect
128
- lcol = self._comparer.left_condition(dialect)._column.column_name
129
- rcol = self._comparer.right_condition(dialect)._column.column_name
130
- return f"{self.tleft.__table_name__}_{lcol}_{rcol}"
131
-
132
- @classmethod
133
- def create_query(cls, orig_table: Table, dialect: Dialect) -> list[str]:
134
- clauses: list[str] = []
135
-
136
- for attr in orig_table.__dict__.values():
137
- if isinstance(attr, ForeignKey):
138
- clauses.append(attr.query(dialect))
139
- return clauses
140
-
141
- def resolved_function[LProp: Any, RProp: Any](self, dialect: Dialect, context: Optional[ClauseContextType] = None) -> Comparer:
142
- """ """
103
+ self._comparer = self.resolved_function()
104
+ # TODOH []: look into why i dropped 'lcol' variable
105
+ return f"{self.tleft.__table_name__}_{self.clause_name}"
106
+
107
+ @_preloaded.preload_module("ormlambda.sql.table")
108
+ def resolved_function(self) -> Comparer:
109
+ util = _preloaded.sql_table
110
+
143
111
  if self._comparer is not None:
144
112
  return self._comparer
145
113
 
146
- left = self._tleft
147
- right = self._tright
114
+ left = util.TableProxy(self._tleft)
115
+ right = util.TableProxy(self._tright)
148
116
  comparer = self._relationship(left, right)
149
- comparer.set_context(context)
150
- comparer._dialect = dialect
151
117
  return comparer
118
+
119
+ def __hash__(self):
120
+ return hash(
121
+ (
122
+ self._tleft,
123
+ self._tright,
124
+ self._clause_name,
125
+ )
126
+ )
127
+
128
+ def __eq__(self, other: ForeignKey):
129
+ return hash(other) == hash(self)
@@ -1,5 +1,4 @@
1
1
  from .max import Max as Max
2
2
  from .min import Min as Min
3
3
  from .concat import Concat as Concat
4
- from ..clauses.group_by import GroupBy as GroupBy
5
4
  from .sum import Sum as Sum
@@ -1,48 +1,45 @@
1
1
  from __future__ import annotations
2
- import typing as tp
2
+ from typing import Any, Iterable
3
3
 
4
- from ormlambda.sql.clause_info import AggregateFunctionBase
5
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
6
- from ormlambda.sql.types import ColumnType, AliasType
7
- from ormlambda.sql.clause_info import ClauseInfo
8
-
9
-
10
- type ConcatResponse[TProp] = tuple[str | ColumnType[TProp]]
11
4
 
5
+ from ormlambda.sql.clause_info import IAggregate
6
+ from ormlambda.sql.elements import ClauseElement
7
+ from ormlambda.sql.types import ColumnType, AliasType
8
+ from ormlambda import ColumnProxy, TableProxy
9
+ from ormlambda.common import GlobalChecker
12
10
 
13
- if tp.TYPE_CHECKING:
14
- from ormlambda.dialects import Dialect
11
+ VALID_CONCAT_TYPES = ColumnProxy | str | TableProxy
15
12
 
16
13
 
17
- class Concat[T](AggregateFunctionBase[T]):
18
- @staticmethod
19
- def FUNCTION_NAME() -> str:
20
- return "CONCAT"
14
+ class Concat[T](ClauseElement, IAggregate):
15
+ __visit_name__ = "concat"
21
16
 
22
17
  def __init__[TProp](
23
18
  self,
24
- values: ConcatResponse[TProp],
25
- alias_clause: AliasType[ColumnType[TProp]] = "concat",
26
- context: ClauseContextType = None,
27
- *,
28
- dialect: Dialect,
19
+ values: tuple[str | ColumnType[TProp] | TableProxy[T], ...],
20
+ alias: AliasType[ColumnType[TProp]] = "concat",
29
21
  ) -> None:
30
- super().__init__(
31
- table=None,
32
- column=values,
33
- alias_clause=alias_clause,
34
- context=context,
35
- dtype=str,
36
- dialect=dialect,
37
- )
38
-
39
- @tp.override
40
- def query(self, dialect: Dialect, **kwargs) -> str:
41
- columns: list[str] = []
42
-
43
- context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
44
-
45
- for clause in self._convert_into_clauseInfo(self.unresolved_column, context=context, dialect=self._dialect):
46
- clause.alias_clause = self.alias_clause
47
- columns.append(clause)
48
- return self._concat_alias_and_column(f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns,dialect=self._dialect)})", self._alias_aggregate)
22
+ if isinstance(values, TableProxy):
23
+ values = GlobalChecker.parser_object(values, values._table_class)
24
+ if not self.is_valid(values):
25
+ raise ValueError(values)
26
+
27
+ self.values = values
28
+ self.alias = alias
29
+
30
+ def used_columns(self):
31
+ return [x for x in self.values if not isinstance(x, str)]
32
+
33
+ @property
34
+ def dtype(self) -> str:
35
+ return str
36
+
37
+ @staticmethod
38
+ def is_valid(values: Any) -> bool:
39
+ if not isinstance(values, Iterable):
40
+ return False
41
+ for val in values:
42
+ if not isinstance(val, VALID_CONCAT_TYPES):
43
+ return False
44
+
45
+ return True
@@ -1,48 +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 Max(AggregateFunctionBase[None]):
14
- @staticmethod
15
- def FUNCTION_NAME() -> str:
16
- return "MAX"
8
+ class Max(ClauseElement, IAggregate):
9
+ __visit_name__ = "max"
17
10
 
18
11
  def __init__[TProp](
19
12
  self,
20
13
  elements: ColumnType[TProp],
21
- alias_clause: AliasType[ColumnType[TProp]] = "max",
22
- context: ClauseContextType = None,
23
- *,
24
- dialect: Dialect,
14
+ alias: AliasType[ColumnType[TProp]] = "max",
25
15
  ):
26
- super().__init__(
27
- table=None,
28
- column=elements,
29
- alias_table=None,
30
- alias_clause=alias_clause,
31
- context=context,
32
- keep_asterisk=False,
33
- preserve_context=False,
34
- dialect=dialect,
35
- )
36
-
37
- @tp.override
38
- def query(self, dialect: Dialect, **kwargs) -> str:
39
- columns: list[str] = []
16
+ self.column = elements
17
+ self.alias = alias
40
18
 
41
- context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
42
- for clause in self._convert_into_clauseInfo(self.unresolved_column, context, dialect=self._dialect):
43
- new_clause = clause
44
- new_clause.alias_clause = None
45
- columns.append(new_clause)
19
+ def used_columns(self):
20
+ return [self.column]
46
21
 
47
- method_string = f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns,dialect=self._dialect)})"
48
- return self._concat_alias_and_column(method_string, self._alias_aggregate)
22
+ @property
23
+ def dtype(self) -> int:
24
+ return int