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.
Files changed (54) hide show
  1. ormlambda/__init__.py +9 -2
  2. ormlambda/common/__init__.py +0 -1
  3. ormlambda/common/abstract_classes/__init__.py +1 -1
  4. ormlambda/common/abstract_classes/abstract_model.py +13 -228
  5. ormlambda/common/abstract_classes/non_query_base.py +7 -5
  6. ormlambda/common/abstract_classes/query_base.py +5 -2
  7. ormlambda/common/enums/__init__.py +1 -1
  8. ormlambda/common/enums/join_type.py +2 -1
  9. ormlambda/common/interfaces/IQueryCommand.py +2 -1
  10. ormlambda/common/interfaces/IRepositoryBase.py +4 -18
  11. ormlambda/common/interfaces/IStatements.py +33 -15
  12. ormlambda/common/interfaces/__init__.py +1 -1
  13. ormlambda/components/delete/abstract_delete.py +6 -3
  14. ormlambda/components/insert/IInsert.py +1 -1
  15. ormlambda/components/insert/abstract_insert.py +8 -4
  16. ormlambda/components/select/ISelect.py +1 -1
  17. ormlambda/components/select/table_column.py +5 -1
  18. ormlambda/components/update/IUpdate.py +1 -1
  19. ormlambda/components/update/__init__.py +1 -1
  20. ormlambda/components/update/abstract_update.py +9 -5
  21. ormlambda/components/upsert/__init__.py +1 -1
  22. ormlambda/components/upsert/abstract_upsert.py +8 -4
  23. ormlambda/components/where/abstract_where.py +6 -2
  24. ormlambda/databases/my_sql/clauses/__init__.py +1 -0
  25. ormlambda/databases/my_sql/clauses/count.py +35 -0
  26. ormlambda/databases/my_sql/clauses/create_database.py +17 -10
  27. ormlambda/databases/my_sql/clauses/delete.py +7 -4
  28. ormlambda/databases/my_sql/clauses/drop_database.py +1 -1
  29. ormlambda/databases/my_sql/clauses/drop_table.py +1 -1
  30. ormlambda/databases/my_sql/clauses/insert.py +4 -3
  31. ormlambda/databases/my_sql/clauses/joins.py +8 -7
  32. ormlambda/databases/my_sql/clauses/limit.py +1 -1
  33. ormlambda/databases/my_sql/clauses/offset.py +1 -1
  34. ormlambda/databases/my_sql/clauses/order.py +3 -3
  35. ormlambda/databases/my_sql/clauses/select.py +5 -5
  36. ormlambda/databases/my_sql/clauses/update.py +3 -3
  37. ormlambda/databases/my_sql/clauses/upsert.py +3 -3
  38. ormlambda/databases/my_sql/clauses/where_condition.py +5 -5
  39. ormlambda/databases/my_sql/repository.py +57 -27
  40. ormlambda/databases/my_sql/statements.py +200 -43
  41. ormlambda/model_base.py +2 -4
  42. ormlambda/utils/column.py +4 -3
  43. ormlambda/utils/dtypes.py +6 -8
  44. ormlambda/utils/foreign_key.py +55 -10
  45. ormlambda/utils/lambda_disassembler/__init__.py +1 -1
  46. ormlambda/utils/lambda_disassembler/dis_types.py +22 -25
  47. ormlambda/utils/lambda_disassembler/tree_instruction.py +1 -1
  48. ormlambda/utils/module_tree/dynamic_module.py +6 -4
  49. ormlambda/utils/table_constructor.py +39 -22
  50. {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/METADATA +13 -9
  51. ormlambda-2.0.2.dist-info/RECORD +71 -0
  52. ormlambda-1.5.0.dist-info/RECORD +0 -70
  53. {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/LICENSE +0 -0
  54. {ormlambda-1.5.0.dist-info → ormlambda-2.0.2.dist-info}/WHEEL +0 -0
@@ -1,11 +1,19 @@
1
- from typing import override, Type
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 ...common.abstract_classes import AbstractSQLStatements
5
- from ...utils import Table
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 mysql.connector import MySQLConnection
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
- # region Private methods
28
- def __repr__(self):
29
- return f"<Model: {self.__class__.__name__}>"
42
+ @property
43
+ @override
44
+ def repository(self) -> IRepositoryBase[MySQLConnection]:
45
+ return self._repository
30
46
 
31
- # endregion
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 INSERT_QUERY(self) -> Type[InsertQuery]:
36
- return InsertQuery
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 UPSERT_QUERY(self) -> Type[UpsertQuery]:
41
- return UpsertQuery
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 UPDATE_QUERY(self) -> Type[UpsertQuery]:
46
- return UpdateQuery
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
- @property
50
- def DELETE_QUERY(self) -> Type[DeleteQuery]:
51
- return DeleteQuery
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 LIMIT_QUERY(self) -> Type[IQuery]:
56
- return LimitQuery
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 OFFSET_QUERY(self) -> Type[IQuery]:
61
- return OffsetQuery
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 JOIN_QUERY(self) -> Type[IQuery]:
66
- return JoinSelector
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 WHERE_QUERY(self) -> Type[IQuery]:
71
- return WhereCondition
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 ORDER_QUERY(self) -> Type[IQuery]:
76
- return OrderQuery
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 SELECT_QUERY(self) -> Type[ISelect]:
81
- return SelectQuery
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 repository(self) -> IRepositoryBase[MySQLConnection]:
86
- return self._repository
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"Repository selected does not exits '{repository}'")
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, # must be the same variable name as the instance variable name in Table's __init__ class
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)
@@ -1,13 +1,51 @@
1
- from collections import defaultdict
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 ForeignKey[Tbl1:Table, Tbl2:Table]:
10
- MAPPED: dict[Tbl1, dict[Tbl2, Callable[[Tbl1, Tbl2], bool]]] = defaultdict(dict)
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: "Table", relationship: Callable[[Tbl1, Tbl2], bool]) -> None:
24
- cls.MAPPED[orig_table][referenced_table] = relationship
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: "Table") -> list[str]:
71
+ def create_query(cls, orig_table: Table) -> list[str]:
29
72
  clauses: list[str] = []
30
- for referenced_table, _lambda in ForeignKey[Tbl1, Tbl2].MAPPED[orig_table].items():
31
- dissambler: Disassembler = Disassembler(_lambda)
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 {referenced_table.__table_name__}({referenced_col})")
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= 'FOR_ITER'
116
- JUMP_FORWARD= 'JUMP_FORWARD'
117
- POP_JUMP_IF_FALSE= 'POP_JUMP_IF_FALSE'
118
- POP_JUMP_IF_TRUE= 'POP_JUMP_IF_TRUE'
119
- SEND= 'SEND'
120
- POP_JUMP_IF_NOT_NONE= 'POP_JUMP_IF_NOT_NONE'
121
- POP_JUMP_IF_NONE= 'POP_JUMP_IF_NONE'
122
- JUMP_BACKWARD_NO_INTERRUPT= 'JUMP_BACKWARD_NO_INTERRUPT'
123
- JUMP_BACKWARD= 'JUMP_BACKWARD'
124
- STORE_NAME= 'STORE_NAME'
125
- DELETE_NAME= 'DELETE_NAME'
126
- STORE_ATTR= 'STORE_ATTR'
127
- DELETE_ATTR= 'DELETE_ATTR'
128
- STORE_GLOBAL= 'STORE_GLOBAL'
129
- DELETE_GLOBAL= 'DELETE_GLOBAL'
130
- LOAD_NAME= 'LOAD_NAME'
131
- LOAD_ATTR= 'LOAD_ATTR'
132
- IMPORT_NAME= 'IMPORT_NAME'
133
- IMPORT_FROM= 'IMPORT_FROM'
134
- LOAD_GLOBAL= 'LOAD_GLOBAL'
135
- LOAD_SUPER_ATTR= 'LOAD_SUPER_ATTR'
136
- LOAD_FROM_DICT_OR_GLOBALS= '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 ...common.enums.condition_types import ConditionType
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 ..table_constructor import Table
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[Type[Table], list[Type[Table]]] = defaultdict(list)
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.__add_fk_if_exists__(cls_object)
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 __add_fk_if_exists__(cls: "Table") -> None:
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, when instantiating the object, we replace the table name with the class itself.
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 data := ForeignKey.MAPPED.get(cls.__table_name__):
173
- ForeignKey.MAPPED[cls] = data
174
- del ForeignKey.MAPPED[cls.__table_name__]
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
- def get_involved_tables(graph: dict[Table, list[Table]], table: Table) -> None:
289
- for tbl in ForeignKey[Table, Table].MAPPED[table].keys():
290
- if ForeignKey.MAPPED[tbl]:
291
- get_involved_tables(graph, tbl)
292
- graph[tbl] = list(ForeignKey.MAPPED[tbl].keys())
293
- return None
294
-
295
- graph: dict[Table, list[Table]] = defaultdict(list)
296
- graph[cls] = list(ForeignKey.MAPPED[cls].keys())
297
- get_involved_tables(graph, cls)
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
- def __hash__(self) -> int:
303
- return hash(self.to_dict().values())
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 hash(self) == hash(__value)
319
+ return all(
320
+ (
321
+ self.__table_name__ == __value.__table_name__,
322
+ tuple(self.to_dict().items()),
323
+ )
324
+ )
308
325
  return False