ormlambda 2.8.0__tar.gz → 2.9.4__tar.gz
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-2.8.0 → ormlambda-2.9.4}/PKG-INFO +3 -2
- {ormlambda-2.8.0 → ormlambda-2.9.4}/pyproject.toml +4 -2
- ormlambda-2.9.4/src/ormlambda/__init__.py +20 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/abstract_model.py +39 -26
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/decomposition_query.py +124 -86
- ormlambda-2.9.4/src/ormlambda/common/errors/__init__.py +3 -0
- ormlambda-2.9.4/src/ormlambda/common/interfaces/ICustomAlias.py +4 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IDecompositionQuery.py +5 -1
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IRepositoryBase.py +1 -1
- ormlambda-2.9.4/src/ormlambda/common/interfaces/IStatements.py +332 -0
- ormlambda-2.9.4/src/ormlambda/databases/my_sql/clauses/alias.py +31 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/group_by.py +2 -2
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/insert.py +24 -12
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/joins.py +39 -1
- ormlambda-2.9.4/src/ormlambda/databases/my_sql/clauses/order.py +47 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/select.py +19 -12
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/update.py +11 -17
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/repository.py +79 -18
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/statements.py +76 -53
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/model_base.py +3 -3
- ormlambda-2.9.4/src/ormlambda/utils/column.py +105 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/dtypes.py +4 -12
- ormlambda-2.9.4/src/ormlambda/utils/fields.py +60 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/table_constructor.py +67 -95
- ormlambda-2.8.0/src/ormlambda/__init__.py +0 -11
- ormlambda-2.8.0/src/ormlambda/common/interfaces/IStatements.py +0 -270
- ormlambda-2.8.0/src/ormlambda/databases/my_sql/clauses/order.py +0 -33
- ormlambda-2.8.0/src/ormlambda/utils/column.py +0 -66
- {ormlambda-2.8.0 → ormlambda-2.9.4}/LICENSE +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/README.md +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/non_query_base.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/query_base.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/condition_types.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/join_type.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IAggregate.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/INonQueryCommand.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IQueryCommand.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/IDelete.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/abstract_delete.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/IInsert.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/abstract_insert.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/IUpdate.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/abstract_update.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/IUpsert.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/abstract_upsert.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/where/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/where/abstract_where.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/count.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/create_database.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/delete.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/drop_database.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/drop_table.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/limit.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/offset.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/upsert.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/where_condition.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/concat.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/max.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/min.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/sum.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/foreign_key.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/dis_types.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/disassembler.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/dtypes.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/name_of.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/nested_element.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/module_tree/__init__.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/module_tree/dfs_traversal.py +0 -0
- {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/module_tree/dynamic_module.py +0 -0
@@ -1,14 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ormlambda
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.9.4
|
4
4
|
Summary: ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions
|
5
5
|
Author: p-hzamora
|
6
6
|
Author-email: p.hzamora@icloud.com
|
7
7
|
Requires-Python: >=3.12,<4.0
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: Programming Language :: Python :: 3.12
|
10
|
-
Requires-Dist: fluent-validation (
|
10
|
+
Requires-Dist: fluent-validation (==4.3.1)
|
11
11
|
Requires-Dist: mysql-connector-python (>=9.0.0,<10.0.0)
|
12
|
+
Requires-Dist: shapely (>=2.0.6,<3.0.0)
|
12
13
|
Description-Content-Type: text/markdown
|
13
14
|
|
14
15
|

|
@@ -3,7 +3,7 @@ line-length = 320
|
|
3
3
|
|
4
4
|
[tool.poetry]
|
5
5
|
name = "ormlambda"
|
6
|
-
version = "2.
|
6
|
+
version = "2.9.4"
|
7
7
|
description = "ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions"
|
8
8
|
authors = ["p-hzamora <p.hzamora@icloud.com>"]
|
9
9
|
readme = "README.md"
|
@@ -11,12 +11,14 @@ readme = "README.md"
|
|
11
11
|
[tool.poetry.dependencies]
|
12
12
|
python = "^3.12"
|
13
13
|
mysql-connector-python= "^9.0.0"
|
14
|
-
fluent-validation = "
|
14
|
+
fluent-validation = "4.3.1"
|
15
|
+
shapely = "^2.0.6"
|
15
16
|
|
16
17
|
[tool.poetry.group.test.dependencies]
|
17
18
|
pandas = "^2.2.2"
|
18
19
|
ruff = "^0.4.5"
|
19
20
|
python-decouple = "^3.8"
|
21
|
+
parameterized = "^0.9.0"
|
20
22
|
|
21
23
|
|
22
24
|
[build-system]
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# region enums
|
2
|
+
from .common.enums import (
|
3
|
+
JoinType as JoinType,
|
4
|
+
ConditionType as ConditionType,
|
5
|
+
)
|
6
|
+
from ormlambda.common.interfaces.IStatements import OrderType as OrderType
|
7
|
+
# endregion
|
8
|
+
|
9
|
+
from .common.abstract_classes import AbstractSQLStatements as AbstractSQLStatements
|
10
|
+
from .common.interfaces import IRepositoryBase as IRepositoryBase
|
11
|
+
from .utils import (
|
12
|
+
Table as Table,
|
13
|
+
Column as Column,
|
14
|
+
ForeignKey as ForeignKey,
|
15
|
+
)
|
16
|
+
from .utils.lambda_disassembler import (
|
17
|
+
Disassembler as Disassembler,
|
18
|
+
nameof as nameof,
|
19
|
+
)
|
20
|
+
from .model_base import BaseModel as BaseModel # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Any, Type, override, Iterable, Literal, TYPE_CHECKING
|
2
|
+
from typing import Any, Type, override, Iterable, Literal, TYPE_CHECKING, Optional
|
3
3
|
from collections import defaultdict
|
4
4
|
import abc
|
5
5
|
|
@@ -10,21 +10,22 @@ from ormlambda.common.interfaces.IAggregate import IAggregate
|
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
13
|
-
from ormlambda.components.select import ISelect
|
14
13
|
from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
|
15
14
|
|
16
15
|
|
17
16
|
ORDER_QUERIES = Literal["select", "join", "where", "order", "with", "group by", "limit", "offset"]
|
18
17
|
|
19
18
|
|
20
|
-
class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
19
|
+
class AbstractSQLStatements[T: Table, *Ts, TRepo](IStatements_two_generic[T, *Ts, TRepo]):
|
21
20
|
__slots__ = ("_model", "_repository", "_query_list")
|
22
21
|
__order__: tuple[str, ...] = ("select", "join", "where", "order", "with", "group by", "limit", "offset")
|
23
22
|
|
24
|
-
def __init__(self, model: T, repository: IRepositoryBase[TRepo]) -> None:
|
23
|
+
def __init__(self, model: tuple[T, *Ts], repository: IRepositoryBase[TRepo]) -> None:
|
25
24
|
self.__valid_repository(repository)
|
26
25
|
|
27
|
-
self.
|
26
|
+
self._query: Optional[str] = None
|
27
|
+
self._model: T = model[0] if isinstance(model, Iterable) else model
|
28
|
+
self._models: tuple[T, *Ts] = self._model if isinstance(model, Iterable) else (model,)
|
28
29
|
self._repository: IRepositoryBase[TRepo] = repository
|
29
30
|
self._query_list: dict[ORDER_QUERIES, list[IQuery]] = defaultdict(list)
|
30
31
|
|
@@ -42,15 +43,11 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
|
42
43
|
def __repr__(self):
|
43
44
|
return f"<Model: {self.__class__.__name__}>"
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
def repository(self) -> IRepositoryBase[TRepo]: ...
|
48
|
-
|
49
|
-
def _return_flavour[TValue](self, query, flavour: Type[TValue]) -> tuple[TValue]:
|
50
|
-
return self._repository.read_sql(query, flavour=flavour)
|
46
|
+
def _return_flavour[TValue](self, query, flavour: Type[TValue], select, **kwargs) -> tuple[TValue]:
|
47
|
+
return self._repository.read_sql(query, flavour=flavour, model=self._model, select=select, **kwargs)
|
51
48
|
|
52
|
-
def _return_model(self, select
|
53
|
-
response_sql = self._repository.read_sql(query, flavour=dict) # store all columns of the SQL query
|
49
|
+
def _return_model(self, select, query: str):
|
50
|
+
response_sql = self._repository.read_sql(query, flavour=dict, model=self._model, select=select) # store all columns of the SQL query
|
54
51
|
|
55
52
|
if isinstance(response_sql, Iterable):
|
56
53
|
return ClusterQuery[T](select, response_sql).clean_response()
|
@@ -60,13 +57,38 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
|
|
60
57
|
@abc.abstractmethod
|
61
58
|
def _build(sef): ...
|
62
59
|
|
60
|
+
@property
|
61
|
+
def query(self) -> str:
|
62
|
+
return self._query
|
63
|
+
|
64
|
+
@property
|
65
|
+
def model(self) -> Type[T]:
|
66
|
+
return self._model
|
67
|
+
|
68
|
+
@property
|
69
|
+
def models(self) -> tuple[*Ts]:
|
70
|
+
return self._models
|
71
|
+
|
72
|
+
@property
|
73
|
+
@override
|
74
|
+
def repository(self) -> IRepositoryBase[TRepo]: ...
|
75
|
+
|
63
76
|
|
64
77
|
class ClusterQuery[T]:
|
65
78
|
def __init__(self, select: DecompositionQueryBase[T], response_sql: tuple[dict[str, Any]]) -> None:
|
66
79
|
self._select: DecompositionQueryBase[T] = select
|
67
80
|
self._response_sql: tuple[dict[str, Any]] = response_sql
|
68
81
|
|
69
|
-
def
|
82
|
+
def clean_response(self) -> tuple[dict[Type[Table], tuple[Table]]]:
|
83
|
+
tbl_dicc: dict[Type[Table], list[Table]] = self.__loop_foo()
|
84
|
+
|
85
|
+
# it not depend of flavour attr
|
86
|
+
for key, val in tbl_dicc.items():
|
87
|
+
tbl_dicc[key] = tuple(val)
|
88
|
+
|
89
|
+
return tuple(tbl_dicc.values())
|
90
|
+
|
91
|
+
def __loop_foo(self) -> dict[Type[Table], list[Table]]:
|
70
92
|
# We must ensure to get the valid attributes for each instance
|
71
93
|
table_initialize = defaultdict(list)
|
72
94
|
|
@@ -74,8 +96,8 @@ class ClusterQuery[T]:
|
|
74
96
|
for dicc_cols in self._response_sql:
|
75
97
|
valid_attr: dict[str, Any] = {}
|
76
98
|
for clause in clauses:
|
77
|
-
if not hasattr(table, clause.column):
|
78
|
-
agg_methods = self.
|
99
|
+
if clause.column is None or not hasattr(table, clause.column):
|
100
|
+
agg_methods = self.__get_all_aggregate_method(clauses)
|
79
101
|
raise ValueError(f"You cannot use aggregation method like '{agg_methods}' to return model objects. Try specifying 'flavour' attribute as 'dict'.")
|
80
102
|
valid_attr[clause.column] = dicc_cols[clause.alias]
|
81
103
|
|
@@ -83,7 +105,7 @@ class ClusterQuery[T]:
|
|
83
105
|
table_initialize[table].append(table(**valid_attr))
|
84
106
|
return table_initialize
|
85
107
|
|
86
|
-
def
|
108
|
+
def __get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
|
87
109
|
res: set[str] = set()
|
88
110
|
|
89
111
|
for clause in clauses:
|
@@ -91,12 +113,3 @@ class ClusterQuery[T]:
|
|
91
113
|
if isinstance(row, IAggregate):
|
92
114
|
res.add(row.__class__.__name__)
|
93
115
|
return ", ".join(res)
|
94
|
-
|
95
|
-
def clean_response(self) -> tuple[dict[Type[Table], tuple[Table]]]:
|
96
|
-
tbl_dicc: dict[Type[Table], list[Table]] = self.loop_foo()
|
97
|
-
|
98
|
-
# it not depend of flavour attr
|
99
|
-
for key, val in tbl_dicc.items():
|
100
|
-
tbl_dicc[key] = tuple(val)
|
101
|
-
|
102
|
-
return tuple(tbl_dicc.values())
|
{ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/decomposition_query.py
RENAMED
@@ -5,13 +5,15 @@ import inspect
|
|
5
5
|
import abc
|
6
6
|
from ormlambda import Table
|
7
7
|
|
8
|
-
from ormlambda.utils.lambda_disassembler.tree_instruction import TreeInstruction
|
8
|
+
from ormlambda.utils.lambda_disassembler.tree_instruction import TreeInstruction, TupleInstruction, NestedElement
|
9
9
|
from ormlambda.common.interfaces import IAggregate, IDecompositionQuery
|
10
10
|
from ormlambda import JoinType, ForeignKey
|
11
11
|
from ormlambda.databases.my_sql.clauses.joins import JoinSelector
|
12
|
-
from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
|
13
12
|
|
14
|
-
|
13
|
+
from ..errors import DifferentTablesAndVariablesError
|
14
|
+
|
15
|
+
type ClauseDataType = property | str
|
16
|
+
type AliasType[T] = tp.Type[Table] | tp.Callable[[tp.Type[Table]], T]
|
15
17
|
|
16
18
|
|
17
19
|
class ClauseInfo[T: tp.Type[Table]]:
|
@@ -44,6 +46,13 @@ class ClauseInfo[T: tp.Type[Table]]:
|
|
44
46
|
def query(self) -> str:
|
45
47
|
return self._query
|
46
48
|
|
49
|
+
@property
|
50
|
+
def dtype[TProp](self) -> tp.Optional[tp.Type[TProp]]:
|
51
|
+
try:
|
52
|
+
return self._table.get_column(self.column).dtype
|
53
|
+
except ValueError:
|
54
|
+
return None
|
55
|
+
|
47
56
|
def _resolve_column(self, data: ClauseDataType) -> str:
|
48
57
|
if isinstance(data, property):
|
49
58
|
return self._table.__properties_mapped__[data]
|
@@ -53,7 +62,7 @@ class ClauseInfo[T: tp.Type[Table]]:
|
|
53
62
|
|
54
63
|
elif isinstance(data, str):
|
55
64
|
# TODOL: refactor to base the condition in dict with '*' as key. '*' must to work as special character
|
56
|
-
return f"'{data}'" if data !=
|
65
|
+
return f"'{data}'" if data != DecompositionQueryBase.CHAR else data
|
57
66
|
|
58
67
|
def __create_value_string(self, name: str) -> str:
|
59
68
|
if isinstance(self._row_column, property):
|
@@ -72,32 +81,57 @@ class ClauseInfo[T: tp.Type[Table]]:
|
|
72
81
|
return f"{column_name} as `{alias}`"
|
73
82
|
|
74
83
|
|
75
|
-
class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
76
|
-
|
84
|
+
class DecompositionQueryBase[T: tp.Type[Table], *Ts](IDecompositionQuery[T, *Ts]):
|
85
|
+
CHAR: str = "*"
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def _asterik_resolver(table: tp.Type[Table]):
|
89
|
+
return table
|
90
|
+
|
91
|
+
@tp.overload
|
92
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple]) -> None: ...
|
93
|
+
@tp.overload
|
94
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ...) -> None: ...
|
95
|
+
@tp.overload
|
96
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ...) -> None: ...
|
97
|
+
@tp.overload
|
98
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ...) -> None: ...
|
99
|
+
@tp.overload
|
100
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ..., replace_asterisk_char: bool = ...) -> None: ...
|
101
|
+
@tp.overload
|
102
|
+
def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ..., replace_asterisk_char: bool = ..., joins: tp.Optional[list[JoinSelector]] = ...) -> None: ...
|
103
|
+
|
104
|
+
def __init__(
|
77
105
|
self,
|
78
|
-
|
106
|
+
tables: tuple[T, *Ts],
|
79
107
|
lambda_query: tp.Callable[[T], tuple[*Ts]],
|
80
108
|
*,
|
81
109
|
alias: bool = True,
|
82
110
|
alias_name: tp.Optional[str] = None,
|
83
111
|
by: JoinType = JoinType.INNER_JOIN,
|
84
112
|
replace_asterisk_char: bool = True,
|
113
|
+
joins: tp.Optional[list[JoinType]] = None,
|
85
114
|
) -> None:
|
86
|
-
self.
|
87
|
-
self._lambda_query: tp.Callable[[T], tuple
|
115
|
+
self._tables: tuple[T, *Ts] = tables if isinstance(tables, tp.Iterable) else (tables,)
|
116
|
+
self._lambda_query: tp.Callable[[T], tuple] = lambda_query
|
88
117
|
self._alias: bool = alias
|
89
118
|
self._alias_name: tp.Optional[str] = alias_name
|
90
119
|
self._by: JoinType = by
|
120
|
+
self._joins: set[JoinSelector] = set(joins) if joins is not None else set()
|
91
121
|
|
92
|
-
self._fk_relationship: set[tuple[tp.Type[Table], tp.Type[Table]]] = set()
|
93
122
|
self._clauses_group_by_tables: dict[tp.Type[Table], list[ClauseInfo[T]]] = defaultdict(list)
|
94
123
|
self._all_clauses: list[ClauseInfo] = []
|
95
|
-
self.alias_cache: dict[str,
|
124
|
+
self.alias_cache: dict[str, AliasType] = {self.CHAR: self._asterik_resolver}
|
96
125
|
self._replace_asterisk_char: bool = replace_asterisk_char
|
97
126
|
self.__assign_lambda_variables_to_table(lambda_query)
|
98
127
|
|
99
128
|
self.__clauses_list_generetor(lambda_query)
|
100
129
|
|
130
|
+
def __getitem__(self, key: str) -> ClauseInfo:
|
131
|
+
for clause in self._all_clauses:
|
132
|
+
if clause.alias == key:
|
133
|
+
return clause
|
134
|
+
|
101
135
|
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
102
136
|
DEFAULT_ALIAS: str = f"{clause_info._table.__table_name__}_{clause_info._column}"
|
103
137
|
|
@@ -120,77 +154,92 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
|
120
154
|
"""
|
121
155
|
lambda_vars = tuple(inspect.signature(_lambda).parameters)
|
122
156
|
|
123
|
-
|
124
|
-
|
157
|
+
# COMMENT: We don't pass a lambda method because lambda reads the las value of 'i'
|
158
|
+
for i, param in enumerate(lambda_vars):
|
159
|
+
self.alias_cache[param] = self._tables[i]
|
125
160
|
return None
|
126
161
|
|
127
162
|
def __clauses_list_generetor(self, function: tp.Callable[[T], tp.Any]) -> None:
|
128
163
|
if not callable(function):
|
129
164
|
return None
|
165
|
+
try:
|
166
|
+
resolved_function = function(*self._tables)
|
167
|
+
except TypeError:
|
168
|
+
raise DifferentTablesAndVariablesError
|
130
169
|
|
131
|
-
|
132
|
-
|
170
|
+
tree_list = TreeInstruction(function).to_list()
|
133
171
|
# Python treats string objects as iterable, so we need to prevent this behavior
|
134
172
|
if isinstance(resolved_function, str) or not isinstance(resolved_function, tp.Iterable):
|
135
173
|
resolved_function = (resolved_function,)
|
136
174
|
|
137
|
-
for
|
138
|
-
|
175
|
+
for col_index, last_data in enumerate(resolved_function):
|
176
|
+
ti = tree_list[col_index] if tree_list else TupleInstruction(self.CHAR, NestedElement(self.CHAR))
|
177
|
+
|
178
|
+
values: ClauseInfo | list[ClauseInfo] = self.__identify_value_type(last_data, ti)
|
139
179
|
|
140
180
|
if isinstance(values, tp.Iterable):
|
141
|
-
[self.
|
181
|
+
[self.__add_clause(x) for x in values]
|
142
182
|
else:
|
143
|
-
self.
|
183
|
+
self.__add_clause(values)
|
144
184
|
|
145
185
|
return None
|
146
186
|
|
147
|
-
def
|
187
|
+
def __identify_value_type[TProp](self, last_data: TProp, tuple_instruction: TupleInstruction) -> ClauseInfo[T]:
|
148
188
|
"""
|
149
189
|
A method that behaves based on the variable's type
|
150
190
|
"""
|
151
|
-
if isinstance(value, property):
|
152
|
-
if value in self._table.__properties_mapped__:
|
153
|
-
return ClauseInfo[T](self._table, value, self.alias_children_resolver)
|
154
|
-
|
155
|
-
return self._search_correct_table_for_prop(index, function, value)
|
156
|
-
|
157
|
-
elif isinstance(value, IAggregate):
|
158
|
-
return ClauseInfo[T](self._table, value, self.alias_children_resolver)
|
159
191
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
192
|
+
if isinstance(last_data, property):
|
193
|
+
if tuple_instruction.var == self.CHAR:
|
194
|
+
table_left = self.table
|
195
|
+
else:
|
196
|
+
table_left = self.alias_cache[tuple_instruction.var]
|
197
|
+
|
198
|
+
if last_data in table_left.__properties_mapped__:
|
199
|
+
# if self.table != table_left:
|
200
|
+
# self._add_fk_relationship(self.table, table_left)
|
201
|
+
return ClauseInfo[T](table_left, last_data, self.alias_children_resolver)
|
202
|
+
|
203
|
+
for table in self.tables:
|
204
|
+
try:
|
205
|
+
return self._search_correct_table_for_prop(table, tuple_instruction, last_data)
|
206
|
+
except ValueError:
|
207
|
+
continue
|
208
|
+
|
209
|
+
elif isinstance(last_data, IAggregate):
|
210
|
+
return ClauseInfo[T](self.table, last_data, self.alias_children_resolver)
|
211
|
+
|
212
|
+
# if value is a Table instance (when you need to retrieve all columns) we'll ensure that all JOINs are added
|
213
|
+
elif isinstance(last_data, type) and issubclass(last_data, Table):
|
214
|
+
if last_data not in self._tables:
|
215
|
+
self.__add_necessary_fk(tuple_instruction, last_data)
|
164
216
|
# all columns
|
165
217
|
clauses: list[ClauseInfo] = []
|
166
|
-
for prop in
|
218
|
+
for prop in last_data.__properties_mapped__:
|
167
219
|
if isinstance(prop, property):
|
168
|
-
clauses.append(self.
|
220
|
+
clauses.append(self.__identify_value_type(prop, tuple_instruction))
|
169
221
|
return clauses
|
170
222
|
|
171
|
-
elif isinstance(
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
elif isinstance(value, bool):
|
178
|
-
...
|
223
|
+
elif isinstance(last_data, str):
|
224
|
+
# COMMENT: use self.table instead self._tables because if we hit this conditional, means that
|
225
|
+
# COMMENT: alias_cache to replace '*' by all columns
|
226
|
+
if self._replace_asterisk_char and (replace_value := self.alias_cache.get(last_data, None)) is not None:
|
227
|
+
return self.__identify_value_type(replace_value(self.table), tuple_instruction)
|
228
|
+
return ClauseInfo[T](self.table, last_data, alias_children_resolver=self.alias_children_resolver)
|
179
229
|
|
180
|
-
raise NotImplementedError(f"type of value '{
|
230
|
+
raise NotImplementedError(f"type of value '{last_data}' is not implemented.")
|
181
231
|
|
182
|
-
def _search_correct_table_for_prop[TTable](self,
|
183
|
-
|
184
|
-
temp_table: tp.Type[Table] = self._table
|
232
|
+
def _search_correct_table_for_prop[TTable](self, table: tp.Type[Table], tuple_instruction: TupleInstruction, prop: property) -> ClauseInfo[TTable]:
|
233
|
+
temp_table: tp.Type[Table] = table
|
185
234
|
|
186
|
-
table_list
|
235
|
+
_, *table_list = tuple_instruction.nested_element.parents
|
187
236
|
counter: int = 0
|
188
237
|
while prop not in temp_table.__properties_mapped__:
|
189
238
|
new_table: TTable = getattr(temp_table(), table_list[counter])
|
190
239
|
|
191
240
|
if not isinstance(new_table, type) or not issubclass(new_table, Table):
|
192
241
|
raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
|
193
|
-
self.
|
242
|
+
self._add_fk_relationship(temp_table, new_table)
|
194
243
|
|
195
244
|
temp_table = new_table
|
196
245
|
counter += 1
|
@@ -198,41 +247,41 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
|
198
247
|
if prop in new_table.__properties_mapped__:
|
199
248
|
return ClauseInfo[TTable](new_table, prop, self.alias_children_resolver)
|
200
249
|
|
201
|
-
raise ValueError(f"property '{prop}' does not exist in any inherit
|
250
|
+
raise ValueError(f"property '{prop}' does not exist in any inherit tables.")
|
202
251
|
|
203
|
-
def
|
252
|
+
def __add_clause[Tc: tp.Type[Table]](self, clause: ClauseInfo[Tc]) -> None:
|
204
253
|
self._all_clauses.append(clause)
|
205
254
|
self._clauses_group_by_tables[clause._table].append(clause)
|
206
|
-
|
207
255
|
return None
|
208
256
|
|
209
|
-
def
|
210
|
-
|
211
|
-
old_table: tp.Type[Table] = self._table
|
257
|
+
def __add_necessary_fk(self, tuple_instruction: TupleInstruction, tables: tp.Type[Table]) -> None:
|
258
|
+
old_table = self.table
|
212
259
|
|
213
|
-
|
260
|
+
table_inherit_list: list[Table] = tuple_instruction.nested_element.parents[1:]
|
214
261
|
counter: int = 0
|
215
|
-
while
|
216
|
-
new_table: tp.Type[Table] = getattr(old_table(),
|
262
|
+
while tables not in old_table.__dict__.values():
|
263
|
+
new_table: tp.Type[Table] = getattr(old_table(), table_inherit_list[counter])
|
217
264
|
|
218
265
|
if not issubclass(new_table, Table):
|
219
266
|
raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
|
220
267
|
|
221
|
-
self.
|
268
|
+
self._add_fk_relationship(old_table, new_table)
|
222
269
|
|
223
|
-
if
|
224
|
-
self.
|
225
|
-
return None
|
270
|
+
if tables in new_table.__dict__.values():
|
271
|
+
return self._add_fk_relationship(new_table, tables)
|
226
272
|
|
227
273
|
old_table = new_table
|
228
274
|
counter += 1
|
229
275
|
|
230
|
-
self.
|
231
|
-
return None
|
276
|
+
return self._add_fk_relationship(old_table, tables)
|
232
277
|
|
233
278
|
@property
|
234
279
|
def table(self) -> T:
|
235
|
-
return self.
|
280
|
+
return self.tables[0] if isinstance(self.tables, tp.Iterable) else self.tables
|
281
|
+
|
282
|
+
@property
|
283
|
+
def tables(self) -> T:
|
284
|
+
return self._tables
|
236
285
|
|
237
286
|
@property
|
238
287
|
def lambda_query[*Ts](self) -> tp.Callable[[T], tuple[*Ts]]:
|
@@ -248,11 +297,11 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
|
248
297
|
|
249
298
|
@property
|
250
299
|
def has_foreign_keys(self) -> bool:
|
251
|
-
return len(self.
|
300
|
+
return len(self._joins) > 0
|
252
301
|
|
253
302
|
@property
|
254
303
|
def fk_relationship(self) -> set[tuple[tp.Type[Table], tp.Type[Table]]]:
|
255
|
-
return self.
|
304
|
+
return self._joins
|
256
305
|
|
257
306
|
@property
|
258
307
|
@abc.abstractmethod
|
@@ -276,25 +325,14 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
|
|
276
325
|
self._alias_name = value
|
277
326
|
|
278
327
|
def stringify_foreign_key(self, sep: str = "\n") -> str:
|
279
|
-
|
280
|
-
for
|
281
|
-
graph[left].append(right)
|
328
|
+
sorted_joins = JoinSelector.sort_join_selectors(self._joins)
|
329
|
+
return f"{sep}".join([join.query for join in sorted_joins])
|
282
330
|
|
283
|
-
|
284
|
-
|
285
|
-
for l_tbl in dfs:
|
286
|
-
list_r_tbl = graph[l_tbl]
|
287
|
-
if not list_r_tbl:
|
288
|
-
continue
|
331
|
+
def _add_fk_relationship[T1: tp.Type[Table], T2: tp.Type[Table]](self, t1: T1, t2: T2) -> None:
|
332
|
+
lambda_relationship = ForeignKey.MAPPED[t1.__table_name__].referenced_tables[t2.__table_name__].relationship
|
289
333
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
return f"{sep}".join(query)
|
297
|
-
|
298
|
-
def __add_fk_relationship(self, t1: Table, t2: Table) -> None:
|
299
|
-
self._fk_relationship.add((t1, t2))
|
300
|
-
return None
|
334
|
+
tables = list(self._tables)
|
335
|
+
if t2 not in tables:
|
336
|
+
tables.append(t2)
|
337
|
+
self._tables = tuple(tables)
|
338
|
+
return self._joins.add(JoinSelector[T1, T2](t1, t2, self._by, where=lambda_relationship))
|
@@ -12,11 +12,15 @@ if tp.TYPE_CHECKING:
|
|
12
12
|
from .IQueryCommand import IQuery
|
13
13
|
|
14
14
|
|
15
|
-
class IDecompositionQuery[T: tp.Type[Table]](IQuery):
|
15
|
+
class IDecompositionQuery[T: tp.Type[Table], *Ts](IQuery):
|
16
16
|
@property
|
17
17
|
@abc.abstractmethod
|
18
18
|
def table(self) -> T: ...
|
19
19
|
|
20
|
+
@property
|
21
|
+
@abc.abstractmethod
|
22
|
+
def tables(self) -> tuple[*Ts]: ...
|
23
|
+
|
20
24
|
@property
|
21
25
|
@abc.abstractmethod
|
22
26
|
def lambda_query[*Ts](self) -> tp.Callable[[T], tuple[*Ts]]: ...
|
@@ -9,7 +9,7 @@ class IRepositoryBase[T](ABC):
|
|
9
9
|
return f"{IRepositoryBase.__name__}: {self.__class__.__name__}"
|
10
10
|
|
11
11
|
@abstractmethod
|
12
|
-
def read_sql[TFlavour](self, query: str, flavour: Optional[Type[TFlavour]], **kwargs) -> tuple[TFlavour]: ...
|
12
|
+
def read_sql[TFlavour](self, cnx: T, query: str, flavour: Optional[Type[TFlavour]], **kwargs) -> tuple[TFlavour]: ...
|
13
13
|
|
14
14
|
@abstractmethod
|
15
15
|
def executemany_with_values(self, query: str, values) -> None: ...
|