ormlambda 2.9.0__py3-none-any.whl → 2.9.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/common/abstract_classes/abstract_model.py +25 -11
- ormlambda/common/abstract_classes/decomposition_query.py +106 -91
- ormlambda/common/errors/__init__.py +3 -0
- ormlambda/common/interfaces/ICustomAlias.py +4 -0
- ormlambda/common/interfaces/IDecompositionQuery.py +5 -1
- ormlambda/common/interfaces/IRepositoryBase.py +1 -1
- ormlambda/common/interfaces/IStatements.py +97 -42
- ormlambda/databases/my_sql/clauses/alias.py +31 -0
- ormlambda/databases/my_sql/clauses/group_by.py +2 -2
- ormlambda/databases/my_sql/clauses/joins.py +39 -1
- ormlambda/databases/my_sql/clauses/select.py +12 -9
- ormlambda/databases/my_sql/repository.py +36 -25
- ormlambda/databases/my_sql/statements.py +52 -37
- ormlambda/model_base.py +3 -3
- {ormlambda-2.9.0.dist-info → ormlambda-2.9.4.dist-info}/METADATA +1 -1
- {ormlambda-2.9.0.dist-info → ormlambda-2.9.4.dist-info}/RECORD +18 -15
- {ormlambda-2.9.0.dist-info → ormlambda-2.9.4.dist-info}/LICENSE +0 -0
- {ormlambda-2.9.0.dist-info → ormlambda-2.9.4.dist-info}/WHEEL +0 -0
| @@ -1,5 +1,5 @@ | |
| 1 1 | 
             
            from __future__ import annotations
         | 
| 2 | 
            -
            from typing import Any, Callable, Iterable, Optional, Literal, Type, Union, overload, TYPE_CHECKING | 
| 2 | 
            +
            from typing import Any, Callable, Iterable, Optional, Literal, Type, Union, overload, TYPE_CHECKING
         | 
| 3 3 | 
             
            from enum import Enum
         | 
| 4 4 | 
             
            from abc import abstractmethod, ABC
         | 
| 5 5 | 
             
            import enum
         | 
| @@ -17,21 +17,48 @@ class OrderType(enum.Enum): | |
| 17 17 | 
             
                DESC = "DESC"
         | 
| 18 18 |  | 
| 19 19 |  | 
| 20 | 
            -
             | 
| 20 | 
            +
            type OrderTypes = Literal["ASC", "DESC"] | OrderType | Iterable[OrderType]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
             | 
| 23 | 
            +
            type Tuple[T] = tuple[T, ...]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            type SelectRes1[T1] = tuple[Tuple[T1]]
         | 
| 26 | 
            +
            type SelectRes2[T1, T2] = tuple[*SelectRes1[T1], Tuple[T2]]
         | 
| 27 | 
            +
            type SelectRes3[T1, T2, T3] = tuple[*SelectRes2[T1, T2], Tuple[T3]]
         | 
| 28 | 
            +
            type SelectRes4[T1, T2, T3, T4] = tuple[*SelectRes3[T1, T2, T3], Tuple[T4]]
         | 
| 29 | 
            +
            type SelectRes5[T1, T2, T3, T4, T5] = tuple[*SelectRes4[T1, T2, T3, T4], Tuple[T5]]
         | 
| 30 | 
            +
            type SelectRes6[T1, T2, T3, T4, T5, T6] = tuple[*SelectRes5[T1, T2, T3, T4, T5], Tuple[T6]]
         | 
| 31 | 
            +
            type SelectRes7[T1, T2, T3, T4, T5, T6, T7] = tuple[*SelectRes6[T1, T2, T3, T4, T5, T6], Tuple[T7]]
         | 
| 32 | 
            +
            type SelectRes8[T1, T2, T3, T4, T5, T6, T7, T8] = tuple[*SelectRes7[T1, T2, T3, T4, T5, T6, T7], Tuple[T8]]
         | 
| 33 | 
            +
            type SelectRes9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = tuple[*SelectRes8[T1, T2, T3, T4, T5, T6, T7, T8], Tuple[T9]]
         | 
| 34 | 
            +
            type SelectRes10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = tuple[*SelectRes9[T1, T2, T3, T4, T5, T6, T7, T8, T9], Tuple[T10]]
         | 
| 35 | 
            +
             | 
| 36 | 
            +
             | 
| 37 | 
            +
            type WhereCondition[T, T1] = Callable[[T, T1], bool]
         | 
| 38 | 
            +
            type JoinCondition[T, T1] = tuple[T1, WhereCondition[T, T1]]
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            type TupleJoins1[T, T1] = tuple[JoinCondition[T, T1]]
         | 
| 41 | 
            +
            type TupleJoins2[T, T1, T2] = tuple[*TupleJoins1[T, T1], JoinCondition[T, T2]]
         | 
| 42 | 
            +
            type TupleJoins3[T, T1, T2, T3] = tuple[*TupleJoins2[T, T1, T2], JoinCondition[T, T3]]
         | 
| 43 | 
            +
            type TupleJoins4[T, T1, T2, T3, T4] = tuple[*TupleJoins3[T, T1, T2, T3], JoinCondition[T, T4]]
         | 
| 44 | 
            +
            type TupleJoins5[T, T1, T2, T3, T4, T5] = tuple[*TupleJoins4[T, T1, T2, T3, T4], JoinCondition[T, T5]]
         | 
| 45 | 
            +
            type TupleJoins6[T, T1, T2, T3, T4, T5, T6] = tuple[*TupleJoins5[T, T1, T2, T3, T4, T5], JoinCondition[T, T6]]
         | 
| 21 46 |  | 
| 22 | 
            -
            OrderTypes = OrderTypeString | OrderType | Iterable[OrderType]
         | 
| 23 47 |  | 
| 24 48 | 
             
            # TODOH: This var is duplicated from 'src\ormlambda\databases\my_sql\clauses\create_database.py'
         | 
| 25 49 | 
             
            TypeExists = Literal["fail", "replace", "append"]
         | 
| 26 50 |  | 
| 27 | 
            -
            T =  | 
| 28 | 
            -
            WhereTypes = Union[Callable[[T], bool], Iterable[Callable[[T], bool]]]
         | 
| 51 | 
            +
            type WhereTypes[T, *Ts] = Union[Callable[[T, *Ts], bool], Iterable[Callable[[T, *Ts], bool]]]
         | 
| 29 52 |  | 
| 30 53 |  | 
| 31 | 
            -
            class IStatements[T | 
| 54 | 
            +
            class IStatements[T, *Ts](ABC):
         | 
| 32 55 | 
             
                @abstractmethod
         | 
| 33 56 | 
             
                def create_table(self, if_exists: TypeExists) -> None: ...
         | 
| 34 57 |  | 
| 58 | 
            +
                # #TODOL [ ]: We must to implement this mehtod
         | 
| 59 | 
            +
                # @abstractmethod
         | 
| 60 | 
            +
                # def drop_table(self)->None: ...
         | 
| 61 | 
            +
             | 
| 35 62 | 
             
                @abstractmethod
         | 
| 36 63 | 
             
                def table_exists(self) -> bool: ...
         | 
| 37 64 |  | 
| @@ -121,15 +148,10 @@ class IStatements[T: Table](ABC): | |
| 121 148 | 
             
                def delete(self, instance: Optional[T | list[T]] = ...) -> None: ...
         | 
| 122 149 |  | 
| 123 150 | 
             
                # endregion
         | 
| 124 | 
            -
                # region join
         | 
| 125 | 
            -
                @abstractmethod
         | 
| 126 | 
            -
                def join(self, table_left: Table, table_right: Table, *, by: str) -> IStatements[T]: ...
         | 
| 127 151 |  | 
| 128 | 
            -
                # endregion
         | 
| 129 152 | 
             
                # region where
         | 
| 130 | 
            -
             | 
| 131 153 | 
             
                @overload
         | 
| 132 | 
            -
                def where(self, conditions: Iterable[Callable[[T], bool]]) -> IStatements[T]:
         | 
| 154 | 
            +
                def where(self, conditions: Iterable[Callable[[T, *Ts], bool]]) -> IStatements[T, *Ts]:
         | 
| 133 155 | 
             
                    """
         | 
| 134 156 | 
             
                    This method creates where clause by passing the Iterable in lambda function
         | 
| 135 157 | 
             
                    EXAMPLE
         | 
| @@ -140,7 +162,7 @@ class IStatements[T: Table](ABC): | |
| 140 162 | 
             
                    ...
         | 
| 141 163 |  | 
| 142 164 | 
             
                @overload
         | 
| 143 | 
            -
                def where(self, conditions: Callable[[T], bool], **kwargs) -> IStatements[T]:
         | 
| 165 | 
            +
                def where(self, conditions: Callable[[T, *Ts], bool], **kwargs) -> IStatements[T, *Ts]:
         | 
| 144 166 | 
             
                    """
         | 
| 145 167 | 
             
                    PARAM
         | 
| 146 168 | 
             
                    -
         | 
| @@ -157,7 +179,7 @@ class IStatements[T: Table](ABC): | |
| 157 179 | 
             
                    ...
         | 
| 158 180 |  | 
| 159 181 | 
             
                @abstractmethod
         | 
| 160 | 
            -
                def where(self, conditions: WhereTypes = lambda: None, **kwargs) -> IStatements[T]: ...
         | 
| 182 | 
            +
                def where(self, conditions: WhereTypes[T, *Ts] = lambda: None, **kwargs) -> IStatements[T, *Ts]: ...
         | 
| 161 183 |  | 
| 162 184 | 
             
                # endregion
         | 
| 163 185 | 
             
                # region order
         | 
| @@ -200,44 +222,68 @@ class IStatements[T: Table](ABC): | |
| 200 222 | 
             
                    alias: bool = ...,
         | 
| 201 223 | 
             
                    alias_name: Optional[str] = ...,
         | 
| 202 224 | 
             
                ) -> TProp: ...
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                # endregion
         | 
| 227 | 
            +
                # region join
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                @overload
         | 
| 230 | 
            +
                def join[T1](self, joins: JoinCondition[T, T1]) -> IStatements[T, T1]: ...
         | 
| 231 | 
            +
                @overload
         | 
| 232 | 
            +
                def join[T1](self, joins: TupleJoins1[T, T1]) -> IStatements[T, T1]: ...
         | 
| 233 | 
            +
                @overload
         | 
| 234 | 
            +
                def join[T1, T2](self, joins: TupleJoins2[T, T1, T2]) -> IStatements[T, T1, T2]: ...
         | 
| 235 | 
            +
                @overload
         | 
| 236 | 
            +
                def join[T1, T2, T3](self, joins: TupleJoins3[T, T1, T2, T3]) -> IStatements[T, T1, T2, T3]: ...
         | 
| 237 | 
            +
                @overload
         | 
| 238 | 
            +
                def join[T1, T2, T3, T4](self, joins: TupleJoins4[T, T1, T2, T3, T4]) -> IStatements[T, T1, T2, T3, T4]: ...
         | 
| 239 | 
            +
                @overload
         | 
| 240 | 
            +
                def join[T1, T2, T3, T4, T5](self, joins: TupleJoins5[T, T1, T2, T3, T4, T5]) -> IStatements[T, T1, T2, T3, T4, T5]: ...
         | 
| 241 | 
            +
                @overload
         | 
| 242 | 
            +
                def join[T1, T2, T3, T4, T5, T6](self, joins: TupleJoins6[T, T1, T2, T3, T4, T5, T6]) -> IStatements[T, T1, T2, T3, T4, T5, T6]: ...
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                @abstractmethod
         | 
| 245 | 
            +
                def join[*FKTables](self, joins) -> IStatements[T, *FKTables]: ...
         | 
| 246 | 
            +
             | 
| 203 247 | 
             
                # endregion
         | 
| 204 248 | 
             
                # region select
         | 
| 205 249 | 
             
                @overload
         | 
| 206 | 
            -
                def select(self) ->  | 
| 250 | 
            +
                def select(self) -> SelectRes1[T]: ...
         | 
| 207 251 | 
             
                @overload
         | 
| 208 | 
            -
                def select[T1](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ...) ->  | 
| 252 | 
            +
                def select[T1](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ...) -> SelectRes1[T1]: ...
         | 
| 209 253 | 
             
                @overload
         | 
| 210 | 
            -
                def select[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ...) ->  | 
| 254 | 
            +
                def select[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ...) -> SelectRes1[T1]: ...
         | 
| 211 255 | 
             
                @overload
         | 
| 212 | 
            -
                def select[T1, T2](self, selector: Callable[[T], tuple[T1, T2]], *, by: Optional[Enum] = ...) ->  | 
| 256 | 
            +
                def select[T1, T2](self, selector: Callable[[T, *Ts], tuple[T1, T2]], *, by: Optional[Enum] = ...) -> SelectRes2[T1, T2]: ...
         | 
| 213 257 | 
             
                @overload
         | 
| 214 | 
            -
                def select[T1, T2, T3](self, selector: Callable[[T], tuple[T1, T2, T3]], *, by: Optional[Enum] = ...) ->  | 
| 258 | 
            +
                def select[T1, T2, T3](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3]], *, by: Optional[Enum] = ...) -> SelectRes3[T1, T2, T3]: ...
         | 
| 215 259 | 
             
                @overload
         | 
| 216 | 
            -
                def select[T1, T2, T3, T4](self, selector: Callable[[T], tuple[T1, T2, T3, T4]], *, by: Optional[Enum] = ...) ->  | 
| 260 | 
            +
                def select[T1, T2, T3, T4](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4]], *, by: Optional[Enum] = ...) -> SelectRes4[T1, T2, T3, T4]: ...
         | 
| 217 261 | 
             
                @overload
         | 
| 218 | 
            -
                def select[T1, T2, T3, T4, T5](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5]], *, by: Optional[Enum] = ...) ->  | 
| 262 | 
            +
                def select[T1, T2, T3, T4, T5](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5]], *, by: Optional[Enum] = ...) -> SelectRes5[T1, T2, T3, T4, T5]: ...
         | 
| 219 263 | 
             
                @overload
         | 
| 220 | 
            -
                def select[T1, T2, T3, T4, T5, T6](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6]], *, by: Optional[Enum] = ...) ->  | 
| 264 | 
            +
                def select[T1, T2, T3, T4, T5, T6](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5, T6]], *, by: Optional[Enum] = ...) -> SelectRes6[T1, T2, T3, T4, T5, T6]: ...
         | 
| 221 265 | 
             
                @overload
         | 
| 222 | 
            -
                def select[T1, T2, T3, T4, T5, T6, T7](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7]], *, by: Optional[Enum] = ...) ->  | 
| 266 | 
            +
                def select[T1, T2, T3, T4, T5, T6, T7](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5, T6, T7]], *, by: Optional[Enum] = ...) -> SelectRes7[T1, T2, T3, T4, T5, T6, T7]: ...
         | 
| 223 267 | 
             
                @overload
         | 
| 224 | 
            -
                def select[T1, T2, T3, T4, T5, T6, T7, T8](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8]], *, by: Optional[Enum] = ...) ->  | 
| 268 | 
            +
                def select[T1, T2, T3, T4, T5, T6, T7, T8](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5, T6, T7, T8]], *, by: Optional[Enum] = ...) -> SelectRes8[T1, T2, T3, T4, T5, T6, T7, T8]: ...
         | 
| 225 269 | 
             
                @overload
         | 
| 226 | 
            -
                def select[T1, T2, T3, T4, T5, T6, T7, T8, T9](self, selector: Callable[[T], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]], *, by: Optional[Enum] = ...) ->  | 
| 270 | 
            +
                def select[T1, T2, T3, T4, T5, T6, T7, T8, T9](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]], *, by: Optional[Enum] = ...) -> SelectRes9[T1, T2, T3, T4, T5, T6, T7, T8, T9]: ...
         | 
| 227 271 | 
             
                @overload
         | 
| 228 | 
            -
                def select[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](
         | 
| 229 | 
            -
             | 
| 230 | 
            -
                ) -> tuple[tuple[T1, ...], tuple[T2, ...], tuple[T3, ...], tuple[T4, ...], tuple[T5, ...], tuple[T6, ...], tuple[T7, ...], tuple[T8, ...], tuple[T9, ...], tuple[T10, ...]]: ...
         | 
| 272 | 
            +
                def select[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](self, selector: Callable[[T, *Ts], tuple[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], *, by: Optional[Enum] = ...) -> SelectRes10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]: ...
         | 
| 273 | 
            +
             | 
| 231 274 | 
             
                @overload
         | 
| 232 | 
            -
                def select[ | 
| 275 | 
            +
                def select[TFlavour](self, selector: Optional[Callable[[T, *Ts], tuple]] = ..., *, cast_to_tuple: bool = ..., flavour: Type[TFlavour], by: Optional[Enum] = ..., **kwargs) -> TFlavour: ...
         | 
| 233 276 | 
             
                @overload
         | 
| 234 | 
            -
                def select[ | 
| 277 | 
            +
                def select[TRes](self, selector: Optional[Callable[[T, *Ts], TRes]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ..., **kwargs) -> tuple[TRes, ...]: ...
         | 
| 235 278 | 
             
                @overload
         | 
| 236 | 
            -
                def select[ | 
| 279 | 
            +
                def select[TRes](self, selector: Optional[Callable[[T, *Ts], tuple[TRes]]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ..., **kwargs) -> tuple[TRes, ...]: ...
         | 
| 237 280 | 
             
                @overload
         | 
| 238 | 
            -
                def select[ | 
| 281 | 
            +
                def select[*TRes](self, selector: Optional[Callable[[T, *Ts], tuple[*TRes]]] = ..., *, flavour: Type[tuple], by: Optional[Enum] = ..., **kwargs) -> tuple[tuple[*TRes]]: ...
         | 
| 282 | 
            +
                @overload
         | 
| 283 | 
            +
                def select[TFlavour](self, selector: Optional[Callable[[T, *Ts], tuple]] = ..., *, flavour: Type[TFlavour], by: Optional[Enum] = ..., **kwargs) -> tuple[TFlavour]: ...
         | 
| 284 | 
            +
             | 
| 239 285 | 
             
                @abstractmethod
         | 
| 240 | 
            -
                def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = ..., *, flavour: Type[TFlavour] = ..., by: JoinType =  | 
| 286 | 
            +
                def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T, *Ts], tuple[TValue, *Ts]]] = ..., *, cast_to_tuple: bool = ..., flavour: Type[TFlavour] = ..., by: JoinType = ..., **kwargs): ...
         | 
| 241 287 |  | 
| 242 288 | 
             
                # endregion
         | 
| 243 289 | 
             
                # region select_one
         | 
| @@ -246,24 +292,24 @@ class IStatements[T: Table](ABC): | |
| 246 292 | 
             
                @overload
         | 
| 247 293 | 
             
                def select_one[TFlavour](self, *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> TFlavour: ...
         | 
| 248 294 | 
             
                @overload
         | 
| 249 | 
            -
                def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
         | 
| 295 | 
            +
                def select_one[T1](self, selector: Callable[[T, *Ts], tuple[T1]], *, by: Optional[Enum] = ...) -> T1: ...
         | 
| 250 296 | 
             
                @overload
         | 
| 251 | 
            -
                def select_one[* | 
| 297 | 
            +
                def select_one[*TRes](self, selector: Callable[[T, *Ts], tuple[*TRes]], *, by: Optional[Enum] = ...) -> tuple[*TRes]: ...
         | 
| 252 298 | 
             
                @overload
         | 
| 253 | 
            -
                def select_one[T1](self, selector: Callable[[T], tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type) -> T1: ...
         | 
| 299 | 
            +
                def select_one[T1](self, selector: Callable[[T, *Ts], tuple[T1]], *, by: Optional[Enum] = ..., flavour: Type) -> T1: ...
         | 
| 254 300 | 
             
                @overload
         | 
| 255 | 
            -
                def select_one[T1, TFlavour](self, selector: Callable[[T], T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> T1: ...
         | 
| 301 | 
            +
                def select_one[T1, TFlavour](self, selector: Callable[[T, *Ts], T1], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> T1: ...
         | 
| 256 302 | 
             
                @overload
         | 
| 257 | 
            -
                def select_one[*Ts](self, selector: Callable[[T], tuple[*Ts]], *, by: Optional[Enum] = ..., flavour: Type[tuple]) -> tuple[*Ts]: ...
         | 
| 303 | 
            +
                def select_one[*Ts](self, selector: Callable[[T, *Ts], tuple[*Ts]], *, by: Optional[Enum] = ..., flavour: Type[tuple]) -> tuple[*Ts]: ...
         | 
| 258 304 | 
             
                @overload
         | 
| 259 | 
            -
                def select_one[TFlavour](self, selector: Callable[[T], tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> TFlavour: ...
         | 
| 305 | 
            +
                def select_one[TFlavour](self, selector: Callable[[T, *Ts], tuple], *, by: Optional[Enum] = ..., flavour: Type[TFlavour]) -> TFlavour: ...
         | 
| 260 306 | 
             
                @abstractmethod
         | 
| 261 | 
            -
                def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Type[TFlavour] = ..., by: Optional[Enum] = ...): ...
         | 
| 307 | 
            +
                def select_one[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T, *Ts], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Type[TFlavour] = ..., by: Optional[Enum] = ...): ...
         | 
| 262 308 |  | 
| 263 309 | 
             
                # endregion
         | 
| 264 310 | 
             
                # region group_by
         | 
| 265 311 | 
             
                @abstractmethod
         | 
| 266 | 
            -
                def group_by[TRepo | 
| 312 | 
            +
                def group_by[TRepo](self, column: Callable[[T, *Ts], TRepo]) -> IStatements[T, *Ts]: ...
         | 
| 267 313 |  | 
| 268 314 | 
             
                # endregion
         | 
| 269 315 |  | 
| @@ -271,7 +317,16 @@ class IStatements[T: Table](ABC): | |
| 271 317 | 
             
                def _build(self) -> str: ...
         | 
| 272 318 |  | 
| 273 319 |  | 
| 274 | 
            -
            class IStatements_two_generic[T: Table, TRepo](IStatements[T]):
         | 
| 320 | 
            +
            class IStatements_two_generic[T: Table, *Ts, TRepo](IStatements[T, *Ts]):
         | 
| 275 321 | 
             
                @property
         | 
| 276 322 | 
             
                @abstractmethod
         | 
| 277 323 | 
             
                def repository(self) -> IRepositoryBase[TRepo]: ...
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                @property
         | 
| 326 | 
            +
                def query(self) -> str: ...
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                @property
         | 
| 329 | 
            +
                def model(self) -> Type[T]: ...
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                @property
         | 
| 332 | 
            +
                def models(self) -> tuple[*Ts]: ...
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            import typing as tp
         | 
| 2 | 
            +
            from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo, DecompositionQueryBase
         | 
| 3 | 
            +
            from ormlambda import Table
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            from ormlambda.common.interfaces import ICustomAlias
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
            class Alias[T: tp.Type[Table], *Ts](DecompositionQueryBase[T,*Ts], ICustomAlias[T,*Ts]):
         | 
| 9 | 
            +
                def __init__(
         | 
| 10 | 
            +
                    self,
         | 
| 11 | 
            +
                    table: T,
         | 
| 12 | 
            +
                    query: tp.Callable[[T, *Ts], tp.Any],
         | 
| 13 | 
            +
                    *,
         | 
| 14 | 
            +
                    alias: bool = True,
         | 
| 15 | 
            +
                    alias_name: str | None = None,
         | 
| 16 | 
            +
                ) -> None:
         | 
| 17 | 
            +
                    super().__init__(
         | 
| 18 | 
            +
                        table,
         | 
| 19 | 
            +
                        lambda_query=query,
         | 
| 20 | 
            +
                        alias=alias,
         | 
| 21 | 
            +
                        alias_name=alias_name,
         | 
| 22 | 
            +
                    )
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
         | 
| 25 | 
            +
                    return self.alias_name
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                @property
         | 
| 28 | 
            +
                def query(self) -> str:
         | 
| 29 | 
            +
                    assert len(self.all_clauses) == 1
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    return self.all_clauses[0].query
         | 
| @@ -5,13 +5,13 @@ from ormlambda.common.interfaces.IAggregate import IAggregate | |
| 5 5 | 
             
            from ormlambda import Table
         | 
| 6 6 |  | 
| 7 7 |  | 
| 8 | 
            -
            class GroupBy[T: tp.Type[Table], TProp](DecompositionQueryBase[T], IAggregate[T]):
         | 
| 8 | 
            +
            class GroupBy[T: tp.Type[Table], *Ts, TProp](DecompositionQueryBase[T], IAggregate[T]):
         | 
| 9 9 | 
             
                CLAUSE: str = "GROUP BY"
         | 
| 10 10 |  | 
| 11 11 | 
             
                def __init__(
         | 
| 12 12 | 
             
                    self,
         | 
| 13 13 | 
             
                    table: T,
         | 
| 14 | 
            -
                    column: tp.Callable[[T], TProp],
         | 
| 14 | 
            +
                    column: tp.Callable[[T, *Ts], TProp],
         | 
| 15 15 | 
             
                    *,
         | 
| 16 16 | 
             
                    alias: bool = True,
         | 
| 17 17 | 
             
                    alias_name: str | None = None,
         | 
| @@ -1,7 +1,9 @@ | |
| 1 1 | 
             
            from __future__ import annotations
         | 
| 2 | 
            -
            from  | 
| 2 | 
            +
            from collections import defaultdict
         | 
| 3 | 
            +
            from typing import override, Callable, overload, Optional, TYPE_CHECKING, Type
         | 
| 3 4 |  | 
| 4 5 |  | 
| 6 | 
            +
            from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
         | 
| 5 7 | 
             
            from ormlambda.common.interfaces.IQueryCommand import IQuery
         | 
| 6 8 | 
             
            from ormlambda import Disassembler
         | 
| 7 9 | 
             
            from ormlambda import JoinType
         | 
| @@ -21,6 +23,13 @@ class JoinSelector[TLeft, TRight](IQuery): | |
| 21 23 | 
             
                    "_compareop",
         | 
| 22 24 | 
             
                )
         | 
| 23 25 |  | 
| 26 | 
            +
                @override
         | 
| 27 | 
            +
                def __repr__(self) -> str:
         | 
| 28 | 
            +
                    table_col_left: str = f"{self._orig_table.__table_name__}.{self._left_col}"
         | 
| 29 | 
            +
                    table_col_right: str = f"{self._table_right.__table_name__}.{self._right_col}"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    return f"{IQuery.__name__}: {self.__class__.__name__} ({table_col_left} == {table_col_right})"
         | 
| 32 | 
            +
             | 
| 24 33 | 
             
                @overload
         | 
| 25 34 | 
             
                def __init__(
         | 
| 26 35 | 
             
                    self,
         | 
| @@ -102,3 +111,32 @@ class JoinSelector[TLeft, TRight](IQuery): | |
| 102 111 | 
             
                        right_col,  # second_col
         | 
| 103 112 | 
             
                    ]
         | 
| 104 113 | 
             
                    return " ".join(list_)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                @classmethod
         | 
| 116 | 
            +
                def sort_join_selectors(cls, joins: set[JoinSelector]) -> tuple[JoinSelector]:
         | 
| 117 | 
            +
                    # FIXME [x]: How to sort when needed because it's not necessary at this point. It is for testing purpouse
         | 
| 118 | 
            +
                    if len(joins) == 1:
         | 
| 119 | 
            +
                        return tuple(joins)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    join_object_map: dict[str, list[JoinSelector]] = defaultdict(list)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    for obj in joins:
         | 
| 124 | 
            +
                        join_object_map[obj._orig_table].append(obj)
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    graph: dict[Type[Table], list[Type[Table]]] = defaultdict(list)
         | 
| 127 | 
            +
                    for join in joins:
         | 
| 128 | 
            +
                        graph[join._orig_table].append(join._table_right)
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    sorted_graph = DFSTraversal.sort(graph)[::-1]
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    if not sorted_graph:
         | 
| 133 | 
            +
                        return tuple(joins)
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    res = []
         | 
| 136 | 
            +
                    for table in sorted_graph:
         | 
| 137 | 
            +
                        tables = join_object_map[table]
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                        if not tables:
         | 
| 140 | 
            +
                            continue
         | 
| 141 | 
            +
                        res.extend(tables)
         | 
| 142 | 
            +
                    return res
         | 
| @@ -1,32 +1,37 @@ | |
| 1 | 
            -
            from  | 
| 1 | 
            +
            from __future__ import annotations
         | 
| 2 | 
            +
            from typing import override, Type, Callable, TYPE_CHECKING, Optional
         | 
| 2 3 |  | 
| 3 4 | 
             
            from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
         | 
| 4 5 | 
             
            from ormlambda.common.enums.join_type import JoinType
         | 
| 5 6 | 
             
            from ormlambda.common.interfaces.IAggregate import IAggregate
         | 
| 6 7 | 
             
            import shapely as shp
         | 
| 7 8 |  | 
| 9 | 
            +
             | 
| 8 10 | 
             
            if TYPE_CHECKING:
         | 
| 9 11 | 
             
                from ormlambda import Table
         | 
| 12 | 
            +
                from .joins import JoinSelector
         | 
| 10 13 |  | 
| 11 14 |  | 
| 12 | 
            -
            class Select[T: Type[Table]](DecompositionQueryBase[T]):
         | 
| 15 | 
            +
            class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts]):
         | 
| 13 16 | 
             
                CLAUSE: str = "SELECT"
         | 
| 14 17 |  | 
| 15 18 | 
             
                def __init__(
         | 
| 16 19 | 
             
                    self,
         | 
| 17 | 
            -
                     | 
| 20 | 
            +
                    tables: tuple[T, *Ts],
         | 
| 18 21 | 
             
                    lambda_query: Callable[[T], tuple] = lambda x: x,
         | 
| 19 22 | 
             
                    *,
         | 
| 20 23 | 
             
                    alias: bool = False,
         | 
| 21 24 | 
             
                    alias_name: str | None = None,
         | 
| 25 | 
            +
                    joins: Optional[list[JoinSelector]] = None,
         | 
| 22 26 | 
             
                    by: JoinType = JoinType.INNER_JOIN,
         | 
| 23 27 | 
             
                ) -> None:
         | 
| 24 28 | 
             
                    super().__init__(
         | 
| 25 | 
            -
                         | 
| 29 | 
            +
                        tables,
         | 
| 26 30 | 
             
                        lambda_query,
         | 
| 27 31 | 
             
                        alias=alias,
         | 
| 28 32 | 
             
                        alias_name=alias_name,
         | 
| 29 33 | 
             
                        by=by,
         | 
| 34 | 
            +
                        joins=joins,
         | 
| 30 35 | 
             
                    )
         | 
| 31 36 |  | 
| 32 37 | 
             
                # @classmethod
         | 
| @@ -40,18 +45,16 @@ class Select[T: Type[Table]](DecompositionQueryBase[T]): | |
| 40 45 | 
             
                    cols: list[str] = []
         | 
| 41 46 | 
             
                    for x in self.all_clauses:
         | 
| 42 47 | 
             
                        if x.dtype is shp.Point:
         | 
| 43 | 
            -
                            cols.append(x.concat_with_alias(f"ST_AsText({self. | 
| 48 | 
            +
                            cols.append(x.concat_with_alias(f"ST_AsText({self.table.__table_name__}.{x.column})"))
         | 
| 44 49 | 
             
                        else:
         | 
| 45 50 | 
             
                            cols.append(x.query)
         | 
| 46 51 |  | 
| 47 52 | 
             
                        if isinstance(x._row_column, IAggregate) and x._row_column.has_foreign_keys:
         | 
| 48 | 
            -
                            self. | 
| 53 | 
            +
                            self._joins.update(x._row_column.fk_relationship)
         | 
| 49 54 |  | 
| 50 55 | 
             
                    col: str = ", ".join(cols)
         | 
| 51 | 
            -
                    query: str = f"{self.CLAUSE} {col} FROM {self. | 
| 52 | 
            -
                    alias = ""
         | 
| 56 | 
            +
                    query: str = f"{self.CLAUSE} {col} FROM {self.table.__table_name__}"
         | 
| 53 57 |  | 
| 54 | 
            -
                    query += alias
         | 
| 55 58 | 
             
                    if self.has_foreign_keys:
         | 
| 56 59 | 
             
                        query += " " + self.stringify_foreign_key(" ")
         | 
| 57 60 |  | 
| @@ -22,13 +22,14 @@ if TYPE_CHECKING: | |
| 22 22 | 
             
                from ormlambda import Table
         | 
| 23 23 | 
             
                from src.ormlambda.databases.my_sql.clauses.select import Select
         | 
| 24 24 |  | 
| 25 | 
            +
            type TResponse[TFlavour, *Ts] = TFlavour | tuple[dict[str, tuple[*Ts]]] | tuple[tuple[*Ts]] | tuple[TFlavour]
         | 
| 26 | 
            +
             | 
| 25 27 |  | 
| 26 28 | 
             
            class Response[TFlavour, *Ts]:
         | 
| 27 | 
            -
                def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], model: Optional[Table] = None, select: Optional[Select] = None | 
| 29 | 
            +
                def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], model: Optional[Table] = None, select: Optional[Select] = None) -> None:
         | 
| 28 30 | 
             
                    self._response_values: list[tuple[*Ts]] = response_values
         | 
| 29 31 | 
             
                    self._columns: tuple[str] = columns
         | 
| 30 32 | 
             
                    self._flavour: Type[TFlavour] = flavour
         | 
| 31 | 
            -
                    self._kwargs: dict[str, Any] = kwargs
         | 
| 32 33 | 
             
                    self._model: Table = model
         | 
| 33 34 | 
             
                    self._select: Select = select
         | 
| 34 35 |  | 
| @@ -47,23 +48,28 @@ class Response[TFlavour, *Ts]: | |
| 47 48 | 
             
                def is_many(self) -> bool:
         | 
| 48 49 | 
             
                    return self._response_values_index > 1
         | 
| 49 50 |  | 
| 50 | 
            -
                 | 
| 51 | 
            -
                def response(self) -> tuple[dict[str, tuple[*Ts]]] | tuple[tuple[*Ts]] | tuple[TFlavour]:
         | 
| 51 | 
            +
                def response(self, _tuple: bool, **kwargs) -> TResponse[TFlavour, *Ts]:
         | 
| 52 52 | 
             
                    if not self.is_there_response:
         | 
| 53 53 | 
             
                        return tuple([])
         | 
| 54 | 
            -
                     | 
| 54 | 
            +
                    cleaned_response = self._response_values
         | 
| 55 | 
            +
             | 
| 55 56 | 
             
                    if self._select is not None:
         | 
| 56 | 
            -
                         | 
| 57 | 
            -
             | 
| 57 | 
            +
                        cleaned_response = self._parser_response()
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    cast_flavour = self._cast_to_flavour(cleaned_response, **kwargs)
         | 
| 60 | 
            +
                    if _tuple is not True:
         | 
| 61 | 
            +
                        return cast_flavour
         | 
| 58 62 |  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 63 | 
            +
                    return tuple(cast_flavour)
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def _cast_to_flavour(self, data: list[tuple[*Ts]], **kwargs) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
         | 
| 66 | 
            +
                    def _dict(**kwargs) -> list[dict[str, tuple[*Ts]]]:
         | 
| 61 67 | 
             
                        return [dict(zip(self._columns, x)) for x in data]
         | 
| 62 68 |  | 
| 63 | 
            -
                    def _tuple() -> list[tuple[*Ts]]:
         | 
| 69 | 
            +
                    def _tuple(**kwargs) -> list[tuple[*Ts]]:
         | 
| 64 70 | 
             
                        return data
         | 
| 65 71 |  | 
| 66 | 
            -
                    def _set() -> list[set]:
         | 
| 72 | 
            +
                    def _set(**kwargs) -> list[set]:
         | 
| 67 73 | 
             
                        for d in data:
         | 
| 68 74 | 
             
                            n = len(d)
         | 
| 69 75 | 
             
                            for i in range(n):
         | 
| @@ -73,16 +79,20 @@ class Response[TFlavour, *Ts]: | |
| 73 79 | 
             
                                    raise TypeError(f"unhashable type '{type(d[i])}' found in '{type(d)}' when attempting to cast the result into a '{set.__name__}' object")
         | 
| 74 80 | 
             
                        return [set(x) for x in data]
         | 
| 75 81 |  | 
| 76 | 
            -
                    def  | 
| 77 | 
            -
                        return [ | 
| 82 | 
            +
                    def _list(**kwargs) -> list[list]:
         | 
| 83 | 
            +
                        return [list(x) for x in data]
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def _default(**kwargs) -> list[TFlavour]:
         | 
| 86 | 
            +
                        return self._flavour(data, **kwargs)
         | 
| 78 87 |  | 
| 79 88 | 
             
                    selector: dict[Type[object], Any] = {
         | 
| 80 89 | 
             
                        dict: _dict,
         | 
| 81 90 | 
             
                        tuple: _tuple,
         | 
| 82 91 | 
             
                        set: _set,
         | 
| 92 | 
            +
                        list: _list,
         | 
| 83 93 | 
             
                    }
         | 
| 84 94 |  | 
| 85 | 
            -
                    return selector.get(self._flavour, _default)()
         | 
| 95 | 
            +
                    return selector.get(self._flavour, _default)(**kwargs)
         | 
| 86 96 |  | 
| 87 97 | 
             
                def _parser_response(self) -> TFlavour:
         | 
| 88 98 | 
             
                    new_response: list[list] = []
         | 
| @@ -123,8 +133,7 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]): | |
| 123 133 | 
             
                    def wrapper(self: MySQLRepository, *args, **kwargs):
         | 
| 124 134 | 
             
                        with self._pool.get_connection() as cnx:
         | 
| 125 135 | 
             
                            try:
         | 
| 126 | 
            -
                                 | 
| 127 | 
            -
                                return foo
         | 
| 136 | 
            +
                                return func(self, cnx._cnx, *args, **kwargs)
         | 
| 128 137 | 
             
                            except Exception as e:
         | 
| 129 138 | 
             
                                cnx._cnx.rollback()
         | 
| 130 139 | 
             
                                raise e
         | 
| @@ -140,7 +149,13 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]): | |
| 140 149 |  | 
| 141 150 | 
             
                @override
         | 
| 142 151 | 
             
                @get_connection
         | 
| 143 | 
            -
                def read_sql[TFlavour]( | 
| 152 | 
            +
                def read_sql[TFlavour](
         | 
| 153 | 
            +
                    self,
         | 
| 154 | 
            +
                    cnx: MySQLConnection,
         | 
| 155 | 
            +
                    query: str,
         | 
| 156 | 
            +
                    flavour: Type[TFlavour] = tuple,
         | 
| 157 | 
            +
                    **kwargs,
         | 
| 158 | 
            +
                ) -> tuple[TFlavour]:
         | 
| 144 159 | 
             
                    """
         | 
| 145 160 | 
             
                    Return tuple of tuples by default.
         | 
| 146 161 |  | 
| @@ -150,19 +165,15 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]): | |
| 150 165 | 
             
                        - flavour: Type[TFlavour]: Useful to return tuple of any Iterable type as dict,set,list...
         | 
| 151 166 | 
             
                    """
         | 
| 152 167 |  | 
| 153 | 
            -
                     | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
                        return None
         | 
| 157 | 
            -
             | 
| 158 | 
            -
                    model: Table = get_and_drop_key("model")
         | 
| 159 | 
            -
                    select: Select = get_and_drop_key("select")
         | 
| 168 | 
            +
                    model: Table = kwargs.pop("model", None)
         | 
| 169 | 
            +
                    select: Select = kwargs.pop("select", None)
         | 
| 170 | 
            +
                    cast_to_tuple: bool = kwargs.pop("cast_to_tuple", True)
         | 
| 160 171 |  | 
| 161 172 | 
             
                    with cnx.cursor(buffered=True) as cursor:
         | 
| 162 173 | 
             
                        cursor.execute(query)
         | 
| 163 174 | 
             
                        values: list[tuple] = cursor.fetchall()
         | 
| 164 175 | 
             
                        columns: tuple[str] = cursor.column_names
         | 
| 165 | 
            -
                        return Response[TFlavour](model=model, response_values=values, columns=columns, flavour=flavour, select=select, **kwargs) | 
| 176 | 
            +
                        return Response[TFlavour](model=model, response_values=values, columns=columns, flavour=flavour, select=select).response(_tuple=cast_to_tuple, **kwargs)
         | 
| 166 177 |  | 
| 167 178 | 
             
                # FIXME [ ]: this method does not comply with the implemented interface
         | 
| 168 179 | 
             
                @get_connection
         |