ormlambda 2.11.2__py3-none-any.whl → 3.7.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.
- ormlambda/__init__.py +11 -9
- ormlambda/caster/__init__.py +3 -0
- ormlambda/caster/base_caster.py +69 -0
- ormlambda/caster/caster.py +48 -0
- ormlambda/caster/interfaces/ICaster.py +26 -0
- ormlambda/caster/interfaces/__init__.py +1 -0
- ormlambda/common/__init__.py +1 -1
- ormlambda/common/abstract_classes/__init__.py +3 -3
- ormlambda/common/abstract_classes/decomposition_query.py +117 -319
- ormlambda/common/abstract_classes/non_query_base.py +1 -1
- ormlambda/common/enums/condition_types.py +2 -1
- ormlambda/common/enums/join_type.py +4 -1
- ormlambda/common/errors/__init__.py +15 -2
- ormlambda/common/global_checker.py +28 -0
- ormlambda/common/interfaces/ICustomAlias.py +4 -1
- ormlambda/common/interfaces/IDecompositionQuery.py +9 -34
- ormlambda/common/interfaces/IJoinSelector.py +21 -0
- ormlambda/common/interfaces/__init__.py +4 -6
- ormlambda/components/__init__.py +4 -0
- ormlambda/components/insert/abstract_insert.py +1 -1
- ormlambda/components/select/ISelect.py +17 -0
- ormlambda/components/select/__init__.py +1 -0
- ormlambda/components/update/abstract_update.py +4 -4
- ormlambda/components/upsert/abstract_upsert.py +1 -1
- ormlambda/databases/__init__.py +5 -0
- ormlambda/databases/my_sql/__init__.py +3 -1
- ormlambda/databases/my_sql/caster/__init__.py +1 -0
- ormlambda/databases/my_sql/caster/caster.py +38 -0
- ormlambda/databases/my_sql/caster/read.py +39 -0
- ormlambda/databases/my_sql/caster/types/__init__.py +8 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +31 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +34 -0
- ormlambda/databases/my_sql/caster/types/float.py +31 -0
- ormlambda/databases/my_sql/caster/types/int.py +31 -0
- ormlambda/databases/my_sql/caster/types/iterable.py +31 -0
- ormlambda/databases/my_sql/caster/types/none.py +30 -0
- ormlambda/databases/my_sql/caster/types/point.py +43 -0
- ormlambda/databases/my_sql/caster/types/string.py +31 -0
- ormlambda/databases/my_sql/caster/write.py +37 -0
- ormlambda/databases/my_sql/clauses/ST_AsText.py +36 -0
- ormlambda/databases/my_sql/clauses/ST_Contains.py +31 -0
- ormlambda/databases/my_sql/clauses/__init__.py +6 -4
- ormlambda/databases/my_sql/clauses/alias.py +24 -21
- ormlambda/databases/my_sql/clauses/count.py +32 -28
- ormlambda/databases/my_sql/clauses/create_database.py +3 -4
- ormlambda/databases/my_sql/clauses/delete.py +10 -10
- ormlambda/databases/my_sql/clauses/drop_database.py +3 -5
- ormlambda/databases/my_sql/clauses/drop_table.py +3 -3
- ormlambda/databases/my_sql/clauses/group_by.py +4 -7
- ormlambda/databases/my_sql/clauses/insert.py +33 -19
- ormlambda/databases/my_sql/clauses/joins.py +66 -59
- ormlambda/databases/my_sql/clauses/limit.py +1 -1
- ormlambda/databases/my_sql/clauses/offset.py +1 -1
- ormlambda/databases/my_sql/clauses/order.py +36 -23
- ormlambda/databases/my_sql/clauses/select.py +25 -36
- ormlambda/databases/my_sql/clauses/update.py +38 -13
- ormlambda/databases/my_sql/clauses/upsert.py +2 -2
- ormlambda/databases/my_sql/clauses/where.py +45 -0
- ormlambda/databases/my_sql/functions/concat.py +24 -27
- ormlambda/databases/my_sql/functions/max.py +32 -28
- ormlambda/databases/my_sql/functions/min.py +32 -28
- ormlambda/databases/my_sql/functions/sum.py +32 -28
- ormlambda/databases/my_sql/join_context.py +75 -0
- ormlambda/databases/my_sql/repository/__init__.py +1 -0
- ormlambda/databases/my_sql/{repository.py → repository/repository.py} +104 -73
- ormlambda/databases/my_sql/statements.py +231 -153
- ormlambda/engine/__init__.py +0 -0
- ormlambda/engine/template.py +47 -0
- ormlambda/model/__init__.py +0 -0
- ormlambda/model/base_model.py +37 -0
- ormlambda/repository/__init__.py +2 -0
- ormlambda/repository/base_repository.py +14 -0
- ormlambda/repository/interfaces/IDatabaseConnection.py +12 -0
- ormlambda/{common → repository}/interfaces/IRepositoryBase.py +6 -5
- ormlambda/repository/interfaces/__init__.py +2 -0
- ormlambda/sql/__init__.py +3 -0
- ormlambda/sql/clause_info/__init__.py +3 -0
- ormlambda/sql/clause_info/clause_info.py +434 -0
- ormlambda/sql/clause_info/clause_info_context.py +87 -0
- ormlambda/sql/clause_info/interface/IAggregate.py +10 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/column.py +126 -0
- ormlambda/sql/comparer.py +156 -0
- ormlambda/sql/foreign_key.py +115 -0
- ormlambda/sql/interfaces/__init__.py +0 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/{utils → sql/table}/fields.py +6 -5
- ormlambda/{utils → sql/table}/table_constructor.py +43 -91
- ormlambda/sql/types.py +25 -0
- ormlambda/statements/__init__.py +2 -0
- ormlambda/statements/base_statement.py +129 -0
- ormlambda/statements/interfaces/IStatements.py +309 -0
- ormlambda/statements/interfaces/__init__.py +1 -0
- ormlambda/statements/types.py +51 -0
- ormlambda/utils/__init__.py +1 -3
- ormlambda/utils/module_tree/__init__.py +1 -0
- ormlambda/utils/module_tree/dynamic_module.py +20 -14
- {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/METADATA +132 -68
- ormlambda-3.7.0.dist-info/RECORD +117 -0
- ormlambda/common/abstract_classes/abstract_model.py +0 -115
- ormlambda/common/interfaces/IAggregate.py +0 -10
- ormlambda/common/interfaces/IStatements.py +0 -348
- ormlambda/components/where/__init__.py +0 -1
- ormlambda/components/where/abstract_where.py +0 -15
- ormlambda/databases/my_sql/clauses/where_condition.py +0 -222
- ormlambda/model_base.py +0 -36
- ormlambda/utils/column.py +0 -105
- ormlambda/utils/foreign_key.py +0 -81
- ormlambda/utils/lambda_disassembler/__init__.py +0 -4
- ormlambda/utils/lambda_disassembler/dis_types.py +0 -133
- ormlambda/utils/lambda_disassembler/disassembler.py +0 -69
- ormlambda/utils/lambda_disassembler/dtypes.py +0 -103
- ormlambda/utils/lambda_disassembler/name_of.py +0 -41
- ormlambda/utils/lambda_disassembler/nested_element.py +0 -44
- ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -145
- ormlambda-2.11.2.dist-info/RECORD +0 -81
- /ormlambda/{utils → sql}/dtypes.py +0 -0
- {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/LICENSE +0 -0
- {ormlambda-2.11.2.dist-info → ormlambda-3.7.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
import re
|
4
|
+
import typing as tp
|
5
|
+
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
6
|
+
|
7
|
+
|
8
|
+
from ormlambda.sql.types import ConditionType, ComparerTypes
|
9
|
+
from ormlambda.sql.clause_info import ClauseInfo
|
10
|
+
from ormlambda import ConditionType as ConditionEnum
|
11
|
+
|
12
|
+
if tp.TYPE_CHECKING:
|
13
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
14
|
+
from ormlambda.sql import Table
|
15
|
+
|
16
|
+
|
17
|
+
class ICleaner(abc.ABC):
|
18
|
+
@staticmethod
|
19
|
+
@abc.abstractmethod
|
20
|
+
def clean(value: str): ...
|
21
|
+
|
22
|
+
|
23
|
+
class IgnoreCase(ICleaner):
|
24
|
+
@staticmethod
|
25
|
+
def clean(value: str) -> str:
|
26
|
+
return value.lower()
|
27
|
+
|
28
|
+
|
29
|
+
DICC_FLAGS = {
|
30
|
+
re.IGNORECASE: IgnoreCase,
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
class CleanValue:
|
35
|
+
def __init__(self, name: str, *flags: re.RegexFlag):
|
36
|
+
self._flags = flags
|
37
|
+
self._filename: str = name
|
38
|
+
|
39
|
+
def clean(self) -> str:
|
40
|
+
temp_name = self._filename
|
41
|
+
for flag in self._flags:
|
42
|
+
cleaner = DICC_FLAGS.get(flag, None)
|
43
|
+
if cleaner:
|
44
|
+
temp_name = cleaner.clean(temp_name)
|
45
|
+
return temp_name
|
46
|
+
|
47
|
+
|
48
|
+
class Comparer[LTable: Table, LProp, RTable: Table, RProp](IQuery):
|
49
|
+
def __init__(
|
50
|
+
self,
|
51
|
+
left_condition: ConditionType[LProp],
|
52
|
+
right_condition: ConditionType[RProp],
|
53
|
+
compare: ComparerTypes,
|
54
|
+
context: ClauseContextType = None,
|
55
|
+
flags: tp.Optional[tp.Iterable[re.RegexFlag]] = None,
|
56
|
+
) -> None:
|
57
|
+
self._context: ClauseContextType = context
|
58
|
+
self._compare: ComparerTypes = compare
|
59
|
+
self._left_condition: Comparer[LTable, LProp, RTable, RProp] | ClauseInfo[LTable] = left_condition
|
60
|
+
self._right_condition: Comparer[LTable, LProp, RTable, RProp] | ClauseInfo[RTable] = right_condition
|
61
|
+
self._flags = flags
|
62
|
+
|
63
|
+
def set_context(self, context: ClauseContextType) -> None:
|
64
|
+
self._context = context
|
65
|
+
|
66
|
+
def __repr__(self) -> str:
|
67
|
+
return f"{Comparer.__name__}: {self.query}"
|
68
|
+
|
69
|
+
def _create_clause_info[TTable](self, cond: ConditionType[LProp]) -> Comparer[LTable, LProp, RTable, RProp] | ClauseInfo[TTable]:
|
70
|
+
from ormlambda import Column
|
71
|
+
|
72
|
+
if isinstance(cond, Comparer):
|
73
|
+
return cond
|
74
|
+
if isinstance(cond, Column):
|
75
|
+
return ClauseInfo(cond.table, cond, alias_clause=None, context=self._context)
|
76
|
+
# it a value that's not depend of any Table
|
77
|
+
return ClauseInfo(None, cond, alias_clause=None, context=self._context)
|
78
|
+
|
79
|
+
@property
|
80
|
+
def left_condition(self) -> Comparer | ClauseInfo[LTable]:
|
81
|
+
return self._create_clause_info(self._left_condition)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def right_condition(self) -> Comparer | ClauseInfo[RTable]:
|
85
|
+
return self._create_clause_info(self._right_condition)
|
86
|
+
|
87
|
+
@property
|
88
|
+
def compare(self) -> ComparerTypes:
|
89
|
+
return self._compare
|
90
|
+
|
91
|
+
@property
|
92
|
+
def query(self) -> str:
|
93
|
+
lcond = self.left_condition.query
|
94
|
+
rcond = self.right_condition.query
|
95
|
+
|
96
|
+
if self._flags:
|
97
|
+
rcond = CleanValue(rcond, self._flags).clean()
|
98
|
+
|
99
|
+
return f"{lcond} {self._compare} {rcond}"
|
100
|
+
|
101
|
+
def __and__(self, other: Comparer, context: ClauseContextType = None) -> Comparer:
|
102
|
+
# Customize the behavior of '&'
|
103
|
+
return Comparer(self, other, "AND", context=context)
|
104
|
+
|
105
|
+
def __or__(self, other: Comparer, context: ClauseContextType = None) -> Comparer:
|
106
|
+
# Customize the behavior of '|'
|
107
|
+
return Comparer(self, other, "OR", context=context)
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
def join_comparers(cls, comparers: list[Comparer], restrictive: bool = True, context: ClauseContextType = None) -> str:
|
111
|
+
if not isinstance(comparers, tp.Iterable):
|
112
|
+
raise ValueError(f"Excepted '{Comparer.__name__}' iterable not {type(comparers).__name__}")
|
113
|
+
if len(comparers) == 1:
|
114
|
+
comparer = comparers[0]
|
115
|
+
comparer.set_context(context)
|
116
|
+
return comparer.query
|
117
|
+
|
118
|
+
join_method = cls.__or__ if not restrictive else cls.__and__
|
119
|
+
|
120
|
+
ini_comparer: Comparer = None
|
121
|
+
for i in range(len(comparers) - 1):
|
122
|
+
if ini_comparer is None:
|
123
|
+
ini_comparer = comparers[i]
|
124
|
+
ini_comparer.set_context(context)
|
125
|
+
right_comparer = comparers[i + 1]
|
126
|
+
right_comparer.set_context(context)
|
127
|
+
new_comparer = join_method(ini_comparer, right_comparer, context=context)
|
128
|
+
ini_comparer = new_comparer
|
129
|
+
return new_comparer.query
|
130
|
+
|
131
|
+
|
132
|
+
class Regex[LProp, RProp](Comparer[None, LProp, None, RProp]):
|
133
|
+
def __init__(
|
134
|
+
self,
|
135
|
+
left_condition: ConditionType[LProp],
|
136
|
+
right_condition: ConditionType[RProp],
|
137
|
+
context: ClauseContextType = None,
|
138
|
+
flags: tp.Optional[tp.Iterable[re.RegexFlag]] = None,
|
139
|
+
):
|
140
|
+
super().__init__(
|
141
|
+
left_condition=left_condition,
|
142
|
+
right_condition=right_condition,
|
143
|
+
compare=ConditionEnum.REGEXP.value,
|
144
|
+
context=context,
|
145
|
+
flags=flags, # Pass as a named parameter instead
|
146
|
+
)
|
147
|
+
|
148
|
+
|
149
|
+
class Like[LProp, RProp](Comparer[None, LProp, None, RProp]):
|
150
|
+
def __init__(
|
151
|
+
self,
|
152
|
+
left_condition: ConditionType[LProp],
|
153
|
+
right_condition: ConditionType[RProp],
|
154
|
+
context: ClauseContextType = None,
|
155
|
+
):
|
156
|
+
super().__init__(left_condition, right_condition, ConditionEnum.LIKE.value, context)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Callable, TYPE_CHECKING, Optional, Any, Type, overload
|
3
|
+
|
4
|
+
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
5
|
+
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from ormlambda.sql.comparer import Comparer
|
9
|
+
from ormlambda import Table
|
10
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
11
|
+
|
12
|
+
|
13
|
+
class ForeignKey[TLeft: Table, TRight: Table](IQuery):
|
14
|
+
stored_calls: set[ForeignKey] = set()
|
15
|
+
|
16
|
+
@overload
|
17
|
+
def __new__[LProp, RProp](self, comparer: Comparer[LProp, RProp], clause_name: str) -> None: ...
|
18
|
+
@overload
|
19
|
+
def __new__[LProp, TRight, RProp](cls, tright: Type[TRight], relationship: Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]) -> TRight: ...
|
20
|
+
|
21
|
+
def __new__[LProp, TRight, RProp](cls, tright: Optional[TRight] = None, relationship: Optional[Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]] = None, *, comparer: Optional[Comparer] = None, clause_name: Optional[str] = None) -> TRight:
|
22
|
+
return super().__new__(cls)
|
23
|
+
|
24
|
+
def __init__[LProp, RProp](
|
25
|
+
self,
|
26
|
+
tright: Optional[TRight] = None,
|
27
|
+
relationship: Optional[Callable[[TLeft, TRight], Any | Comparer[TLeft, LProp, TRight, RProp]]] = None,
|
28
|
+
*,
|
29
|
+
comparer: Optional[Comparer] = None,
|
30
|
+
clause_name: Optional[str] = None,
|
31
|
+
) -> None:
|
32
|
+
if comparer is not None and clause_name is not None:
|
33
|
+
self.__init__with_comparer(comparer, clause_name)
|
34
|
+
else:
|
35
|
+
self.__init_with_callable(tright, relationship)
|
36
|
+
|
37
|
+
def __init__with_comparer[LProp, RProp](self, comparer: Comparer[LProp, RProp], clause_name: str) -> None:
|
38
|
+
self._relationship = None
|
39
|
+
self._tleft: TLeft = comparer.left_condition.table
|
40
|
+
self._tright: TRight = comparer.right_condition.table
|
41
|
+
self._clause_name: str = clause_name
|
42
|
+
self._comparer: Comparer[LProp, RProp] = comparer
|
43
|
+
|
44
|
+
def __init_with_callable[LProp, RProp](self, tright: Optional[TRight], relationship: Optional[Callable[[TLeft, TRight], Comparer[LProp, RProp]]]) -> None:
|
45
|
+
self._relationship: Callable[[TLeft, TRight], Comparer[LProp, RProp]] = relationship
|
46
|
+
self._tleft: TLeft = None
|
47
|
+
self._tright: TRight = tright
|
48
|
+
self._clause_name: str = None
|
49
|
+
self._comparer: Optional[Comparer] = None
|
50
|
+
|
51
|
+
def __set_name__(self, owner: TLeft, name) -> None:
|
52
|
+
self._tleft: TLeft = owner
|
53
|
+
self._clause_name: str = name
|
54
|
+
|
55
|
+
def __get__(self, obj: Optional[TRight], objtype=None) -> ForeignKey[TLeft, TRight] | TRight:
|
56
|
+
if not obj:
|
57
|
+
ForeignKey.stored_calls.add(self)
|
58
|
+
return self
|
59
|
+
return self._tright
|
60
|
+
|
61
|
+
def __set__(self, obj, value):
|
62
|
+
raise AttributeError(f"The {ForeignKey.__name__} '{self._clause_name}' in the '{self._tleft.__table_name__}' table cannot be overwritten.")
|
63
|
+
|
64
|
+
def __getattr__(self, name: str):
|
65
|
+
if self._tright is None:
|
66
|
+
raise AttributeError("No right table assigned to ForeignKey")
|
67
|
+
return getattr(self._tright, name)
|
68
|
+
|
69
|
+
def __repr__(self) -> str:
|
70
|
+
return f"{self.__class__.__name__}(" f"left={self._tleft.__name__ if self._tleft else 'None'}, " f"right={self._tright.__name__ if self._tright else 'None'}, " f"name={self._clause_name})"
|
71
|
+
|
72
|
+
@property
|
73
|
+
def tleft(self) -> TLeft:
|
74
|
+
return self._tleft
|
75
|
+
|
76
|
+
@property
|
77
|
+
def tright(self) -> TRight:
|
78
|
+
return self._tright
|
79
|
+
|
80
|
+
@property
|
81
|
+
def clause_name(self) -> str:
|
82
|
+
return self._clause_name
|
83
|
+
|
84
|
+
@property
|
85
|
+
def query(self) -> str:
|
86
|
+
compare = self.resolved_function()
|
87
|
+
rcon = alias if (alias := compare.right_condition.alias_table) else compare.right_condition.table.__table_name__
|
88
|
+
return f"FOREIGN KEY ({self._tleft.__table_name__}) REFERENCES {rcon}({compare.right_condition._column.column_name})"
|
89
|
+
|
90
|
+
@property
|
91
|
+
def alias(self) -> str:
|
92
|
+
self._comparer = self.resolved_function()
|
93
|
+
lcol = self._comparer.left_condition._column.column_name
|
94
|
+
rcol = self._comparer.right_condition._column.column_name
|
95
|
+
return f"{self.tleft.__table_name__}_{lcol}_{rcol}"
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def create_query(cls, orig_table: Table) -> list[str]:
|
99
|
+
clauses: list[str] = []
|
100
|
+
|
101
|
+
for attr in vars(orig_table):
|
102
|
+
if isinstance(attr, ForeignKey):
|
103
|
+
clauses.append(attr.query)
|
104
|
+
return clauses
|
105
|
+
|
106
|
+
def resolved_function[LProp: Any, RProp: Any](self, context: ClauseContextType = None) -> Comparer[LProp, RProp]:
|
107
|
+
""" """
|
108
|
+
if self._comparer is not None:
|
109
|
+
return self._comparer
|
110
|
+
|
111
|
+
left = self._tleft
|
112
|
+
right = self._tright
|
113
|
+
comparer = self._relationship(left, right)
|
114
|
+
comparer.set_context(context)
|
115
|
+
return comparer
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
from .table_constructor import Table, TableMeta # noqa: F401
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import typing as tp
|
2
|
-
from .
|
2
|
+
from ormlambda.sql import Column
|
3
3
|
|
4
4
|
__all__ = ["get_fields"]
|
5
5
|
|
6
|
-
MISSING = lambda: Column() # COMMENT: Very Important to avoid reusing the same variable across different classes. # noqa: E731
|
6
|
+
MISSING = lambda x: Column(x) # COMMENT: Very Important to avoid reusing the same variable across different classes. # noqa: E731
|
7
7
|
|
8
8
|
|
9
9
|
class Field[TProp: tp.AnnotatedAny]:
|
@@ -34,7 +34,7 @@ class Field[TProp: tp.AnnotatedAny]:
|
|
34
34
|
|
35
35
|
@property
|
36
36
|
def assginment(self) -> str:
|
37
|
-
return f"self.
|
37
|
+
return f"self.{self.name} = {self.default.column_name}"
|
38
38
|
|
39
39
|
|
40
40
|
def get_fields[T, TProp](cls: tp.Type[T]) -> tp.Iterable[Field]:
|
@@ -50,11 +50,12 @@ def get_fields[T, TProp](cls: tp.Type[T]) -> tp.Iterable[Field]:
|
|
50
50
|
# type_ must by Column object
|
51
51
|
field_type: TProp = type_
|
52
52
|
|
53
|
-
default: Column = getattr(cls, name, MISSING())
|
53
|
+
default: Column = getattr(cls, name, MISSING(field_type))
|
54
|
+
Column.__set_name__(default, cls, name)
|
54
55
|
|
55
|
-
default.dtype = field_type # COMMENT: Useful for setting the dtype variable after instantiation.
|
56
56
|
fields.append(Field[TProp](name, field_type, default))
|
57
57
|
|
58
58
|
# Update __annotations__ to create Columns
|
59
59
|
cls.__annotations__[name] = default
|
60
|
+
setattr(cls, name, default)
|
60
61
|
return fields
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from decimal import Decimal
|
3
|
-
from typing import Any, Optional, Type, dataclass_transform
|
3
|
+
from typing import Any, Optional, Type, dataclass_transform
|
4
4
|
import base64
|
5
5
|
import datetime
|
6
6
|
import json
|
@@ -8,21 +8,15 @@ import json
|
|
8
8
|
import shapely as sph
|
9
9
|
|
10
10
|
|
11
|
-
from .
|
12
|
-
from .
|
11
|
+
from ormlambda.sql import Column
|
12
|
+
from ormlambda.sql import ForeignKey
|
13
|
+
from ormlambda.sql.dtypes import get_query_clausule
|
13
14
|
from .fields import get_fields
|
14
|
-
from .
|
15
|
-
from .module_tree.dfs_traversal import DFSTraversal
|
16
|
-
|
17
|
-
if TYPE_CHECKING:
|
18
|
-
from .fields import Field
|
15
|
+
from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
|
19
16
|
|
20
17
|
|
21
18
|
@dataclass_transform()
|
22
19
|
def __init_constructor__[T](cls: Type[T]) -> Type[T]:
|
23
|
-
# create '__properties_mapped__' dictionary for each Table to avoid shared information
|
24
|
-
# TODOL: I don't know if it's better to create a global dictionary like in commit '7de69443d7a8e7264b8d5d604c95da0e5d7e9cc0'
|
25
|
-
setattr(cls, "__properties_mapped__", {})
|
26
20
|
fields = get_fields(cls)
|
27
21
|
|
28
22
|
locals_: dict[str, Any] = {}
|
@@ -34,21 +28,20 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
|
|
34
28
|
continue
|
35
29
|
|
36
30
|
locals_[field.type_name] = field.type_
|
37
|
-
locals_[field.default_name] =
|
31
|
+
locals_[field.default_name] = None
|
38
32
|
|
39
33
|
init_args.append(field.init_arg)
|
40
34
|
assignments.append(field.assginment)
|
41
|
-
__create_properties(cls, field)
|
42
35
|
|
43
36
|
string_locals_ = ", ".join(locals_)
|
44
37
|
string_init_args = ", ".join(init_args)
|
45
|
-
string_assignments = "\n\t\t"
|
38
|
+
string_assignments = "".join([f"\n\t\t{x}" for x in assignments])
|
46
39
|
|
47
40
|
wrapper_fn = "\n".join(
|
48
41
|
[
|
49
42
|
f"def wrapper({string_locals_}):",
|
50
43
|
f"\n\tdef __init__({string_init_args}):",
|
51
|
-
|
44
|
+
string_assignments,
|
52
45
|
"\treturn __init__",
|
53
46
|
]
|
54
47
|
)
|
@@ -62,21 +55,6 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
|
|
62
55
|
return cls
|
63
56
|
|
64
57
|
|
65
|
-
def __create_properties(cls: Type["Table"], field: Field) -> property:
|
66
|
-
_name: str = f"_{field.name}"
|
67
|
-
|
68
|
-
# we need to get Table attributes (Column class) and then called __getattribute__ or __setattr__ to make changes inside of Column
|
69
|
-
prop = property(
|
70
|
-
fget=lambda self: getattr(self, _name).__getattribute__("column_value"),
|
71
|
-
fset=lambda self, value: getattr(self, _name).__setattr__("column_value", value),
|
72
|
-
)
|
73
|
-
|
74
|
-
# set property in public name
|
75
|
-
setattr(cls, field.name, prop)
|
76
|
-
cls.__properties_mapped__[prop] = field.name
|
77
|
-
return None
|
78
|
-
|
79
|
-
|
80
58
|
class TableMeta(type):
|
81
59
|
def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
|
82
60
|
"""
|
@@ -95,27 +73,12 @@ class TableMeta(type):
|
|
95
73
|
if not isinstance(cls_object.__table_name__, str):
|
96
74
|
raise Exception(f"class variable '__table_name__' of '{cls_object.__name__}' class must be 'str'")
|
97
75
|
|
98
|
-
TableMeta.__add_to_ForeignKey(cls_object)
|
99
76
|
self = __init_constructor__(cls_object)
|
100
77
|
return self
|
101
78
|
|
102
79
|
def __repr__(cls: "Table") -> str:
|
103
80
|
return f"{TableMeta.__name__}: {cls.__table_name__}"
|
104
81
|
|
105
|
-
@staticmethod
|
106
|
-
def __add_to_ForeignKey(cls: "Table") -> None:
|
107
|
-
"""
|
108
|
-
When creating a Table class, we cannot pass the class itself as a parameter in a function that initializes a class variable.
|
109
|
-
To fix this, we first add the table name as key and then, we add the class itself in the TableInfo class.
|
110
|
-
"""
|
111
|
-
if table_info := ForeignKey.MAPPED.get(cls.__table_name__, None):
|
112
|
-
table_info.table_object = cls
|
113
|
-
else:
|
114
|
-
ForeignKey.MAPPED[cls.__table_name__] = TableInfo()
|
115
|
-
ForeignKey.MAPPED[cls.__table_name__].table_object = cls
|
116
|
-
|
117
|
-
return None
|
118
|
-
|
119
82
|
|
120
83
|
@dataclass_transform(eq_default=False)
|
121
84
|
class Table(metaclass=TableMeta):
|
@@ -130,7 +93,7 @@ class Table(metaclass=TableMeta):
|
|
130
93
|
>>> class Address(Table):
|
131
94
|
>>> __table_name__ = "address"
|
132
95
|
|
133
|
-
>>> address_id: int = Column
|
96
|
+
>>> address_id: int = Column(int, is_primary_key=True)
|
134
97
|
>>> address: str
|
135
98
|
>>> address2: str
|
136
99
|
>>> district: str
|
@@ -138,20 +101,19 @@ class Table(metaclass=TableMeta):
|
|
138
101
|
>>> postal_code: datetime
|
139
102
|
>>> phone: str
|
140
103
|
>>> location: datetime
|
141
|
-
>>> last_update: datetime = Column
|
104
|
+
>>> last_update: datetime = Column(datetime, is_auto_generated=True)
|
142
105
|
|
143
|
-
>>> city = ForeignKey["Address", City](
|
106
|
+
>>> city = ForeignKey["Address", City](City, lambda a, c: a.city_id == c.city_id)
|
144
107
|
"""
|
145
108
|
|
146
109
|
__table_name__: str = ...
|
147
|
-
__properties_mapped__: dict[property, str] = ...
|
148
110
|
|
149
111
|
def __str__(self) -> str:
|
150
112
|
params = self.to_dict()
|
151
113
|
return json.dumps(params, ensure_ascii=False, indent=2)
|
152
114
|
|
153
|
-
def __getattr__[T](self,
|
154
|
-
return self.__dict__.get(
|
115
|
+
def __getattr__[T](self, _name: str) -> Column[T]:
|
116
|
+
return self.__dict__.get(_name, None)
|
155
117
|
|
156
118
|
def __repr__(self: "Table") -> str:
|
157
119
|
def __cast_long_variables(value: Any):
|
@@ -165,6 +127,12 @@ class Table(metaclass=TableMeta):
|
|
165
127
|
equal_loop = ["=".join((x, __cast_long_variables(y))) for x, y in dicc.items()]
|
166
128
|
return f'{self.__class__.__name__}({", ".join(equal_loop)})'
|
167
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
|
+
|
168
136
|
def to_dict(self) -> dict[str, str | int]:
|
169
137
|
dicc: dict[str, Any] = {}
|
170
138
|
for x in self.__annotations__:
|
@@ -190,17 +158,22 @@ class Table(metaclass=TableMeta):
|
|
190
158
|
return transform_map[dtype](_value)
|
191
159
|
return _value
|
192
160
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
return col_obj
|
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
|
199
166
|
return None
|
200
167
|
|
201
168
|
@classmethod
|
202
169
|
def get_columns(cls) -> tuple[str, ...]:
|
203
|
-
return tuple(cls.__annotations__.
|
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
|
204
177
|
|
205
178
|
@classmethod
|
206
179
|
def create_table_query(cls) -> str:
|
@@ -218,16 +191,17 @@ class Table(metaclass=TableMeta):
|
|
218
191
|
It's imperative to instantiate cls() to initialize the 'Table' object and create private variables that will be Column objects.
|
219
192
|
Otherwise, we only can access to property method
|
220
193
|
"""
|
221
|
-
|
222
|
-
annotations: dict[str, Column] = table_init_.__annotations__
|
194
|
+
annotations: dict[str, Column] = cls.__annotations__
|
223
195
|
all_columns: list = []
|
224
|
-
for
|
225
|
-
|
226
|
-
all_columns.append(get_query_clausule(col_object))
|
196
|
+
for col_obj in annotations.values():
|
197
|
+
all_columns.append(get_query_clausule(col_obj))
|
227
198
|
return all_columns
|
228
199
|
|
229
200
|
@classmethod
|
230
201
|
def find_dependent_tables(cls) -> tuple["Table", ...]:
|
202
|
+
"""Work in progress"""
|
203
|
+
return
|
204
|
+
|
231
205
|
# TODOL: Dive into new way to return dependent tables
|
232
206
|
def get_involved_tables(graph: dict[Table, list[Table]], table_name: str) -> None:
|
233
207
|
"""
|
@@ -266,33 +240,11 @@ class Table(metaclass=TableMeta):
|
|
266
240
|
return False
|
267
241
|
|
268
242
|
@classmethod
|
269
|
-
def
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
return name
|
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__
|
274
247
|
|
275
|
-
@overload
|
276
248
|
@classmethod
|
277
|
-
def
|
278
|
-
|
279
|
-
@classmethod
|
280
|
-
def get_column(cls, column: property) -> Column: ...
|
281
|
-
@overload
|
282
|
-
@classmethod
|
283
|
-
def get_column[TProp](cls, column: property, value: TProp) -> Column[TProp]: ...
|
284
|
-
@overload
|
285
|
-
@classmethod
|
286
|
-
def get_column[TProp](cls, column: str, value: TProp) -> Column[TProp]: ...
|
287
|
-
@classmethod
|
288
|
-
def get_column[TProp](cls, column: str | property, value: Optional[TProp] = None) -> Column[TProp]:
|
289
|
-
if isinstance(column, property):
|
290
|
-
_column = cls.get_property_name(column)
|
291
|
-
elif isinstance(column, str) and column in cls.get_columns():
|
292
|
-
_column = column
|
293
|
-
else:
|
294
|
-
raise ValueError(f"'Column' param with value'{column}' is not expected.")
|
295
|
-
|
296
|
-
instance_table: Table = cls(**{_column: value})
|
297
|
-
|
298
|
-
return getattr(instance_table, f"_{_column}")
|
249
|
+
def foreign_keys(cls) -> dict[str, ForeignKey]:
|
250
|
+
return {key: value for key, value in cls.__dict__.items() if isinstance(value, ForeignKey)}
|
ormlambda/sql/types.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
import typing as tp
|
2
|
+
|
3
|
+
|
4
|
+
if tp.TYPE_CHECKING:
|
5
|
+
from ormlambda import Table, Column, ForeignKey
|
6
|
+
from ormlambda.sql.comparer import Comparer
|
7
|
+
from ormlambda import ConditionType as ConditionEnum
|
8
|
+
from ormlambda.common.enums.join_type import JoinType
|
9
|
+
|
10
|
+
|
11
|
+
type AsteriskType = str
|
12
|
+
type TableType[T: Table] = tp.Type[T] | ForeignKey[T]
|
13
|
+
type ColumnType[TProp] = TProp | Column[TProp] | AsteriskType | tuple[Column]
|
14
|
+
type AliasType[T] = tp.Optional[str | tp.Callable[[T], str]]
|
15
|
+
|
16
|
+
# region Comparer Types
|
17
|
+
type ComparerType = tp.Literal["=", "!=", "<", "<=", ">", ">=", "in"]
|
18
|
+
type ConditionType[TProp] = Comparer | ColumnType[TProp]
|
19
|
+
type UnionType = tp.Literal["AND", "OR", ""]
|
20
|
+
type ComparerTypes = ComparerType | UnionType | ConditionEnum
|
21
|
+
# endregion
|
22
|
+
|
23
|
+
type TupleJoinType[T] = tuple[Comparer[T], JoinType]
|
24
|
+
|
25
|
+
ASTERISK: AsteriskType = "*"
|