ormlambda 2.0.2__py3-none-any.whl → 2.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/common/abstract_classes/abstract_model.py +28 -13
- ormlambda/common/abstract_classes/decomposition_query.py +301 -0
- ormlambda/common/interfaces/IAggregate.py +10 -0
- ormlambda/common/interfaces/IDecompositionQuery.py +56 -0
- ormlambda/common/interfaces/IRepositoryBase.py +1 -10
- ormlambda/common/interfaces/IStatements.py +8 -2
- ormlambda/common/interfaces/__init__.py +2 -0
- ormlambda/databases/my_sql/clauses/__init__.py +16 -14
- ormlambda/databases/my_sql/clauses/count.py +27 -23
- ormlambda/databases/my_sql/clauses/select.py +28 -155
- ormlambda/databases/my_sql/clauses/where_condition.py +2 -2
- ormlambda/databases/my_sql/functions/__init__.py +3 -0
- ormlambda/databases/my_sql/functions/concat.py +41 -0
- ormlambda/databases/my_sql/functions/group_by.py +37 -0
- ormlambda/databases/my_sql/functions/max.py +39 -0
- ormlambda/databases/my_sql/repository.py +29 -44
- ormlambda/databases/my_sql/statements.py +18 -32
- ormlambda/model_base.py +2 -0
- ormlambda/utils/table_constructor.py +5 -4
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/METADATA +1 -1
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/RECORD +23 -19
- ormlambda/components/select/ISelect.py +0 -14
- ormlambda/components/select/__init__.py +0 -2
- ormlambda/components/select/table_column.py +0 -43
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/LICENSE +0 -0
- {ormlambda-2.0.2.dist-info → ormlambda-2.6.1.dist-info}/WHEEL +0 -0
@@ -1,172 +1,45 @@
|
|
1
|
-
from typing import
|
2
|
-
import inspect
|
1
|
+
from typing import override, Type, Callable, TYPE_CHECKING
|
3
2
|
|
4
|
-
from ormlambda.
|
5
|
-
from ormlambda.
|
6
|
-
from ormlambda import Table, ForeignKey
|
7
|
-
from ormlambda.utils.table_constructor import TableMeta
|
3
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
4
|
+
from ormlambda.common.enums.join_type import JoinType
|
8
5
|
|
9
|
-
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from ormlambda import Table
|
10
8
|
|
11
9
|
|
12
|
-
class
|
13
|
-
|
10
|
+
class Select[T: Type[Table]](DecompositionQueryBase[T]):
|
11
|
+
CLAUSE: str = "SELECT"
|
14
12
|
|
15
13
|
def __init__(
|
16
14
|
self,
|
17
|
-
|
18
|
-
|
15
|
+
table: T,
|
16
|
+
lambda_query: Callable[[T], tuple] = lambda x: x,
|
19
17
|
*,
|
18
|
+
alias: bool = False,
|
19
|
+
alias_name: str | None = None,
|
20
20
|
by: JoinType = JoinType.INNER_JOIN,
|
21
21
|
) -> None:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
self._select_list: list[TableColumn] = self._rename_recursive_column_list(select_lambda)
|
34
|
-
|
35
|
-
def _rename_recursive_column_list(self, _lambda: Optional[Callable[[T], None]]) -> list[TableColumn]:
|
36
|
-
"""
|
37
|
-
Recursive function tu replace variable names by Select Query
|
38
|
-
|
39
|
-
lambda a: (a.pk_address, a.city.pk_city, a.city.country.pk_country)
|
40
|
-
|
41
|
-
>>> # convert lambda expression into list of values
|
42
|
-
>>> select_list = [
|
43
|
-
>>> "a.pk_address",
|
44
|
-
>>> "a.city",
|
45
|
-
>>> "a.city.pk_city",
|
46
|
-
>>> "a.city.country",
|
47
|
-
>>> "a.city.country.pk_country",
|
48
|
-
>>> ]
|
49
|
-
>>> result = _rename_recursive_column_list(select_list)
|
50
|
-
>>> print(result)
|
51
|
-
>>> # result = [
|
52
|
-
>>> # "address.pk_address"
|
53
|
-
>>> # "city.*"
|
54
|
-
>>> # "city.pk_city"
|
55
|
-
>>> # "country.*"
|
56
|
-
>>> # "country.pk_country"
|
57
|
-
]
|
58
|
-
"""
|
59
|
-
instruction_list: list[TupleInstruction] = TreeInstruction(_lambda).to_list()
|
60
|
-
column_list: list[TableColumn] = []
|
61
|
-
|
62
|
-
for ti in instruction_list:
|
63
|
-
obj = self._lambda_var_to_table_dicc[ti.var]
|
64
|
-
|
65
|
-
var = obj.__table_name__
|
66
|
-
new_nested = ti.nested_element.parents
|
67
|
-
new_nested[0] = var
|
68
|
-
ti = TupleInstruction(var, NestedElement(new_nested))
|
69
|
-
self._get_parents(obj, ti, column_list)
|
70
|
-
return column_list
|
71
|
-
|
72
|
-
def _get_parents(self, tbl_obj: Table, tuple_inst: TupleInstruction, column_list: list[TableColumn]) -> None:
|
73
|
-
if self._user_want_all_col(tbl_obj, tuple_inst):
|
74
|
-
column_list.extend(list(TableColumn.all_columns(tbl_obj)))
|
75
|
-
return None
|
76
|
-
|
77
|
-
# if the 'last_el' var is a property, we'll know the user will want retrieve a column of the same instance of the 'tbl_obj'. Otherwise the user will want to get a column of the other instance
|
78
|
-
last_el: str = tuple_inst.nested_element.name
|
79
|
-
if self._user_want_column_of_the_same_table(tbl_obj, tuple_inst):
|
80
|
-
return column_list.append(TableColumn(tbl_obj, last_el))
|
81
|
-
|
82
|
-
parents: list[str] = tuple_inst.nested_element.parents
|
83
|
-
first_el = parents[1]
|
84
|
-
new_ti = TupleInstruction(first_el, NestedElement[str](parents[1:])) # create new TupleInstruction from the second parent to the top
|
85
|
-
new_attr = self.get_attribute_of(tbl_obj, first_el) # could be Table or property
|
86
|
-
|
87
|
-
self._add_fk_relationship(tbl_obj, new_attr)
|
88
|
-
return self._get_parents(new_attr, new_ti, column_list)
|
89
|
-
|
90
|
-
def _add_fk_relationship(self, t1: Table, t2: Table) -> None:
|
91
|
-
tuple_ = tuple([t1, t2])
|
92
|
-
if tuple_ not in self._tables_heritage:
|
93
|
-
self._tables_heritage.append(tuple_)
|
94
|
-
return None
|
95
|
-
|
96
|
-
@staticmethod
|
97
|
-
def _user_want_all_col(tbl: Table, ti: TupleInstruction) -> bool:
|
98
|
-
"""
|
99
|
-
if ti.nested_element.parents length is 1 says that the element is the table itself (table.*)
|
100
|
-
"""
|
101
|
-
return issubclass(tbl.__class__, Table | TableMeta) and len(ti.nested_element.parents) == 1
|
102
|
-
|
103
|
-
def _user_want_column_of_the_same_table(self, table: Table, ti: TupleInstruction) -> bool:
|
104
|
-
last_el: str = ti.nested_element.name
|
105
|
-
first_el = ti.nested_element.parents[1]
|
106
|
-
|
107
|
-
table_attr = self.get_attribute_of(table, first_el)
|
108
|
-
|
109
|
-
return last_el in table.__dict__ and isinstance(table_attr, property)
|
110
|
-
|
111
|
-
@staticmethod
|
112
|
-
def get_attribute_of[TProp: Table](table: TProp, _value: str) -> Optional[TProp | property]:
|
113
|
-
try:
|
114
|
-
return getattr(table.__class__, _value)
|
115
|
-
except Exception:
|
116
|
-
return getattr(table, _value, None)
|
117
|
-
|
118
|
-
def _assign_lambda_variables_to_table(self, _lambda: Callable[[T], None]) -> dict[str, Type[Table]]:
|
119
|
-
"""
|
120
|
-
return a dictionary with the lambda's parameters as keys and Type[Table] as the values
|
121
|
-
|
122
|
-
|
123
|
-
>>> res = _assign_lambda_variables_to_table(lambda a,ci,co: ...)
|
124
|
-
>>> print(res)
|
125
|
-
>>> # {
|
126
|
-
>>> # "a": Address,
|
127
|
-
>>> # "ci": City,
|
128
|
-
>>> # "co": Country,
|
129
|
-
>>> # }
|
130
|
-
"""
|
131
|
-
lambda_vars = tuple(inspect.signature(_lambda).parameters)
|
132
|
-
|
133
|
-
dicc: dict[str, Table] = {}
|
134
|
-
for i in range(len(lambda_vars)):
|
135
|
-
dicc[lambda_vars[i]] = self._tables[i]
|
136
|
-
return dicc
|
137
|
-
|
138
|
-
def _convert_select_list(self) -> str:
|
139
|
-
self._select_list = self._select_list if self._select_list else tuple(TableColumn.all_columns(self._first_table))
|
140
|
-
|
141
|
-
return ", ".join(col.column for col in self._select_list)
|
22
|
+
super().__init__(
|
23
|
+
table,
|
24
|
+
lambda_query,
|
25
|
+
alias=alias,
|
26
|
+
alias_name=alias_name,
|
27
|
+
by=by,
|
28
|
+
)
|
29
|
+
|
30
|
+
# @classmethod
|
31
|
+
# def alias_children_resolver[Tclause: Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
32
|
+
# return f"{clause.table.__table_name__}_{name}"
|
142
33
|
|
143
34
|
@override
|
144
35
|
@property
|
145
36
|
def query(self) -> str:
|
146
|
-
|
147
|
-
query: str = f"{self.
|
37
|
+
col: str = ", ".join([x.query for x in self.all_clauses])
|
38
|
+
query: str = f"{self.CLAUSE} {col} FROM {self._table.__table_name__}"
|
39
|
+
alias = ""
|
148
40
|
|
149
|
-
|
150
|
-
if
|
151
|
-
|
41
|
+
query += alias
|
42
|
+
if self.has_foreign_keys:
|
43
|
+
query += " " + self.stringify_foreign_key(" ")
|
152
44
|
|
153
|
-
sub_query: str = ""
|
154
|
-
for l_tbl, r_tbl in involved_tables:
|
155
|
-
join = JoinSelector(l_tbl, r_tbl, by=self._by, where=ForeignKey.MAPPED[l_tbl.__table_name__].referenced_tables[r_tbl.__table_name__].relationship)
|
156
|
-
sub_query += f" {join.query}"
|
157
|
-
|
158
|
-
query += sub_query
|
159
45
|
return query
|
160
|
-
|
161
|
-
@override
|
162
|
-
@property
|
163
|
-
def select_list(self) -> list[TableColumn]:
|
164
|
-
return self._select_list
|
165
|
-
|
166
|
-
@override
|
167
|
-
@property
|
168
|
-
def tables_heritage(self) -> list[tuple[Table, Table]]:
|
169
|
-
return self._tables_heritage
|
170
|
-
|
171
|
-
def get_involved_tables(self) -> tuple[tuple[Table, Table]]:
|
172
|
-
return tuple(self._tables_heritage)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any, Callable, Optional, override
|
1
|
+
from typing import Any, Callable, Optional, override, Type
|
2
2
|
import inspect
|
3
3
|
|
4
4
|
from ormlambda.common.enums import ConditionType
|
@@ -19,7 +19,7 @@ class WhereConditionByArg[TProp1, TProp2](IQuery):
|
|
19
19
|
return f"WHERE {self.cond1} {self.symbol.value} {self.cond2}"
|
20
20
|
|
21
21
|
|
22
|
-
class WhereCondition[*Inst](AbstractWhere):
|
22
|
+
class WhereCondition[T: Type[Table], *Inst](AbstractWhere):
|
23
23
|
"""
|
24
24
|
The purpose of this class is to create 'WHERE' condition queries properly.
|
25
25
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from ormlambda.common.interfaces.IAggregate import IAggregate
|
2
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase, ClauseInfo
|
3
|
+
|
4
|
+
|
5
|
+
import typing as tp
|
6
|
+
from ormlambda.common.enums.join_type import JoinType
|
7
|
+
|
8
|
+
if tp.TYPE_CHECKING:
|
9
|
+
from ormlambda import Table
|
10
|
+
|
11
|
+
|
12
|
+
class Concat[T: tp.Type[Table]](DecompositionQueryBase[T], IAggregate[T]):
|
13
|
+
CLAUSE = "CONCAT"
|
14
|
+
|
15
|
+
def __init__[*Ts](
|
16
|
+
self,
|
17
|
+
table: T,
|
18
|
+
lambda_query: str | tp.Callable[[T], tuple[*Ts]],
|
19
|
+
*,
|
20
|
+
alias: bool = True,
|
21
|
+
alias_name: str = "CONCAT",
|
22
|
+
by: JoinType = JoinType.INNER_JOIN,
|
23
|
+
) -> None:
|
24
|
+
super().__init__(
|
25
|
+
table,
|
26
|
+
lambda_query,
|
27
|
+
alias=alias,
|
28
|
+
alias_name=alias_name,
|
29
|
+
by=by,
|
30
|
+
)
|
31
|
+
|
32
|
+
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
33
|
+
if isinstance(clause_info._row_column, IAggregate):
|
34
|
+
return clause_info._row_column.alias
|
35
|
+
return None
|
36
|
+
|
37
|
+
@property
|
38
|
+
def query(self) -> str:
|
39
|
+
col: str = ", ".join([x.query for x in self.all_clauses])
|
40
|
+
|
41
|
+
return f"{self.CLAUSE}({col})"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import typing as tp
|
2
|
+
from ormlambda.common.enums.join_type import JoinType
|
3
|
+
from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo, DecompositionQueryBase
|
4
|
+
from ormlambda.common.interfaces.IAggregate import IAggregate
|
5
|
+
from ormlambda import Table
|
6
|
+
|
7
|
+
|
8
|
+
class GroupBy[T: tp.Type[Table], TProp](DecompositionQueryBase[T], IAggregate[T]):
|
9
|
+
CLAUSE: str = "GROUP BY"
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
table: T,
|
14
|
+
column: tp.Callable[[T], TProp],
|
15
|
+
*,
|
16
|
+
alias: bool = True,
|
17
|
+
alias_name: str | None = None,
|
18
|
+
by: JoinType = JoinType.INNER_JOIN,
|
19
|
+
) -> None:
|
20
|
+
super().__init__(
|
21
|
+
table,
|
22
|
+
lambda_query=column,
|
23
|
+
alias=alias,
|
24
|
+
alias_name=alias_name,
|
25
|
+
by=by,
|
26
|
+
)
|
27
|
+
|
28
|
+
self._column: TProp = column
|
29
|
+
|
30
|
+
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
31
|
+
return None
|
32
|
+
|
33
|
+
@property
|
34
|
+
def query(self) -> str:
|
35
|
+
col: str = ", ".join([x.query for x in self.all_clauses])
|
36
|
+
|
37
|
+
return f"{self.CLAUSE} {col}"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from ormlambda.common.interfaces import IAggregate
|
2
|
+
import typing as tp
|
3
|
+
|
4
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase, ClauseInfo
|
5
|
+
|
6
|
+
if tp.TYPE_CHECKING:
|
7
|
+
from ormlambda import Table
|
8
|
+
|
9
|
+
|
10
|
+
class Max[T: tp.Type[Table]](DecompositionQueryBase[T], IAggregate[T]):
|
11
|
+
NAME: str = "MAX"
|
12
|
+
|
13
|
+
@tp.overload
|
14
|
+
def __init__[T: tp.Type[Table]](self, table: T, column: tp.Callable[[T], tp.Any], *, alias: bool = True, alias_name: str = "max") -> None: ...
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
table: T,
|
19
|
+
column: str | tp.Callable[[T], tuple],
|
20
|
+
*,
|
21
|
+
alias: bool = True,
|
22
|
+
alias_name: str = "max",
|
23
|
+
) -> None:
|
24
|
+
super().__init__(
|
25
|
+
table,
|
26
|
+
lambda_query=column,
|
27
|
+
alias=alias,
|
28
|
+
alias_name=alias_name,
|
29
|
+
)
|
30
|
+
|
31
|
+
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
32
|
+
if isinstance(clause_info._row_column, IAggregate):
|
33
|
+
return clause_info._row_column.alias
|
34
|
+
return None
|
35
|
+
|
36
|
+
@property
|
37
|
+
def query(self) -> str:
|
38
|
+
col = ", ".join([x.query for x in self.all_clauses])
|
39
|
+
return f"{self.NAME}({col})"
|
@@ -77,42 +77,27 @@ class Response[TFlavour, *Ts]:
|
|
77
77
|
class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
78
78
|
def get_connection(func: Callable[..., Any]):
|
79
79
|
@functools.wraps(func)
|
80
|
-
def wrapper(self:
|
81
|
-
self.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
def wrapper(self: MySQLRepository, *args, **kwargs):
|
81
|
+
with self._pool.get_connection() as cnx:
|
82
|
+
try:
|
83
|
+
foo = func(self, cnx, *args, **kwargs)
|
84
|
+
return foo
|
85
|
+
except Exception as e:
|
86
|
+
self.connection.rollback()
|
87
|
+
raise e
|
88
88
|
|
89
89
|
return wrapper
|
90
90
|
|
91
91
|
def __init__(self, **kwargs: Any) -> None:
|
92
92
|
self._data_config: dict[str, Any] = kwargs
|
93
93
|
self._pool: MySQLConnectionPool = self.__create_MySQLConnectionPool()
|
94
|
-
self._connection: PooledMySQLConnection = None
|
95
94
|
|
96
95
|
def __create_MySQLConnectionPool(self):
|
97
|
-
return MySQLConnectionPool(pool_name="mypool",pool_size=10, **self._data_config)
|
98
|
-
@override
|
99
|
-
def is_connected(self) -> bool:
|
100
|
-
return self._connection._cnx is not None if self._connection else False
|
101
|
-
|
102
|
-
@override
|
103
|
-
def connect(self) -> None:
|
104
|
-
self._connection = self._pool.get_connection()
|
105
|
-
return None
|
106
|
-
|
107
|
-
@override
|
108
|
-
def close_connection(self) -> None:
|
109
|
-
if self.is_connected():
|
110
|
-
self._connection.close()
|
111
|
-
return None
|
96
|
+
return MySQLConnectionPool(pool_name="mypool", pool_size=10, **self._data_config)
|
112
97
|
|
113
98
|
@override
|
114
99
|
@get_connection
|
115
|
-
def read_sql[TFlavour](self, query: str, flavour: Type[TFlavour] = tuple, **kwargs) -> tuple[TFlavour]:
|
100
|
+
def read_sql[TFlavour](self, cnx: MySQLConnection, query: str, flavour: Type[TFlavour] = tuple, **kwargs) -> tuple[TFlavour]:
|
116
101
|
"""
|
117
102
|
Return tuple of tuples by default.
|
118
103
|
|
@@ -122,7 +107,7 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
122
107
|
- flavour: Type[TFlavour]: Useful to return tuple of any Iterable type as dict,set,list...
|
123
108
|
"""
|
124
109
|
|
125
|
-
with
|
110
|
+
with cnx.cursor(buffered=True) as cursor:
|
126
111
|
cursor.execute(query)
|
127
112
|
values: list[tuple] = cursor.fetchall()
|
128
113
|
columns: tuple[str] = cursor.column_names
|
@@ -130,7 +115,7 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
130
115
|
|
131
116
|
# FIXME [ ]: this method does not comply with the implemented interface
|
132
117
|
@get_connection
|
133
|
-
def create_tables_code_first(self, path: str | Path) -> None:
|
118
|
+
def create_tables_code_first(self, cnx: MySQLConnection, path: str | Path) -> None:
|
134
119
|
if not isinstance(path, Path | str):
|
135
120
|
raise ValueError
|
136
121
|
|
@@ -145,33 +130,33 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
145
130
|
queries_list: list[str] = module_tree.get_queries()
|
146
131
|
|
147
132
|
for query in queries_list:
|
148
|
-
with
|
133
|
+
with cnx.cursor(buffered=True) as cursor:
|
149
134
|
cursor.execute(query)
|
150
|
-
|
135
|
+
cnx.commit()
|
151
136
|
return None
|
152
137
|
|
153
138
|
@override
|
154
139
|
@get_connection
|
155
|
-
def executemany_with_values(self, query: str, values) -> None:
|
156
|
-
with
|
140
|
+
def executemany_with_values(self, cnx: MySQLConnection, query: str, values) -> None:
|
141
|
+
with cnx.cursor(buffered=True) as cursor:
|
157
142
|
cursor.executemany(query, values)
|
158
|
-
|
143
|
+
cnx.commit()
|
159
144
|
return None
|
160
145
|
|
161
146
|
@override
|
162
147
|
@get_connection
|
163
|
-
def execute_with_values(self, query: str, values) -> None:
|
164
|
-
with
|
148
|
+
def execute_with_values(self, cnx: MySQLConnection, query: str, values) -> None:
|
149
|
+
with cnx.cursor(buffered=True) as cursor:
|
165
150
|
cursor.execute(query, values)
|
166
|
-
|
151
|
+
cnx.commit()
|
167
152
|
return None
|
168
153
|
|
169
154
|
@override
|
170
155
|
@get_connection
|
171
|
-
def execute(self, query: str) -> None:
|
172
|
-
with
|
156
|
+
def execute(self, cnx: MySQLConnection, query: str) -> None:
|
157
|
+
with cnx.cursor(buffered=True) as cursor:
|
173
158
|
cursor.execute(query)
|
174
|
-
|
159
|
+
cnx.commit()
|
175
160
|
return None
|
176
161
|
|
177
162
|
@override
|
@@ -180,9 +165,9 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
180
165
|
|
181
166
|
@override
|
182
167
|
@get_connection
|
183
|
-
def database_exists(self, name: str) -> bool:
|
168
|
+
def database_exists(self, cnx: MySQLConnection, name: str) -> bool:
|
184
169
|
query = "SHOW DATABASES LIKE %s;"
|
185
|
-
with
|
170
|
+
with cnx.cursor(buffered=True) as cursor:
|
186
171
|
cursor.execute(query, (name,))
|
187
172
|
res = cursor.fetchmany(1)
|
188
173
|
return len(res) > 0
|
@@ -193,12 +178,12 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
193
178
|
|
194
179
|
@override
|
195
180
|
@get_connection
|
196
|
-
def table_exists(self, name: str) -> bool:
|
197
|
-
if not
|
181
|
+
def table_exists(self, cnx: MySQLConnection, name: str) -> bool:
|
182
|
+
if not cnx.database:
|
198
183
|
raise Exception("No database selected")
|
199
184
|
|
200
185
|
query = "SHOW TABLES LIKE %s;"
|
201
|
-
with
|
186
|
+
with cnx.cursor(buffered=True) as cursor:
|
202
187
|
cursor.execute(query, (name,))
|
203
188
|
res = cursor.fetchmany(1)
|
204
189
|
return len(res) > 0
|
@@ -219,4 +204,4 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
|
|
219
204
|
@database.setter
|
220
205
|
def database(self, value: str) -> None:
|
221
206
|
self._data_config["database"] = value
|
222
|
-
self._pool = self.__create_MySQLConnectionPool()
|
207
|
+
self._pool = self.__create_MySQLConnectionPool()
|
@@ -3,15 +3,10 @@ from typing import override, Type, TYPE_CHECKING, Any, Callable, Optional
|
|
3
3
|
|
4
4
|
if TYPE_CHECKING:
|
5
5
|
from ormlambda import Table
|
6
|
-
from ormlambda.components.select import ISelect
|
7
6
|
from ormlambda.components.where.abstract_where import AbstractWhere
|
8
7
|
from ormlambda.common.interfaces.IStatements import OrderType
|
9
8
|
from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
|
10
|
-
from
|
11
|
-
|
12
|
-
from ormlambda.databases.my_sql.clauses.select import SelectQuery
|
13
|
-
from ormlambda.databases.my_sql.clauses.count import CountQuery
|
14
|
-
|
9
|
+
from ormlambda.common.interfaces.IRepositoryBase import TypeExists
|
15
10
|
|
16
11
|
from ormlambda import AbstractSQLStatements
|
17
12
|
from .clauses import DeleteQuery
|
@@ -20,11 +15,13 @@ from .clauses import JoinSelector
|
|
20
15
|
from .clauses import LimitQuery
|
21
16
|
from .clauses import OffsetQuery
|
22
17
|
from .clauses import OrderQuery
|
23
|
-
from .clauses import
|
18
|
+
from .clauses.select import Select
|
19
|
+
|
24
20
|
from .clauses import UpsertQuery
|
25
21
|
from .clauses import UpdateQuery
|
26
22
|
from .clauses import WhereCondition
|
27
|
-
from .clauses import
|
23
|
+
from .clauses import Count
|
24
|
+
from .clauses import GroupBy
|
28
25
|
|
29
26
|
from mysql.connector import MySQLConnection, errors, errorcode
|
30
27
|
|
@@ -129,10 +126,10 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
|
|
129
126
|
return self
|
130
127
|
|
131
128
|
@override
|
132
|
-
def count(self) -> int:
|
133
|
-
count_select: IQuery =
|
129
|
+
def count(self, selection: Callable[[T], tuple] = lambda x: "*") -> int:
|
130
|
+
count_select: IQuery = Select[T](self._model, lambda x: Count[T](self._model, selection))
|
134
131
|
self._query_list["select"].append(count_select)
|
135
|
-
query = self.
|
132
|
+
query = self._build()
|
136
133
|
return self.repository.read_sql(query)[0][0]
|
137
134
|
|
138
135
|
@override
|
@@ -165,10 +162,10 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
|
|
165
162
|
if flavour:
|
166
163
|
return result
|
167
164
|
return () if not result else result[0]
|
168
|
-
select
|
165
|
+
select = Select[T](self._model, lambda_query=selector, by=by, alias=False)
|
169
166
|
self._query_list["select"].append(select)
|
170
167
|
|
171
|
-
query: str = self.
|
168
|
+
query: str = self._build()
|
172
169
|
if flavour:
|
173
170
|
result = self._return_flavour(query, flavour)
|
174
171
|
if issubclass(flavour, tuple) and isinstance(selector(self._model), property):
|
@@ -194,12 +191,17 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
|
|
194
191
|
return tuple([res[0] for res in response])
|
195
192
|
|
196
193
|
@override
|
197
|
-
def
|
194
|
+
def group_by[TRepo, *Ts](self, column: Callable[[T], TRepo], select_query: Callable[[T], tuple[*Ts]]) -> tuple[tuple[*Ts]]:
|
195
|
+
return GroupBy[T, TRepo, tuple[*Ts]](self._model, column, select_query)
|
196
|
+
|
197
|
+
@override
|
198
|
+
def _build(self) -> str:
|
198
199
|
query: str = ""
|
199
200
|
|
200
|
-
self.__create_necessary_inner_join()
|
201
|
+
# self.__create_necessary_inner_join()
|
201
202
|
for x in self.__order__:
|
202
|
-
|
203
|
+
sub_query: Optional[list[IQuery]] = self._query_list.get(x, None)
|
204
|
+
if sub_query is not None:
|
203
205
|
if isinstance(sub_query[0], WhereCondition):
|
204
206
|
query_ = self.__build_where_clause(sub_query)
|
205
207
|
|
@@ -225,19 +227,3 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
|
|
225
227
|
and_, clause = q.split(" ", maxsplit=1)
|
226
228
|
query += f" {and_} ({clause})"
|
227
229
|
return query
|
228
|
-
|
229
|
-
def __create_necessary_inner_join(self) -> None:
|
230
|
-
# When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
|
231
|
-
if "where" not in self._query_list:
|
232
|
-
return None
|
233
|
-
|
234
|
-
where: AbstractWhere = self._query_list["where"][0]
|
235
|
-
involved_tables = where.get_involved_tables()
|
236
|
-
|
237
|
-
select: ISelect = self._query_list["select"][0]
|
238
|
-
if not involved_tables or (set(involved_tables) == set(select.tables_heritage)):
|
239
|
-
return None
|
240
|
-
|
241
|
-
for l_tbl, r_tbl in involved_tables:
|
242
|
-
# FIXME [ ]: Checked what function was called by the self.join method before the change
|
243
|
-
self.join(l_tbl, r_tbl, by="INNER JOIN")
|
ormlambda/model_base.py
CHANGED
@@ -24,6 +24,8 @@ class BaseModel[T: Type[Table]]:
|
|
24
24
|
# region Constructor
|
25
25
|
|
26
26
|
def __new__[TRepo](cls, model: T, repository: IRepositoryBase[TRepo]) -> IStatements_two_generic[T, TRepo]:
|
27
|
+
if repository is None:
|
28
|
+
raise ValueError("`None` cannot be passed to the `repository` attribute when calling the `BaseModel` class")
|
27
29
|
cls: AbstractSQLStatements[T, TRepo] = cls.statements_dicc.get(type(repository), None)
|
28
30
|
|
29
31
|
if not cls:
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import base64
|
2
2
|
import datetime
|
3
3
|
from decimal import Decimal
|
4
|
-
from typing import Any, Iterable, Optional, Type, dataclass_transform
|
4
|
+
from typing import Any, Iterable, Optional, Type, dataclass_transform, get_type_hints
|
5
5
|
import json
|
6
6
|
|
7
7
|
from .dtypes import get_query_clausule
|
@@ -14,9 +14,9 @@ MISSING = Column()
|
|
14
14
|
|
15
15
|
|
16
16
|
class Field:
|
17
|
-
def __init__(self, name: str, type_:
|
17
|
+
def __init__(self, name: str, type_: Type, default: object) -> None:
|
18
18
|
self.name: str = name
|
19
|
-
self.type_:
|
19
|
+
self.type_: Type = type_
|
20
20
|
self.default: Column = default
|
21
21
|
|
22
22
|
def __repr__(self) -> str:
|
@@ -54,7 +54,8 @@ def delete_special_variables(dicc: dict[str, object]) -> None:
|
|
54
54
|
|
55
55
|
|
56
56
|
def get_fields[T](cls: Type[T]) -> Iterable[Field]:
|
57
|
-
annotations
|
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("_")}
|
58
59
|
|
59
60
|
# delete_special_variables(annotations)
|
60
61
|
fields = []
|