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 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
@@ -23,6 +23,7 @@ class MySQLCaster(ICaster):
23
23
  NoneType: NoneTypeCaster,
24
24
  datetime: DatetimeCaster,
25
25
  bytes: BytesCaster,
26
+ bytearray: BytesCaster,
26
27
  tuple: IterableCaster,
27
28
  list: IterableCaster,
28
29
  }
@@ -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, Column
4
+ from ormlambda import Table
5
5
  from ormlambda.sql.clause_info import ClauseInfo
6
- from ormlambda.common.interfaces.IQueryCommand import IQuery
7
- from ormlambda.sql.clause_info import ClauseInfoContext, IAggregate
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, TProp](ClauseInfo, IQuery):
16
- def __init__(
15
+ class Alias[T: Table](ClauseInfo[T]):
16
+ def __init__[TProp](
17
17
  self,
18
- element: IAggregate | ClauseInfo[T] | ColumnType[TProp],
19
- alias_clause: tp.Optional[AliasType[ClauseInfo[T]]],
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 isinstance(element, Column):
22
- context = ClauseInfoContext()
23
- element = ClauseInfo(table=element.table, column=element, alias_clause=alias_clause)
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.common.abstract_classes.decomposition_query import DecompositionQueryBase
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](DecompositionQueryBase[T], IAggregate):
9
- CLAUSE: str = "GROUP BY"
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
- table: T,
14
- column: tp.Callable[[T, *Ts], TProp],
15
- *,
16
- alias: bool = True,
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
- columns=column,
23
- alias=alias,
24
- alias_name=alias_name,
25
- by=by,
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
- col: str = ", ".join([x.query for x in self.all_clauses])
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].value}")
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
- @staticmethod
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 Where(*comparers, restrictive=restrictive, context=context).query
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
- class Concat[*Ts](AggregateFunctionBase):
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: ColumnType[Ts] | tuple[ColumnType[Ts], ...],
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 = None
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: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
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
- replacer_dicc: dict[str, str] = {x.alias_clause: x.column for x in self._select.all_clauses}
96
+ nonlocal data
97
+ replacer_dicc: dict[str, str] = {}
91
98
 
92
- cleaned_column_names = [replacer_dicc[col] for col in self._columns]
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
- super().__init__(MySQLConnectionPool, **kwargs)
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(query, (name,))
268
- res = cursor.fetchmany(1)
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(query, (name,))
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._data_config.get("database", None)
338
+ return self._pool._cnx_config.get("database", None)
301
339
 
302
340
  @database.setter
303
341
  def database(self, value: str) -> None:
304
- self._data_config["database"] = value
305
- self._pool.set_config(**self._data_config)
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.select import Select
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", "Order", "GroupBy", "Limit", "Offset")
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: dict[str, IQuery] = {}
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 _ in range(len(ForeignKey.stored_calls)):
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 | Iterable[Table] | Callable[[T], tuple] = lambda x: "*",
273
- alias_clause="count",
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, alias_clause, False), flavour=dict)[alias_clause]
300
+ return self.select_one(self.count(selection, alias, False), flavour=dict)[alias]
278
301
 
279
- if GlobalChecker.is_lambda_function(selection):
280
- selection = selection(*self.models)
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
- if GlobalChecker.is_lambda_function(conditions):
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) if GlobalChecker.is_lambda_function(columns) else columns
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[*Ts](self, selector: Callable[[T], tuple[*Ts]], alias: bool = True, alias_name: str = "CONCAT") -> IAggregate:
303
- return func.Concat[T](self._model, selector, alias=alias, alias_name=alias_name, context=self._query_builder._context)
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](self, column: Callable[[T], TProp], alias: str = "max", execute: bool = False) -> 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](self, column: Callable[[T], TProp], alias: str = "min", execute: bool = False) -> 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](self, column: Callable[[T], TProp], alias: str = "sum", execute: bool = False) -> 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) if GlobalChecker.is_lambda_function(selector) else selector
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 group_by(self, column: str | Callable[[T, *Ts], Any]):
410
- if isinstance(column, str):
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) -> Alias[T, TProp]:
420
- ci = ClauseInfo(table=column.table, column=column, alias_clause=alias, context=self._query_builder._context)
421
- return Alias(ci, alias_clause=alias)
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
+ )
@@ -0,0 +1,2 @@
1
+ from .create import create_engine # noqa: F401
2
+ from .url import URL # noqa: F401
@@ -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)