ormlambda 2.9.0__tar.gz → 2.10.0__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 (82) hide show
  1. {ormlambda-2.9.0 → ormlambda-2.10.0}/PKG-INFO +1 -1
  2. {ormlambda-2.9.0 → ormlambda-2.10.0}/pyproject.toml +1 -1
  3. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/abstract_classes/abstract_model.py +25 -11
  4. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/abstract_classes/decomposition_query.py +106 -91
  5. ormlambda-2.10.0/src/ormlambda/common/errors/__init__.py +3 -0
  6. ormlambda-2.10.0/src/ormlambda/common/interfaces/ICustomAlias.py +4 -0
  7. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/IDecompositionQuery.py +5 -1
  8. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/IRepositoryBase.py +1 -1
  9. ormlambda-2.10.0/src/ormlambda/common/interfaces/IStatements.py +339 -0
  10. ormlambda-2.10.0/src/ormlambda/databases/my_sql/clauses/alias.py +31 -0
  11. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/group_by.py +2 -2
  12. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/joins.py +39 -1
  13. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/select.py +12 -9
  14. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/repository.py +36 -25
  15. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/statements.py +63 -37
  16. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/model_base.py +3 -3
  17. ormlambda-2.9.0/src/ormlambda/common/interfaces/IStatements.py +0 -277
  18. {ormlambda-2.9.0 → ormlambda-2.10.0}/LICENSE +0 -0
  19. {ormlambda-2.9.0 → ormlambda-2.10.0}/README.md +0 -0
  20. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/__init__.py +0 -0
  21. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/__init__.py +0 -0
  22. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/abstract_classes/__init__.py +0 -0
  23. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/abstract_classes/non_query_base.py +0 -0
  24. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/abstract_classes/query_base.py +0 -0
  25. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/enums/__init__.py +0 -0
  26. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/enums/condition_types.py +0 -0
  27. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/enums/join_type.py +0 -0
  28. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/IAggregate.py +0 -0
  29. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/INonQueryCommand.py +0 -0
  30. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/IQueryCommand.py +0 -0
  31. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/common/interfaces/__init__.py +0 -0
  32. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/__init__.py +0 -0
  33. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/delete/IDelete.py +0 -0
  34. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/delete/__init__.py +0 -0
  35. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/delete/abstract_delete.py +0 -0
  36. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/insert/IInsert.py +0 -0
  37. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/insert/__init__.py +0 -0
  38. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/insert/abstract_insert.py +0 -0
  39. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/update/IUpdate.py +0 -0
  40. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/update/__init__.py +0 -0
  41. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/update/abstract_update.py +0 -0
  42. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/upsert/IUpsert.py +0 -0
  43. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/upsert/__init__.py +0 -0
  44. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/upsert/abstract_upsert.py +0 -0
  45. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/where/__init__.py +0 -0
  46. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/components/where/abstract_where.py +0 -0
  47. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/__init__.py +0 -0
  48. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/__init__.py +0 -0
  49. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/__init__.py +0 -0
  50. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/count.py +0 -0
  51. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/create_database.py +0 -0
  52. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/delete.py +0 -0
  53. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/drop_database.py +0 -0
  54. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/drop_table.py +0 -0
  55. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/insert.py +0 -0
  56. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/limit.py +0 -0
  57. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/offset.py +0 -0
  58. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/order.py +0 -0
  59. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/update.py +0 -0
  60. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/upsert.py +0 -0
  61. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/clauses/where_condition.py +0 -0
  62. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/functions/__init__.py +0 -0
  63. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/functions/concat.py +0 -0
  64. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/functions/max.py +0 -0
  65. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/functions/min.py +0 -0
  66. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/databases/my_sql/functions/sum.py +0 -0
  67. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/__init__.py +0 -0
  68. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/column.py +0 -0
  69. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/dtypes.py +0 -0
  70. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/fields.py +0 -0
  71. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/foreign_key.py +0 -0
  72. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/__init__.py +0 -0
  73. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/dis_types.py +0 -0
  74. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/disassembler.py +0 -0
  75. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/dtypes.py +0 -0
  76. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/name_of.py +0 -0
  77. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/nested_element.py +0 -0
  78. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -0
  79. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/module_tree/__init__.py +0 -0
  80. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/module_tree/dfs_traversal.py +0 -0
  81. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/module_tree/dynamic_module.py +0 -0
  82. {ormlambda-2.9.0 → ormlambda-2.10.0}/src/ormlambda/utils/table_constructor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ormlambda
3
- Version: 2.9.0
3
+ Version: 2.10.0
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
@@ -3,7 +3,7 @@ line-length = 320
3
3
 
4
4
  [tool.poetry]
5
5
  name = "ormlambda"
6
- version = "2.9.0"
6
+ version = "2.10.0"
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"
@@ -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
 
@@ -16,14 +16,16 @@ if TYPE_CHECKING:
16
16
  ORDER_QUERIES = Literal["select", "join", "where", "order", "with", "group by", "limit", "offset"]
17
17
 
18
18
 
19
- class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
19
+ class AbstractSQLStatements[T: Table, *Ts, TRepo](IStatements_two_generic[T, *Ts, TRepo]):
20
20
  __slots__ = ("_model", "_repository", "_query_list")
21
21
  __order__: tuple[str, ...] = ("select", "join", "where", "order", "with", "group by", "limit", "offset")
22
22
 
23
- def __init__(self, model: T, repository: IRepositoryBase[TRepo]) -> None:
23
+ def __init__(self, model: tuple[T, *Ts], repository: IRepositoryBase[TRepo]) -> None:
24
24
  self.__valid_repository(repository)
25
25
 
26
- 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,)
27
29
  self._repository: IRepositoryBase[TRepo] = repository
28
30
  self._query_list: dict[ORDER_QUERIES, list[IQuery]] = defaultdict(list)
29
31
 
@@ -41,12 +43,8 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
41
43
  def __repr__(self):
42
44
  return f"<Model: {self.__class__.__name__}>"
43
45
 
44
- @property
45
- @override
46
- def repository(self) -> IRepositoryBase[TRepo]: ...
47
-
48
- def _return_flavour[TValue](self, query, flavour: Type[TValue], select) -> tuple[TValue]:
49
- return self._repository.read_sql(query, flavour=flavour, model=self._model, select=select)
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)
50
48
 
51
49
  def _return_model(self, select, query: str):
52
50
  response_sql = self._repository.read_sql(query, flavour=dict, model=self._model, select=select) # store all columns of the SQL query
@@ -59,6 +57,22 @@ class AbstractSQLStatements[T: Table, TRepo](IStatements_two_generic[T, TRepo]):
59
57
  @abc.abstractmethod
60
58
  def _build(sef): ...
61
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
+
62
76
 
63
77
  class ClusterQuery[T]:
64
78
  def __init__(self, select: DecompositionQueryBase[T], response_sql: tuple[dict[str, Any]]) -> None:
@@ -82,7 +96,7 @@ class ClusterQuery[T]:
82
96
  for dicc_cols in self._response_sql:
83
97
  valid_attr: dict[str, Any] = {}
84
98
  for clause in clauses:
85
- if not hasattr(table, clause.column):
99
+ if clause.column is None or not hasattr(table, clause.column):
86
100
  agg_methods = self.__get_all_aggregate_method(clauses)
87
101
  raise ValueError(f"You cannot use aggregation method like '{agg_methods}' to return model objects. Try specifying 'flavour' attribute as 'dict'.")
88
102
  valid_attr[clause.column] = dicc_cols[clause.alias]
@@ -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]]:
@@ -60,7 +62,7 @@ class ClauseInfo[T: tp.Type[Table]]:
60
62
 
61
63
  elif isinstance(data, str):
62
64
  # TODOL: refactor to base the condition in dict with '*' as key. '*' must to work as special character
63
- return f"'{data}'" if data != "*" else data
65
+ return f"'{data}'" if data != DecompositionQueryBase.CHAR else data
64
66
 
65
67
  def __create_value_string(self, name: str) -> str:
66
68
  if isinstance(self._row_column, property):
@@ -79,38 +81,47 @@ class ClauseInfo[T: tp.Type[Table]]:
79
81
  return f"{column_name} as `{alias}`"
80
82
 
81
83
 
82
- class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
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
+
83
91
  @tp.overload
84
- def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]]) -> None: ...
92
+ def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple]) -> None: ...
85
93
  @tp.overload
86
- def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ...) -> None: ...
94
+ def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ...) -> None: ...
87
95
  @tp.overload
88
- def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ...) -> None: ...
96
+ def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ...) -> None: ...
89
97
  @tp.overload
90
- def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ...) -> None: ...
98
+ def __init__(self, tables: T, lambda_query: tp.Callable[[T], tuple], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ...) -> None: ...
91
99
  @tp.overload
92
- def __init__[*Ts](self, table: T, lambda_query: tp.Callable[[T], tuple[*Ts]], *, alias: bool = ..., alias_name: tp.Optional[str] = ..., by: JoinType = ..., replace_asterisk_char: bool = ...) -> None: ...
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: ...
93
103
 
94
- def __init__[*Ts](
104
+ def __init__(
95
105
  self,
96
- table: T,
106
+ tables: tuple[T, *Ts],
97
107
  lambda_query: tp.Callable[[T], tuple[*Ts]],
98
108
  *,
99
109
  alias: bool = True,
100
110
  alias_name: tp.Optional[str] = None,
101
111
  by: JoinType = JoinType.INNER_JOIN,
102
112
  replace_asterisk_char: bool = True,
113
+ joins: tp.Optional[list[JoinType]] = None,
103
114
  ) -> None:
104
- self._table: T = table
105
- 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
106
117
  self._alias: bool = alias
107
118
  self._alias_name: tp.Optional[str] = alias_name
108
119
  self._by: JoinType = by
120
+ self._joins: set[JoinSelector] = set(joins) if joins is not None else set()
109
121
 
110
- self._fk_relationship: set[tuple[tp.Type[Table], tp.Type[Table]]] = set()
111
122
  self._clauses_group_by_tables: dict[tp.Type[Table], list[ClauseInfo[T]]] = defaultdict(list)
112
123
  self._all_clauses: list[ClauseInfo] = []
113
- self.alias_cache: dict[str, tp.Any] = {"*": lambda x: x}
124
+ self.alias_cache: dict[str, AliasType] = {self.CHAR: self._asterik_resolver}
114
125
  self._replace_asterisk_char: bool = replace_asterisk_char
115
126
  self.__assign_lambda_variables_to_table(lambda_query)
116
127
 
@@ -143,77 +154,92 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
143
154
  """
144
155
  lambda_vars = tuple(inspect.signature(_lambda).parameters)
145
156
 
146
- for param in lambda_vars:
147
- 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]
148
160
  return None
149
161
 
150
162
  def __clauses_list_generetor(self, function: tp.Callable[[T], tp.Any]) -> None:
151
163
  if not callable(function):
152
164
  return None
165
+ try:
166
+ resolved_function = function(*self._tables)
167
+ except TypeError:
168
+ raise DifferentTablesAndVariablesError
153
169
 
154
- resolved_function = function(self._table)
155
-
170
+ tree_list = TreeInstruction(function).to_list()
156
171
  # Python treats string objects as iterable, so we need to prevent this behavior
157
172
  if isinstance(resolved_function, str) or not isinstance(resolved_function, tp.Iterable):
158
173
  resolved_function = (resolved_function,)
159
174
 
160
- for index, value in enumerate(resolved_function):
161
- 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)
162
179
 
163
180
  if isinstance(values, tp.Iterable):
164
- [self.add_clause(x) for x in values]
181
+ [self.__add_clause(x) for x in values]
165
182
  else:
166
- self.add_clause(values)
183
+ self.__add_clause(values)
167
184
 
168
185
  return None
169
186
 
170
- 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]:
171
188
  """
172
189
  A method that behaves based on the variable's type
173
190
  """
174
- if isinstance(value, property):
175
- if value in self._table.__properties_mapped__:
176
- return ClauseInfo[T](self._table, value, self.alias_children_resolver)
177
191
 
178
- return self._search_correct_table_for_prop(index, function, value)
179
-
180
- elif isinstance(value, IAggregate):
181
- return ClauseInfo[T](self._table, value, self.alias_children_resolver)
182
-
183
- # if value is a Table instance (when you need to retrieve all columns) we'll ensure that all INNER JOINs are added
184
- elif isinstance(value, type) and issubclass(value, Table):
185
- if self._table != value:
186
- 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)
187
216
  # all columns
188
217
  clauses: list[ClauseInfo] = []
189
- for prop in value.__properties_mapped__:
218
+ for prop in last_data.__properties_mapped__:
190
219
  if isinstance(prop, property):
191
- clauses.append(self._identify_value_type(index, prop, function))
220
+ clauses.append(self.__identify_value_type(prop, tuple_instruction))
192
221
  return clauses
193
222
 
194
- elif isinstance(value, str):
195
- # TODOM: alias_cache to replace '*' by all columns
196
- if self._replace_asterisk_char and (replace_value := self.alias_cache.get(value, None)) is not None:
197
- return self._identify_value_type(index, replace_value(self._table), function)
198
- return ClauseInfo[T](self._table, value, alias_children_resolver=self.alias_children_resolver)
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)
199
229
 
200
- elif isinstance(value, bool):
201
- ...
230
+ raise NotImplementedError(f"type of value '{last_data}' is not implemented.")
202
231
 
203
- raise NotImplementedError(f"type of value '{value}' is not implemented.")
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
204
234
 
205
- def _search_correct_table_for_prop[TTable](self, index: int, function: tp.Callable[[T], tp.Any], prop: property) -> ClauseInfo[TTable]:
206
- tree_list = TreeInstruction(function).to_list()
207
- temp_table: tp.Type[Table] = self._table
208
-
209
- table_list: list[Table] = tree_list[index].nested_element.parents[1:]
235
+ _, *table_list = tuple_instruction.nested_element.parents
210
236
  counter: int = 0
211
237
  while prop not in temp_table.__properties_mapped__:
212
238
  new_table: TTable = getattr(temp_table(), table_list[counter])
213
239
 
214
240
  if not isinstance(new_table, type) or not issubclass(new_table, Table):
215
241
  raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
216
- self.__add_fk_relationship(temp_table, new_table)
242
+ self._add_fk_relationship(temp_table, new_table)
217
243
 
218
244
  temp_table = new_table
219
245
  counter += 1
@@ -221,41 +247,41 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
221
247
  if prop in new_table.__properties_mapped__:
222
248
  return ClauseInfo[TTable](new_table, prop, self.alias_children_resolver)
223
249
 
224
- 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.")
225
251
 
226
- 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:
227
253
  self._all_clauses.append(clause)
228
254
  self._clauses_group_by_tables[clause._table].append(clause)
229
-
230
255
  return None
231
256
 
232
- def _add_necessary_fk(self, index: int, function: tp.Callable[[T], tp.Any], table: tp.Type[Table]) -> None:
233
- tree_list = TreeInstruction(function).to_list()
234
- 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
235
259
 
236
- table_list: list[Table] = tree_list[index].nested_element.parents[1:]
260
+ table_inherit_list: list[Table] = tuple_instruction.nested_element.parents[1:]
237
261
  counter: int = 0
238
- while table not in old_table.__dict__.values():
239
- 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])
240
264
 
241
265
  if not issubclass(new_table, Table):
242
266
  raise ValueError(f"new_table var must be '{Table.__class__}' type and is '{type(new_table)}'")
243
267
 
244
- self.__add_fk_relationship(old_table, new_table)
268
+ self._add_fk_relationship(old_table, new_table)
245
269
 
246
- if table in new_table.__dict__.values():
247
- self.__add_fk_relationship(new_table, table)
248
- return None
270
+ if tables in new_table.__dict__.values():
271
+ return self._add_fk_relationship(new_table, tables)
249
272
 
250
273
  old_table = new_table
251
274
  counter += 1
252
275
 
253
- self.__add_fk_relationship(old_table, table)
254
- return None
276
+ return self._add_fk_relationship(old_table, tables)
255
277
 
256
278
  @property
257
279
  def table(self) -> T:
258
- 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
259
285
 
260
286
  @property
261
287
  def lambda_query[*Ts](self) -> tp.Callable[[T], tuple[*Ts]]:
@@ -271,11 +297,11 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
271
297
 
272
298
  @property
273
299
  def has_foreign_keys(self) -> bool:
274
- return len(self._fk_relationship) > 0
300
+ return len(self._joins) > 0
275
301
 
276
302
  @property
277
303
  def fk_relationship(self) -> set[tuple[tp.Type[Table], tp.Type[Table]]]:
278
- return self._fk_relationship
304
+ return self._joins
279
305
 
280
306
  @property
281
307
  @abc.abstractmethod
@@ -299,25 +325,14 @@ class DecompositionQueryBase[T: tp.Type[Table]](IDecompositionQuery[T]):
299
325
  self._alias_name = value
300
326
 
301
327
  def stringify_foreign_key(self, sep: str = "\n") -> str:
302
- graph: dict[tp.Type[Table], list[tp.Type[Table]]] = defaultdict(list)
303
- for left, right in self.fk_relationship:
304
- graph[left].append(right)
305
-
306
- dfs = DFSTraversal.sort(graph)[::-1]
307
- query: list = []
308
- for l_tbl in dfs:
309
- list_r_tbl = graph[l_tbl]
310
- if not list_r_tbl:
311
- continue
312
-
313
- for r_tbl in list_r_tbl:
314
- lambda_relationship = ForeignKey.MAPPED[l_tbl.__table_name__].referenced_tables[r_tbl.__table_name__].relationship
328
+ sorted_joins = JoinSelector.sort_join_selectors(self._joins)
329
+ return f"{sep}".join([join.query for join in sorted_joins])
315
330
 
316
- join = JoinSelector(l_tbl, r_tbl, by=self._by, where=lambda_relationship)
317
- query.append(join.query)
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
318
333
 
319
- return f"{sep}".join(query)
320
-
321
- def __add_fk_relationship(self, t1: Table, t2: Table) -> None:
322
- self._fk_relationship.add((t1, t2))
323
- 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: ...