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.
Files changed (85) hide show
  1. {ormlambda-2.8.0 → ormlambda-2.9.4}/PKG-INFO +3 -2
  2. {ormlambda-2.8.0 → ormlambda-2.9.4}/pyproject.toml +4 -2
  3. ormlambda-2.9.4/src/ormlambda/__init__.py +20 -0
  4. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/abstract_model.py +39 -26
  5. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/decomposition_query.py +124 -86
  6. ormlambda-2.9.4/src/ormlambda/common/errors/__init__.py +3 -0
  7. ormlambda-2.9.4/src/ormlambda/common/interfaces/ICustomAlias.py +4 -0
  8. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IDecompositionQuery.py +5 -1
  9. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IRepositoryBase.py +1 -1
  10. ormlambda-2.9.4/src/ormlambda/common/interfaces/IStatements.py +332 -0
  11. ormlambda-2.9.4/src/ormlambda/databases/my_sql/clauses/alias.py +31 -0
  12. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/group_by.py +2 -2
  13. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/insert.py +24 -12
  14. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/joins.py +39 -1
  15. ormlambda-2.9.4/src/ormlambda/databases/my_sql/clauses/order.py +47 -0
  16. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/select.py +19 -12
  17. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/update.py +11 -17
  18. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/repository.py +79 -18
  19. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/statements.py +76 -53
  20. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/model_base.py +3 -3
  21. ormlambda-2.9.4/src/ormlambda/utils/column.py +105 -0
  22. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/dtypes.py +4 -12
  23. ormlambda-2.9.4/src/ormlambda/utils/fields.py +60 -0
  24. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/table_constructor.py +67 -95
  25. ormlambda-2.8.0/src/ormlambda/__init__.py +0 -11
  26. ormlambda-2.8.0/src/ormlambda/common/interfaces/IStatements.py +0 -270
  27. ormlambda-2.8.0/src/ormlambda/databases/my_sql/clauses/order.py +0 -33
  28. ormlambda-2.8.0/src/ormlambda/utils/column.py +0 -66
  29. {ormlambda-2.8.0 → ormlambda-2.9.4}/LICENSE +0 -0
  30. {ormlambda-2.8.0 → ormlambda-2.9.4}/README.md +0 -0
  31. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/__init__.py +0 -0
  32. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/__init__.py +0 -0
  33. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/non_query_base.py +0 -0
  34. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/abstract_classes/query_base.py +0 -0
  35. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/__init__.py +0 -0
  36. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/condition_types.py +0 -0
  37. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/enums/join_type.py +0 -0
  38. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IAggregate.py +0 -0
  39. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/INonQueryCommand.py +0 -0
  40. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/IQueryCommand.py +0 -0
  41. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/common/interfaces/__init__.py +0 -0
  42. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/__init__.py +0 -0
  43. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/IDelete.py +0 -0
  44. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/__init__.py +0 -0
  45. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/delete/abstract_delete.py +0 -0
  46. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/IInsert.py +0 -0
  47. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/__init__.py +0 -0
  48. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/insert/abstract_insert.py +0 -0
  49. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/IUpdate.py +0 -0
  50. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/__init__.py +0 -0
  51. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/update/abstract_update.py +0 -0
  52. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/IUpsert.py +0 -0
  53. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/__init__.py +0 -0
  54. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/upsert/abstract_upsert.py +0 -0
  55. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/where/__init__.py +0 -0
  56. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/components/where/abstract_where.py +0 -0
  57. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/__init__.py +0 -0
  58. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/__init__.py +0 -0
  59. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/__init__.py +0 -0
  60. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/count.py +0 -0
  61. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/create_database.py +0 -0
  62. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/delete.py +0 -0
  63. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/drop_database.py +0 -0
  64. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/drop_table.py +0 -0
  65. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/limit.py +0 -0
  66. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/offset.py +0 -0
  67. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/upsert.py +0 -0
  68. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/clauses/where_condition.py +0 -0
  69. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/__init__.py +0 -0
  70. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/concat.py +0 -0
  71. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/max.py +0 -0
  72. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/min.py +0 -0
  73. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/databases/my_sql/functions/sum.py +0 -0
  74. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/__init__.py +0 -0
  75. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/foreign_key.py +0 -0
  76. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/__init__.py +0 -0
  77. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/dis_types.py +0 -0
  78. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/disassembler.py +0 -0
  79. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/dtypes.py +0 -0
  80. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/name_of.py +0 -0
  81. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/nested_element.py +0 -0
  82. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -0
  83. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/module_tree/__init__.py +0 -0
  84. {ormlambda-2.8.0 → ormlambda-2.9.4}/src/ormlambda/utils/module_tree/dfs_traversal.py +0 -0
  85. {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.8.0
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 (>=3.1.0,<4.0.0)
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
  ![PyPI version](https://img.shields.io/pypi/v/ormlambda.svg)
@@ -3,7 +3,7 @@ line-length = 320
3
3
 
4
4
  [tool.poetry]
5
5
  name = "ormlambda"
6
- version = "2.8.0"
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 = "^3.1.0"
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._model: T = model
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
- @property
46
- @override
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: ISelect, query: str):
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 loop_foo(self) -> dict[Type[Table], list[Table]]:
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.get_all_aggregate_method(clauses)
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 get_all_aggregate_method(self, clauses: list[ClauseInfo]) -> str:
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())
@@ -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
- ClauseDataType = tp.TypeVar("ClauseDataType", bound=tp.Union[property, str])
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 != "*" else 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
- def __init__[*Ts](
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
- table: T,
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._table: T = table
87
- self._lambda_query: tp.Callable[[T], tuple[Ts]] = lambda_query
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, tp.Any] = {"*": lambda x: x}
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
- for param in lambda_vars:
124
- self.alias_cache[param] = lambda x: self._table
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
- resolved_function = function(self._table)
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 index, value in enumerate(resolved_function):
138
- values: ClauseInfo | list[ClauseInfo] = self._identify_value_type(index, value, function)
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.add_clause(x) for x in values]
181
+ [self.__add_clause(x) for x in values]
142
182
  else:
143
- self.add_clause(values)
183
+ self.__add_clause(values)
144
184
 
145
185
  return None
146
186
 
147
- def _identify_value_type[TProp](self, index: int, value: TProp, function) -> ClauseInfo[T]:
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
- # if value is a Table instance (when you need to retrieve all columns) we'll ensure that all INNER JOINs are added
161
- elif isinstance(value, type) and issubclass(value, Table):
162
- if self._table != value:
163
- self._add_necessary_fk(index, function, value)
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 value.__properties_mapped__:
218
+ for prop in last_data.__properties_mapped__:
167
219
  if isinstance(prop, property):
168
- clauses.append(self._identify_value_type(index, prop, function))
220
+ clauses.append(self.__identify_value_type(prop, tuple_instruction))
169
221
  return clauses
170
222
 
171
- elif isinstance(value, str):
172
- # TODOM: alias_cache to replace '*' by all columns
173
- if self._replace_asterisk_char and (replace_value := self.alias_cache.get(value, None)) is not None:
174
- return self._identify_value_type(index, replace_value(self._table), function)
175
- return ClauseInfo[T](self._table, value, alias_children_resolver=self.alias_children_resolver)
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 '{value}' is not implemented.")
230
+ raise NotImplementedError(f"type of value '{last_data}' is not implemented.")
181
231
 
182
- def _search_correct_table_for_prop[TTable](self, index: int, function: tp.Callable[[T], tp.Any], prop: property) -> ClauseInfo[TTable]:
183
- tree_list = TreeInstruction(function).to_list()
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: list[Table] = tree_list[index].nested_element.parents[1:]
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.__add_fk_relationship(temp_table, new_table)
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 table.")
250
+ raise ValueError(f"property '{prop}' does not exist in any inherit tables.")
202
251
 
203
- def add_clause[Tc: tp.Type[Table]](self, clause: ClauseInfo[Tc]) -> None:
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 _add_necessary_fk(self, index: int, function: tp.Callable[[T], tp.Any], table: tp.Type[Table]) -> None:
210
- tree_list = TreeInstruction(function).to_list()
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
- table_list: list[Table] = tree_list[index].nested_element.parents[1:]
260
+ table_inherit_list: list[Table] = tuple_instruction.nested_element.parents[1:]
214
261
  counter: int = 0
215
- while table not in old_table.__dict__.values():
216
- new_table: tp.Type[Table] = getattr(old_table(), table_list[counter])
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.__add_fk_relationship(old_table, new_table)
268
+ self._add_fk_relationship(old_table, new_table)
222
269
 
223
- if table in new_table.__dict__.values():
224
- self.__add_fk_relationship(new_table, table)
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.__add_fk_relationship(old_table, table)
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._table
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._fk_relationship) > 0
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._fk_relationship
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
- graph: dict[tp.Type[Table], list[tp.Type[Table]]] = defaultdict(list)
280
- for left, right in self.fk_relationship:
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
- dfs = DFSTraversal.sort(graph)[::-1]
284
- query: list = []
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
- for r_tbl in list_r_tbl:
291
- lambda_relationship = ForeignKey.MAPPED[l_tbl.__table_name__].referenced_tables[r_tbl.__table_name__].relationship
292
-
293
- join = JoinSelector(l_tbl, r_tbl, by=self._by, where=lambda_relationship)
294
- query.append(join.query)
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))
@@ -0,0 +1,3 @@
1
+ class DifferentTablesAndVariablesError(Exception):
2
+ def __str__(self) -> str:
3
+ return "The number of tables and the variables used in the lambda select are not 'consistent'"
@@ -0,0 +1,4 @@
1
+ from ormlambda.common.interfaces import IDecompositionQuery
2
+
3
+
4
+ class ICustomAlias[T,*Ts](IDecompositionQuery[T,*Ts]): ...
@@ -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: ...