ormlambda 3.11.2__py3-none-any.whl → 3.34.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 (154) hide show
  1. ormlambda/__init__.py +3 -1
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +65 -0
  5. ormlambda/common/abstract_classes/decomposition_query.py +27 -68
  6. ormlambda/common/abstract_classes/non_query_base.py +10 -8
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/ICustomAlias.py +1 -1
  10. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  11. ormlambda/dialects/__init__.py +39 -0
  12. ormlambda/dialects/default/__init__.py +1 -0
  13. ormlambda/dialects/default/base.py +39 -0
  14. ormlambda/dialects/interface/__init__.py +1 -0
  15. ormlambda/dialects/interface/dialect.py +78 -0
  16. ormlambda/dialects/mysql/__init__.py +38 -0
  17. ormlambda/dialects/mysql/base.py +388 -0
  18. ormlambda/dialects/mysql/caster/caster.py +39 -0
  19. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +1 -0
  20. ormlambda/dialects/mysql/caster/types/boolean.py +35 -0
  21. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +7 -7
  22. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +7 -7
  23. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +7 -7
  24. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +7 -7
  25. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +7 -7
  26. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +8 -7
  27. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +4 -4
  28. ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +7 -7
  29. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_AsText.py +8 -7
  30. ormlambda/{databases/my_sql → dialects/mysql}/clauses/ST_Contains.py +10 -5
  31. ormlambda/dialects/mysql/clauses/__init__.py +13 -0
  32. ormlambda/dialects/mysql/clauses/count.py +33 -0
  33. ormlambda/dialects/mysql/clauses/delete.py +9 -0
  34. ormlambda/dialects/mysql/clauses/group_by.py +17 -0
  35. ormlambda/dialects/mysql/clauses/having.py +12 -0
  36. ormlambda/dialects/mysql/clauses/insert.py +9 -0
  37. ormlambda/dialects/mysql/clauses/joins.py +14 -0
  38. ormlambda/dialects/mysql/clauses/limit.py +6 -0
  39. ormlambda/dialects/mysql/clauses/offset.py +6 -0
  40. ormlambda/dialects/mysql/clauses/order.py +8 -0
  41. ormlambda/dialects/mysql/clauses/update.py +8 -0
  42. ormlambda/dialects/mysql/clauses/upsert.py +9 -0
  43. ormlambda/dialects/mysql/clauses/where.py +7 -0
  44. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  45. ormlambda/dialects/mysql/repository/__init__.py +1 -0
  46. ormlambda/dialects/mysql/repository/repository.py +212 -0
  47. ormlambda/dialects/mysql/types.py +732 -0
  48. ormlambda/dialects/sqlite/__init__.py +5 -0
  49. ormlambda/dialects/sqlite/base.py +47 -0
  50. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  51. ormlambda/engine/__init__.py +1 -0
  52. ormlambda/engine/base.py +77 -0
  53. ormlambda/engine/create.py +9 -23
  54. ormlambda/engine/url.py +34 -19
  55. ormlambda/env.py +30 -0
  56. ormlambda/errors.py +17 -0
  57. ormlambda/model/base_model.py +7 -9
  58. ormlambda/repository/base_repository.py +36 -5
  59. ormlambda/repository/interfaces/IRepositoryBase.py +119 -12
  60. ormlambda/repository/response.py +134 -0
  61. ormlambda/sql/clause_info/__init__.py +2 -1
  62. ormlambda/sql/clause_info/aggregate_function_base.py +96 -0
  63. ormlambda/sql/clause_info/clause_info.py +35 -115
  64. ormlambda/sql/clause_info/interface/IClauseInfo.py +37 -0
  65. ormlambda/sql/clause_info/interface/__init__.py +1 -0
  66. ormlambda/sql/clauses/__init__.py +14 -0
  67. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  68. ormlambda/{databases/my_sql → sql}/clauses/count.py +15 -1
  69. ormlambda/{databases/my_sql → sql}/clauses/delete.py +22 -7
  70. ormlambda/sql/clauses/group_by.py +30 -0
  71. ormlambda/{databases/my_sql → sql}/clauses/having.py +7 -2
  72. ormlambda/{databases/my_sql → sql}/clauses/insert.py +16 -9
  73. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  74. ormlambda/sql/clauses/join/__init__.py +1 -0
  75. ormlambda/{databases/my_sql → sql/clauses/join}/join_context.py +15 -7
  76. ormlambda/{databases/my_sql → sql}/clauses/joins.py +29 -19
  77. ormlambda/sql/clauses/limit.py +15 -0
  78. ormlambda/sql/clauses/offset.py +15 -0
  79. ormlambda/{databases/my_sql → sql}/clauses/order.py +14 -24
  80. ormlambda/{databases/my_sql → sql}/clauses/select.py +14 -13
  81. ormlambda/{databases/my_sql → sql}/clauses/update.py +24 -11
  82. ormlambda/{databases/my_sql → sql}/clauses/upsert.py +19 -10
  83. ormlambda/{databases/my_sql → sql}/clauses/where.py +28 -8
  84. ormlambda/sql/column/__init__.py +1 -0
  85. ormlambda/sql/{column.py → column/column.py} +85 -22
  86. ormlambda/sql/comparer.py +51 -37
  87. ormlambda/sql/compiler.py +668 -0
  88. ormlambda/sql/ddl.py +82 -0
  89. ormlambda/sql/elements.py +36 -0
  90. ormlambda/sql/foreign_key.py +61 -39
  91. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  92. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  93. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  94. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  95. ormlambda/sql/sqltypes.py +647 -0
  96. ormlambda/sql/table/__init__.py +1 -1
  97. ormlambda/sql/table/table.py +175 -0
  98. ormlambda/sql/table/table_constructor.py +1 -208
  99. ormlambda/sql/type_api.py +35 -0
  100. ormlambda/sql/types.py +3 -1
  101. ormlambda/sql/visitors.py +74 -0
  102. ormlambda/statements/__init__.py +1 -0
  103. ormlambda/statements/base_statement.py +34 -40
  104. ormlambda/statements/interfaces/IStatements.py +28 -21
  105. ormlambda/statements/query_builder.py +163 -0
  106. ormlambda/{databases/my_sql → statements}/statements.py +68 -210
  107. ormlambda/statements/types.py +2 -2
  108. ormlambda/types/__init__.py +24 -0
  109. ormlambda/types/metadata.py +42 -0
  110. ormlambda/util/__init__.py +87 -0
  111. ormlambda/{utils → util}/module_tree/dynamic_module.py +4 -3
  112. ormlambda/util/plugin_loader.py +32 -0
  113. ormlambda/util/typing.py +6 -0
  114. ormlambda-3.34.0.dist-info/AUTHORS +32 -0
  115. {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/METADATA +56 -10
  116. ormlambda-3.34.0.dist-info/RECORD +152 -0
  117. ormlambda/components/__init__.py +0 -4
  118. ormlambda/components/delete/__init__.py +0 -2
  119. ormlambda/components/delete/abstract_delete.py +0 -17
  120. ormlambda/components/insert/__init__.py +0 -2
  121. ormlambda/components/insert/abstract_insert.py +0 -25
  122. ormlambda/components/select/__init__.py +0 -1
  123. ormlambda/components/update/__init__.py +0 -2
  124. ormlambda/components/update/abstract_update.py +0 -29
  125. ormlambda/components/upsert/__init__.py +0 -2
  126. ormlambda/components/upsert/abstract_upsert.py +0 -25
  127. ormlambda/databases/__init__.py +0 -5
  128. ormlambda/databases/my_sql/__init__.py +0 -4
  129. ormlambda/databases/my_sql/caster/caster.py +0 -39
  130. ormlambda/databases/my_sql/clauses/__init__.py +0 -20
  131. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  132. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  133. ormlambda/databases/my_sql/clauses/drop_table.py +0 -23
  134. ormlambda/databases/my_sql/clauses/group_by.py +0 -31
  135. ormlambda/databases/my_sql/clauses/limit.py +0 -17
  136. ormlambda/databases/my_sql/clauses/offset.py +0 -17
  137. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  138. ormlambda/databases/my_sql/repository/repository.py +0 -351
  139. ormlambda/engine/template.py +0 -47
  140. ormlambda/sql/dtypes.py +0 -94
  141. ormlambda/utils/__init__.py +0 -1
  142. ormlambda-3.11.2.dist-info/RECORD +0 -120
  143. /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
  144. /ormlambda/{databases/my_sql/types.py → dialects/mysql/repository/pool_types.py} +0 -0
  145. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  146. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  147. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  148. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  149. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  150. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  151. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  152. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  153. {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/LICENSE +0 -0
  154. {ormlambda-3.11.2.dist-info → ormlambda-3.34.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+ from typing import Any, Optional, Type, dataclass_transform, TYPE_CHECKING
3
+ import json
4
+
5
+ from ormlambda.sql import Column
6
+ from ormlambda.sql import ForeignKey
7
+ from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
8
+ from ormlambda.sql.ddl import CreateTable, DropTable
9
+
10
+ if TYPE_CHECKING:
11
+ from ormlambda.dialects import Dialect
12
+
13
+ from .table_constructor import __init_constructor__
14
+
15
+
16
+ class TableMeta(type):
17
+ def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
18
+ """
19
+ That's the class we use to recreate the table's metadata.
20
+ 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.
21
+ We simply call '__init_constructor__' to create all the necessary variables and the method.
22
+ """
23
+ cls_object: Table = super().__new__(cls, name, bases, dct)
24
+
25
+ if name == "Table":
26
+ return cls_object
27
+
28
+ if cls_object.__table_name__ is Ellipsis:
29
+ raise Exception(f"class variable '__table_name__' must be declared in '{cls_object.__name__}' class")
30
+
31
+ if not isinstance(cls_object.__table_name__, str):
32
+ raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
33
+
34
+ self = __init_constructor__(cls_object)
35
+ return self
36
+
37
+ def __repr__(cls: "Table") -> str:
38
+ return f"{TableMeta.__name__}: {cls.__table_name__}"
39
+
40
+
41
+ @dataclass_transform(eq_default=False)
42
+ class Table(metaclass=TableMeta):
43
+ """
44
+ Class to mapped database tables with Python classes.
45
+
46
+ 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
47
+ and it do not going to appear when you instantiate the object itself.
48
+
49
+ This principle it so powerful due to we can create Foreign Key references without break __init__ class method.
50
+
51
+ >>> class Address(Table):
52
+ >>> __table_name__ = "address"
53
+
54
+ >>> address_id: int = Column(int, is_primary_key=True)
55
+ >>> address: str
56
+ >>> address2: str
57
+ >>> district: str
58
+ >>> city_id: int
59
+ >>> postal_code: datetime
60
+ >>> phone: str
61
+ >>> location: datetime
62
+ >>> last_update: datetime = Column(datetime, is_auto_generated=True)
63
+
64
+ >>> city = ForeignKey["Address", City](City, lambda a, c: a.city_id == c.city_id)
65
+ """
66
+
67
+ __table_name__: str = ...
68
+
69
+ def __str__(self) -> str:
70
+ params = self.to_dict()
71
+ return json.dumps(params, ensure_ascii=False, indent=2)
72
+
73
+ def __getattr__[T](self, _name: str) -> Column[T]:
74
+ return self.__dict__.get(_name, None)
75
+
76
+ def __repr__(self: "Table") -> str:
77
+ def __cast_long_variables(value: Any):
78
+ if not isinstance(value, str):
79
+ value = str(value)
80
+ if len(value) > 20:
81
+ return value[:20] + "..."
82
+ return value
83
+
84
+ dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
85
+ equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
86
+ return f'{self.__class__.__name__}({", ".join(equal_loop)})'
87
+
88
+ def __getitem__[TProp](self, value: str | Column[TProp]) -> Optional[TProp]:
89
+ name = value if isinstance(value, str) else value.column_name
90
+ if hasattr(self, name):
91
+ return getattr(self, name)
92
+ return None
93
+
94
+ def to_dict(self) -> dict[str, str | int]:
95
+ dicc: dict[str, Any] = {}
96
+ for x in self.__annotations__:
97
+ dicc[x] = getattr(self, x)
98
+ return dicc
99
+
100
+ @classmethod
101
+ def get_pk(cls) -> Optional[Column]:
102
+ for obj in cls.__dict__.values():
103
+ if isinstance(obj, Column) and obj.is_primary_key:
104
+ return obj
105
+ return None
106
+
107
+ @classmethod
108
+ def get_columns(cls) -> tuple[Column, ...]:
109
+ return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
110
+
111
+ @classmethod
112
+ def get_column[TProp](cls, name: str) -> Column[TProp]:
113
+ for key, value in cls.__annotations__.items():
114
+ if name == key:
115
+ return value
116
+
117
+ @classmethod
118
+ def create_table(cls, dialect: Dialect) -> str:
119
+ return CreateTable(cls).compile(dialect).string
120
+
121
+ @classmethod
122
+ def drop_table(cls, dialect:Dialect)->str:
123
+ return DropTable(cls).compile(dialect).string
124
+
125
+ @classmethod
126
+ def find_dependent_tables(cls) -> tuple["Table", ...]:
127
+ """Work in progress"""
128
+ return
129
+
130
+ # TODOL: Dive into new way to return dependent tables
131
+ def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
132
+ """
133
+ Create a graph to be ordered
134
+ """
135
+ table = ForeignKey[Table, Table].MAPPED[table_name]
136
+ for x in table.referenced_tables:
137
+ if data := ForeignKey.MAPPED.get(x, None):
138
+ get_involved_tables(graph, data.table_object.__table_name__)
139
+
140
+ graph[table.table_object.__table_name__] = list(table.referenced_tables)
141
+ return None
142
+
143
+ graph: dict[Table, list[Table]] = {}
144
+ dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
145
+ if dependent is None:
146
+ return tuple([])
147
+
148
+ graph[cls.__table_name__] = list(dependent.referenced_tables)
149
+ get_involved_tables(graph, cls.__table_name__)
150
+
151
+ dfs = DFSTraversal.sort(graph)
152
+
153
+ order_table = dfs[: dfs.index(cls.__table_name__)]
154
+
155
+ return [ForeignKey.MAPPED[x].table_object for x in order_table]
156
+
157
+ def __eq__(self, __value: Any) -> bool:
158
+ if isinstance(__value, Table):
159
+ return all(
160
+ (
161
+ self.__table_name__ == __value.__table_name__,
162
+ tuple(self.to_dict().items()),
163
+ )
164
+ )
165
+ return False
166
+
167
+ @classmethod
168
+ def table_alias(cls, column: Optional[str] = None) -> str:
169
+ if column:
170
+ return f"`{cls.__table_name__}_{column}`"
171
+ return cls.__table_name__
172
+
173
+ @classmethod
174
+ def foreign_keys(cls) -> dict[str, ForeignKey]:
175
+ return {key: value for key, value in cls.__dict__.items() if isinstance(value, ForeignKey)}
@@ -1,18 +1,6 @@
1
- from __future__ import annotations
2
- from decimal import Decimal
3
- from typing import Any, Optional, Type, dataclass_transform
4
- import base64
5
- import datetime
6
- import json
1
+ from typing import Any, Type, dataclass_transform
7
2
 
8
- import shapely as sph
9
-
10
-
11
- from ormlambda.sql import Column
12
- from ormlambda.sql import ForeignKey
13
- from ormlambda.sql.dtypes import get_query_clausule
14
3
  from .fields import get_fields
15
- from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
16
4
 
17
5
 
18
6
  @dataclass_transform()
@@ -53,198 +41,3 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
53
41
 
54
42
  setattr(cls, "__init__", init_fn)
55
43
  return cls
56
-
57
-
58
- class TableMeta(type):
59
- def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
60
- """
61
- That's the class we use to recreate the table's metadata.
62
- 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.
63
- We simply call '__init_constructor__' to create all the necessary variables and the method.
64
- """
65
- cls_object: Table = super().__new__(cls, name, bases, dct)
66
-
67
- if name == "Table":
68
- return cls_object
69
-
70
- if cls_object.__table_name__ is Ellipsis:
71
- raise Exception(f"class variable '__table_name__' must be declared in '{cls_object.__name__}' class")
72
-
73
- if not isinstance(cls_object.__table_name__, str):
74
- raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
75
-
76
- self = __init_constructor__(cls_object)
77
- return self
78
-
79
- def __repr__(cls: "Table") -> str:
80
- return f"{TableMeta.__name__}: {cls.__table_name__}"
81
-
82
-
83
- @dataclass_transform(eq_default=False)
84
- class Table(metaclass=TableMeta):
85
- """
86
- Class to mapped database tables with Python classes.
87
-
88
- 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
89
- and it do not going to appear when you instantiate the object itself.
90
-
91
- This principle it so powerful due to we can create Foreign Key references without break __init__ class method.
92
-
93
- >>> class Address(Table):
94
- >>> __table_name__ = "address"
95
-
96
- >>> address_id: int = Column(int, is_primary_key=True)
97
- >>> address: str
98
- >>> address2: str
99
- >>> district: str
100
- >>> city_id: int
101
- >>> postal_code: datetime
102
- >>> phone: str
103
- >>> location: datetime
104
- >>> last_update: datetime = Column(datetime, is_auto_generated=True)
105
-
106
- >>> city = ForeignKey["Address", City](City, lambda a, c: a.city_id == c.city_id)
107
- """
108
-
109
- __table_name__: str = ...
110
-
111
- def __str__(self) -> str:
112
- params = self.to_dict()
113
- return json.dumps(params, ensure_ascii=False, indent=2)
114
-
115
- def __getattr__[T](self, _name: str) -> Column[T]:
116
- return self.__dict__.get(_name, None)
117
-
118
- def __repr__(self: "Table") -> str:
119
- def __cast_long_variables(value: Any):
120
- if not isinstance(value, str):
121
- value = str(value)
122
- if len(value) > 20:
123
- return value[:20] + "..."
124
- return value
125
-
126
- dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
127
- equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
128
- return f'{self.__class__.__name__}({", ".join(equal_loop)})'
129
-
130
- def __getitem__[TProp](self, value: str | Column[TProp]) -> Optional[TProp]:
131
- name = value if isinstance(value, str) else value.column_name
132
- if hasattr(self, name):
133
- return getattr(self, name)
134
- return None
135
-
136
- def to_dict(self) -> dict[str, str | int]:
137
- dicc: dict[str, Any] = {}
138
- for x in self.__annotations__:
139
- transform_data = self.__transform_data__(getattr(self, x))
140
- dicc[x] = transform_data
141
- return dicc
142
-
143
- @staticmethod
144
- def __transform_data__[T](_value: T) -> T:
145
- def byte_to_string(value: bytes):
146
- return base64.b64encode(value).decode("utf-8")
147
-
148
- transform_map: dict = {
149
- datetime.datetime: datetime.datetime.isoformat,
150
- datetime.date: datetime.date.isoformat,
151
- Decimal: str,
152
- bytes: byte_to_string,
153
- set: list,
154
- sph.Point: lambda x: sph.to_wkt(x, rounding_precision=-1),
155
- }
156
-
157
- if (dtype := type(_value)) in transform_map:
158
- return transform_map[dtype](_value)
159
- return _value
160
-
161
- @classmethod
162
- def get_pk(cls) -> Optional[Column]:
163
- for obj in cls.__dict__.values():
164
- if isinstance(obj, Column) and obj.is_primary_key:
165
- return obj
166
- return None
167
-
168
- @classmethod
169
- def get_columns(cls) -> tuple[str, ...]:
170
- return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
171
-
172
- @classmethod
173
- def get_column[TProp](cls, name: str) -> Column[TProp]:
174
- for key, value in cls.__annotations__.items():
175
- if name == key:
176
- return value
177
-
178
- @classmethod
179
- def create_table_query(cls) -> str:
180
- """It's classmethod because of it does not matter the columns values to create the table"""
181
- all_clauses: list[str] = []
182
-
183
- all_clauses.extend(cls._create_sql_column_query())
184
- all_clauses.extend(ForeignKey.create_query(cls))
185
-
186
- return f"CREATE TABLE {cls.__table_name__} ({', '.join(all_clauses)});"
187
-
188
- @classmethod
189
- def _create_sql_column_query(cls) -> list[str]:
190
- """
191
- It's imperative to instantiate cls() to initialize the 'Table' object and create private variables that will be Column objects.
192
- Otherwise, we only can access to property method
193
- """
194
- annotations: dict[str, Column] = cls.__annotations__
195
- all_columns: list = []
196
- for col_obj in annotations.values():
197
- all_columns.append(get_query_clausule(col_obj))
198
- return all_columns
199
-
200
- @classmethod
201
- def find_dependent_tables(cls) -> tuple["Table", ...]:
202
- """Work in progress"""
203
- return
204
-
205
- # TODOL: Dive into new way to return dependent tables
206
- def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
207
- """
208
- Create a graph to be ordered
209
- """
210
- table = ForeignKey[Table, Table].MAPPED[table_name]
211
- for x in table.referenced_tables:
212
- if data := ForeignKey.MAPPED.get(x, None):
213
- get_involved_tables(graph, data.table_object.__table_name__)
214
-
215
- graph[table.table_object.__table_name__] = list(table.referenced_tables)
216
- return None
217
-
218
- graph: dict[Table, list[Table]] = {}
219
- dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
220
- if dependent is None:
221
- return tuple([])
222
-
223
- graph[cls.__table_name__] = list(dependent.referenced_tables)
224
- get_involved_tables(graph, cls.__table_name__)
225
-
226
- dfs = DFSTraversal.sort(graph)
227
-
228
- order_table = dfs[: dfs.index(cls.__table_name__)]
229
-
230
- return [ForeignKey.MAPPED[x].table_object for x in order_table]
231
-
232
- def __eq__(self, __value: Any) -> bool:
233
- if isinstance(__value, Table):
234
- return all(
235
- (
236
- self.__table_name__ == __value.__table_name__,
237
- tuple(self.to_dict().items()),
238
- )
239
- )
240
- return False
241
-
242
- @classmethod
243
- def table_alias(cls, column: Optional[str] = None) -> str:
244
- if column:
245
- return f"`{cls.__table_name__}_{column}`"
246
- return cls.__table_name__
247
-
248
- @classmethod
249
- def foreign_keys(cls) -> dict[str, ForeignKey]:
250
- return {key: value for key, value in cls.__dict__.items() if isinstance(value, ForeignKey)}
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+ from typing import Any, ClassVar
3
+ from ormlambda.sql.visitors import Element
4
+ import abc
5
+
6
+
7
+ class TypeEngine[T: Any](Element, abc.ABC):
8
+ """
9
+ Base class for all SQL types.
10
+ """
11
+
12
+ __visit_name__ = "type_engine"
13
+
14
+ _sqla_type: ClassVar[bool] = True
15
+ _isnull: ClassVar[bool] = False
16
+ _is_tuple_type: ClassVar[bool] = False
17
+ _is_table_value: ClassVar[bool] = False
18
+ _is_array: ClassVar[bool] = False
19
+ _is_type_decorator: ClassVar[bool] = False
20
+ _type: ClassVar[T]
21
+
22
+ @property
23
+ @abc.abstractmethod
24
+ def python_type(self) -> T: ...
25
+
26
+ def _resolve_for_literal_value(self, value: T) -> TypeEngine[T]:
27
+ return self
28
+
29
+ def coerce_compared_value[TType](self, value: TType) -> TypeEngine[TType]:
30
+ from .sqltypes import resolve_primitive_types, NULLTYPE
31
+
32
+ _coerced_type = resolve_primitive_types(value)
33
+ if _coerced_type is NULLTYPE:
34
+ return self
35
+ return _coerced_type
ormlambda/sql/types.py CHANGED
@@ -20,6 +20,8 @@ type UnionType = tp.Literal["AND", "OR", ""]
20
20
  type ComparerTypes = ComparerType | UnionType | ConditionEnum
21
21
  # endregion
22
22
 
23
- type TupleJoinType[T] = tuple[Comparer[T], JoinType]
23
+ type TupleJoinType[T] = tuple[Comparer]
24
24
 
25
25
  ASTERISK: AsteriskType = "*"
26
+
27
+ from .compiler import *
@@ -0,0 +1,74 @@
1
+ # Apply visitor pattern
2
+
3
+ from __future__ import annotations
4
+ import abc
5
+ from typing import TYPE_CHECKING, ClassVar
6
+ import operator
7
+
8
+ if TYPE_CHECKING:
9
+ ...
10
+
11
+
12
+ class Visitor(abc.ABC):
13
+ """
14
+ Abstract base class for all visitors in the SQL AST.
15
+ """
16
+
17
+ ensure_kwarg: ClassVar[str] = r"visit_\w+"
18
+
19
+ ...
20
+
21
+
22
+ class Element:
23
+ """
24
+ Base class for all elements in the SQL AST.
25
+ """
26
+
27
+ __slots__ = ()
28
+
29
+ __visit_name__: ClassVar[str]
30
+ """
31
+ The name of the element used for dispatching in the visitor pattern.
32
+ This should be a string that uniquely identifies the type of element.
33
+ """
34
+
35
+ if TYPE_CHECKING:
36
+
37
+ def _compiler_dispatch(self, visitor: Visitor, **kw) -> str:
38
+ """
39
+ Dispatches the visitor to the appropriate method based on the type of the element.
40
+ """
41
+ ...
42
+
43
+ @classmethod
44
+ def __init_subclass__(cls):
45
+ if "__visit_name__" in cls.__dict__:
46
+ cls._generate_compiler_dispatch()
47
+ return super().__init_subclass__()
48
+
49
+ @classmethod
50
+ def _generate_compiler_dispatch(cls):
51
+ """
52
+ Generates the _compiler_dispatch method for the class.
53
+ """
54
+ visit_name = cls.__visit_name__
55
+
56
+ if not isinstance(visit_name, str):
57
+ raise TypeError(f"__visit_name__ must be a string, not {type(visit_name).__name__}")
58
+
59
+ name = f"visit_{visit_name}"
60
+ getter = operator.attrgetter(name)
61
+
62
+ def _compiler_dispatch(self, visitor: Visitor, **kw) -> str:
63
+ """
64
+ Dispatches the visitor to the appropriate method based on the type of the element.
65
+ """
66
+ try:
67
+ meth = getter(visitor)
68
+ return meth(self, **kw)
69
+
70
+ except AttributeError as err:
71
+ raise err
72
+ # return visitor.visit_unsupported_compilation(self, err, **kw) # type: ignore # noqa: E501
73
+
74
+ cls._compiler_dispatch = _compiler_dispatch
@@ -1,2 +1,3 @@
1
1
  from .base_statement import BaseStatement # noqa: F401
2
2
  from .types import OrderType # noqa: F401
3
+ from .statements import Statements # noqa: F401
@@ -7,32 +7,42 @@ from ormlambda.repository import BaseRepository
7
7
  from ormlambda.statements.interfaces import IStatements_two_generic
8
8
  from ormlambda import Table
9
9
 
10
- from ormlambda.sql.clause_info import AggregateFunctionBase
11
- from ormlambda.caster.caster import Caster
12
-
10
+ from ormlambda.common.errors import AggregateFunctionError
13
11
 
14
12
  if TYPE_CHECKING:
15
- from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
16
- from ormlambda.sql.clause_info import ClauseInfo
13
+ from ormlambda.engine.base import Engine
14
+ from ormlambda.dialects.interface.dialect import Dialect
15
+ from ormlambda.sql.clauses import Select
17
16
 
18
17
 
19
18
  ORDER_QUERIES = Literal["select", "join", "where", "order", "with", "group by", "limit", "offset"]
20
19
 
21
20
 
22
21
  class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
23
- def __init__(self, model: tuple[T], repository: BaseRepository) -> None:
24
- self.__valid_repository(repository)
25
-
22
+ def __init__(self, model: tuple[T, ...], engine: Engine) -> None:
23
+ self._engine = engine
24
+ self._dialect = engine.dialect
26
25
  self._query: Optional[str] = None
27
26
  self._model: T = model[0] if isinstance(model, Iterable) else model
28
27
  self._models: tuple[T] = self._model if isinstance(model, Iterable) else (model,)
29
- self._repository: BaseRepository = repository
28
+
29
+ repository = engine.repository
30
+ self.__valid_repository(repository)
31
+ self._repository: BaseRepository[TRepo] = repository
30
32
 
31
33
  if not issubclass(self._model, Table):
32
34
  # Deben heredar de Table ya que es la forma que tenemos para identificar si estamos pasando una instancia del tipo que corresponde o no cuando llamamos a insert o upsert.
33
35
  # Si no heredase de Table no sabriamos identificar el tipo de dato del que se trata porque al llamar a isinstance, obtendriamos el nombre de la clase que mapea a la tabla, Encargo, Edificio, Presupuesto y no podriamos crear una clase generica
34
36
  raise Exception(f"'{model}' class does not inherit from Table class")
35
37
 
38
+ @property
39
+ def dialect(self) -> Dialect:
40
+ return self._dialect
41
+
42
+ @override
43
+ def table_exists(self) -> bool:
44
+ return self._repository.table_exists(self._model.__table_name__)
45
+
36
46
  @staticmethod
37
47
  def __valid_repository(repository: Any) -> bool:
38
48
  if not isinstance(repository, BaseRepository):
@@ -43,13 +53,12 @@ class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
43
53
  return f"<Model: {self.__class__.__name__}>"
44
54
 
45
55
  def _return_flavour[TValue](self, query, flavour: Type[TValue], select, **kwargs) -> tuple[TValue]:
46
- return self._repository.read_sql(query, flavour=flavour, model=self._model, select=select, **kwargs)
56
+ return self._repository.read_sql(query, flavour=flavour, select=select, **kwargs)
47
57
 
48
58
  def _return_model(self, select, query: str) -> tuple[tuple[T]]:
49
- response_sql = self._repository.read_sql(query, flavour=dict, model=self._model, select=select) # store all columns of the SQL query
50
-
59
+ response_sql = self._repository.read_sql(query, flavour=dict, select=select) # store all columns of the SQL query
51
60
  if response_sql and isinstance(response_sql, Iterable):
52
- return ClusterQuery(self.repository, select, response_sql).clean_response()
61
+ return ClusterResponse(self._dialect, select, response_sql).cluster()
53
62
 
54
63
  return response_sql
55
64
 
@@ -67,19 +76,19 @@ class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
67
76
  return self._models
68
77
 
69
78
  @property
70
- @override
71
- def repository(self) -> BaseRepository: ...
79
+ def repository(self) -> BaseRepository:
80
+ return self._repository
72
81
 
73
82
 
74
- class ClusterQuery[T]:
75
- def __init__(self, repository: BaseRepository, select: DecompositionQueryBase[T], response_sql: tuple[dict[str, Any]]) -> None:
76
- self._repository: BaseRepository = repository
77
- self._select: DecompositionQueryBase[T] = select
83
+ class ClusterResponse[T]:
84
+ def __init__(self, dialect: Dialect, select: Select[T], response_sql: tuple[dict[str, Any]]) -> None:
85
+ self._dialect: Dialect = dialect
86
+ self._select: Select[T] = select
78
87
  self._response_sql: tuple[dict[str, Any]] = response_sql
79
- self._caster = Caster(repository)
88
+ self._caster = dialect.caster
80
89
 
81
- def clean_response(self) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
82
- tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self.__loop_foo()
90
+ def cluster(self) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
91
+ tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self._create_cluster()
83
92
 
84
93
  response = {}
85
94
  tuple_response = []
@@ -87,13 +96,12 @@ class ClusterQuery[T]:
87
96
  for table, attribute_list in tbl_dicc.items():
88
97
  new_instance = []
89
98
  for attrs in attribute_list:
90
- casted_attr = {key: self._caster.for_value(value, table.get_column(key).dtype).from_database for key, value in attrs.items()}
91
- new_instance.append(table(**casted_attr))
99
+ new_instance.append(table(**attrs))
92
100
  response[table] = tuple(new_instance)
93
101
  tuple_response.append(tuple(new_instance))
94
102
  return tuple(tuple_response)
95
103
 
96
- def __loop_foo(self) -> dict[Type[Table], list[dict[str, Any]]]:
104
+ def _create_cluster(self) -> dict[Type[Table], list[dict[str, Any]]]:
97
105
  # We'll create a default list of dicts *once* we know how many rows are in _response_sql
98
106
  row_count = len(self._response_sql)
99
107
 
@@ -108,22 +116,8 @@ class ClusterQuery[T]:
108
116
  col = clause.column
109
117
 
110
118
  if col is None or not hasattr(table, col):
111
- agg_methods = self.__get_all_aggregate_method(clause)
112
- raise ValueError(f"You cannot use aggregation method like '{agg_methods}' to return model objects. Try specifying 'flavour' attribute as 'dict'.")
119
+ raise AggregateFunctionError(clause)
113
120
 
114
121
  table_attr_dict[table][i][col] = dicc_cols[clause.alias_clause]
115
-
116
122
  # Convert back to a normal dict if you like (defaultdict is a dict subclass).
117
123
  return dict(table_attr_dict)
118
-
119
- def __get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
120
- """
121
- Get the class name of those classes that inherit from 'AggregateFunctionBase' class in order to create a better error message.
122
- """
123
- res: set[str] = set()
124
- if not isinstance(clauses, Iterable):
125
- return clauses.__class__.__name__
126
- for clause in clauses:
127
- if isinstance(clause, AggregateFunctionBase):
128
- res.add(clause.__class__.__name__)
129
- return ", ".join(res)