ormlambda 1.5.0__py3-none-any.whl → 2.0.2__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 +9 -2
- ormlambda/common/__init__.py +0 -1
- ormlambda/common/abstract_classes/__init__.py +1 -1
- ormlambda/common/abstract_classes/abstract_model.py +13 -228
- ormlambda/common/abstract_classes/non_query_base.py +7 -5
- ormlambda/common/abstract_classes/query_base.py +5 -2
- ormlambda/common/enums/__init__.py +1 -1
- ormlambda/common/enums/join_type.py +2 -1
- ormlambda/common/interfaces/IQueryCommand.py +2 -1
- ormlambda/common/interfaces/IRepositoryBase.py +4 -18
- ormlambda/common/interfaces/IStatements.py +33 -15
- ormlambda/common/interfaces/__init__.py +1 -1
- ormlambda/components/delete/abstract_delete.py +6 -3
- ormlambda/components/insert/IInsert.py +1 -1
- ormlambda/components/insert/abstract_insert.py +8 -4
- ormlambda/components/select/ISelect.py +1 -1
- ormlambda/components/select/table_column.py +5 -1
- ormlambda/components/update/IUpdate.py +1 -1
- ormlambda/components/update/__init__.py +1 -1
- ormlambda/components/update/abstract_update.py +9 -5
- ormlambda/components/upsert/__init__.py +1 -1
- ormlambda/components/upsert/abstract_upsert.py +8 -4
- ormlambda/components/where/abstract_where.py +6 -2
- ormlambda/databases/my_sql/clauses/__init__.py +1 -0
- ormlambda/databases/my_sql/clauses/count.py +35 -0
- ormlambda/databases/my_sql/clauses/create_database.py +17 -10
- ormlambda/databases/my_sql/clauses/delete.py +7 -4
- ormlambda/databases/my_sql/clauses/drop_database.py +1 -1
- ormlambda/databases/my_sql/clauses/drop_table.py +1 -1
- ormlambda/databases/my_sql/clauses/insert.py +4 -3
- ormlambda/databases/my_sql/clauses/joins.py +8 -7
- ormlambda/databases/my_sql/clauses/limit.py +1 -1
- ormlambda/databases/my_sql/clauses/offset.py +1 -1
- ormlambda/databases/my_sql/clauses/order.py +3 -3
- ormlambda/databases/my_sql/clauses/select.py +5 -5
- ormlambda/databases/my_sql/clauses/update.py +3 -3
- ormlambda/databases/my_sql/clauses/upsert.py +3 -3
- ormlambda/databases/my_sql/clauses/where_condition.py +5 -5
- ormlambda/databases/my_sql/repository.py +57 -27
- ormlambda/databases/my_sql/statements.py +200 -43
- ormlambda/model_base.py +2 -4
- ormlambda/utils/column.py +4 -3
- ormlambda/utils/dtypes.py +6 -8
- ormlambda/utils/foreign_key.py +55 -10
- ormlambda/utils/lambda_disassembler/__init__.py +1 -1
- ormlambda/utils/lambda_disassembler/dis_types.py +22 -25
- ormlambda/utils/lambda_disassembler/tree_instruction.py +1 -1
- ormlambda/utils/module_tree/dynamic_module.py +6 -4
- ormlambda/utils/table_constructor.py +39 -22
- {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/METADATA +13 -9
- ormlambda-2.0.2.dist-info/RECORD +71 -0
- ormlambda-1.5.0.dist-info/RECORD +0 -70
- {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/LICENSE +0 -0
- {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/WHEEL +0 -0
@@ -1,11 +1,19 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import override, Type, TYPE_CHECKING, Any, Callable, Optional
|
2
3
|
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from ormlambda import Table
|
6
|
+
from ormlambda.components.select import ISelect
|
7
|
+
from ormlambda.components.where.abstract_where import AbstractWhere
|
8
|
+
from ormlambda.common.interfaces.IStatements import OrderType
|
9
|
+
from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
|
10
|
+
from src.ormlambda.common.interfaces.IRepositoryBase import TypeExists
|
3
11
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from ...common.interfaces import IQuery, IRepositoryBase
|
7
|
-
from ...components.select import ISelect
|
12
|
+
from ormlambda.databases.my_sql.clauses.select import SelectQuery
|
13
|
+
from ormlambda.databases.my_sql.clauses.count import CountQuery
|
8
14
|
|
15
|
+
|
16
|
+
from ormlambda import AbstractSQLStatements
|
9
17
|
from .clauses import DeleteQuery
|
10
18
|
from .clauses import InsertQuery
|
11
19
|
from .clauses import JoinSelector
|
@@ -16,71 +24,220 @@ from .clauses import SelectQuery
|
|
16
24
|
from .clauses import UpsertQuery
|
17
25
|
from .clauses import UpdateQuery
|
18
26
|
from .clauses import WhereCondition
|
27
|
+
from .clauses import CountQuery
|
28
|
+
|
29
|
+
from mysql.connector import MySQLConnection, errors, errorcode
|
30
|
+
|
31
|
+
|
32
|
+
import inspect
|
19
33
|
|
20
|
-
from
|
34
|
+
from ormlambda.utils import ForeignKey, Table
|
35
|
+
from ormlambda.common.enums import JoinType
|
21
36
|
|
22
37
|
|
23
38
|
class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
|
24
39
|
def __init__(self, model: T, repository: IRepositoryBase[MySQLConnection]) -> None:
|
25
40
|
super().__init__(model, repository=repository)
|
26
41
|
|
27
|
-
|
28
|
-
|
29
|
-
|
42
|
+
@property
|
43
|
+
@override
|
44
|
+
def repository(self) -> IRepositoryBase[MySQLConnection]:
|
45
|
+
return self._repository
|
30
46
|
|
31
|
-
|
47
|
+
@override
|
48
|
+
def create_table(self, if_exists: TypeExists = "fail") -> None:
|
49
|
+
name: str = self._model.__table_name__
|
50
|
+
if self._repository.table_exists(name):
|
51
|
+
if if_exists == "replace":
|
52
|
+
self._repository.drop_table(name)
|
53
|
+
|
54
|
+
elif if_exists == "fail":
|
55
|
+
raise errors.ProgrammingError(msg=f"Table '{self._model.__table_name__}' already exists", errno=errorcode.ER_TABLE_EXISTS_ERROR)
|
56
|
+
|
57
|
+
elif if_exists == "append":
|
58
|
+
counter: int = 0
|
59
|
+
char: str = ""
|
60
|
+
while self._repository.table_exists(name + char):
|
61
|
+
counter += 1
|
62
|
+
char = f"_{counter}"
|
63
|
+
name += char
|
64
|
+
self._model.__table_name__ = name
|
65
|
+
|
66
|
+
query = self._model.create_table_query()
|
67
|
+
self._repository.execute(query)
|
68
|
+
return None
|
32
69
|
|
33
|
-
@property
|
34
70
|
@override
|
35
|
-
def
|
36
|
-
return
|
71
|
+
def table_exists(self) -> bool:
|
72
|
+
return self._repository.table_exists(self._model.__table_name__)
|
37
73
|
|
38
|
-
@property
|
39
74
|
@override
|
40
|
-
def
|
41
|
-
|
75
|
+
def insert(self, instances: T | list[T]) -> None:
|
76
|
+
insert = InsertQuery(self._model, self._repository)
|
77
|
+
insert.insert(instances)
|
78
|
+
insert.execute()
|
79
|
+
self._query_list.clear()
|
80
|
+
return None
|
42
81
|
|
43
|
-
@property
|
44
82
|
@override
|
45
|
-
def
|
46
|
-
|
83
|
+
def delete(self, instances: Optional[T | list[T]] = None) -> None:
|
84
|
+
if instances is None:
|
85
|
+
response = self.select()
|
86
|
+
if len(response) == 0:
|
87
|
+
return None
|
88
|
+
# [0] because if we do not select anything, we retrieve all columns of the unic model, stored in tuple[tuple[model]] structure.
|
89
|
+
# We always going to have a tuple of one element
|
90
|
+
return self.delete(response)
|
91
|
+
|
92
|
+
delete = DeleteQuery(self._model, self._repository)
|
93
|
+
delete.delete(instances)
|
94
|
+
delete.execute()
|
95
|
+
# not necessary to call self._query_list.clear() because select() method already call it
|
96
|
+
return None
|
47
97
|
|
48
98
|
@override
|
49
|
-
|
50
|
-
|
51
|
-
|
99
|
+
def upsert(self, instances: T | list[T]) -> None:
|
100
|
+
upsert = UpsertQuery(self._model, self._repository)
|
101
|
+
upsert.upsert(instances)
|
102
|
+
upsert.execute()
|
103
|
+
self._query_list.clear()
|
104
|
+
return None
|
52
105
|
|
53
|
-
@property
|
54
106
|
@override
|
55
|
-
def
|
56
|
-
|
107
|
+
def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
|
108
|
+
update = UpdateQuery(self._model, self._repository, self._query_list["where"])
|
109
|
+
update.update(dicc)
|
110
|
+
update.execute()
|
111
|
+
self._query_list.clear()
|
112
|
+
return None
|
57
113
|
|
58
|
-
@property
|
59
114
|
@override
|
60
|
-
def
|
61
|
-
|
115
|
+
def limit(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
|
116
|
+
limit = LimitQuery(number)
|
117
|
+
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
118
|
+
limit_list = self._query_list["limit"]
|
119
|
+
if len(limit_list) > 0:
|
120
|
+
self._query_list["limit"] = [limit]
|
121
|
+
else:
|
122
|
+
self._query_list["limit"].append(limit)
|
123
|
+
return self
|
62
124
|
|
63
|
-
@property
|
64
125
|
@override
|
65
|
-
def
|
66
|
-
|
126
|
+
def offset(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
|
127
|
+
offset = OffsetQuery(number)
|
128
|
+
self._query_list["offset"].append(offset)
|
129
|
+
return self
|
67
130
|
|
68
|
-
@property
|
69
131
|
@override
|
70
|
-
def
|
71
|
-
|
132
|
+
def count(self) -> int:
|
133
|
+
count_select: IQuery = CountQuery(self._model)
|
134
|
+
self._query_list["select"].append(count_select)
|
135
|
+
query = self.build()
|
136
|
+
return self.repository.read_sql(query)[0][0]
|
72
137
|
|
73
|
-
@property
|
74
138
|
@override
|
75
|
-
def
|
76
|
-
|
139
|
+
def join(self, table_left: Table, table_right: Table, *, by: str) -> IStatements_two_generic[T, MySQLConnection]:
|
140
|
+
where = ForeignKey.MAPPED[table_left.__table_name__][table_right.__table_name__]
|
141
|
+
join_query = JoinSelector[table_left, Table](table_left, table_right, JoinType(by), where=where)
|
142
|
+
self._query_list["join"].append(join_query)
|
143
|
+
return self
|
77
144
|
|
78
|
-
@property
|
79
145
|
@override
|
80
|
-
def
|
81
|
-
|
146
|
+
def where(self, lambda_: Callable[[T], bool] = lambda: None, **kwargs) -> IStatements_two_generic[T, MySQLConnection]:
|
147
|
+
# FIXME [x]: I've wrapped self._model into tuple to pass it instance attr. Idk if it's correct
|
148
|
+
where_query = WhereCondition[T](function=lambda_, instances=(self._model,), **kwargs)
|
149
|
+
self._query_list["where"].append(where_query)
|
150
|
+
return self
|
82
151
|
|
83
|
-
@property
|
84
152
|
@override
|
85
|
-
def
|
86
|
-
|
153
|
+
def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderType) -> IStatements_two_generic[T, MySQLConnection]:
|
154
|
+
order = OrderQuery[T](self._model, _lambda_col, order_type)
|
155
|
+
self._query_list["order"].append(order)
|
156
|
+
return self
|
157
|
+
|
158
|
+
@override
|
159
|
+
def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN):
|
160
|
+
if len(inspect.signature(selector).parameters) == 0:
|
161
|
+
# COMMENT: if we do not specify any lambda function we assumed the user want to retreive only elements of the Model itself avoiding other models
|
162
|
+
result = self.select(selector=lambda x: (x,), flavour=flavour, by=by)
|
163
|
+
# COMMENT: Always we want to retrieve tuple[tuple[Any]]. That's the reason to return result[0] when we ensure the user want only objects of the first table.
|
164
|
+
# Otherwise, we wil return the result itself
|
165
|
+
if flavour:
|
166
|
+
return result
|
167
|
+
return () if not result else result[0]
|
168
|
+
select: ISelect = SelectQuery(self._model, select_lambda=selector, by=by)
|
169
|
+
self._query_list["select"].append(select)
|
170
|
+
|
171
|
+
query: str = self.build()
|
172
|
+
if flavour:
|
173
|
+
result = self._return_flavour(query, flavour)
|
174
|
+
if issubclass(flavour, tuple) and isinstance(selector(self._model), property):
|
175
|
+
return tuple([x[0] for x in result])
|
176
|
+
return result
|
177
|
+
return self._return_model(select, query)
|
178
|
+
|
179
|
+
@override
|
180
|
+
def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN):
|
181
|
+
self.limit(1)
|
182
|
+
if len(inspect.signature(selector).parameters) == 0:
|
183
|
+
response = self.select(selector=lambda x: (x,), flavour=flavour, by=by)
|
184
|
+
else:
|
185
|
+
response = self.select(selector=selector, flavour=flavour, by=by)
|
186
|
+
|
187
|
+
if flavour:
|
188
|
+
return response[0] if response else None
|
189
|
+
|
190
|
+
# response var could be return more than one element when we work with models an we
|
191
|
+
# select columns from different tables using a join query
|
192
|
+
if len(response) == 1 and len(response[0]) == 1:
|
193
|
+
return response[0][0]
|
194
|
+
return tuple([res[0] for res in response])
|
195
|
+
|
196
|
+
@override
|
197
|
+
def build(self) -> str:
|
198
|
+
query: str = ""
|
199
|
+
|
200
|
+
self.__create_necessary_inner_join()
|
201
|
+
for x in self.__order__:
|
202
|
+
if sub_query := self._query_list.get(x, None):
|
203
|
+
if isinstance(sub_query[0], WhereCondition):
|
204
|
+
query_ = self.__build_where_clause(sub_query)
|
205
|
+
|
206
|
+
# we must check if any join already exists on query string
|
207
|
+
elif isinstance(sub_query[0], JoinSelector):
|
208
|
+
select_query: str = self._query_list["select"][0].query
|
209
|
+
query_ = ""
|
210
|
+
for join in sub_query:
|
211
|
+
if join.query not in select_query:
|
212
|
+
query_ += f"\n{join.query}"
|
213
|
+
else:
|
214
|
+
query_ = "\n".join([x.query for x in sub_query])
|
215
|
+
|
216
|
+
query += f"\n{query_}" if query != "" else query_
|
217
|
+
self._query_list.clear()
|
218
|
+
return query
|
219
|
+
|
220
|
+
def __build_where_clause(self, where_condition: list[AbstractWhere]) -> str:
|
221
|
+
query: str = where_condition[0].query
|
222
|
+
|
223
|
+
for where in where_condition[1:]:
|
224
|
+
q = where.query.replace(where.WHERE, "AND")
|
225
|
+
and_, clause = q.split(" ", maxsplit=1)
|
226
|
+
query += f" {and_} ({clause})"
|
227
|
+
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
@@ -1,4 +1,3 @@
|
|
1
|
-
# region imports
|
2
1
|
from typing import Type
|
3
2
|
|
4
3
|
|
@@ -8,11 +7,10 @@ from .common.abstract_classes import AbstractSQLStatements
|
|
8
7
|
from .databases.my_sql import MySQLStatements, MySQLRepository
|
9
8
|
|
10
9
|
|
11
|
-
|
12
10
|
# endregion
|
13
11
|
|
14
12
|
|
15
|
-
class BaseModel[T: Table]:
|
13
|
+
class BaseModel[T: Type[Table]]:
|
16
14
|
"""
|
17
15
|
Class to select the correct AbstractSQLStatements class depends on the repository.
|
18
16
|
|
@@ -29,7 +27,7 @@ class BaseModel[T: Table]:
|
|
29
27
|
cls: AbstractSQLStatements[T, TRepo] = cls.statements_dicc.get(type(repository), None)
|
30
28
|
|
31
29
|
if not cls:
|
32
|
-
raise Exception(f"
|
30
|
+
raise Exception(f"The selected repository '{repository}' does not exist.")
|
33
31
|
|
34
32
|
self = object().__new__(cls)
|
35
33
|
cls.__init__(self, model, repository)
|
ormlambda/utils/column.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from typing import Type
|
2
2
|
|
3
|
+
|
3
4
|
class Column[T]:
|
4
5
|
__slots__ = (
|
5
6
|
"dtype",
|
@@ -13,7 +14,7 @@ class Column[T]:
|
|
13
14
|
|
14
15
|
def __init__(
|
15
16
|
self,
|
16
|
-
dtype:Type[T] = None,
|
17
|
+
dtype: Type[T] = None,
|
17
18
|
column_name: str = None,
|
18
19
|
column_value: T = None,
|
19
20
|
*,
|
@@ -35,9 +36,9 @@ class Column[T]:
|
|
35
36
|
|
36
37
|
def __to_string__(self, name: str, var_name: T, type_: str):
|
37
38
|
dicc: dict = {
|
38
|
-
"dtype":type_,
|
39
|
+
"dtype": type_,
|
39
40
|
"column_name": f"'{name}'",
|
40
|
-
"column_value": var_name,
|
41
|
+
"column_value": var_name, # must be the same variable name as the instance variable name in Table's __init__ class
|
41
42
|
}
|
42
43
|
exec_str: str = f"{Column.__name__}[{type_}]("
|
43
44
|
for x in self.__slots__:
|
ormlambda/utils/dtypes.py
CHANGED
@@ -49,7 +49,7 @@ MySQL 8.0 does not support year in two-digit format.
|
|
49
49
|
|
50
50
|
from decimal import Decimal
|
51
51
|
import datetime
|
52
|
-
from typing import Any,Literal
|
52
|
+
from typing import Any, Literal
|
53
53
|
import numpy as np
|
54
54
|
|
55
55
|
from .column import Column
|
@@ -61,10 +61,6 @@ NUMERIC_UNSIGNED = Literal["BIT(size)", "TINYINT(size)", "BOOL", "BOOLEAN", "SMA
|
|
61
61
|
DATE = Literal["DATE", "DATETIME(fsp)", "TIMESTAMP(fsp)", "TIME(fsp)", "YEAR"]
|
62
62
|
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
64
|
# FIXME [ ]: this method does not comply with the implemented interface; we need to adjust it in the future to scale it to other databases
|
69
65
|
@staticmethod
|
70
66
|
def transform_py_dtype_into_query_dtype(dtype: Any) -> str:
|
@@ -77,15 +73,17 @@ def transform_py_dtype_into_query_dtype(dtype: Any) -> str:
|
|
77
73
|
datetime.datetime: "DATETIME",
|
78
74
|
datetime.date: "DATE",
|
79
75
|
bytes: "BLOB",
|
76
|
+
bytearray: "BLOB",
|
80
77
|
str: "VARCHAR(255)",
|
81
|
-
np.uint64: "BIGINT UNSIGNED"
|
78
|
+
np.uint64: "BIGINT UNSIGNED",
|
82
79
|
}
|
83
80
|
|
81
|
+
res = dicc.get(dtype, None)
|
82
|
+
if res is None:
|
83
|
+
raise ValueError(f"datatype '{dtype}' is not expected.")
|
84
84
|
return dicc[dtype]
|
85
85
|
|
86
86
|
|
87
|
-
|
88
|
-
|
89
87
|
# FIXME [ ]: this method does not comply with the implemented interface; we need to adjust it in the future to scale it to other databases
|
90
88
|
def get_query_clausule(column_obj: Column) -> str:
|
91
89
|
dtype: str = transform_py_dtype_into_query_dtype(column_obj.dtype)
|
ormlambda/utils/foreign_key.py
CHANGED
@@ -1,13 +1,51 @@
|
|
1
|
-
from
|
2
|
-
from typing import Callable, TYPE_CHECKING, Type
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Callable, TYPE_CHECKING, NamedTuple, Type, Optional, overload
|
3
3
|
from .lambda_disassembler import Disassembler
|
4
4
|
|
5
5
|
if TYPE_CHECKING:
|
6
6
|
from .table_constructor import Table
|
7
7
|
|
8
8
|
|
9
|
-
class
|
10
|
-
|
9
|
+
class ReferencedTable[T1: Type[Table], T2: Type[Table]](NamedTuple):
|
10
|
+
obj: T2
|
11
|
+
relationship: Callable[[T1, T2], bool]
|
12
|
+
|
13
|
+
|
14
|
+
class TableInfo[T1: Type[Table], T2: Type[Table]]:
|
15
|
+
@overload
|
16
|
+
def __init__(self) -> None: ...
|
17
|
+
@overload
|
18
|
+
def __init__(self, table_object: T1) -> None: ...
|
19
|
+
|
20
|
+
def __init__(self, table_object: Optional[T1] = None) -> None:
|
21
|
+
self._table_object: Optional[T1] = table_object
|
22
|
+
self._referenced_tables: dict[str, ReferencedTable[T1, T2]] = {}
|
23
|
+
|
24
|
+
def __repr__(self) -> str:
|
25
|
+
return f"<{TableInfo.__name__}> class '{self.table_object}' dependent tables -> [{', '.join(tuple(self.referenced_tables))}]"
|
26
|
+
|
27
|
+
@property
|
28
|
+
def referenced_tables(self) -> dict[str, ReferencedTable[T1, T2]]:
|
29
|
+
return self._referenced_tables
|
30
|
+
|
31
|
+
def update_referenced_tables(self, referenced_table: Type[Table], relationship: Callable[[T1, T2], bool]) -> None:
|
32
|
+
self._referenced_tables.update({referenced_table.__table_name__: ReferencedTable[T1, T2](referenced_table, relationship)})
|
33
|
+
|
34
|
+
@property
|
35
|
+
def table_object(self) -> Optional[Type[Table]]:
|
36
|
+
return self._table_object
|
37
|
+
|
38
|
+
@table_object.setter
|
39
|
+
def table_object(self, value: Type[Table]) -> None:
|
40
|
+
self._table_object = value
|
41
|
+
|
42
|
+
@property
|
43
|
+
def has_relationship(self) -> bool:
|
44
|
+
return len(self._referenced_tables) > 0
|
45
|
+
|
46
|
+
|
47
|
+
class ForeignKey[Tbl1: Type[Table], Tbl2: Type[Table]]:
|
48
|
+
MAPPED: dict[str, TableInfo[Tbl1, Tbl2]] = {}
|
11
49
|
|
12
50
|
def __new__(
|
13
51
|
cls,
|
@@ -20,17 +58,24 @@ class ForeignKey[Tbl1:Table, Tbl2:Table]:
|
|
20
58
|
return referenced_table
|
21
59
|
|
22
60
|
@classmethod
|
23
|
-
def add_foreign_key(cls, orig_table: str, referenced_table:
|
24
|
-
cls.MAPPED
|
61
|
+
def add_foreign_key(cls, orig_table: str, referenced_table: Table, relationship: Callable[[Tbl1, Tbl2], bool]) -> None:
|
62
|
+
if orig_table not in cls.MAPPED:
|
63
|
+
cls.MAPPED[orig_table] = TableInfo()
|
64
|
+
|
65
|
+
# if referenced_table not in cls.MAPPED[orig_table]:
|
66
|
+
cls.MAPPED[orig_table].update_referenced_tables(referenced_table, relationship)
|
67
|
+
|
25
68
|
return None
|
26
69
|
|
27
70
|
@classmethod
|
28
|
-
def create_query(cls, orig_table:
|
71
|
+
def create_query(cls, orig_table: Table) -> list[str]:
|
29
72
|
clauses: list[str] = []
|
30
|
-
|
31
|
-
|
73
|
+
|
74
|
+
fk: TableInfo[Tbl1, Tbl2] = ForeignKey[Tbl1, Tbl2].MAPPED[orig_table.__table_name__]
|
75
|
+
for referenced_table_obj in fk.referenced_tables.values():
|
76
|
+
dissambler: Disassembler = Disassembler(referenced_table_obj.relationship)
|
32
77
|
orig_col: str = dissambler.cond_1.name
|
33
78
|
referenced_col: str = dissambler.cond_2.name
|
34
79
|
|
35
|
-
clauses.append(f"FOREIGN KEY ({orig_col}) REFERENCES {
|
80
|
+
clauses.append(f"FOREIGN KEY ({orig_col}) REFERENCES {referenced_table_obj.obj.__table_name__}({referenced_col})")
|
36
81
|
return clauses
|
@@ -1,4 +1,4 @@
|
|
1
1
|
from .disassembler import Disassembler # noqa: F401
|
2
2
|
from .nested_element import NestedElement # noqa: F401
|
3
3
|
from .tree_instruction import TreeInstruction, TupleInstruction # noqa: F401
|
4
|
-
from .name_of import nameof # noqa: F401
|
4
|
+
from .name_of import nameof # noqa: F401
|
@@ -1,9 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
4
|
class OpName(Enum):
|
8
5
|
CACHE = "CACHE"
|
9
6
|
POP_TOP = "POP_TOP"
|
@@ -112,25 +109,25 @@ class OpName(Enum):
|
|
112
109
|
INSTRUMENTED_END_SEND = "INSTRUMENTED_END_SEND"
|
113
110
|
INSTRUMENTED_INSTRUCTION = "INSTRUMENTED_INSTRUCTION"
|
114
111
|
INSTRUMENTED_LINE = "INSTRUMENTED_LINE"
|
115
|
-
FOR_ITER=
|
116
|
-
JUMP_FORWARD=
|
117
|
-
POP_JUMP_IF_FALSE=
|
118
|
-
POP_JUMP_IF_TRUE=
|
119
|
-
SEND=
|
120
|
-
POP_JUMP_IF_NOT_NONE=
|
121
|
-
POP_JUMP_IF_NONE=
|
122
|
-
JUMP_BACKWARD_NO_INTERRUPT=
|
123
|
-
JUMP_BACKWARD=
|
124
|
-
STORE_NAME=
|
125
|
-
DELETE_NAME=
|
126
|
-
STORE_ATTR=
|
127
|
-
DELETE_ATTR=
|
128
|
-
STORE_GLOBAL=
|
129
|
-
DELETE_GLOBAL=
|
130
|
-
LOAD_NAME=
|
131
|
-
LOAD_ATTR=
|
132
|
-
IMPORT_NAME=
|
133
|
-
IMPORT_FROM=
|
134
|
-
LOAD_GLOBAL=
|
135
|
-
LOAD_SUPER_ATTR=
|
136
|
-
LOAD_FROM_DICT_OR_GLOBALS=
|
112
|
+
FOR_ITER = "FOR_ITER"
|
113
|
+
JUMP_FORWARD = "JUMP_FORWARD"
|
114
|
+
POP_JUMP_IF_FALSE = "POP_JUMP_IF_FALSE"
|
115
|
+
POP_JUMP_IF_TRUE = "POP_JUMP_IF_TRUE"
|
116
|
+
SEND = "SEND"
|
117
|
+
POP_JUMP_IF_NOT_NONE = "POP_JUMP_IF_NOT_NONE"
|
118
|
+
POP_JUMP_IF_NONE = "POP_JUMP_IF_NONE"
|
119
|
+
JUMP_BACKWARD_NO_INTERRUPT = "JUMP_BACKWARD_NO_INTERRUPT"
|
120
|
+
JUMP_BACKWARD = "JUMP_BACKWARD"
|
121
|
+
STORE_NAME = "STORE_NAME"
|
122
|
+
DELETE_NAME = "DELETE_NAME"
|
123
|
+
STORE_ATTR = "STORE_ATTR"
|
124
|
+
DELETE_ATTR = "DELETE_ATTR"
|
125
|
+
STORE_GLOBAL = "STORE_GLOBAL"
|
126
|
+
DELETE_GLOBAL = "DELETE_GLOBAL"
|
127
|
+
LOAD_NAME = "LOAD_NAME"
|
128
|
+
LOAD_ATTR = "LOAD_ATTR"
|
129
|
+
IMPORT_NAME = "IMPORT_NAME"
|
130
|
+
IMPORT_FROM = "IMPORT_FROM"
|
131
|
+
LOAD_GLOBAL = "LOAD_GLOBAL"
|
132
|
+
LOAD_SUPER_ATTR = "LOAD_SUPER_ATTR"
|
133
|
+
LOAD_FROM_DICT_OR_GLOBALS = "LOAD_FROM_DICT_OR_GLOBALS"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
from typing import Any, Callable, NamedTuple, Self, Optional
|
3
3
|
from dis import Instruction, Bytecode
|
4
|
-
from
|
4
|
+
from ormlambda.common.enums.condition_types import ConditionType
|
5
5
|
from .dis_types import OpName
|
6
6
|
from .nested_element import NestedElement
|
7
7
|
|
@@ -6,7 +6,9 @@ import importlib.util
|
|
6
6
|
import inspect
|
7
7
|
import re
|
8
8
|
|
9
|
-
from
|
9
|
+
from ormlambda import ForeignKey
|
10
|
+
|
11
|
+
from ormlambda import Table
|
10
12
|
from .dfs_traversal import DFSTraversal
|
11
13
|
|
12
14
|
|
@@ -180,12 +182,12 @@ class ModuleTree:
|
|
180
182
|
"""
|
181
183
|
tables: list[tuple[str, Table]] = self.get_member_table(self.load_module("", self.module_path))
|
182
184
|
|
183
|
-
graph: dict[
|
185
|
+
graph: dict[str, list[str]] = defaultdict(list)
|
184
186
|
for _, tbl in tables:
|
185
|
-
graph[tbl] = tbl.find_dependent_tables()
|
187
|
+
graph[tbl.__table_name__] = [x.__table_name__ for x in tbl.find_dependent_tables()]
|
186
188
|
|
187
189
|
sorted_tables = DFSTraversal.sort(graph)
|
188
|
-
res = [x.create_table_query() for x in sorted_tables]
|
190
|
+
res = [ForeignKey.MAPPED[x].table_object.create_table_query() for x in sorted_tables]
|
189
191
|
return res
|
190
192
|
|
191
193
|
@staticmethod
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import base64
|
2
|
-
from collections import defaultdict
|
3
2
|
import datetime
|
4
3
|
from decimal import Decimal
|
5
4
|
from typing import Any, Iterable, Optional, Type, dataclass_transform
|
@@ -9,7 +8,7 @@ from .dtypes import get_query_clausule
|
|
9
8
|
from .module_tree.dfs_traversal import DFSTraversal
|
10
9
|
from .column import Column
|
11
10
|
|
12
|
-
from .foreign_key import ForeignKey
|
11
|
+
from .foreign_key import ForeignKey, TableInfo
|
13
12
|
|
14
13
|
MISSING = Column()
|
15
14
|
|
@@ -156,7 +155,7 @@ class TableMeta(type):
|
|
156
155
|
if not isinstance(cls_object.__table_name__, str):
|
157
156
|
raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
|
158
157
|
|
159
|
-
TableMeta.
|
158
|
+
TableMeta.__add_to_ForeignKey(cls_object)
|
160
159
|
self = __init_constructor__(cls_object)
|
161
160
|
return self
|
162
161
|
|
@@ -164,14 +163,17 @@ class TableMeta(type):
|
|
164
163
|
return f"{TableMeta.__name__}: {cls.__table_name__}"
|
165
164
|
|
166
165
|
@staticmethod
|
167
|
-
def
|
166
|
+
def __add_to_ForeignKey(cls: "Table") -> None:
|
168
167
|
"""
|
169
168
|
When creating a Table class, we cannot pass the class itself as a parameter in a function that initializes a class variable.
|
170
|
-
To fix this, we first add the table name and then,
|
169
|
+
To fix this, we first add the table name as key and then, we add the class itself in the TableInfo class.
|
171
170
|
"""
|
172
|
-
if
|
173
|
-
|
174
|
-
|
171
|
+
if table_info := ForeignKey.MAPPED.get(cls.__table_name__, None):
|
172
|
+
table_info.table_object = cls
|
173
|
+
else:
|
174
|
+
ForeignKey.MAPPED[cls.__table_name__] = TableInfo()
|
175
|
+
ForeignKey.MAPPED[cls.__table_name__].table_object = cls
|
176
|
+
|
175
177
|
return None
|
176
178
|
|
177
179
|
|
@@ -285,24 +287,39 @@ class Table(metaclass=TableMeta):
|
|
285
287
|
|
286
288
|
@classmethod
|
287
289
|
def find_dependent_tables(cls) -> tuple["Table", ...]:
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
290
|
+
# TODOL: Dive into new way to return dependent tables
|
291
|
+
def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
|
292
|
+
"""
|
293
|
+
Create a graph to be ordered
|
294
|
+
"""
|
295
|
+
table = ForeignKey[Table, Table].MAPPED[table_name]
|
296
|
+
for x in table.referenced_tables:
|
297
|
+
if data := ForeignKey.MAPPED.get(x, None):
|
298
|
+
get_involved_tables(graph, data.table_object.__table_name__)
|
299
|
+
|
300
|
+
graph[table.table_object.__table_name__] = list(table.referenced_tables)
|
301
|
+
return None
|
302
|
+
|
303
|
+
graph: dict[Table, list[Table]] = {}
|
304
|
+
dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
|
305
|
+
if dependent is None:
|
306
|
+
return tuple([])
|
307
|
+
|
308
|
+
graph[cls.__table_name__] = list(dependent.referenced_tables)
|
309
|
+
get_involved_tables(graph, cls.__table_name__)
|
298
310
|
|
299
311
|
dfs = DFSTraversal.sort(graph)
|
300
|
-
return dfs[: dfs.index(cls)]
|
301
312
|
|
302
|
-
|
303
|
-
|
313
|
+
order_table = dfs[: dfs.index(cls.__table_name__)]
|
314
|
+
|
315
|
+
return [ForeignKey.MAPPED[x].table_object for x in order_table]
|
304
316
|
|
305
317
|
def __eq__(self, __value: Any) -> bool:
|
306
318
|
if isinstance(__value, Table):
|
307
|
-
return
|
319
|
+
return all(
|
320
|
+
(
|
321
|
+
self.__table_name__ == __value.__table_name__,
|
322
|
+
tuple(self.to_dict().items()),
|
323
|
+
)
|
324
|
+
)
|
308
325
|
return False
|