ormlambda 0.1.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.
Files changed (70) hide show
  1. ormlambda/__init__.py +4 -0
  2. ormlambda/common/__init__.py +2 -0
  3. ormlambda/common/abstract_classes/__init__.py +3 -0
  4. ormlambda/common/abstract_classes/abstract_model.py +302 -0
  5. ormlambda/common/abstract_classes/non_query_base.py +33 -0
  6. ormlambda/common/abstract_classes/query_base.py +10 -0
  7. ormlambda/common/enums/__init__.py +2 -0
  8. ormlambda/common/enums/condition_types.py +16 -0
  9. ormlambda/common/enums/join_type.py +10 -0
  10. ormlambda/common/interfaces/INonQueryCommand.py +9 -0
  11. ormlambda/common/interfaces/IQueryCommand.py +11 -0
  12. ormlambda/common/interfaces/IRepositoryBase.py +67 -0
  13. ormlambda/common/interfaces/IStatements.py +227 -0
  14. ormlambda/common/interfaces/__init__.py +4 -0
  15. ormlambda/components/__init__.py +0 -0
  16. ormlambda/components/delete/IDelete.py +6 -0
  17. ormlambda/components/delete/__init__.py +2 -0
  18. ormlambda/components/delete/abstract_delete.py +14 -0
  19. ormlambda/components/insert/IInsert.py +6 -0
  20. ormlambda/components/insert/__init__.py +2 -0
  21. ormlambda/components/insert/abstract_insert.py +21 -0
  22. ormlambda/components/select/ISelect.py +14 -0
  23. ormlambda/components/select/__init__.py +2 -0
  24. ormlambda/components/select/table_column.py +39 -0
  25. ormlambda/components/update/IUpdate.py +7 -0
  26. ormlambda/components/update/__init__.py +2 -0
  27. ormlambda/components/update/abstract_update.py +25 -0
  28. ormlambda/components/upsert/IUpsert.py +6 -0
  29. ormlambda/components/upsert/__init__.py +2 -0
  30. ormlambda/components/upsert/abstract_upsert.py +21 -0
  31. ormlambda/components/where/__init__.py +1 -0
  32. ormlambda/components/where/abstract_where.py +11 -0
  33. ormlambda/databases/__init__.py +0 -0
  34. ormlambda/databases/my_sql/__init__.py +2 -0
  35. ormlambda/databases/my_sql/clauses/__init__.py +13 -0
  36. ormlambda/databases/my_sql/clauses/create_database.py +29 -0
  37. ormlambda/databases/my_sql/clauses/delete.py +54 -0
  38. ormlambda/databases/my_sql/clauses/drop_database.py +19 -0
  39. ormlambda/databases/my_sql/clauses/drop_table.py +23 -0
  40. ormlambda/databases/my_sql/clauses/insert.py +70 -0
  41. ormlambda/databases/my_sql/clauses/joins.py +103 -0
  42. ormlambda/databases/my_sql/clauses/limit.py +17 -0
  43. ormlambda/databases/my_sql/clauses/offset.py +17 -0
  44. ormlambda/databases/my_sql/clauses/order.py +29 -0
  45. ormlambda/databases/my_sql/clauses/select.py +172 -0
  46. ormlambda/databases/my_sql/clauses/update.py +52 -0
  47. ormlambda/databases/my_sql/clauses/upsert.py +68 -0
  48. ormlambda/databases/my_sql/clauses/where_condition.py +219 -0
  49. ormlambda/databases/my_sql/repository.py +192 -0
  50. ormlambda/databases/my_sql/statements.py +86 -0
  51. ormlambda/model_base.py +36 -0
  52. ormlambda/utils/__init__.py +3 -0
  53. ormlambda/utils/column.py +65 -0
  54. ormlambda/utils/dtypes.py +104 -0
  55. ormlambda/utils/foreign_key.py +36 -0
  56. ormlambda/utils/lambda_disassembler/__init__.py +4 -0
  57. ormlambda/utils/lambda_disassembler/dis_types.py +136 -0
  58. ormlambda/utils/lambda_disassembler/disassembler.py +69 -0
  59. ormlambda/utils/lambda_disassembler/dtypes.py +103 -0
  60. ormlambda/utils/lambda_disassembler/name_of.py +41 -0
  61. ormlambda/utils/lambda_disassembler/nested_element.py +44 -0
  62. ormlambda/utils/lambda_disassembler/tree_instruction.py +145 -0
  63. ormlambda/utils/module_tree/__init__.py +0 -0
  64. ormlambda/utils/module_tree/dfs_traversal.py +60 -0
  65. ormlambda/utils/module_tree/dynamic_module.py +237 -0
  66. ormlambda/utils/table_constructor.py +308 -0
  67. ormlambda-0.1.0.dist-info/LICENSE +21 -0
  68. ormlambda-0.1.0.dist-info/METADATA +268 -0
  69. ormlambda-0.1.0.dist-info/RECORD +70 -0
  70. ormlambda-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,237 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from typing import Optional, Type
4
+ from collections import defaultdict
5
+ import importlib.util
6
+ import inspect
7
+ import re
8
+
9
+ from ..table_constructor import Table
10
+ from .dfs_traversal import DFSTraversal
11
+
12
+
13
+ class Node:
14
+ pattern: str = r"from \.(\w+) import (\w+)"
15
+
16
+ def __init__(
17
+ self,
18
+ file: str | Path,
19
+ relative_path: str | Path,
20
+ class_name: str = None,
21
+ fks: list[str, str] = None,
22
+ ):
23
+ self._file: Path = self._set_paths(file)
24
+ self._relative_path: Path = self._set_paths(relative_path)
25
+ self._class_name: str = class_name
26
+ self._fks: list[str, str] = fks
27
+
28
+ self._relative_modules: list["Node"] = self.extract_modules()
29
+
30
+ def __repr__(self) -> str:
31
+ return f"Module: {self.module_name}, class: {self.class_name} ->({Node.__name__})"
32
+
33
+ def __eq__(self, __value: "Node") -> bool:
34
+ return hash(__value) == hash(self)
35
+
36
+ def __hash__(self) -> int:
37
+ return hash(
38
+ (
39
+ self.class_name,
40
+ self.code,
41
+ )
42
+ )
43
+
44
+ def _validate_paths(self, path: Path | str) -> bool:
45
+ if isinstance(path, str):
46
+ return Path(path).exists()
47
+ return path.exists()
48
+
49
+ def _set_paths(self, path: str | Path):
50
+ if not self._validate_paths(path):
51
+ raise OSError(path)
52
+ return Path(path).resolve()
53
+
54
+ def extract_modules(self) -> list["Node"]:
55
+ if self._fks:
56
+ rel_modules: list[tuple[str, str]] = self._fks
57
+ else:
58
+ rel_modules: list[tuple[str, str]] = re.findall(self.pattern, self.code) if self._file is not None else []
59
+
60
+ if not rel_modules:
61
+ return []
62
+ return [
63
+ Node(
64
+ file=self._relative_path.joinpath(module_).with_suffix(".py"),
65
+ relative_path=self._relative_path,
66
+ class_name=class_,
67
+ )
68
+ for module_, class_ in rel_modules
69
+ ]
70
+
71
+ @property
72
+ def code(self) -> str:
73
+ if not self._file:
74
+ return None
75
+ return self._file.read_text()
76
+
77
+ @property
78
+ def file(self) -> Path:
79
+ return self._file
80
+
81
+ @property
82
+ def class_name(self) -> str:
83
+ if not self._class_name:
84
+ pattern = re.compile(r"class\s(\w+)\(.*Table.*\):")
85
+
86
+ condition = pattern.search(self.code)
87
+ if condition:
88
+ self._class_name = condition.group(1)
89
+ return self.class_name
90
+ return None
91
+ return self._class_name
92
+
93
+ @property
94
+ def module_name(self) -> str:
95
+ return self._file.stem
96
+
97
+ @property
98
+ def relative_modules(self) -> list["Node"]:
99
+ return self._relative_modules
100
+
101
+ @relative_modules.setter
102
+ def relative_modules(self, value: list):
103
+ self._relative_modules = value
104
+
105
+ @property
106
+ def is_dependent(self) -> bool:
107
+ return len(self.relative_modules) > 0
108
+
109
+
110
+ class ModuleTree:
111
+ def __init__(self, module_path: Path) -> None:
112
+ if isinstance(module_path, str):
113
+ module_path = Path(module_path)
114
+
115
+ self.module_path: Path = module_path
116
+ self.order_module_tuple: tuple[Node | str] = self.get_order_module_tuple_from_path()
117
+
118
+ def get_order_module_tuple_from_path(self) -> tuple[Node | str]:
119
+ if self.module_path.is_dir():
120
+ return self.order_modules_from_folder()
121
+ return self.order_modules_from_file()
122
+
123
+ def order_modules_from_folder(self) -> tuple[Node]:
124
+ """
125
+ Method whose main used is sorting all .py inside of folder to avoid import errors and overall for the creation of tables in SQL, comply with foreign key referenced table
126
+ This method's main purpose is to sort all .py inside a folder to avoid import errors and to ensure that tables referenced by foreign key in other tables are created first
127
+ """
128
+ order_list: list[Node] = []
129
+ unorder_module_list: list[Node] = []
130
+
131
+ for p in self.module_path.iterdir():
132
+ if not p.is_dir():
133
+ unorder_module_list.append(Node(file=p, relative_path=self.module_path))
134
+
135
+ self.sort_dicc(unorder_module_list, order_list)
136
+ return tuple(order_list)
137
+
138
+ @staticmethod
139
+ def sort_dicc(list_nodes: list[Node], new_list: list[Node]) -> None:
140
+ """
141
+ Iterated throughout list_nodes var and feed 'new_list' mutable var
142
+
143
+ Must create a mutable object such as list, to fill it with ordered items
144
+ """
145
+
146
+ def add_children(list_nodes: list[Node], new_list: list[Node], node: Node):
147
+ """
148
+ Recursive method called when try to sort all Nodes
149
+
150
+ ARGUMENT
151
+ -
152
+
153
+ - list_nodes: list[Node]
154
+ - new_list:list[Node]
155
+ """
156
+ all_children_added = all(x in new_list for x in node.relative_modules)
157
+
158
+ if node not in new_list and node._file is not None and ((node.is_dependent and all_children_added) or not node.is_dependent):
159
+ new_list.append(node)
160
+ return None
161
+
162
+ if node in new_list and not node.is_dependent:
163
+ return None
164
+
165
+ for child_node in node.relative_modules:
166
+ add_children(list_nodes, new_list, child_node)
167
+
168
+ for node in list_nodes:
169
+ add_children(list_nodes, new_list, node)
170
+
171
+ if node not in new_list and all([child_node in new_list for child_node in node.relative_modules]):
172
+ new_list.append(node)
173
+
174
+ return None
175
+
176
+ def order_modules_from_file(self) -> tuple[str]:
177
+ """
178
+ Method whose main used is sorting all .py inside of folder to avoid import errors and overall for the creation of tables in SQL, comply with foreign key referenced table
179
+ This method's main purpose is to sort all .py inside a folder to avoid import errors and to ensure that tables referenced by foreign key in other tables are created first
180
+ """
181
+ tables: list[tuple[str, Table]] = self.get_member_table(self.load_module("", self.module_path))
182
+
183
+ graph: dict[Type[Table], list[Type[Table]]] = defaultdict(list)
184
+ for _, tbl in tables:
185
+ graph[tbl] = tbl.find_dependent_tables()
186
+
187
+ sorted_tables = DFSTraversal.sort(graph)
188
+ res = [x.create_table_query() for x in sorted_tables]
189
+ return res
190
+
191
+ @staticmethod
192
+ def find_module(module_name: str, nodes: list["Node"]) -> Optional["Node"]:
193
+ for node in nodes:
194
+ if module_name == node.class_name:
195
+ return node
196
+ return None
197
+
198
+ @staticmethod
199
+ def load_module(module_name: str, module_path: Path):
200
+ """
201
+ Method whose main purpose is the dynamic addtion of modules using the importlib module.
202
+ Both the module name and its path must be specified.
203
+
204
+ !important: we need to add the dynamic modules to sys.modules for the loading to be successful
205
+ """
206
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
207
+ module = importlib.util.module_from_spec(spec)
208
+ sys.modules[spec.name] = module
209
+ spec.loader.exec_module(module)
210
+ return module
211
+
212
+ def get_queries(self) -> tuple[str, ...]:
213
+ table_list: list[Table] = []
214
+ if isinstance(self.order_module_tuple[0], str):
215
+ return self.order_module_tuple
216
+
217
+ for node in self.order_module_tuple:
218
+ if node.class_name is None:
219
+ continue
220
+ # avoid __init__ because we can get relative import not found error
221
+ if node.file.is_dir() or node.class_name == "__init__":
222
+ continue
223
+
224
+ # loop over order modules tuple to load it into sys.modules
225
+ # COMMENT!: Checked why changing 'class_name' by 'module_name' the method works
226
+ submodule = self.load_module(f"{self.module_path.stem}.{node.module_name}", node.file)
227
+ table_class = self.get_member_table(submodule)
228
+
229
+ # we need to ensure that the object we going to add in table_list is the same
230
+ for name, obj in table_class:
231
+ if name == node.class_name:
232
+ table_list.append(obj.create_table_query())
233
+ return tuple(table_list)
234
+
235
+ @staticmethod
236
+ def get_member_table(module) -> list[tuple[str, Type[Table]]]:
237
+ return inspect.getmembers(module, lambda x: inspect.isclass(x) and issubclass(x, Table) and x is not Table)
@@ -0,0 +1,308 @@
1
+ import base64
2
+ from collections import defaultdict
3
+ import datetime
4
+ from decimal import Decimal
5
+ from typing import Any, Iterable, Optional, Type, dataclass_transform
6
+ import json
7
+
8
+ from .dtypes import get_query_clausule
9
+ from .module_tree.dfs_traversal import DFSTraversal
10
+ from .column import Column
11
+
12
+ from .foreign_key import ForeignKey
13
+
14
+ MISSING = Column()
15
+
16
+
17
+ class Field:
18
+ def __init__(self, name: str, type_: type, default: object) -> None:
19
+ self.name: str = name
20
+ self.type_: type = type_
21
+ self.default: Column = default
22
+
23
+ def __repr__(self) -> str:
24
+ return f"{Field.__name__}(name = {self.name}, type_ = {self.type_}, default = {self.default})"
25
+
26
+ @property
27
+ def has_default(self) -> bool:
28
+ return self.default is not MISSING
29
+
30
+ @property
31
+ def init_arg(self) -> str:
32
+ # default = f"={self.default_name if self.has_default else None}"
33
+ default = f"={None}"
34
+
35
+ return f"{self.name}: {self.type_name}{default}"
36
+
37
+ @property
38
+ def default_name(self) -> str:
39
+ return f"_dflt_{self.name}"
40
+
41
+ @property
42
+ def type_name(self) -> str:
43
+ return f"_type_{self.name}"
44
+
45
+ @property
46
+ def assginment(self) -> str:
47
+ return f"self._{self.name} = {self.default.__to_string__(self.name,self.name,self.type_name)}"
48
+
49
+
50
+ def delete_special_variables(dicc: dict[str, object]) -> None:
51
+ keys = tuple(dicc.keys())
52
+ for key in keys:
53
+ if key.startswith("__"):
54
+ del dicc[key]
55
+
56
+
57
+ def get_fields[T](cls: Type[T]) -> Iterable[Field]:
58
+ annotations = getattr(cls, "__annotations__", {})
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))
69
+
70
+ # Update __annotations__ to create Columns
71
+ cls.__annotations__[name] = Column[field_type]
72
+ return fields
73
+
74
+
75
+ @dataclass_transform()
76
+ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
77
+ # create '__properties_mapped__' dictionary for each Table to avoid shared information
78
+ # TODOL: I don't know if it's better to create a global dictionary like in commit '7de69443d7a8e7264b8d5d604c95da0e5d7e9cc0'
79
+ setattr(cls, "__properties_mapped__", {})
80
+ fields = get_fields(cls)
81
+ locals_ = {}
82
+ init_args = []
83
+
84
+ for field in fields:
85
+ if not field.name.startswith("__"):
86
+ locals_[field.type_name] = field.type_
87
+
88
+ init_args.append(field.init_arg)
89
+ locals_[field.default_name] = None # field.default.column_value
90
+ __create_properties(cls, field)
91
+
92
+ wrapper_fn = "\n".join(
93
+ [
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__",
98
+ ]
99
+ )
100
+
101
+ namespace = {}
102
+
103
+ exec(wrapper_fn, None, namespace)
104
+ init_fn = namespace["wrapper"](**locals_)
105
+
106
+ setattr(cls, "__init__", init_fn)
107
+ return cls
108
+
109
+
110
+ def __create_properties(cls: Type["Table"], field: Field) -> property:
111
+ _name: str = f"_{field.name}"
112
+ type_ = field.type_
113
+ # we need to get Table attributes (Column class) and then called __getattribute__ or __setattr__ to make changes inside of Column
114
+ prop = property(
115
+ fget=lambda self: __transform_getter(getattr(self, _name), type_),
116
+ fset=lambda self, value: __transform_setter(getattr(self, _name), value, type_),
117
+ )
118
+
119
+ # set property in public name
120
+ setattr(cls, field.name, prop)
121
+ cls.__properties_mapped__[prop] = field.name
122
+ return None
123
+
124
+
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
+ class TableMeta(type):
142
+ def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
143
+ """
144
+ That's the class we use to recreate the table's metadata.
145
+ It's useful because we can dynamically create the __init__ method just by using the type hints of the variables we want to use as column names.
146
+ We simply call '__init_constructor__' to create all the necessary variables and the method.
147
+ """
148
+ cls_object: Table = super().__new__(cls, name, bases, dct)
149
+
150
+ if name == "Table":
151
+ return cls_object
152
+
153
+ if cls_object.__table_name__ is Ellipsis:
154
+ raise Exception(f"class variable '__table_name__' must be declared in '{cls_object.__name__}' class")
155
+
156
+ if not isinstance(cls_object.__table_name__, str):
157
+ raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
158
+
159
+ TableMeta.__add_fk_if_exists__(cls_object)
160
+ self = __init_constructor__(cls_object)
161
+ return self
162
+
163
+ def __repr__(cls: "Table") -> str:
164
+ return f"{TableMeta.__name__}: {cls.__table_name__}"
165
+
166
+ @staticmethod
167
+ def __add_fk_if_exists__(cls: "Table") -> None:
168
+ """
169
+ 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.
171
+ """
172
+ if data := ForeignKey.MAPPED.get(cls.__table_name__):
173
+ ForeignKey.MAPPED[cls] = data
174
+ del ForeignKey.MAPPED[cls.__table_name__]
175
+ return None
176
+
177
+
178
+ @dataclass_transform(eq_default=False)
179
+ class Table(metaclass=TableMeta):
180
+ """
181
+ Class to mapped database tables with Python classes.
182
+
183
+ It uses __annotations__ special var to store all table columns. If you do not type class var it means this var is not store as table column
184
+ and it do not going to appear when you instantiate the object itself.
185
+
186
+ This principle it so powerful due to we can create Foreign Key references without break __init__ class method.
187
+
188
+ >>> class Address(Table):
189
+ >>> __table_name__ = "address"
190
+
191
+ >>> address_id: int = Column[int](is_primary_key=True)
192
+ >>> address: str
193
+ >>> address2: str
194
+ >>> district: str
195
+ >>> city_id: int
196
+ >>> postal_code: datetime
197
+ >>> phone: str
198
+ >>> location: datetime
199
+ >>> last_update: datetime = Column[datetime](is_auto_generated=True)
200
+
201
+ >>> city = ForeignKey["Address", City](__table_name__, City, lambda a, c: a.city_id == c.city_id)
202
+ """
203
+
204
+ __table_name__: str = ...
205
+ __properties_mapped__: dict[property, str] = ...
206
+
207
+ def __str__(self) -> str:
208
+ params = self.to_dict()
209
+ return json.dumps(params, ensure_ascii=False, indent=2)
210
+
211
+ def __getattr__[T](self, __name: str) -> Column[T]:
212
+ return self.__dict__.get(__name, None)
213
+
214
+ def __repr__(self: "Table") -> str:
215
+ def __cast_long_variables(value: Any):
216
+ if not isinstance(value, str):
217
+ value = str(value)
218
+ if len(value) > 20:
219
+ return value[:20] + "..."
220
+ return value
221
+
222
+ dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
223
+ equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
224
+ return f'{self.__class__.__name__}({", ".join(equal_loop)})'
225
+
226
+ def to_dict(self) -> dict[str, str | int]:
227
+ dicc: dict[str, Any] = {}
228
+ for x in self.__annotations__:
229
+ transform_data = self.__transform_data__(getattr(self, x))
230
+ dicc[x] = transform_data
231
+ return dicc
232
+
233
+ @staticmethod
234
+ def __transform_data__[T](_value: T) -> T:
235
+ def byte_to_string(value: bytes):
236
+ return base64.b64encode(value).decode("utf-8")
237
+
238
+ transform_map: dict = {
239
+ datetime.datetime: datetime.datetime.isoformat,
240
+ datetime.date: datetime.date.isoformat,
241
+ Decimal: str,
242
+ bytes: byte_to_string,
243
+ set: list,
244
+ }
245
+
246
+ if (dtype := type(_value)) in transform_map:
247
+ return transform_map[dtype](_value)
248
+ return _value
249
+
250
+ def get_pk(self) -> Optional[Column]:
251
+ for col_name in self.__annotations__.keys():
252
+ private_col = f"_{col_name}"
253
+ col_obj = getattr(self, private_col)
254
+ if isinstance(col_obj, Column) and col_obj.is_primary_key:
255
+ return col_obj
256
+ return None
257
+
258
+ @classmethod
259
+ def get_columns(cls) -> tuple[str, ...]:
260
+ return tuple(cls.__annotations__.keys())
261
+
262
+ @classmethod
263
+ def create_table_query(cls) -> str:
264
+ """It's classmethod because of it does not matter the columns values to create the table"""
265
+ all_clauses: list[str] = []
266
+
267
+ all_clauses.extend(cls._create_sql_column_query())
268
+ all_clauses.extend(ForeignKey.create_query(cls))
269
+
270
+ return f"CREATE TABLE {cls.__table_name__} ({', '.join(all_clauses)});"
271
+
272
+ @classmethod
273
+ def _create_sql_column_query(cls) -> list[str]:
274
+ """
275
+ It's imperative to instantiate cls() to initialize the 'Table' object and create private variables that will be Column objects.
276
+ Otherwise, we only can access to property method
277
+ """
278
+ table_init_ = cls()
279
+ annotations: dict[str, Column] = table_init_.__annotations__
280
+ all_columns: list = []
281
+ for col_name in annotations.keys():
282
+ col_object: Column = getattr(table_init_, f"_{col_name}")
283
+ all_columns.append(get_query_clausule(col_object))
284
+ return all_columns
285
+
286
+ @classmethod
287
+ 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)
298
+
299
+ dfs = DFSTraversal.sort(graph)
300
+ return dfs[: dfs.index(cls)]
301
+
302
+ def __hash__(self) -> int:
303
+ return hash(self.to_dict().values())
304
+
305
+ def __eq__(self, __value: Any) -> bool:
306
+ if isinstance(__value, Table):
307
+ return hash(self) == hash(__value)
308
+ return False
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Pablo Hernández Zamora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.