ormlambda 3.7.2__py3-none-any.whl → 3.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/__init__.py +2 -0
- ormlambda/databases/my_sql/caster/caster.py +1 -0
- ormlambda/databases/my_sql/clauses/__init__.py +1 -0
- ormlambda/databases/my_sql/clauses/alias.py +15 -21
- ormlambda/databases/my_sql/clauses/group_by.py +19 -20
- ormlambda/databases/my_sql/clauses/having.py +16 -0
- ormlambda/databases/my_sql/clauses/order.py +6 -1
- ormlambda/databases/my_sql/clauses/where.py +3 -3
- ormlambda/databases/my_sql/functions/concat.py +8 -6
- ormlambda/databases/my_sql/functions/max.py +1 -1
- ormlambda/databases/my_sql/join_context.py +3 -2
- ormlambda/databases/my_sql/repository/repository.py +57 -11
- ormlambda/databases/my_sql/statements.py +83 -31
- ormlambda/engine/__init__.py +2 -0
- ormlambda/engine/create.py +35 -0
- ormlambda/engine/url.py +744 -0
- ormlambda/engine/utils.py +17 -0
- ormlambda/repository/base_repository.py +0 -1
- ormlambda/sql/column.py +27 -2
- ormlambda/sql/foreign_key.py +36 -4
- ormlambda/statements/interfaces/IStatements.py +49 -42
- ormlambda/statements/types.py +9 -3
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.1.dist-info}/METADATA +107 -8
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.1.dist-info}/RECORD +26 -22
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.1.dist-info}/LICENSE +0 -0
- {ormlambda-3.7.2.dist-info → ormlambda-3.11.1.dist-info}/WHEEL +0 -0
ormlambda/__init__.py
CHANGED
@@ -20,3 +20,5 @@ from .model.base_model import (
|
|
20
20
|
BaseModel as BaseModel,
|
21
21
|
ORM as ORM,
|
22
22
|
) # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
|
23
|
+
|
24
|
+
from .engine import create_engine, URL # noqa: F401
|
@@ -12,6 +12,7 @@ from .order import Order as Order
|
|
12
12
|
from .update import UpdateQuery as UpdateQuery
|
13
13
|
from .upsert import UpsertQuery as UpsertQuery
|
14
14
|
from .where import Where as Where
|
15
|
+
from .having import Having as Having
|
15
16
|
from .count import Count as Count
|
16
17
|
from .group_by import GroupBy as GroupBy
|
17
18
|
from .alias import Alias as Alias
|
@@ -1,10 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
3
|
|
4
|
-
from ormlambda import Table
|
4
|
+
from ormlambda import Table
|
5
5
|
from ormlambda.sql.clause_info import ClauseInfo
|
6
|
-
from ormlambda.
|
7
|
-
from ormlambda.sql.
|
6
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
7
|
+
from ormlambda.sql.types import TableType
|
8
8
|
|
9
9
|
if tp.TYPE_CHECKING:
|
10
10
|
from ormlambda.sql.types import ColumnType
|
@@ -12,23 +12,17 @@ if tp.TYPE_CHECKING:
|
|
12
12
|
from ormlambda.sql.types import AliasType
|
13
13
|
|
14
14
|
|
15
|
-
class Alias[T: Table
|
16
|
-
def __init__(
|
15
|
+
class Alias[T: Table](ClauseInfo[T]):
|
16
|
+
def __init__[TProp](
|
17
17
|
self,
|
18
|
-
|
19
|
-
|
18
|
+
table: TableType[T],
|
19
|
+
column: tp.Optional[ColumnType[TProp]] = None,
|
20
|
+
alias_table: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
21
|
+
alias_clause: tp.Optional[AliasType[ClauseInfo[T]]] = None,
|
22
|
+
context: ClauseContextType = None,
|
23
|
+
keep_asterisk: bool = False,
|
24
|
+
preserve_context: bool = False,
|
20
25
|
):
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
else:
|
25
|
-
context = ClauseInfoContext(table_context=element.context._table_context, clause_context={})
|
26
|
-
element.context = context
|
27
|
-
element._alias_clause = alias_clause
|
28
|
-
|
29
|
-
self._element = element
|
30
|
-
|
31
|
-
@tp.override
|
32
|
-
@property
|
33
|
-
def query(self) -> str:
|
34
|
-
return self._element.query
|
26
|
+
if not alias_clause:
|
27
|
+
raise TypeError
|
28
|
+
super().__init__(table, column, alias_table, alias_clause, context, keep_asterisk, preserve_context)
|
@@ -1,32 +1,31 @@
|
|
1
1
|
import typing as tp
|
2
|
-
from ormlambda.common.enums.join_type import JoinType
|
3
|
-
from ormlambda.sql.clause_info import IAggregate
|
4
2
|
from ormlambda import Table
|
5
|
-
from ormlambda.
|
3
|
+
from ormlambda.sql.clause_info.clause_info import AggregateFunctionBase
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
5
|
+
from ormlambda.sql.types import ColumnType
|
6
6
|
|
7
7
|
|
8
|
-
class GroupBy[T: tp.Type[Table], *Ts, TProp](
|
9
|
-
|
8
|
+
class GroupBy[T: tp.Type[Table], *Ts, TProp](AggregateFunctionBase):
|
9
|
+
@classmethod
|
10
|
+
def FUNCTION_NAME(self) -> str:
|
11
|
+
return "GROUP BY"
|
10
12
|
|
11
13
|
def __init__(
|
12
14
|
self,
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
alias_name: str | None = None,
|
18
|
-
by: JoinType = JoinType.INNER_JOIN,
|
19
|
-
) -> None:
|
15
|
+
column: ColumnType,
|
16
|
+
context: ClauseInfoContext,
|
17
|
+
**kwargs,
|
18
|
+
):
|
20
19
|
super().__init__(
|
21
|
-
table,
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
table=column.table,
|
21
|
+
column=column,
|
22
|
+
alias_table=None,
|
23
|
+
alias_clause=None,
|
24
|
+
context=context,
|
25
|
+
**kwargs,
|
26
26
|
)
|
27
27
|
|
28
28
|
@property
|
29
29
|
def query(self) -> str:
|
30
|
-
|
31
|
-
|
32
|
-
return f"{self.CLAUSE} {col}"
|
30
|
+
column = self._create_query()
|
31
|
+
return f"{self.FUNCTION_NAME()} {column}"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from .where import Where
|
4
|
+
|
5
|
+
|
6
|
+
class Having(Where):
|
7
|
+
"""
|
8
|
+
The purpose of this class is to create 'WHERE' condition queries properly.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, *comparer, restrictive=True, context=None):
|
12
|
+
super().__init__(*comparer, restrictive=restrictive, context=context)
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def FUNCTION_NAME() -> str:
|
16
|
+
return "HAVING"
|
@@ -48,13 +48,18 @@ class Order(AggregateFunctionBase):
|
|
48
48
|
columns = self.unresolved_column
|
49
49
|
|
50
50
|
# if this attr is not iterable means that we only pass one column without wrapped in a list or tuple
|
51
|
+
if isinstance(columns, str):
|
52
|
+
string_columns = f"{columns} {str(self._order_type[0])}"
|
53
|
+
return f"{self.FUNCTION_NAME()} {string_columns}"
|
54
|
+
|
51
55
|
if not isinstance(columns, tp.Iterable):
|
52
56
|
columns = (columns,)
|
57
|
+
|
53
58
|
assert len(columns) == len(self._order_type)
|
54
59
|
|
55
60
|
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
56
61
|
for index, clause in enumerate(self._convert_into_clauseInfo(columns, context)):
|
57
62
|
clause.alias_clause = None
|
58
|
-
string_columns.append(f"{clause.query} {self._order_type[index]
|
63
|
+
string_columns.append(f"{clause.query} {str(self._order_type[index])}")
|
59
64
|
|
60
65
|
return f"{self.FUNCTION_NAME()} {', '.join(string_columns)}"
|
@@ -32,8 +32,8 @@ class Where(AggregateFunctionBase):
|
|
32
32
|
def alias_clause(self) -> None:
|
33
33
|
return None
|
34
34
|
|
35
|
-
@
|
36
|
-
def join_condition(wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
35
|
+
@classmethod
|
36
|
+
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
37
37
|
if not isinstance(wheres, tp.Iterable):
|
38
38
|
wheres = (wheres,)
|
39
39
|
|
@@ -42,4 +42,4 @@ class Where(AggregateFunctionBase):
|
|
42
42
|
for c in where._comparer:
|
43
43
|
c.set_context(context)
|
44
44
|
comparers.append(c)
|
45
|
-
return
|
45
|
+
return cls(*comparers, restrictive=restrictive, context=context).query
|
@@ -1,20 +1,22 @@
|
|
1
|
+
import typing as tp
|
2
|
+
|
1
3
|
from ormlambda.sql.clause_info import AggregateFunctionBase
|
2
4
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
3
|
-
|
4
|
-
|
5
|
-
import typing as tp
|
6
5
|
from ormlambda.sql.types import ColumnType, AliasType
|
7
6
|
from ormlambda.sql.clause_info import ClauseInfo
|
8
7
|
|
9
8
|
|
10
|
-
|
9
|
+
type ConcatResponse[TProp] = tuple[str | ColumnType[TProp]]
|
10
|
+
|
11
|
+
|
12
|
+
class Concat[T](AggregateFunctionBase):
|
11
13
|
@staticmethod
|
12
14
|
def FUNCTION_NAME() -> str:
|
13
15
|
return "CONCAT"
|
14
16
|
|
15
17
|
def __init__[TProp](
|
16
18
|
self,
|
17
|
-
values:
|
19
|
+
values: ConcatResponse[TProp],
|
18
20
|
alias_clause: AliasType[ColumnType[TProp]] = "concat",
|
19
21
|
context: ClauseContextType = None,
|
20
22
|
) -> None:
|
@@ -33,6 +35,6 @@ class Concat[*Ts](AggregateFunctionBase):
|
|
33
35
|
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
34
36
|
|
35
37
|
for clause in self._convert_into_clauseInfo(self.unresolved_column, context=context):
|
36
|
-
clause.alias_clause =
|
38
|
+
clause.alias_clause = self.alias_clause
|
37
39
|
columns.append(clause)
|
38
40
|
return self._concat_alias_and_column(f"{self.FUNCTION_NAME()}({ClauseInfo.join_clauses(columns)})", self._alias_aggregate)
|
@@ -14,7 +14,7 @@ class Max(AggregateFunctionBase[None]):
|
|
14
14
|
|
15
15
|
def __init__[TProp](
|
16
16
|
self,
|
17
|
-
elements:
|
17
|
+
elements: ColumnType[TProp],
|
18
18
|
alias_clause: AliasType[ColumnType[TProp]] = "max",
|
19
19
|
context: ClauseContextType = None,
|
20
20
|
):
|
@@ -27,12 +27,12 @@ class JoinContext[TParent: Table, TRepo]:
|
|
27
27
|
for comparer, by in self._joins:
|
28
28
|
fk_clause, alias = self.get_fk_clause(comparer)
|
29
29
|
|
30
|
-
foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias)
|
30
|
+
foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True)
|
31
31
|
fk_clause.alias_table = foreign_key.alias
|
32
32
|
self._context.add_clause_to_context(fk_clause)
|
33
33
|
setattr(self._parent, alias, foreign_key)
|
34
34
|
|
35
|
-
# TODOH []: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
|
35
|
+
# TODOH [x]: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
|
36
36
|
# Keep in mind that 'ForeignKey.stored_calls' is cleared every time we call methods like
|
37
37
|
# .select(), .select_one(), .insert(), .update(), or .count(). This means we only retain
|
38
38
|
# the context from the first call of any of these methods.
|
@@ -49,6 +49,7 @@ class JoinContext[TParent: Table, TRepo]:
|
|
49
49
|
fk: ForeignKey = getattr(self._parent, attribute)
|
50
50
|
delattr(self._parent, attribute)
|
51
51
|
del self._context._table_context[fk.tright]
|
52
|
+
ForeignKey.stored_calls.remove(fk)
|
52
53
|
return None
|
53
54
|
|
54
55
|
def __getattr__(self, name: str) -> TParent:
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
import contextlib
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Any, Generator, Iterable, Optional, Type, override, TYPE_CHECKING, Unpack
|
5
|
+
import uuid
|
5
6
|
import shapely as shp
|
6
7
|
|
7
8
|
# from mysql.connector.pooling import MySQLConnectionPool
|
@@ -16,6 +17,7 @@ from ormlambda.caster import Caster
|
|
16
17
|
from ..clauses import CreateDatabase, TypeExists
|
17
18
|
from ..clauses import DropDatabase
|
18
19
|
from ..clauses import DropTable
|
20
|
+
from ..clauses import Alias
|
19
21
|
|
20
22
|
|
21
23
|
if TYPE_CHECKING:
|
@@ -68,12 +70,15 @@ class Response[TFlavour, *Ts]:
|
|
68
70
|
|
69
71
|
def _cast_to_flavour(self, data: list[tuple[*Ts]], **kwargs) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
|
70
72
|
def _dict(**kwargs) -> list[dict[str, tuple[*Ts]]]:
|
73
|
+
nonlocal data
|
71
74
|
return [dict(zip(self._columns, x)) for x in data]
|
72
75
|
|
73
76
|
def _tuple(**kwargs) -> list[tuple[*Ts]]:
|
77
|
+
nonlocal data
|
74
78
|
return data
|
75
79
|
|
76
80
|
def _set(**kwargs) -> list[set]:
|
81
|
+
nonlocal data
|
77
82
|
for d in data:
|
78
83
|
n = len(d)
|
79
84
|
for i in range(n):
|
@@ -84,12 +89,19 @@ class Response[TFlavour, *Ts]:
|
|
84
89
|
return [set(x) for x in data]
|
85
90
|
|
86
91
|
def _list(**kwargs) -> list[list]:
|
92
|
+
nonlocal data
|
87
93
|
return [list(x) for x in data]
|
88
94
|
|
89
95
|
def _default(**kwargs) -> list[TFlavour]:
|
90
|
-
|
96
|
+
nonlocal data
|
97
|
+
replacer_dicc: dict[str, str] = {}
|
91
98
|
|
92
|
-
|
99
|
+
for col in self._select.all_clauses:
|
100
|
+
if hasattr(col, "_alias_aggregate") or col.alias_clause is None or isinstance(col, Alias):
|
101
|
+
continue
|
102
|
+
replacer_dicc[col.alias_clause] = col.column
|
103
|
+
|
104
|
+
cleaned_column_names = [replacer_dicc.get(col, col) for col in self._columns]
|
93
105
|
|
94
106
|
result = []
|
95
107
|
for attr in data:
|
@@ -157,7 +169,34 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
157
169
|
#
|
158
170
|
|
159
171
|
def __init__(self, **kwargs: Unpack[MySQLArgs]):
|
160
|
-
|
172
|
+
timeout = self.__add_connection_timeout(kwargs)
|
173
|
+
name = self.__add_pool_name(kwargs)
|
174
|
+
size = self.__add_pool_size(kwargs)
|
175
|
+
attr = kwargs.copy()
|
176
|
+
attr["connection_timeout"] = timeout
|
177
|
+
attr["pool_name"] = name
|
178
|
+
attr["pool_size"] = size
|
179
|
+
|
180
|
+
super().__init__(MySQLConnectionPool, **attr)
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def __add_connection_timeout(kwargs: MySQLArgs) -> int:
|
184
|
+
if "connection_timeout" not in kwargs.keys():
|
185
|
+
return 60
|
186
|
+
return int(kwargs.pop("connection_timeout"))
|
187
|
+
|
188
|
+
@staticmethod
|
189
|
+
def __add_pool_name(kwargs: MySQLArgs) -> str:
|
190
|
+
if "pool_name" not in kwargs.keys():
|
191
|
+
return str(uuid.uuid4())
|
192
|
+
|
193
|
+
return kwargs.pop("pool_name")
|
194
|
+
|
195
|
+
@staticmethod
|
196
|
+
def __add_pool_size(kwargs: MySQLArgs) -> int:
|
197
|
+
if "pool_size" not in kwargs.keys():
|
198
|
+
return 5
|
199
|
+
return int(kwargs.pop("pool_size"))
|
161
200
|
|
162
201
|
@contextlib.contextmanager
|
163
202
|
def get_connection(self) -> Generator[MySQLConnection, None, None]:
|
@@ -254,7 +293,6 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
254
293
|
|
255
294
|
@override
|
256
295
|
def database_exists(self, name: str) -> bool:
|
257
|
-
query = "SHOW DATABASES LIKE %s;"
|
258
296
|
temp_config = self._pool._cnx_config
|
259
297
|
|
260
298
|
config_without_db = temp_config.copy()
|
@@ -262,10 +300,11 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
262
300
|
if "database" in config_without_db:
|
263
301
|
config_without_db.pop("database")
|
264
302
|
self._pool.set_config(**config_without_db)
|
303
|
+
|
265
304
|
with self.get_connection() as cnx:
|
266
305
|
with cnx.cursor(buffered=True) as cursor:
|
267
|
-
cursor.execute(
|
268
|
-
|
306
|
+
cursor.execute("SHOW DATABASES LIKE %s;", (name,))
|
307
|
+
res = cursor.fetchmany(1)
|
269
308
|
|
270
309
|
self._pool.set_config(**temp_config)
|
271
310
|
return len(res) > 0
|
@@ -276,12 +315,11 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
276
315
|
|
277
316
|
@override
|
278
317
|
def table_exists(self, name: str) -> bool:
|
279
|
-
query = "SHOW TABLES LIKE %s;"
|
280
318
|
with self.get_connection() as cnx:
|
281
319
|
if not cnx.database:
|
282
320
|
raise Exception("No database selected")
|
283
321
|
with cnx.cursor(buffered=True) as cursor:
|
284
|
-
cursor.execute(
|
322
|
+
cursor.execute("SHOW TABLES LIKE %s;", (name,))
|
285
323
|
res = cursor.fetchmany(1)
|
286
324
|
return len(res) > 0
|
287
325
|
|
@@ -297,9 +335,17 @@ class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
|
297
335
|
|
298
336
|
@property
|
299
337
|
def database(self) -> Optional[str]:
|
300
|
-
return self.
|
338
|
+
return self._pool._cnx_config.get("database", None)
|
301
339
|
|
302
340
|
@database.setter
|
303
341
|
def database(self, value: str) -> None:
|
304
|
-
|
305
|
-
|
342
|
+
"""Change the current database using USE statement"""
|
343
|
+
|
344
|
+
if not self.database_exists(value):
|
345
|
+
raise ValueError(f"You cannot set the non-existent '{value}' database.")
|
346
|
+
|
347
|
+
old_config: MySQLArgs = self._pool._cnx_config.copy()
|
348
|
+
old_config["database"] = value
|
349
|
+
|
350
|
+
self._pool._remove_connections()
|
351
|
+
self._pool = type(self)(**old_config)._pool
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Concatenate, Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
|
2
|
+
from typing import Concatenate, Iterable, TypedDict, override, Type, TYPE_CHECKING, Any, Callable, Optional
|
3
3
|
from mysql.connector import errors, errorcode
|
4
4
|
|
5
5
|
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
|
14
14
|
from ormlambda import Table
|
15
15
|
from ormlambda.statements.types import OrderTypes
|
16
16
|
from ormlambda.sql.types import ColumnType
|
17
|
+
from ormlambda.statements.types import SelectCols
|
17
18
|
from ormlambda.repository.interfaces import IRepositoryBase
|
18
19
|
from ormlambda.statements.interfaces import IStatements_two_generic
|
19
20
|
from ormlambda.repository.interfaces.IRepositoryBase import TypeExists
|
@@ -28,11 +29,12 @@ from .clauses import InsertQuery
|
|
28
29
|
from .clauses import Limit
|
29
30
|
from .clauses import Offset
|
30
31
|
from .clauses import Order
|
31
|
-
from .clauses
|
32
|
+
from .clauses import Select
|
32
33
|
|
33
34
|
from .clauses import UpsertQuery
|
34
35
|
from .clauses import UpdateQuery
|
35
36
|
from .clauses import Where
|
37
|
+
from .clauses import Having
|
36
38
|
from .clauses import Count
|
37
39
|
from .clauses import GroupBy
|
38
40
|
from .clauses import Alias
|
@@ -60,12 +62,23 @@ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Calla
|
|
60
62
|
return wrapper
|
61
63
|
|
62
64
|
|
65
|
+
class OrderType(TypedDict):
|
66
|
+
Select: Select
|
67
|
+
JoinSelector: JoinSelector
|
68
|
+
Where: Where
|
69
|
+
Order: Order
|
70
|
+
GroupBy: GroupBy
|
71
|
+
Having: Having
|
72
|
+
Limit: Limit
|
73
|
+
Offset: Offset
|
74
|
+
|
75
|
+
|
63
76
|
class QueryBuilder(IQuery):
|
64
|
-
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "
|
77
|
+
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
|
65
78
|
|
66
79
|
def __init__(self, by: JoinType = JoinType.INNER_JOIN):
|
67
80
|
self._context = ClauseInfoContext()
|
68
|
-
self._query_list:
|
81
|
+
self._query_list: OrderType = {}
|
69
82
|
self._by = by
|
70
83
|
|
71
84
|
self._joins: Optional[IQuery] = None
|
@@ -113,6 +126,15 @@ class QueryBuilder(IQuery):
|
|
113
126
|
def GROUP_BY(self) -> IQuery:
|
114
127
|
return self._query_list.get("GroupBy", None)
|
115
128
|
|
129
|
+
@property
|
130
|
+
def HAVING(self) -> IQuery:
|
131
|
+
where = self._query_list.get("Having", None)
|
132
|
+
if not isinstance(where, Iterable):
|
133
|
+
if not where:
|
134
|
+
return ()
|
135
|
+
return (where,)
|
136
|
+
return where
|
137
|
+
|
116
138
|
@property
|
117
139
|
def LIMIT(self) -> IQuery:
|
118
140
|
return self._query_list.get("Limit", None)
|
@@ -133,8 +155,9 @@ class QueryBuilder(IQuery):
|
|
133
155
|
self.SELECT.query,
|
134
156
|
JOINS,
|
135
157
|
Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
|
136
|
-
self.ORDER.query if self.ORDER else None,
|
137
158
|
self.GROUP_BY.query if self.GROUP_BY else None,
|
159
|
+
Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
|
160
|
+
self.ORDER.query if self.ORDER else None,
|
138
161
|
self.LIMIT.query if self.LIMIT else None,
|
139
162
|
self.OFFSET.query if self.OFFSET else None,
|
140
163
|
)
|
@@ -154,8 +177,8 @@ class QueryBuilder(IQuery):
|
|
154
177
|
joins = set()
|
155
178
|
# Always it's gonna be a set of two
|
156
179
|
# FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
|
157
|
-
for
|
158
|
-
fk = ForeignKey.stored_calls.pop()
|
180
|
+
for fk in ForeignKey.stored_calls.copy():
|
181
|
+
fk = ForeignKey.stored_calls.pop(fk)
|
159
182
|
self._context._add_table_alias(fk.tright, fk.alias)
|
160
183
|
join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
|
161
184
|
joins.add(join)
|
@@ -267,55 +290,83 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
267
290
|
return self
|
268
291
|
|
269
292
|
@override
|
270
|
-
def count(
|
293
|
+
def count[TProp](
|
271
294
|
self,
|
272
|
-
selection: None |
|
273
|
-
|
295
|
+
selection: None | SelectCols[T,TProp] = lambda x: "*",
|
296
|
+
alias="count",
|
274
297
|
execute: bool = False,
|
275
298
|
) -> Optional[int]:
|
276
299
|
if execute is True:
|
277
|
-
return self.select_one(self.count(selection,
|
300
|
+
return self.select_one(self.count(selection, alias, False), flavour=dict)[alias]
|
278
301
|
|
279
|
-
|
280
|
-
|
281
|
-
return Count[T](element=selection, alias_clause=alias_clause, context=self._query_builder._context)
|
302
|
+
selection = GlobalChecker.resolved_callback_object(selection, self.models)
|
303
|
+
return Count(element=selection, alias_clause=alias, context=self._query_builder._context)
|
282
304
|
|
283
305
|
@override
|
284
306
|
def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
285
307
|
# FIXME [x]: I've wrapped self._model into tuple to pass it instance attr. Idk if it's correct
|
286
308
|
|
287
|
-
|
288
|
-
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
309
|
+
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
289
310
|
if not isinstance(conditions, Iterable):
|
290
311
|
conditions = (conditions,)
|
291
312
|
self._query_builder.add_statement(Where(*conditions))
|
292
313
|
return self
|
293
314
|
|
315
|
+
@override
|
316
|
+
def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
317
|
+
conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
|
318
|
+
if not isinstance(conditions, Iterable):
|
319
|
+
conditions = (conditions,)
|
320
|
+
self._query_builder.add_statement(Having(*conditions))
|
321
|
+
return self
|
322
|
+
|
294
323
|
@override
|
295
324
|
def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
|
296
|
-
query = GlobalChecker.resolved_callback_object(columns, self._models)
|
325
|
+
query = GlobalChecker.resolved_callback_object(columns, self._models)
|
297
326
|
order = Order(query, order_type)
|
298
327
|
self._query_builder.add_statement(order)
|
299
328
|
return self
|
300
329
|
|
301
330
|
@override
|
302
|
-
def concat
|
303
|
-
return func.Concat[T](
|
331
|
+
def concat(self, selector: SelectCols[T, str], alias: str = "concat") -> IAggregate:
|
332
|
+
return func.Concat[T](
|
333
|
+
values=selector,
|
334
|
+
alias_clause=alias,
|
335
|
+
context=self._query_builder._context,
|
336
|
+
)
|
304
337
|
|
305
338
|
@override
|
306
|
-
def max[TProp](
|
339
|
+
def max[TProp](
|
340
|
+
self,
|
341
|
+
column: SelectCols[T, TProp],
|
342
|
+
alias: str = "max",
|
343
|
+
execute: bool = False,
|
344
|
+
) -> int:
|
345
|
+
column = GlobalChecker.resolved_callback_object(column, self.models)
|
307
346
|
if execute is True:
|
308
347
|
return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
|
309
348
|
return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
|
310
349
|
|
311
350
|
@override
|
312
|
-
def min[TProp](
|
351
|
+
def min[TProp](
|
352
|
+
self,
|
353
|
+
column: SelectCols[T, TProp],
|
354
|
+
alias: str = "min",
|
355
|
+
execute: bool = False,
|
356
|
+
) -> int:
|
357
|
+
column = GlobalChecker.resolved_callback_object(column, self.models)
|
313
358
|
if execute is True:
|
314
359
|
return self.select_one(self.min(column, alias, execute=False), flavour=dict)[alias]
|
315
360
|
return func.Min(elements=column, alias_clause=alias, context=self._query_builder._context)
|
316
361
|
|
317
362
|
@override
|
318
|
-
def sum[TProp](
|
363
|
+
def sum[TProp](
|
364
|
+
self,
|
365
|
+
column: SelectCols[T, TProp],
|
366
|
+
alias: str = "sum",
|
367
|
+
execute: bool = False,
|
368
|
+
) -> int:
|
369
|
+
column = GlobalChecker.resolved_callback_object(column, self.models)
|
319
370
|
if execute is True:
|
320
371
|
return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
|
321
372
|
return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
|
@@ -334,7 +385,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
334
385
|
by: JoinType = JoinType.INNER_JOIN,
|
335
386
|
**kwargs,
|
336
387
|
):
|
337
|
-
select_clause = GlobalChecker.resolved_callback_object(selector, self._models)
|
388
|
+
select_clause = GlobalChecker.resolved_callback_object(selector, self._models)
|
338
389
|
|
339
390
|
if selector is None:
|
340
391
|
# COMMENT: if we do not specify any lambda function we assumed the user want to retreive only elements of the Model itself avoiding other models
|
@@ -406,16 +457,17 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
|
|
406
457
|
)
|
407
458
|
|
408
459
|
@override
|
409
|
-
def
|
410
|
-
|
411
|
-
groupby = GroupBy[T, tuple[*Ts]](self._models, lambda x: column)
|
412
|
-
else:
|
413
|
-
groupby = GroupBy[T, tuple[*Ts]](self._models, column)
|
460
|
+
def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T, *Ts], Any]):
|
461
|
+
groupby = GroupBy(column=column, context=self._query_builder._context)
|
414
462
|
# Only can be one LIMIT SQL parameter. We only use the last LimitQuery
|
415
463
|
self._query_builder.add_statement(groupby)
|
416
464
|
return self
|
417
465
|
|
418
466
|
@override
|
419
|
-
def alias[TProp](self, column: ColumnType[TProp], alias: str) ->
|
420
|
-
|
421
|
-
|
467
|
+
def alias[TProp](self, column: ColumnType[TProp], alias: str) -> ClauseInfo[T, TProp]:
|
468
|
+
return Alias(
|
469
|
+
table=column.table,
|
470
|
+
column=column,
|
471
|
+
alias_clause=alias,
|
472
|
+
context=self._query_builder._context,
|
473
|
+
)
|
ormlambda/engine/__init__.py
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from ormlambda.engine.url import URL, make_url
|
4
|
+
from ormlambda import BaseRepository
|
5
|
+
|
6
|
+
|
7
|
+
def create_engine(url: URL | str, **kwargs: Any) -> BaseRepository:
|
8
|
+
from ormlambda.databases import MySQLRepository
|
9
|
+
|
10
|
+
# create url.URL object
|
11
|
+
u = make_url(url)
|
12
|
+
url, kwargs = u._instantiate_plugins(kwargs)
|
13
|
+
|
14
|
+
repo_selector = {
|
15
|
+
"mysql": MySQLRepository,
|
16
|
+
}
|
17
|
+
|
18
|
+
if url.drivername not in repo_selector:
|
19
|
+
raise ValueError(f"drivername '{url.drivername}' not expected to load Repository class")
|
20
|
+
|
21
|
+
default_config = {
|
22
|
+
"user": url.username,
|
23
|
+
"password": url.password,
|
24
|
+
"host": url.host,
|
25
|
+
"database": url.database,
|
26
|
+
**kwargs,
|
27
|
+
}
|
28
|
+
|
29
|
+
if url.port:
|
30
|
+
try:
|
31
|
+
default_config["port"] = int(url.port)
|
32
|
+
except ValueError:
|
33
|
+
raise ValueError(f"The port must be an int. '{url.port}' passed.")
|
34
|
+
|
35
|
+
return repo_selector[url.drivername](**default_config)
|