ormlambda 3.12.2__py3-none-any.whl → 3.34.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/__init__.py +2 -0
- ormlambda/caster/__init__.py +1 -1
- ormlambda/caster/caster.py +29 -12
- ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
- ormlambda/common/abstract_classes/decomposition_query.py +17 -2
- ormlambda/common/abstract_classes/non_query_base.py +9 -7
- ormlambda/common/abstract_classes/query_base.py +3 -1
- ormlambda/common/errors/__init__.py +29 -0
- ormlambda/common/interfaces/IQueryCommand.py +6 -2
- ormlambda/databases/__init__.py +0 -1
- ormlambda/databases/my_sql/__init__.py +0 -1
- ormlambda/databases/my_sql/caster/caster.py +23 -19
- ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
- ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
- ormlambda/databases/my_sql/caster/types/date.py +34 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
- ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
- ormlambda/databases/my_sql/caster/types/float.py +7 -7
- ormlambda/databases/my_sql/caster/types/int.py +7 -7
- ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
- ormlambda/databases/my_sql/caster/types/none.py +8 -7
- ormlambda/databases/my_sql/caster/types/point.py +4 -4
- ormlambda/databases/my_sql/caster/types/string.py +7 -7
- ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
- ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
- ormlambda/databases/my_sql/clauses/__init__.py +4 -10
- ormlambda/databases/my_sql/clauses/count.py +5 -15
- ormlambda/databases/my_sql/clauses/delete.py +3 -50
- ormlambda/databases/my_sql/clauses/group_by.py +3 -16
- ormlambda/databases/my_sql/clauses/having.py +2 -6
- ormlambda/databases/my_sql/clauses/insert.py +4 -92
- ormlambda/databases/my_sql/clauses/joins.py +5 -140
- ormlambda/databases/my_sql/clauses/limit.py +4 -15
- ormlambda/databases/my_sql/clauses/offset.py +4 -15
- ormlambda/databases/my_sql/clauses/order.py +4 -61
- ormlambda/databases/my_sql/clauses/update.py +4 -67
- ormlambda/databases/my_sql/clauses/upsert.py +3 -66
- ormlambda/databases/my_sql/clauses/where.py +4 -42
- ormlambda/databases/my_sql/repository.py +217 -0
- ormlambda/dialects/__init__.py +39 -0
- ormlambda/dialects/default/__init__.py +1 -0
- ormlambda/dialects/default/base.py +39 -0
- ormlambda/dialects/interface/__init__.py +1 -0
- ormlambda/dialects/interface/dialect.py +78 -0
- ormlambda/dialects/mysql/__init__.py +8 -0
- ormlambda/dialects/mysql/base.py +387 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -0
- ormlambda/dialects/mysql/types.py +732 -0
- ormlambda/dialects/sqlite/__init__.py +5 -0
- ormlambda/dialects/sqlite/base.py +47 -0
- ormlambda/dialects/sqlite/pysqlite.py +32 -0
- ormlambda/engine/__init__.py +1 -0
- ormlambda/engine/base.py +58 -0
- ormlambda/engine/create.py +9 -23
- ormlambda/engine/url.py +31 -19
- ormlambda/env.py +30 -0
- ormlambda/errors.py +17 -0
- ormlambda/model/base_model.py +7 -9
- ormlambda/repository/base_repository.py +36 -5
- ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
- ormlambda/repository/response.py +134 -0
- ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
- ormlambda/sql/clause_info/clause_info.py +34 -17
- ormlambda/sql/clauses/__init__.py +14 -0
- ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
- ormlambda/sql/clauses/count.py +57 -0
- ormlambda/sql/clauses/delete.py +71 -0
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/sql/clauses/having.py +21 -0
- ormlambda/sql/clauses/insert.py +104 -0
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
- ormlambda/sql/clauses/joins.py +159 -0
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/sql/clauses/order.py +55 -0
- ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
- ormlambda/sql/clauses/update.py +84 -0
- ormlambda/sql/clauses/upsert.py +77 -0
- ormlambda/sql/clauses/where.py +65 -0
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/{column.py → column/column.py} +82 -22
- ormlambda/sql/comparer.py +51 -37
- ormlambda/sql/compiler.py +427 -0
- ormlambda/sql/ddl.py +68 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +43 -39
- ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
- ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
- ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
- ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
- ormlambda/sql/sqltypes.py +647 -0
- ormlambda/sql/table/__init__.py +1 -1
- ormlambda/sql/table/table.py +179 -0
- ormlambda/sql/table/table_constructor.py +1 -208
- ormlambda/sql/type_api.py +35 -0
- ormlambda/sql/types.py +3 -1
- ormlambda/sql/visitors.py +74 -0
- ormlambda/statements/__init__.py +1 -0
- ormlambda/statements/base_statement.py +28 -38
- ormlambda/statements/interfaces/IStatements.py +5 -4
- ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
- ormlambda/{databases/my_sql → statements}/statements.py +50 -60
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +88 -0
- ormlambda/util/load_module.py +21 -0
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.1.dist-info/AUTHORS +32 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
- ormlambda-3.34.1.dist-info/RECORD +157 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
- ormlambda/components/__init__.py +0 -4
- ormlambda/components/delete/__init__.py +0 -2
- ormlambda/components/delete/abstract_delete.py +0 -17
- ormlambda/components/insert/__init__.py +0 -2
- ormlambda/components/insert/abstract_insert.py +0 -25
- ormlambda/components/select/__init__.py +0 -1
- ormlambda/components/update/__init__.py +0 -2
- ormlambda/components/update/abstract_update.py +0 -29
- ormlambda/components/upsert/__init__.py +0 -2
- ormlambda/components/upsert/abstract_upsert.py +0 -25
- ormlambda/databases/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
- ormlambda/databases/my_sql/repository/__init__.py +0 -1
- ormlambda/databases/my_sql/repository/repository.py +0 -351
- ormlambda/engine/template.py +0 -47
- ormlambda/sql/dtypes.py +0 -94
- ormlambda/utils/__init__.py +0 -1
- ormlambda-3.12.2.dist-info/RECORD +0 -125
- /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
- /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
- /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
- /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
- /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
- /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
- /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
- /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
- /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,179 @@
|
|
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
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from ormlambda.statements import BaseStatement
|
12
|
+
from ormlambda.dialects import Dialect
|
13
|
+
|
14
|
+
from .table_constructor import __init_constructor__
|
15
|
+
|
16
|
+
|
17
|
+
class TableMeta(type):
|
18
|
+
def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
|
19
|
+
"""
|
20
|
+
That's the class we use to recreate the table's metadata.
|
21
|
+
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.
|
22
|
+
We simply call '__init_constructor__' to create all the necessary variables and the method.
|
23
|
+
"""
|
24
|
+
cls_object: Table = super().__new__(cls, name, bases, dct)
|
25
|
+
|
26
|
+
if name == "Table":
|
27
|
+
return cls_object
|
28
|
+
|
29
|
+
if cls_object.__table_name__ is Ellipsis:
|
30
|
+
raise Exception(f"class variable '__table_name__' must be declared in '{cls_object.__name__}' class")
|
31
|
+
|
32
|
+
if not isinstance(cls_object.__table_name__, str):
|
33
|
+
raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
|
34
|
+
|
35
|
+
self = __init_constructor__(cls_object)
|
36
|
+
return self
|
37
|
+
|
38
|
+
def __repr__(cls: "Table") -> str:
|
39
|
+
return f"{TableMeta.__name__}: {cls.__table_name__}"
|
40
|
+
|
41
|
+
|
42
|
+
@dataclass_transform(eq_default=False)
|
43
|
+
class Table(metaclass=TableMeta):
|
44
|
+
"""
|
45
|
+
Class to mapped database tables with Python classes.
|
46
|
+
|
47
|
+
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
|
48
|
+
and it do not going to appear when you instantiate the object itself.
|
49
|
+
|
50
|
+
This principle it so powerful due to we can create Foreign Key references without break __init__ class method.
|
51
|
+
|
52
|
+
>>> class Address(Table):
|
53
|
+
>>> __table_name__ = "address"
|
54
|
+
|
55
|
+
>>> address_id: int = Column(int, is_primary_key=True)
|
56
|
+
>>> address: str
|
57
|
+
>>> address2: str
|
58
|
+
>>> district: str
|
59
|
+
>>> city_id: int
|
60
|
+
>>> postal_code: datetime
|
61
|
+
>>> phone: str
|
62
|
+
>>> location: datetime
|
63
|
+
>>> last_update: datetime = Column(datetime, is_auto_generated=True)
|
64
|
+
|
65
|
+
>>> city = ForeignKey["Address", City](City, lambda a, c: a.city_id == c.city_id)
|
66
|
+
"""
|
67
|
+
|
68
|
+
__table_name__: str = ...
|
69
|
+
|
70
|
+
def __str__(self) -> str:
|
71
|
+
params = self.to_dict()
|
72
|
+
return json.dumps(params, ensure_ascii=False, indent=2)
|
73
|
+
|
74
|
+
def __getattr__[T](self, _name: str) -> Column[T]:
|
75
|
+
return self.__dict__.get(_name, None)
|
76
|
+
|
77
|
+
def __repr__(self: "Table") -> str:
|
78
|
+
def __cast_long_variables(value: Any):
|
79
|
+
if not isinstance(value, str):
|
80
|
+
value = str(value)
|
81
|
+
if len(value) > 20:
|
82
|
+
return value[:20] + "..."
|
83
|
+
return value
|
84
|
+
|
85
|
+
dicc: dict[str, str] = {x: str(getattr(self, x)) for x in self.__annotations__}
|
86
|
+
equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
|
87
|
+
return f'{self.__class__.__name__}({", ".join(equal_loop)})'
|
88
|
+
|
89
|
+
def __getitem__[TProp](self, value: str | Column[TProp]) -> Optional[TProp]:
|
90
|
+
name = value if isinstance(value, str) else value.column_name
|
91
|
+
if hasattr(self, name):
|
92
|
+
return getattr(self, name)
|
93
|
+
return None
|
94
|
+
|
95
|
+
def to_dict(self) -> dict[str, str | int]:
|
96
|
+
dicc: dict[str, Any] = {}
|
97
|
+
for x in self.__annotations__:
|
98
|
+
dicc[x] = getattr(self, x)
|
99
|
+
return dicc
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def get_pk(cls) -> Optional[Column]:
|
103
|
+
for obj in cls.__dict__.values():
|
104
|
+
if isinstance(obj, Column) and obj.is_primary_key:
|
105
|
+
return obj
|
106
|
+
return None
|
107
|
+
|
108
|
+
@classmethod
|
109
|
+
def get_columns(cls) -> tuple[Column, ...]:
|
110
|
+
return tuple([x for x in cls.__annotations__.values() if isinstance(x, Column)])
|
111
|
+
|
112
|
+
@classmethod
|
113
|
+
def get_column[TProp](cls, name: str) -> Column[TProp]:
|
114
|
+
for key, value in cls.__annotations__.items():
|
115
|
+
if name == key:
|
116
|
+
return value
|
117
|
+
|
118
|
+
@classmethod
|
119
|
+
def create_table_query(cls, statement: BaseStatement) -> str:
|
120
|
+
"""It's classmethod because of it does not matter the columns values to create the table"""
|
121
|
+
from ormlambda.sql.schema_generator import SchemaGeneratorFactory
|
122
|
+
|
123
|
+
return SchemaGeneratorFactory.get_generator(statement._dialect).create_table(cls)
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
def create_table(cls, dialect: Dialect) -> str:
|
127
|
+
return CreateTable(cls).compile(dialect).string
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def find_dependent_tables(cls) -> tuple["Table", ...]:
|
131
|
+
"""Work in progress"""
|
132
|
+
return
|
133
|
+
|
134
|
+
# TODOL: Dive into new way to return dependent tables
|
135
|
+
def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
|
136
|
+
"""
|
137
|
+
Create a graph to be ordered
|
138
|
+
"""
|
139
|
+
table = ForeignKey[Table, Table].MAPPED[table_name]
|
140
|
+
for x in table.referenced_tables:
|
141
|
+
if data := ForeignKey.MAPPED.get(x, None):
|
142
|
+
get_involved_tables(graph, data.table_object.__table_name__)
|
143
|
+
|
144
|
+
graph[table.table_object.__table_name__] = list(table.referenced_tables)
|
145
|
+
return None
|
146
|
+
|
147
|
+
graph: dict[Table, list[Table]] = {}
|
148
|
+
dependent = ForeignKey.MAPPED.get(cls.__table_name__, None)
|
149
|
+
if dependent is None:
|
150
|
+
return tuple([])
|
151
|
+
|
152
|
+
graph[cls.__table_name__] = list(dependent.referenced_tables)
|
153
|
+
get_involved_tables(graph, cls.__table_name__)
|
154
|
+
|
155
|
+
dfs = DFSTraversal.sort(graph)
|
156
|
+
|
157
|
+
order_table = dfs[: dfs.index(cls.__table_name__)]
|
158
|
+
|
159
|
+
return [ForeignKey.MAPPED[x].table_object for x in order_table]
|
160
|
+
|
161
|
+
def __eq__(self, __value: Any) -> bool:
|
162
|
+
if isinstance(__value, Table):
|
163
|
+
return all(
|
164
|
+
(
|
165
|
+
self.__table_name__ == __value.__table_name__,
|
166
|
+
tuple(self.to_dict().items()),
|
167
|
+
)
|
168
|
+
)
|
169
|
+
return False
|
170
|
+
|
171
|
+
@classmethod
|
172
|
+
def table_alias(cls, column: Optional[str] = None) -> str:
|
173
|
+
if column:
|
174
|
+
return f"`{cls.__table_name__}_{column}`"
|
175
|
+
return cls.__table_name__
|
176
|
+
|
177
|
+
@classmethod
|
178
|
+
def foreign_keys(cls) -> dict[str, ForeignKey]:
|
179
|
+
return {key: value for key, value in cls.__dict__.items() if isinstance(value, ForeignKey)}
|
@@ -1,18 +1,6 @@
|
|
1
|
-
from
|
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
|
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
|
ormlambda/statements/__init__.py
CHANGED
@@ -7,32 +7,38 @@ 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.
|
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.
|
16
|
-
from ormlambda.
|
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],
|
24
|
-
self.
|
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
|
-
|
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
|
+
|
36
42
|
@override
|
37
43
|
def table_exists(self) -> bool:
|
38
44
|
return self._repository.table_exists(self._model.__table_name__)
|
@@ -47,13 +53,12 @@ class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
|
47
53
|
return f"<Model: {self.__class__.__name__}>"
|
48
54
|
|
49
55
|
def _return_flavour[TValue](self, query, flavour: Type[TValue], select, **kwargs) -> tuple[TValue]:
|
50
|
-
return self._repository.read_sql(query, flavour=flavour,
|
56
|
+
return self._repository.read_sql(query, flavour=flavour, select=select, **kwargs)
|
51
57
|
|
52
58
|
def _return_model(self, select, query: str) -> tuple[tuple[T]]:
|
53
|
-
response_sql = self._repository.read_sql(query, flavour=dict,
|
54
|
-
|
59
|
+
response_sql = self._repository.read_sql(query, flavour=dict, select=select) # store all columns of the SQL query
|
55
60
|
if response_sql and isinstance(response_sql, Iterable):
|
56
|
-
return
|
61
|
+
return ClusterResponse(self._dialect, select, response_sql).cluster()
|
57
62
|
|
58
63
|
return response_sql
|
59
64
|
|
@@ -75,15 +80,15 @@ class BaseStatement[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
|
75
80
|
return self._repository
|
76
81
|
|
77
82
|
|
78
|
-
class
|
79
|
-
def __init__(self,
|
80
|
-
self.
|
81
|
-
self._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
|
82
87
|
self._response_sql: tuple[dict[str, Any]] = response_sql
|
83
|
-
self._caster =
|
88
|
+
self._caster = dialect.caster
|
84
89
|
|
85
|
-
def
|
86
|
-
tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self.
|
90
|
+
def cluster(self) -> tuple[dict[Type[Table], tuple[Table, ...]]]:
|
91
|
+
tbl_dicc: dict[Type[Table], list[dict[str, Any]]] = self._create_cluster()
|
87
92
|
|
88
93
|
response = {}
|
89
94
|
tuple_response = []
|
@@ -91,13 +96,12 @@ class ClusterQuery[T]:
|
|
91
96
|
for table, attribute_list in tbl_dicc.items():
|
92
97
|
new_instance = []
|
93
98
|
for attrs in attribute_list:
|
94
|
-
|
95
|
-
new_instance.append(table(**casted_attr))
|
99
|
+
new_instance.append(table(**attrs))
|
96
100
|
response[table] = tuple(new_instance)
|
97
101
|
tuple_response.append(tuple(new_instance))
|
98
102
|
return tuple(tuple_response)
|
99
103
|
|
100
|
-
def
|
104
|
+
def _create_cluster(self) -> dict[Type[Table], list[dict[str, Any]]]:
|
101
105
|
# We'll create a default list of dicts *once* we know how many rows are in _response_sql
|
102
106
|
row_count = len(self._response_sql)
|
103
107
|
|
@@ -112,22 +116,8 @@ class ClusterQuery[T]:
|
|
112
116
|
col = clause.column
|
113
117
|
|
114
118
|
if col is None or not hasattr(table, col):
|
115
|
-
|
116
|
-
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)
|
117
120
|
|
118
121
|
table_attr_dict[table][i][col] = dicc_cols[clause.alias_clause]
|
119
|
-
|
120
122
|
# Convert back to a normal dict if you like (defaultdict is a dict subclass).
|
121
123
|
return dict(table_attr_dict)
|
122
|
-
|
123
|
-
def __get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
|
124
|
-
"""
|
125
|
-
Get the class name of those classes that inherit from 'AggregateFunctionBase' class in order to create a better error message.
|
126
|
-
"""
|
127
|
-
res: set[str] = set()
|
128
|
-
if not isinstance(clauses, Iterable):
|
129
|
-
return clauses.__class__.__name__
|
130
|
-
for clause in clauses:
|
131
|
-
if isinstance(clause, AggregateFunctionBase):
|
132
|
-
res.add(clause.__class__.__name__)
|
133
|
-
return ", ".join(res)
|