ormlambda 2.8.0__py3-none-any.whl → 2.9.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.
ormlambda/__init__.py CHANGED
@@ -1,11 +1,20 @@
1
- # enums
2
- from .common.enums import ( # noqa: F401
3
- JoinType,
4
- ConditionType,
1
+ # region enums
2
+ from .common.enums import (
3
+ JoinType as JoinType,
4
+ ConditionType as ConditionType,
5
5
  )
6
+ from ormlambda.common.interfaces.IStatements import OrderType as OrderType
7
+ # endregion
6
8
 
7
- from .common.abstract_classes import AbstractSQLStatements # noqa: F401
8
- from .common.interfaces import IRepositoryBase # noqa: F401
9
- from .utils import Table, Column, ForeignKey # noqa: F401
10
- from .utils.lambda_disassembler import Disassembler, nameof # noqa: F401
11
- from .model_base import BaseModel # noqa: F401 # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
9
+ from .common.abstract_classes import AbstractSQLStatements as AbstractSQLStatements
10
+ from .common.interfaces import IRepositoryBase as IRepositoryBase
11
+ from .utils import (
12
+ Table as Table,
13
+ Column as Column,
14
+ ForeignKey as ForeignKey,
15
+ )
16
+ from .utils.lambda_disassembler import (
17
+ Disassembler as Disassembler,
18
+ nameof as nameof,
19
+ )
20
+ from .model_base import BaseModel as BaseModel # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
@@ -10,7 +10,6 @@ from ormlambda.common.interfaces.IAggregate import IAggregate
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
13
- from ormlambda.components.select import ISelect
14
13
  from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
15
14
 
16
15
 
@@ -46,11 +45,11 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
46
45
  @override
47
46
  def repository(self) -> IRepositoryBase[TRepo]: ...
48
47
 
49
- def _return_flavour[TValue](self, query, flavour: Type[TValue]) -> tuple[TValue]:
50
- return self._repository.read_sql(query, flavour=flavour)
48
+ def _return_flavour[TValue](self, query, flavour: Type[TValue], select) -> tuple[TValue]:
49
+ return self._repository.read_sql(query, flavour=flavour, model=self._model, select=select)
51
50
 
52
- def _return_model(self, select: ISelect, query: str):
53
- response_sql = self._repository.read_sql(query, flavour=dict) # store all columns of the SQL query
51
+ def _return_model(self, select, query: str):
52
+ response_sql = self._repository.read_sql(query, flavour=dict, model=self._model, select=select) # store all columns of the SQL query
54
53
 
55
54
  if isinstance(response_sql, Iterable):
56
55
  return ClusterQuery[T](select, response_sql).clean_response()
@@ -66,7 +65,16 @@ class ClusterQuery[T]:
66
65
  self._select: DecompositionQueryBase[T] = select
67
66
  self._response_sql: tuple[dict[str, Any]] = response_sql
68
67
 
69
- def loop_foo(self) -> dict[Type[Table], list[Table]]:
68
+ def clean_response(self) -> tuple[dict[Type[Table], tuple[Table]]]:
69
+ tbl_dicc: dict[Type[Table], list[Table]] = self.__loop_foo()
70
+
71
+ # it not depend of flavour attr
72
+ for key, val in tbl_dicc.items():
73
+ tbl_dicc[key] = tuple(val)
74
+
75
+ return tuple(tbl_dicc.values())
76
+
77
+ def __loop_foo(self) -> dict[Type[Table], list[Table]]:
70
78
  # We must ensure to get the valid attributes for each instance
71
79
  table_initialize = defaultdict(list)
72
80
 
@@ -75,7 +83,7 @@ class ClusterQuery[T]:
75
83
  valid_attr: dict[str, Any] = {}
76
84
  for clause in clauses:
77
85
  if not hasattr(table, clause.column):
78
- agg_methods = self.get_all_aggregate_method(clauses)
86
+ agg_methods = self.__get_all_aggregate_method(clauses)
79
87
  raise ValueError(f"You cannot use aggregation method like '{agg_methods}' to return model objects. Try specifying 'flavour' attribute as 'dict'.")
80
88
  valid_attr[clause.column] = dicc_cols[clause.alias]
81
89
 
@@ -83,7 +91,7 @@ class ClusterQuery[T]:
83
91
  table_initialize[table].append(table(**valid_attr))
84
92
  return table_initialize
85
93
 
86
- def get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
94
+ def __get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
87
95
  res: set[str] = set()
88
96
 
89
97
  for clause in clauses:
@@ -91,12 +99,3 @@ class ClusterQuery[T]:
91
99
  if isinstance(row, IAggregate):
92
100
  res.add(row.__class__.__name__)
93
101
  return ", ".join(res)
94
-
95
- def clean_response(self) -> tuple[dict[Type[Table], tuple[Table]]]:
96
- tbl_dicc: dict[Type[Table], list[Table]] = self.loop_foo()
97
-
98
- # it not depend of flavour attr
99
- for key, val in tbl_dicc.items():
100
- tbl_dicc[key] = tuple(val)
101
-
102
- return tuple(tbl_dicc.values())
@@ -44,6 +44,13 @@ class ClauseInfo[T: tp.Type[Table]]:
44
44
  def query(self) -> str:
45
45
  return self._query
46
46
 
47
+ @property
48
+ def dtype[TProp](self) -> tp.Optional[tp.Type[TProp]]:
49
+ try:
50
+ return self._table.get_column(self.column).dtype
51
+ except ValueError:
52
+ return None
53
+
47
54
  def _resolve_column(self, data: ClauseDataType) -> str:
48
55
  if isinstance(data, property):
49
56
  return self._table.__properties_mapped__[data]
@@ -73,6 +80,17 @@ class ClauseInfo[T: tp.Type[Table]]:
73
80
 
74
81
 
75
82
  class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
83
+ @tp.overload
84
+ def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]]) -> None: ...
85
+ @tp.overload
86
+ def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ...) -> None: ...
87
+ @tp.overload
88
+ def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ...) -> None: ...
89
+ @tp.overload
90
+ def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ...) -> None: ...
91
+ @tp.overload
92
+ def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ..., replace_asterisk_char: bool = ...) -> None: ...
93
+
76
94
  def __init__[*Ts](
77
95
  self,
78
96
  table: T,
@@ -98,6 +116,11 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
98
116
 
99
117
  self.__clauses_list_generetor(lambda_query)
100
118
 
119
+ def __getitem__(self, key: str) -> ClauseInfo:
120
+ for clause in self._all_clauses:
121
+ if clause.alias == key:
122
+ return clause
123
+
101
124
  def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
102
125
  DEFAULT_ALIAS: str = f"{clause_info._table.__table_name__}_{clause_info._column}"
103
126
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  from typing import Any, Callable, Iterable, Optional, Literal, Type, Union, overload, TYPE_CHECKING, TypeVar
3
3
  from enum import Enum
4
4
  from abc import abstractmethod, ABC
5
+ import enum
5
6
 
6
7
  from .IRepositoryBase import IRepositoryBase
7
8
  from ormlambda.common.enums import JoinType
@@ -10,7 +11,15 @@ if TYPE_CHECKING:
10
11
  from ormlambda import Table
11
12
  from .IAggregate import IAggregate
12
13
 
13
- OrderType = Literal["ASC", "DESC"]
14
+
15
+ class OrderType(enum.Enum):
16
+ ASC = "ASC"
17
+ DESC = "DESC"
18
+
19
+
20
+ OrderTypeString = Literal["ASC", "DESC"]
21
+
22
+ OrderTypes = OrderTypeString | OrderType | Iterable[OrderType]
14
23
 
15
24
  # TODOH: This var is duplicated from 'src\ormlambda\databases\my_sql\clauses\create_database.py'
16
25
  TypeExists = Literal["fail", "replace", "append"]
@@ -109,7 +118,7 @@ class IStatements[T: Table](ABC):
109
118
  @overload
110
119
  def delete(self, instance: list[T]) -> None: ...
111
120
  @abstractmethod
112
- def delete(self, instance: Optional[T | list[T]] = None) -> None: ...
121
+ def delete(self, instance: Optional[T | list[T]] = ...) -> None: ...
113
122
 
114
123
  # endregion
115
124
  # region join
@@ -155,9 +164,9 @@ class IStatements[T: Table](ABC):
155
164
  @overload
156
165
  def order[TValue](self, _lambda_col: Callable[[T], TValue]) -> IStatements[T]: ...
157
166
  @overload
158
- def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderType) -> IStatements[T]: ...
167
+ def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderTypes) -> IStatements[T]: ...
159
168
  @abstractmethod
160
- def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderType) -> IStatements[T]: ...
169
+ def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderTypes) -> IStatements[T]: ...
161
170
 
162
171
  # endregion
163
172
  # region concat
@@ -196,62 +205,60 @@ class IStatements[T: Table](ABC):
196
205
  @overload
197
206
  def select(self) -> tuple[T, ...]: ...
198
207
  @overload
199
- def select[T1](self, selector: Callable[[T], T1], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...]]: ...
208
+ def select[T1](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...]]: ...
200
209
  @overload
201
- def select[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...]]: ...
210
+ def select[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...]]: ...
202
211
  @overload
203
- def select[T1, T2](self, selector: Callable[[T], tuple[T1, T2]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...]]: ...
212
+ def select[T1, T2](self, selector: Callable[[T], tuple[T1, T2]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...]]: ...
204
213
  @overload
205
- def select[T1, T2, T3](self, selector: Callable[[T], tuple[T1, T2, T3]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...]]: ...
214
+ def select[T1, T2, T3](self, selector: Callable[[T], tuple[T1, T2, T3]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...]]: ...
206
215
  @overload
207
- def select[T1, T2, T3, T4](self, selector: Callable[[T], tuple[T1, T2, T3, T4]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...]]: ...
216
+ def select[T1, T2, T3, T4](self, selector: Callable[[T], tuple[T1, T2, T3, T4]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...]]: ...
208
217
  @overload
209
- def select[T1, T2, T3, T4, T5](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...]]: ...
218
+ def select[T1, T2, T3, T4, T5](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...]]: ...
210
219
  @overload
211
- def select[T1, T2, T3, T4, T5, T6](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...]]: ...
220
+ def select[T1, T2, T3, T4, T5, T6](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...]]: ...
212
221
  @overload
213
- def select[T1, T2, T3, T4, T5, T6, T7](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...]]: ...
222
+ def select[T1, T2, T3, T4, T5, T6, T7](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...]]: ...
214
223
  @overload
215
- def select[T1, T2, T3, T4, T5, T6, T7, T8](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...]]: ...
224
+ def select[T1, T2, T3, T4, T5, T6, T7, T8](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...]]: ...
216
225
  @overload
217
- def select[T1, T2, T3, T4, T5, T6, T7, T8, T9](
218
- self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]], *, by: Optional[Enum] = JoinType.INNER_JOIN
219
- ) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...], tuple[T9, ...]]: ...
226
+ def select[T1, T2, T3, T4, T5, T6, T7, T8, T9](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]], *, by: Optional[Enum] = ...) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...], tuple[T9, ...]]: ...
220
227
  @overload
221
228
  def select[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](
222
- self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], *, by: Optional[Enum] = JoinType.INNER_JOIN
229
+ self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], *, by: Optional[Enum] = ...
223
230
  ) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...], tuple[T9, ...], tuple[T10, ...]]: ...
224
231
  @overload
225
- def select[Ts](self, selector: Optional[Callable[[T], Ts]] = None, *, flavour: Type[tuple], by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[Ts, ...]: ...
232
+ def select[Ts](self, selector: Optional[Callable[[T], Ts]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ...) -> tuple[Ts, ...]: ...
226
233
  @overload
227
- def select[Ts](self, selector: Optional[Callable[[T], tuple[Ts]]] = None, *, flavour: Type[tuple], by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[Ts, ...]: ...
234
+ def select[Ts](self, selector: Optional[Callable[[T], tuple[Ts]]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ...) -> tuple[Ts, ...]: ...
228
235
  @overload
229
- def select[*Ts](self, selector: Optional[Callable[[T], tuple[*Ts]]] = None, *, flavour: Type[tuple], by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[tuple[*Ts]]: ...
236
+ def select[*Ts](self, selector: Optional[Callable[[T], tuple[*Ts]]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ...) -> tuple[tuple[*Ts]]: ...
230
237
  @overload
231
- def select[TFlavour](self, selector: Optional[Callable[[T], tuple]] = None, *, flavour: Type[TFlavour], by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[TFlavour]: ...
238
+ def select[TFlavour](self, selector: Optional[Callable[[T], tuple]] = ..., *, flavour: Type[TFlavour], by: Optional[Enum] = ...) -> tuple[TFlavour]: ...
232
239
  @abstractmethod
233
- def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Type[TFlavour] = None, by: JoinType = JoinType.INNER_JOIN): ...
240
+ def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = ..., *, flavour: Type[TFlavour] = ..., by: JoinType = ...): ...
234
241
 
235
242
  # endregion
236
243
  # region select_one
237
244
  @overload
238
245
  def select_one(self) -> T: ...
239
246
  @overload
240
- def select_one[TFlavour](self, *, by: Optional[Enum] = JoinType.INNER_JOIN, flavour: Type[TFlavour]) -> TFlavour: ...
247
+ def select_one[TFlavour](self, *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> TFlavour: ...
241
248
  @overload
242
- def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> T1: ...
249
+ def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
243
250
  @overload
244
- def select_one[*Ts](self, selector: Callable[[T], tuple[*Ts]], *, by: Optional[Enum] = JoinType.INNER_JOIN) -> tuple[*Ts]: ...
251
+ def select_one[*Ts](self, selector: Callable[[T], tuple[*Ts]], *, by: Optional[Enum] = ...) -> tuple[*Ts]: ...
245
252
  @overload
246
- def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = JoinType.INNER_JOIN, flavour: Type) -> T1: ...
253
+ def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type) -> T1: ...
247
254
  @overload
248
- def select_one[T1, TFlavour](self, selector: Callable[[T], T1], *, by: Optional[Enum] = JoinType.INNER_JOIN, flavour: Type[TFlavour]) -> T1: ...
255
+ def select_one[T1, TFlavour](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> T1: ...
249
256
  @overload
250
- def select_one[*Ts](self, selector: Callable[[T], tuple[*Ts]], *, by: Optional[Enum] = JoinType.INNER_JOIN, flavour: Type[tuple]) -> tuple[*Ts]: ...
257
+ def select_one[*Ts](self, selector: Callable[[T], tuple[*Ts]], *, by: Optional[Enum] = ..., flavour: Type[tuple]) -> tuple[*Ts]: ...
251
258
  @overload
252
- def select_one[TFlavour](self, selector: Callable[[T], tuple], *, by: Optional[Enum] = JoinType.INNER_JOIN, flavour: Type[TFlavour]) -> TFlavour: ...
259
+ def select_one[TFlavour](self, selector: Callable[[T], tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> TFlavour: ...
253
260
  @abstractmethod
254
- def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Type[TFlavour] = None, by: Optional[Enum] = JoinType.INNER_JOIN): ...
261
+ def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Type[TFlavour] = ..., by: Optional[Enum] = ...): ...
255
262
 
256
263
  # endregion
257
264
  # region group_by
@@ -1,4 +1,4 @@
1
- from typing import Any, override, Iterable
1
+ from typing import override, Iterable
2
2
  from mysql.connector import MySQLConnection
3
3
 
4
4
  from ormlambda import Table
@@ -24,13 +24,24 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
24
24
 
25
25
  @override
26
26
  def insert(self, instances: T | list[T]) -> None:
27
- new_dict_list: list[dict[str, Any]] = []
28
- self.__fill_dict_list(new_dict_list, instances)
29
- cols_tuple = new_dict_list[0].keys()
30
- join_cols = ", ".join(cols_tuple)
31
- unknown_rows = f'({", ".join(["%s"]*len(cols_tuple))})' # The number of "%s" must match the dict 'dicc_0' length
27
+ valid_cols: list[list[Column]] = []
28
+ self.__fill_dict_list(valid_cols, instances)
32
29
 
33
- self._values = [tuple(x.values()) for x in new_dict_list]
30
+ col_names: list[str] = []
31
+ wildcards: list[str] = []
32
+ col_values: list[list[str]] = []
33
+ for i, cols in enumerate(valid_cols):
34
+ col_values.append([])
35
+ for col in cols:
36
+ if i == 0:
37
+ col_names.append(col.column_name)
38
+ wildcards.append(col.placeholder)
39
+ col_values[-1].append(col.column_value_to_query)
40
+
41
+ join_cols = ", ".join(col_names)
42
+ unknown_rows = f'({", ".join(wildcards)})' # The number of "%s" must match the dict 'dicc_0' length
43
+
44
+ self._values = [tuple(x) for x in col_values]
34
45
  self._query = f"{self.CLAUSE} {self._model.__table_name__} {f'({join_cols})'} VALUES {unknown_rows}"
35
46
  return None
36
47
 
@@ -55,17 +66,18 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
55
66
  return False
56
67
  return True
57
68
 
58
- def __fill_dict_list(self, list_dict: list[dict], values: T | list[T]):
69
+ def __fill_dict_list[TProp](self, list_dict: list[str, TProp], values: T | list[T]) -> list[Column]:
59
70
  if issubclass(values.__class__, Table):
60
- dicc: dict = {}
71
+ new_list = []
61
72
  for col in values.__dict__.values():
62
73
  if isinstance(col, Column) and self.__is_valid(col):
63
- dicc.update({col.column_name: col.column_value})
64
- list_dict.append(dicc)
65
- return list_dict
74
+ new_list.append(col)
75
+
76
+ list_dict.append(new_list)
66
77
 
67
78
  elif isinstance(values, Iterable):
68
79
  for x in values:
69
80
  self.__fill_dict_list(list_dict, x)
70
81
  else:
71
82
  raise Exception(f"Tipo de dato'{type(values)}' no esperado")
83
+ return None
@@ -1,33 +1,47 @@
1
1
  from __future__ import annotations
2
- from typing import override, Callable, TYPE_CHECKING
2
+ from typing import override, Callable, TYPE_CHECKING, Any, Iterable
3
+
4
+ from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
5
+
3
6
 
4
7
  if TYPE_CHECKING:
5
8
  from ormlambda import Table
6
9
 
7
- from ormlambda.utils.lambda_disassembler.tree_instruction import TreeInstruction
8
- from ormlambda.common.interfaces.IQueryCommand import IQuery
10
+ from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
9
11
  from ormlambda.common.interfaces.IStatements import OrderType
10
12
 
11
- ASC = "ASC"
12
- DESC = "DESC"
13
13
 
14
-
15
- class OrderQuery[T:Table](IQuery):
14
+ class OrderQuery[T: Table](DecompositionQueryBase[T]):
16
15
  ORDER = "ORDER BY"
17
16
 
18
- def __init__(self, instance: T, order_lambda: Callable[[T], None], order_type: OrderType) -> None:
19
- if not self._valid_order_type(order_type):
20
- raise Exception("order_type only can be 'ASC' or 'DESC'")
17
+ def __init__[*Ts](self, instance: T, lambda_query: Callable[[Any], tuple[*Ts]], order_type: Iterable[OrderType]) -> None:
18
+ super().__init__(instance, lambda_query)
19
+
20
+ if isinstance(order_type, str) or not isinstance(order_type, Iterable):
21
+ order_type = (order_type,)
21
22
 
22
- self._instance: T = instance
23
- self._order_lambda: Callable[[T], None] = order_lambda
24
- self._order_type: str = order_type
25
- self._column: str = TreeInstruction(order_lambda).to_list()[0].nested_element.name
23
+ self._order_type: list[OrderType] = [self.__cast_to_OrderType(x) for x in order_type]
26
24
 
27
- def _valid_order_type(self, _value: str) -> bool:
28
- return _value in (ASC, DESC)
25
+ def __cast_to_OrderType(self, _value: Any) -> Iterable[OrderType]:
26
+ if isinstance(_value, OrderType):
27
+ return _value
28
+
29
+ if isinstance(_value, str):
30
+ try:
31
+ return OrderType(_value)
32
+ except Exception:
33
+ pass
34
+ raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
35
+
36
+ def alias_children_resolver[Tclause](self, clause_info: ClauseInfo[Tclause]):
37
+ return None
29
38
 
30
39
  @override
31
40
  @property
32
41
  def query(self) -> str:
33
- return f"{self.ORDER} {self._instance.__table_name__}.{self._column} {self._order_type}"
42
+ assert len(self.all_clauses) == len(self._order_type)
43
+
44
+ query: list[str] = []
45
+ for index, x in enumerate(self.all_clauses):
46
+ query.append(f"{x.query} {self._order_type[index].value}")
47
+ return f"{self.ORDER} {", ".join(query)}"
@@ -3,6 +3,7 @@ from typing import override, Type, Callable, TYPE_CHECKING
3
3
  from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
4
4
  from ormlambda.common.enums.join_type import JoinType
5
5
  from ormlambda.common.interfaces.IAggregate import IAggregate
6
+ import shapely as shp
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from ormlambda import Table
@@ -32,17 +33,20 @@ class Select[T: Type[Table]](DecompositionQueryBase[T]):
32
33
  # def alias_children_resolver[Tclause: Type[Table]](self, clause_info: ClauseInfo[Tclause]):
33
34
  # return f"{clause.table.__table_name__}_{name}"
34
35
 
36
+ # TODOL: see who to deal when we will have to add more mysql methods
35
37
  @override
36
38
  @property
37
39
  def query(self) -> str:
38
- cols:list[str] = []
40
+ cols: list[str] = []
39
41
  for x in self.all_clauses:
40
- cols.append(x.query)
42
+ if x.dtype is shp.Point:
43
+ cols.append(x.concat_with_alias(f"ST_AsText({self._table.__table_name__}.{x.column})"))
44
+ else:
45
+ cols.append(x.query)
41
46
 
42
- if isinstance(x._row_column,IAggregate) and x._row_column.has_foreign_keys:
47
+ if isinstance(x._row_column, IAggregate) and x._row_column.has_foreign_keys:
43
48
  self._fk_relationship.update(x._row_column.fk_relationship)
44
49
 
45
-
46
50
  col: str = ", ".join(cols)
47
51
  query: str = f"{self.CLAUSE} {col} FROM {self._table.__table_name__}"
48
52
  alias = ""
@@ -27,26 +27,20 @@ class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase[MySQLConnec
27
27
  if not isinstance(dicc, dict):
28
28
  raise TypeError
29
29
 
30
- name_cols: list[str] = []
30
+ name_cols: list[Column] = []
31
31
 
32
32
  for col, value in dicc.items():
33
- if isinstance(col, str):
34
- string_col = col
35
- else:
36
- string_col = self._model.__properties_mapped__.get(col, None)
37
- if not string_col:
38
- raise KeyError(f"Class '{self._model.__name__}' has not {col} mapped.")
39
- if self.__is_valid__(string_col, value):
40
- name_cols.append(string_col)
41
- self._values.append(value)
42
-
43
- set_query: str = ",".join(["=".join([col, "%s"]) for col in name_cols])
33
+ col: Column = self._model.get_column(col, value)
34
+
35
+ if self.__is_valid__(col):
36
+ name_cols.append(col)
37
+ self._values.append(col.column_value_to_query)
38
+
39
+ set_query: str = ",".join(["=".join([col.column_name, col.placeholder]) for col in name_cols])
44
40
 
45
41
  self._query = f"{self.CLAUSE} {self._model.__table_name__} SET {set_query}"
42
+ self._values = tuple(self._values)
46
43
  return None
47
44
 
48
- def __is_valid__(self, col: str, value: Any) -> bool:
49
- instance_table: Table = self._model(**{col: value})
50
-
51
- column: Column = getattr(instance_table, f"_{col}")
52
- return not column.is_auto_generated
45
+ def __is_valid__(self, col: Column) -> bool:
46
+ return not col.is_auto_generated
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
  from pathlib import Path
3
- from typing import Any, Optional, Type, override, Callable
3
+ from typing import Any, Optional, Type, override, Callable, TYPE_CHECKING
4
4
  import functools
5
+ import shapely as shp
5
6
 
6
7
  # from mysql.connector.pooling import MySQLConnectionPool
7
8
  from mysql.connector import MySQLConnection, Error # noqa: F401
@@ -16,12 +17,20 @@ from .clauses import DropDatabase
16
17
  from .clauses import DropTable
17
18
 
18
19
 
20
+ if TYPE_CHECKING:
21
+ from src.ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
22
+ from ormlambda import Table
23
+ from src.ormlambda.databases.my_sql.clauses.select import Select
24
+
25
+
19
26
  class Response[TFlavour, *Ts]:
20
- def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], **kwargs) -> None:
27
+ def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], model: Optional[Table] = None, select: Optional[Select] = None, **kwargs) -> None:
21
28
  self._response_values: list[tuple[*Ts]] = response_values
22
29
  self._columns: tuple[str] = columns
23
30
  self._flavour: Type[TFlavour] = flavour
24
31
  self._kwargs: dict[str, Any] = kwargs
32
+ self._model: Table = model
33
+ self._select: Select = select
25
34
 
26
35
  self._response_values_index: int = len(self._response_values)
27
36
  # self.select_values()
@@ -42,8 +51,10 @@ class Response[TFlavour, *Ts]:
42
51
  def response(self) -> tuple[dict[str, tuple[*Ts]]] | tuple[tuple[*Ts]] | tuple[TFlavour]:
43
52
  if not self.is_there_response:
44
53
  return tuple([])
45
-
46
- return tuple(self._cast_to_flavour(self._response_values))
54
+ clean_response = self._response_values
55
+ if self._select is not None:
56
+ clean_response = self._parser_response()
57
+ return tuple(self._cast_to_flavour(clean_response))
47
58
 
48
59
  def _cast_to_flavour(self, data: list[tuple[*Ts]]) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
49
60
  def _dict() -> list[dict[str, tuple[*Ts]]]:
@@ -73,6 +84,38 @@ class Response[TFlavour, *Ts]:
73
84
 
74
85
  return selector.get(self._flavour, _default)()
75
86
 
87
+ def _parser_response(self) -> TFlavour:
88
+ new_response: list[list] = []
89
+ for row in self._response_values:
90
+ new_row: list = []
91
+ for i, data in enumerate(row):
92
+ alias = self._columns[i]
93
+ clause_info = self._select[alias]
94
+ if not self._is_parser_required(clause_info):
95
+ new_row = row
96
+ break
97
+ else:
98
+ parser_data = self.parser_data(clause_info, data)
99
+ new_row.append(parser_data)
100
+ if not isinstance(new_row, tuple):
101
+ new_row = tuple(new_row)
102
+
103
+ new_response.append(new_row)
104
+ return new_response
105
+
106
+ @staticmethod
107
+ def _is_parser_required[T: Table](clause_info: ClauseInfo[T]) -> bool:
108
+ if clause_info is None:
109
+ return False
110
+
111
+ return clause_info.dtype is shp.Point
112
+
113
+ @staticmethod
114
+ def parser_data[T: Table, TProp](clause_info: ClauseInfo[T], data: TProp):
115
+ if clause_info.dtype is shp.Point:
116
+ return shp.from_wkt(data)
117
+ return data
118
+
76
119
 
77
120
  class MySQLRepository(IRepositoryBase[MySQLConnection]):
78
121
  def get_connection(func: Callable[..., Any]):
@@ -107,11 +150,19 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
107
150
  - flavour: Type[TFlavour]: Useful to return tuple of any Iterable type as dict,set,list...
108
151
  """
109
152
 
153
+ def get_and_drop_key(key: str) -> Optional[Any]:
154
+ if key in kwargs:
155
+ return kwargs.pop(key)
156
+ return None
157
+
158
+ model: Table = get_and_drop_key("model")
159
+ select: Select = get_and_drop_key("select")
160
+
110
161
  with cnx.cursor(buffered=True) as cursor:
111
162
  cursor.execute(query)
112
163
  values: list[tuple] = cursor.fetchall()
113
164
  columns: tuple[str] = cursor.column_names
114
- return Response[TFlavour](response_values=values, columns=columns, flavour=flavour, **kwargs).response
165
+ return Response[TFlavour](model=model, response_values=values, columns=columns, flavour=flavour, select=select, **kwargs).response
115
166
 
116
167
  # FIXME [ ]: this method does not comply with the implemented interface
117
168
  @get_connection
@@ -192,7 +243,6 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
192
243
  def create_database(self, name: str, if_exists: TypeExists = "fail") -> None:
193
244
  return CreateDatabase(self).execute(name, if_exists)
194
245
 
195
-
196
246
  @property
197
247
  def database(self) -> Optional[str]:
198
248
  return self._data_config.get("database", None)
@@ -2,12 +2,12 @@ from __future__ import annotations
2
2
  from typing import Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
3
3
  import inspect
4
4
  from mysql.connector import MySQLConnection, errors, errorcode
5
-
5
+ import functools
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from ormlambda import Table
9
9
  from ormlambda.components.where.abstract_where import AbstractWhere
10
- from ormlambda.common.interfaces.IStatements import OrderType
10
+ from ormlambda.common.interfaces.IStatements import OrderTypes
11
11
  from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
12
12
  from ormlambda.common.interfaces.IRepositoryBase import TypeExists
13
13
  from ormlambda.common.interfaces import IAggregate
@@ -34,6 +34,18 @@ from ormlambda.common.enums import JoinType
34
34
  from . import functions as func
35
35
 
36
36
 
37
+ # COMMENT: It's so important to prevent information generated by other tests from being retained in the class.
38
+ def clear_list(f: Callable[..., Any]):
39
+ @functools.wraps(f)
40
+ def wrapper(self: MySQLStatements, *args, **kwargs):
41
+ try:
42
+ return f(self, *args, **kwargs)
43
+ finally:
44
+ self._query_list.clear()
45
+
46
+ return wrapper
47
+
48
+
37
49
  class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
38
50
  def __init__(self, model: T, repository: IRepositoryBase[MySQLConnection]) -> None:
39
51
  super().__init__(model, repository=repository)
@@ -71,11 +83,11 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
71
83
  return self._repository.table_exists(self._model.__table_name__)
72
84
 
73
85
  @override
86
+ @clear_list
74
87
  def insert(self, instances: T | list[T]) -> None:
75
88
  insert = InsertQuery(self._model, self._repository)
76
89
  insert.insert(instances)
77
90
  insert.execute()
78
- self._query_list.clear()
79
91
  return None
80
92
 
81
93
  @override
@@ -95,36 +107,33 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
95
107
  return None
96
108
 
97
109
  @override
110
+ @clear_list
98
111
  def upsert(self, instances: T | list[T]) -> None:
99
112
  upsert = UpsertQuery(self._model, self._repository)
100
113
  upsert.upsert(instances)
101
114
  upsert.execute()
102
- self._query_list.clear()
103
115
  return None
104
116
 
105
117
  @override
118
+ @clear_list
106
119
  def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
107
120
  update = UpdateQuery(self._model, self._repository, self._query_list["where"])
108
121
  update.update(dicc)
109
122
  update.execute()
110
- self._query_list.clear()
123
+
111
124
  return None
112
125
 
113
126
  @override
114
127
  def limit(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
115
128
  limit = LimitQuery(number)
116
129
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
117
- limit_list = self._query_list["limit"]
118
- if len(limit_list) > 0:
119
- self._query_list["limit"] = [limit]
120
- else:
121
- self._query_list["limit"].append(limit)
130
+ self._query_list["limit"] = [limit]
122
131
  return self
123
132
 
124
133
  @override
125
134
  def offset(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
126
135
  offset = OffsetQuery(number)
127
- self._query_list["offset"].append(offset)
136
+ self._query_list["offset"] = [offset]
128
137
  return self
129
138
 
130
139
  @override
@@ -157,7 +166,7 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
157
166
  return self
158
167
 
159
168
  @override
160
- def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderType) -> IStatements_two_generic[T, MySQLConnection]:
169
+ def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
161
170
  order = OrderQuery[T](self._model, _lambda_col, order_type)
162
171
  self._query_list["order"].append(order)
163
172
  return self
@@ -193,7 +202,7 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
193
202
 
194
203
  query: str = self._build()
195
204
  if flavour:
196
- result = self._return_flavour(query, flavour)
205
+ result = self._return_flavour(query, flavour, select)
197
206
  if issubclass(flavour, tuple) and isinstance(selector(self._model), property):
198
207
  return tuple([x[0] for x in result])
199
208
  return result
@@ -227,9 +236,9 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
227
236
  return self
228
237
 
229
238
  @override
239
+ @clear_list
230
240
  def _build(self) -> str:
231
- query: str = ""
232
-
241
+ query_list: list[str] = []
233
242
  for x in self.__order__:
234
243
  sub_query: Optional[list[IQuery]] = self._query_list.get(x, None)
235
244
  if sub_query is None:
@@ -256,9 +265,8 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
256
265
  else:
257
266
  query_ = "\n".join([x.query for x in sub_query])
258
267
 
259
- query += f"\n{query_}" if query != "" else query_
260
- self._query_list.clear()
261
- return query
268
+ query_list.append(query_)
269
+ return "\n".join(query_list)
262
270
 
263
271
  def __build_where_clause(self, where_condition: list[AbstractWhere]) -> str:
264
272
  query: str = where_condition[0].query
ormlambda/utils/column.py CHANGED
@@ -1,7 +1,14 @@
1
- from typing import Type
1
+ from __future__ import annotations
2
+ from typing import Type, Optional, Callable, TYPE_CHECKING, Any
3
+ import shapely as sph
4
+
5
+ if TYPE_CHECKING:
6
+ from .table_constructor import Field
2
7
 
3
8
 
4
9
  class Column[T]:
10
+ CHAR: str = "%s"
11
+
5
12
  __slots__ = (
6
13
  "dtype",
7
14
  "column_name",
@@ -31,22 +38,54 @@ class Column[T]:
31
38
  self.is_auto_increment: bool = is_auto_increment
32
39
  self.is_unique: bool = is_unique
33
40
 
41
+ @property
42
+ def column_value_to_query(self) -> T:
43
+ """
44
+ This property must ensure that any variable requiring casting by different database methods is properly wrapped.
45
+ """
46
+ if self.dtype is sph.Point:
47
+ return sph.to_wkt(self.column_value, -1)
48
+ return self.column_value
49
+
50
+ @property
51
+ def placeholder(self) -> str:
52
+ return self.placeholder_resolutor(self.dtype)
53
+
54
+ @property
55
+ def placeholder_resolutor(self) -> Callable[[Type, T], str]:
56
+ return self.__fetch_wrapped_method
57
+
58
+ # FIXME [ ]: this method is allocating the Column class with MySQL database
59
+ @classmethod
60
+ def __fetch_wrapped_method(cls, type_: Type) -> Optional[str]:
61
+ """
62
+ This method must ensure that any variable requiring casting by different database methods is properly wrapped.
63
+ """
64
+ caster: dict[Type[Any], Callable[[str], str]] = {
65
+ sph.Point: lambda x: f"ST_GeomFromText({x})",
66
+ }
67
+ return caster.get(type_, lambda x: x)(cls.CHAR)
68
+
34
69
  def __repr__(self) -> str:
35
- return f"<Column: {self.column_name}>"
70
+ return f"<Column: {self.dtype}>"
36
71
 
37
- def __to_string__(self, name: str, var_name: T, type_: str):
38
- dicc: dict = {
39
- "dtype": type_,
40
- "column_name": f"'{name}'",
41
- "column_value": var_name, # must be the same variable name as the instance variable name in Table's __init__ class
72
+ def __to_string__(self, field: Field):
73
+ column_class_string: str = f"{Column.__name__}[{field.type_name}]("
74
+
75
+ dicc: dict[str, Callable[[Field], str]] = {
76
+ "dtype": lambda field: field.type_name,
77
+ "column_name": lambda field: f"'{field.name}'",
78
+ "column_value": lambda field: field.name, # must be the same variable name as the instance variable name in Table's __init__ class
42
79
  }
43
- exec_str: str = f"{Column.__name__}[{type_}]("
44
- for x in self.__slots__:
45
- self_value = getattr(self, x)
80
+ for self_var in self.__init__.__annotations__:
81
+ if not hasattr(self, self_var):
82
+ continue
83
+
84
+ self_value = dicc.get(self_var, lambda field: getattr(self, self_var))(field)
85
+ column_class_string += f" {self_var}={self_value}, "
46
86
 
47
- exec_str += f" {x}={dicc.get(x,self_value)},\n"
48
- exec_str += ")"
49
- return exec_str
87
+ column_class_string += ")"
88
+ return column_class_string
50
89
 
51
90
  def __hash__(self) -> int:
52
91
  return hash(
ormlambda/utils/dtypes.py CHANGED
@@ -48,8 +48,10 @@ MySQL 8.0 does not support year in two-digit format.
48
48
  """
49
49
 
50
50
  from decimal import Decimal
51
- import datetime
52
51
  from typing import Any, Literal
52
+ import datetime
53
+
54
+ from shapely import Point
53
55
  import numpy as np
54
56
 
55
57
  from .column import Column
@@ -66,17 +68,7 @@ DATE = Literal["DATE", "DATETIME(fsp)", "TIMESTAMP(fsp)", "TIME(fsp)", "YEAR"]
66
68
  def transform_py_dtype_into_query_dtype(dtype: Any) -> str:
67
69
  # TODOL: must be found a better way to convert python data type into SQL clauses
68
70
  # float -> DECIMAL(5,2) is an error
69
- dicc: dict[Any, str] = {
70
- int: "INTEGER",
71
- float: "FLOAT(5,2)",
72
- Decimal: "FLOAT",
73
- datetime.datetime: "DATETIME",
74
- datetime.date: "DATE",
75
- bytes: "BLOB",
76
- bytearray: "BLOB",
77
- str: "VARCHAR(255)",
78
- np.uint64: "BIGINT UNSIGNED",
79
- }
71
+ dicc: dict[Any, str] = {int: "INTEGER", float: "FLOAT(5,2)", Decimal: "FLOAT", datetime.datetime: "DATETIME", datetime.date: "DATE", bytes: "BLOB", bytearray: "BLOB", str: "VARCHAR(255)", np.uint64: "BIGINT UNSIGNED", Point: "Point"}
80
72
 
81
73
  res = dicc.get(dtype, None)
82
74
  if res is None:
@@ -0,0 +1,60 @@
1
+ import typing as tp
2
+ from .column import Column
3
+
4
+ __all__ = ["get_fields"]
5
+
6
+ MISSING = lambda: Column() # COMMENT: Very Important to avoid reusing the same variable across different classes. # noqa: E731
7
+
8
+
9
+ class Field[TProp: tp.AnnotatedAny]:
10
+ def __init__(self, name: str, type_: tp.Type, default: Column[TProp]) -> None:
11
+ self.name: str = name
12
+ self.type_: tp.Type[TProp] = type_
13
+ self.default: Column[TProp] = default
14
+
15
+ def __repr__(self) -> str:
16
+ return f"{Field.__name__}(name = {self.name}, type_ = {self.type_}, default = {self.default})"
17
+
18
+ @property
19
+ def has_default(self) -> bool:
20
+ return self.default is not MISSING()
21
+
22
+ @property
23
+ def init_arg(self) -> str:
24
+ default = f"={self.default_name}" # if self.has_default else ""}"
25
+ return f"{self.name}: {self.type_name}{default}"
26
+
27
+ @property
28
+ def default_name(self) -> str:
29
+ return f"_dflt_{self.name}"
30
+
31
+ @property
32
+ def type_name(self) -> str:
33
+ return f"_type_{self.name}"
34
+
35
+ @property
36
+ def assginment(self) -> str:
37
+ return f"self._{self.name} = {self.default.__to_string__(self)}"
38
+
39
+
40
+ def get_fields[T, TProp](cls: tp.Type[T]) -> tp.Iterable[Field]:
41
+ # COMMENT: Used the 'get_type_hints' method to resolve typing when 'from __future__ import annotations' is in use
42
+ annotations = {key: val for key, val in tp.get_type_hints(cls).items() if not key.startswith("_")}
43
+
44
+ # delete_special_variables(annotations)
45
+ fields = []
46
+ for name, type_ in annotations.items():
47
+ if hasattr(type_, "__origin__") and type_.__origin__ is Column: # __origin__ to get type of Generic value
48
+ field_type = type_.__args__[0]
49
+ else:
50
+ # type_ must by Column object
51
+ field_type: TProp = type_
52
+
53
+ default: Column = getattr(cls, name, MISSING())
54
+
55
+ default.dtype = field_type # COMMENT: Useful for setting the dtype variable after instantiation.
56
+ fields.append(Field[TProp](name, field_type, default))
57
+
58
+ # Update __annotations__ to create Columns
59
+ cls.__annotations__[name] = default
60
+ return fields
@@ -1,75 +1,21 @@
1
+ from __future__ import annotations
2
+ from decimal import Decimal
3
+ from typing import Any, Optional, Type, dataclass_transform, overload, TYPE_CHECKING
1
4
  import base64
2
5
  import datetime
3
- from decimal import Decimal
4
- from typing import Any, Iterable, Optional, Type, dataclass_transform, get_type_hints
5
6
  import json
6
7
 
7
- from .dtypes import get_query_clausule
8
- from .module_tree.dfs_traversal import DFSTraversal
9
- from .column import Column
10
-
11
- from .foreign_key import ForeignKey, TableInfo
12
-
13
- MISSING = Column()
14
-
15
-
16
- class Field:
17
- def __init__(self, name: str, type_: Type, default: object) -> None:
18
- self.name: str = name
19
- self.type_: Type = type_
20
- self.default: Column = default
21
-
22
- def __repr__(self) -> str:
23
- return f"{Field.__name__}(name = {self.name}, type_ = {self.type_}, default = {self.default})"
24
-
25
- @property
26
- def has_default(self) -> bool:
27
- return self.default is not MISSING
8
+ import shapely as sph
28
9
 
29
- @property
30
- def init_arg(self) -> str:
31
- # default = f"={self.default_name if self.has_default else None}"
32
- default = f"={None}"
33
10
 
34
- return f"{self.name}: {self.type_name}{default}"
35
-
36
- @property
37
- def default_name(self) -> str:
38
- return f"_dflt_{self.name}"
39
-
40
- @property
41
- def type_name(self) -> str:
42
- return f"_type_{self.name}"
43
-
44
- @property
45
- def assginment(self) -> str:
46
- return f"self._{self.name} = {self.default.__to_string__(self.name,self.name,self.type_name)}"
47
-
48
-
49
- def delete_special_variables(dicc: dict[str, object]) -> None:
50
- keys = tuple(dicc.keys())
51
- for key in keys:
52
- if key.startswith("__"):
53
- del dicc[key]
54
-
55
-
56
- def get_fields[T](cls: Type[T]) -> Iterable[Field]:
57
- # COMMENT: Used the 'get_type_hints' method to resolve typing when 'from __future__ import annotations' is in use
58
- annotations = {key: val for key, val in get_type_hints(cls).items() if not key.startswith("_")}
59
-
60
- # delete_special_variables(annotations)
61
- fields = []
62
- for name, type_ in annotations.items():
63
- # type_ must by Column object
64
- field_type = type_
65
- if hasattr(type_, "__origin__") and type_.__origin__ is Column: # __origin__ to get type of Generic value
66
- field_type = type_.__args__[0]
67
- default: Column = getattr(cls, name, MISSING)
68
- fields.append(Field(name, field_type, default))
11
+ from .column import Column
12
+ from .dtypes import get_query_clausule
13
+ from .fields import get_fields
14
+ from .foreign_key import ForeignKey, TableInfo
15
+ from .module_tree.dfs_traversal import DFSTraversal
69
16
 
70
- # Update __annotations__ to create Columns
71
- cls.__annotations__[name] = Column[field_type]
72
- return fields
17
+ if TYPE_CHECKING:
18
+ from .fields import Field
73
19
 
74
20
 
75
21
  @dataclass_transform()
@@ -78,23 +24,32 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
78
24
  # TODOL: I don't know if it's better to create a global dictionary like in commit '7de69443d7a8e7264b8d5d604c95da0e5d7e9cc0'
79
25
  setattr(cls, "__properties_mapped__", {})
80
26
  fields = get_fields(cls)
81
- locals_ = {}
82
- init_args = []
27
+
28
+ locals_: dict[str, Any] = {}
29
+ init_args: list[str] = ["self"]
30
+ assignments: list[str] = []
83
31
 
84
32
  for field in fields:
85
- if not field.name.startswith("__"):
86
- locals_[field.type_name] = field.type_
33
+ if field.name.startswith("__"):
34
+ continue
35
+
36
+ locals_[field.type_name] = field.type_
37
+ locals_[field.default_name] = field.default.column_value
87
38
 
88
- init_args.append(field.init_arg)
89
- locals_[field.default_name] = None # field.default.column_value
90
- __create_properties(cls, field)
39
+ init_args.append(field.init_arg)
40
+ assignments.append(field.assginment)
41
+ __create_properties(cls, field)
42
+
43
+ string_locals_ = ", ".join(locals_)
44
+ string_init_args = ", ".join(init_args)
45
+ string_assignments = "\n\t\t".join(assignments)
91
46
 
92
47
  wrapper_fn = "\n".join(
93
48
  [
94
- f"def wrapper({', '.join(locals_.keys())}):",
95
- f" def __init__(self, {', '.join(init_args)}):",
96
- "\n".join([f" {f.assginment}" for f in fields]) or " pass",
97
- " return __init__",
49
+ f"def wrapper({string_locals_}):",
50
+ f"\n\tdef __init__({string_init_args}):",
51
+ f"\n\t\t{string_assignments}",
52
+ "\treturn __init__",
98
53
  ]
99
54
  )
100
55
 
@@ -109,11 +64,11 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
109
64
 
110
65
  def __create_properties(cls: Type["Table"], field: Field) -> property:
111
66
  _name: str = f"_{field.name}"
112
- type_ = field.type_
67
+
113
68
  # we need to get Table attributes (Column class) and then called __getattribute__ or __setattr__ to make changes inside of Column
114
69
  prop = property(
115
- fget=lambda self: __transform_getter(getattr(self, _name), type_),
116
- fset=lambda self, value: __transform_setter(getattr(self, _name), value, type_),
70
+ fget=lambda self: getattr(self, _name).__getattribute__("column_value"),
71
+ fset=lambda self, value: getattr(self, _name).__setattr__("column_value", value),
117
72
  )
118
73
 
119
74
  # set property in public name
@@ -122,22 +77,6 @@ def __create_properties(cls: Type["Table"], field: Field) -> property:
122
77
  return None
123
78
 
124
79
 
125
- def __transform_getter[T](obj: object, type_: T) -> T:
126
- return obj.__getattribute__("column_value")
127
-
128
- # if type_ is str and isinstance(eval(obj), Iterable):
129
- # getter = eval(obj)
130
- # return getter
131
-
132
-
133
- def __transform_setter[T](obj: object, value: Any, type_: T) -> None:
134
- return obj.__setattr__("column_value", value)
135
-
136
- # if type_ is list:
137
- # setter = str(setter)
138
- # return None
139
-
140
-
141
80
  class TableMeta(type):
142
81
  def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
143
82
  """
@@ -244,6 +183,7 @@ class Table(metaclass=TableMeta):
244
183
  Decimal: str,
245
184
  bytes: byte_to_string,
246
185
  set: list,
186
+ sph.Point: lambda x: sph.to_wkt(x, rounding_precision=-1),
247
187
  }
248
188
 
249
189
  if (dtype := type(_value)) in transform_map:
@@ -324,3 +264,35 @@ class Table(metaclass=TableMeta):
324
264
  )
325
265
  )
326
266
  return False
267
+
268
+ @classmethod
269
+ def get_property_name(cls, _property: property) -> str:
270
+ name: str = cls.__properties_mapped__.get(_property, None)
271
+ if not name:
272
+ raise KeyError(f"Class '{cls.__name__}' has not propery '{_property}' mapped.")
273
+ return name
274
+
275
+ @overload
276
+ @classmethod
277
+ def get_column(cls, column: str) -> Column: ...
278
+ @overload
279
+ @classmethod
280
+ def get_column(cls, column: property) -> Column: ...
281
+ @overload
282
+ @classmethod
283
+ def get_column[TProp](cls, column: property, value: TProp) -> Column[TProp]: ...
284
+ @overload
285
+ @classmethod
286
+ def get_column[TProp](cls, column: str, value: TProp) -> Column[TProp]: ...
287
+ @classmethod
288
+ def get_column[TProp](cls, column: str | property, value: Optional[TProp] = None) -> Column[TProp]:
289
+ if isinstance(column, property):
290
+ _column = cls.get_property_name(column)
291
+ elif isinstance(column, str) and column in cls.get_columns():
292
+ _column = column
293
+ else:
294
+ raise ValueError(f"'Column' param with value'{column}' is not expected.")
295
+
296
+ instance_table: Table = cls(**{_column: value})
297
+
298
+ return getattr(instance_table, f"_{_column}")
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ormlambda
3
- Version: 2.8.0
3
+ Version: 2.9.0
4
4
  Summary: ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions
5
5
  Author: p-hzamora
6
6
  Author-email: p.hzamora@icloud.com
7
7
  Requires-Python: >=3.12,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
- Requires-Dist: fluent-validation (>=3.1.0,<4.0.0)
10
+ Requires-Dist: fluent-validation (==4.3.1)
11
11
  Requires-Dist: mysql-connector-python (>=9.0.0,<10.0.0)
12
+ Requires-Dist: shapely (>=2.0.6,<3.0.0)
12
13
  Description-Content-Type: text/markdown
13
14
 
14
15
  ![PyPI version](https://img.shields.io/pypi/v/ormlambda.svg)
@@ -1,8 +1,8 @@
1
- ormlambda/__init__.py,sha256=L-Enc4FPmW3ldBMnWOU3gLn9i4FEc7sQzLWy2mIEti8,538
1
+ ormlambda/__init__.py,sha256=lWQxjf2cQ2bXenNaDpoy2Bar6imiPlzc4nK0hkIYhC0,723
2
2
  ormlambda/common/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
3
3
  ormlambda/common/abstract_classes/__init__.py,sha256=tk2J4Mn_nD-1ZjtMVBE5FH7KR_8ppN_1Kx1s6c38GjM,167
4
- ormlambda/common/abstract_classes/abstract_model.py,sha256=0mGoW-MF0bX1VrAp2AJZ8Nk_BV7N_2NtzZoUFCg5Wm4,4595
5
- ormlambda/common/abstract_classes/decomposition_query.py,sha256=c-Vo0f2dnVCv9Qsmqt9AZaZAYvyYhtZFdc3HKyHCMZ0,11396
4
+ ormlambda/common/abstract_classes/abstract_model.py,sha256=W7E3DDeFeQbnBX0ccLqmhUX3dCHHDCADK3bT2wyPEtk,4618
5
+ ormlambda/common/abstract_classes/decomposition_query.py,sha256=0k1cTFftZZaRmjuYOEnZ-r7nqljTwcSlz1ZbEElnlbk,12586
6
6
  ormlambda/common/abstract_classes/non_query_base.py,sha256=5jhvyT7OZaJxlGp9XMP3vQ4ei5QQZBn-fFtJnD640mE,980
7
7
  ormlambda/common/abstract_classes/query_base.py,sha256=6qUFPwsVx45kUW3b66pHiSyjhcH4mzbdkddlGeUnG7c,266
8
8
  ormlambda/common/enums/__init__.py,sha256=4lVKCHi1JalwgNzjsAXqX-C54NJEH83y2v5baMO8fN4,103
@@ -13,7 +13,7 @@ ormlambda/common/interfaces/IDecompositionQuery.py,sha256=dHdCxezbWwy-c4U4po2hQB
13
13
  ormlambda/common/interfaces/INonQueryCommand.py,sha256=7CjLW4sKqkR5zUIGvhRXOtzTs6vypJW1a9EJHlgCw2c,260
14
14
  ormlambda/common/interfaces/IQueryCommand.py,sha256=hfzCosK4-n8RJIb2PYs8b0qU3TNmfYluZXBf47KxxKs,331
15
15
  ormlambda/common/interfaces/IRepositoryBase.py,sha256=zGm7tDrFLKj3FMYN-MWI-em3INws27g2yByNYYMx0qk,1126
16
- ormlambda/common/interfaces/IStatements.py,sha256=df3pGRFSwyBcFct0ntMy8E9RNCHreXTO-qirdetS05M,10674
16
+ ormlambda/common/interfaces/IStatements.py,sha256=XJ0hCp7_7wMclPvZmavpdr7unQshOXKZDasgVk6RfwU,10409
17
17
  ormlambda/common/interfaces/__init__.py,sha256=00ca9a-u_A8DzyEyxPfBMxfqLKFzzUgJaeNmoRitAbA,360
18
18
  ormlambda/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  ormlambda/components/delete/IDelete.py,sha256=06ZEdbKBxsHSwsGMBu0E1om4WJjojZAm-L3b95eQrcc,139
@@ -39,13 +39,13 @@ ormlambda/databases/my_sql/clauses/delete.py,sha256=nUKNQgwF5YUfzk2icWpecYjrGk5D
39
39
  ormlambda/databases/my_sql/clauses/drop_database.py,sha256=nMM0YUbcH0D9X3bU70Uc6S_dsIrAZy-IrYuIKrQZNrg,505
40
40
  ormlambda/databases/my_sql/clauses/drop_table.py,sha256=meX4e-pVPQ7UwlPSHV5e9HHRblI1BlkLSc7ssl8WUiI,592
41
41
  ormlambda/databases/my_sql/clauses/group_by.py,sha256=xG8YhdaDWBm99EFMkzCsBJNhHOJy5lvXmpaHu3jClro,1042
42
- ormlambda/databases/my_sql/clauses/insert.py,sha256=LO9H8VVK3j62dICXqpEUXKxOHPxkD1LGvogmDq2zmho,2805
42
+ ormlambda/databases/my_sql/clauses/insert.py,sha256=mnOy62U4go0Ub6tmmgn8DRRWvPN12WRE5lqpCNbyR70,3136
43
43
  ormlambda/databases/my_sql/clauses/joins.py,sha256=U6JnUvQo7AXyEeK-X1jMvckXefgAB7ugSmJCZhH1XQI,3058
44
44
  ormlambda/databases/my_sql/clauses/limit.py,sha256=a4lI8FVRKpfXwBQTXdkbVtlQkmzcjE20ymiCy1IaSc4,391
45
45
  ormlambda/databases/my_sql/clauses/offset.py,sha256=81170JhsQndjKlDfQj1ll-tiYHQbW8SuU4IE3mhQF7Y,395
46
- ormlambda/databases/my_sql/clauses/order.py,sha256=RVqyEtD0InnOPAmRvjtNJ-vrAy0V9OHMLoFB8TkpX0I,1137
47
- ormlambda/databases/my_sql/clauses/select.py,sha256=unJBteFK5D360KnIPyqz8OMaVJqXJlI5Kkp6u0JJ1vU,1574
48
- ormlambda/databases/my_sql/clauses/update.py,sha256=3Htw0_PT3EckEiF214-V2r63lcNoRBroKZI9yg48Ddg,1867
46
+ ormlambda/databases/my_sql/clauses/order.py,sha256=XTMtR5ObAJxq8eTHp392rykQD991ecJ95dyXXLUpgeU,1680
47
+ ormlambda/databases/my_sql/clauses/select.py,sha256=o0gjCt8cZfTkMsJwZcCt0XkSeFa2rlKz-0Ymi7XL5xU,1833
48
+ ormlambda/databases/my_sql/clauses/update.py,sha256=d2nSLiGe8OUk0ASgGxchit_1DL-Yg8U-DUtBbgpfkto,1571
49
49
  ormlambda/databases/my_sql/clauses/upsert.py,sha256=eW2pQ4ax-GKuXiaWKoSRSS1GrHuILJBsmj83ADbBQ34,1951
50
50
  ormlambda/databases/my_sql/clauses/where_condition.py,sha256=UgU5vnTqFAx91wKwnECpww5tETDZ9F7IdC_SiZOgZhI,8336
51
51
  ormlambda/databases/my_sql/functions/__init__.py,sha256=hA8t3mUpV2p-pO4TVp5rjC5Yp7aIkWPsS8NpLi3DUh0,171
@@ -53,12 +53,13 @@ ormlambda/databases/my_sql/functions/concat.py,sha256=cZztl5eSATpYMKVKfyPbul6Ooc
53
53
  ormlambda/databases/my_sql/functions/max.py,sha256=zrO_RBKsHhyokEmSpPI6Yg5OY6Jf4GGp2RveBJdOuuA,1190
54
54
  ormlambda/databases/my_sql/functions/min.py,sha256=SEVuUdIJNz9zM5za1kLTWalFkhErjsjyBb8SU8n0F94,1190
55
55
  ormlambda/databases/my_sql/functions/sum.py,sha256=akKYr2dI8TZS5MDvybfHn_idhPOvEH0cj6mDRQIHe-M,1188
56
- ormlambda/databases/my_sql/repository.py,sha256=fSAAmO_T5mC1bodOVZn5CY9JSfRq4VhsdlWT1JLl7Zw,6927
57
- ormlambda/databases/my_sql/statements.py,sha256=inUEyLUl3Z6TxE7PpJUWw0SZBoO_JbnrS_XL_wCyHn4,11873
56
+ ormlambda/databases/my_sql/repository.py,sha256=fj_RcMY6QhANvKRyfAnu6PEfvkrpiBXrlTvmeXUf5Vo,8823
57
+ ormlambda/databases/my_sql/statements.py,sha256=ukP8ypxegCnxOuIEH99pNgWVUyOP-ELHD6GzfLEWUx4,12045
58
58
  ormlambda/model_base.py,sha256=RrAATQWX_bHJ0ZQ5sCHyJKA4NiR7ZJY4I6dqhnGWWAc,1216
59
59
  ormlambda/utils/__init__.py,sha256=ywMdWqmA2jHj19-W-S04yfaYF5hv4IZ1lZDq0B8Jnjs,142
60
- ormlambda/utils/column.py,sha256=5FAGzCU4yvNS4MhwJJ5i73h7RvHD5UCVox0NdzMsMiE,1945
61
- ormlambda/utils/dtypes.py,sha256=1kRsT5JggNS1DMWgSUdBb67CefQHnZ-VTN1WI7nN2yQ,7879
60
+ ormlambda/utils/column.py,sha256=SqbX8N63_RwXNrMXuVMOpatDw--aLc47o3kG1sEXhNs,3413
61
+ ormlambda/utils/dtypes.py,sha256=Ji4QOyKD0n9bKe7yXIIduy9uEX5BaXolQSIsx0NXYJY,7843
62
+ ormlambda/utils/fields.py,sha256=jyUJXJKblhLGqFGgAXkWJeE0FYLd5Ywd7H2pjQsfz8c,2128
62
63
  ormlambda/utils/foreign_key.py,sha256=ewGLPtf1MrFogvwGm_jsSnOLAH2G9virXvLcc3co36I,2973
63
64
  ormlambda/utils/lambda_disassembler/__init__.py,sha256=q23_F2Vp1_XgVpQSbWQPM5wxzRCZOr7ZMb9X5VG_YxU,229
64
65
  ormlambda/utils/lambda_disassembler/dis_types.py,sha256=Myuo9-KSBIJSyr9jfLSDDe1jbrzyOqZNLufv6ODHm98,4824
@@ -70,8 +71,8 @@ ormlambda/utils/lambda_disassembler/tree_instruction.py,sha256=QUnhG89WKJyAlEWAj
70
71
  ormlambda/utils/module_tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
72
  ormlambda/utils/module_tree/dfs_traversal.py,sha256=lSF03G63XtJFLp03ueAmsHMBvhUkjptDbK3IugXm8iU,1425
72
73
  ormlambda/utils/module_tree/dynamic_module.py,sha256=zwvjU3U2cz6H2CDp9Gncs5D5bSAyfITNa2SDqFDl8rw,8551
73
- ormlambda/utils/table_constructor.py,sha256=8Apm44K6MiYMK3PQyK74MUV18OatbFI9eDLAVklQO0w,11660
74
- ormlambda-2.8.0.dist-info/LICENSE,sha256=xBprFw8GJLdHMOoUqDk0427EvjIcbEREvXXVFULuuXU,1080
75
- ormlambda-2.8.0.dist-info/METADATA,sha256=HV3vUXqD2StgsPEk4VwRtPLp44DRjIUgSoMTRsWAkNw,8428
76
- ormlambda-2.8.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
77
- ormlambda-2.8.0.dist-info/RECORD,,
74
+ ormlambda/utils/table_constructor.py,sha256=ch1geCSJIQnnv_D_uzfYk6do533kjtZyeCIh2t0_dkU,10863
75
+ ormlambda-2.9.0.dist-info/LICENSE,sha256=xBprFw8GJLdHMOoUqDk0427EvjIcbEREvXXVFULuuXU,1080
76
+ ormlambda-2.9.0.dist-info/METADATA,sha256=t2WNOq0P72brj6Qr1h_PXY0oXRJ85NjY6oYbn7l2REs,8461
77
+ ormlambda-2.9.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
78
+ ormlambda-2.9.0.dist-info/RECORD,,