ormlambda 3.7.2__py3-none-any.whl → 3.11.0__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)
@@ -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
@@ -28,11 +28,12 @@ from .clauses import InsertQuery
28
28
  from .clauses import Limit
29
29
  from .clauses import Offset
30
30
  from .clauses import Order
31
- from .clauses.select import Select
31
+ from .clauses import Select
32
32
 
33
33
  from .clauses import UpsertQuery
34
34
  from .clauses import UpdateQuery
35
35
  from .clauses import Where
36
+ from .clauses import Having
36
37
  from .clauses import Count
37
38
  from .clauses import GroupBy
38
39
  from .clauses import Alias
@@ -60,12 +61,23 @@ def clear_list[T, **P](f: Callable[Concatenate[MySQLStatements, P], T]) -> Calla
60
61
  return wrapper
61
62
 
62
63
 
64
+ class OrderType(TypedDict):
65
+ Select: Select
66
+ JoinSelector: JoinSelector
67
+ Where: Where
68
+ Order: Order
69
+ GroupBy: GroupBy
70
+ Having: Having
71
+ Limit: Limit
72
+ Offset: Offset
73
+
74
+
63
75
  class QueryBuilder(IQuery):
64
- __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "Order", "GroupBy", "Limit", "Offset")
76
+ __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
65
77
 
66
78
  def __init__(self, by: JoinType = JoinType.INNER_JOIN):
67
79
  self._context = ClauseInfoContext()
68
- self._query_list: dict[str, IQuery] = {}
80
+ self._query_list: OrderType = {}
69
81
  self._by = by
70
82
 
71
83
  self._joins: Optional[IQuery] = None
@@ -113,6 +125,15 @@ class QueryBuilder(IQuery):
113
125
  def GROUP_BY(self) -> IQuery:
114
126
  return self._query_list.get("GroupBy", None)
115
127
 
128
+ @property
129
+ def HAVING(self) -> IQuery:
130
+ where = self._query_list.get("Having", None)
131
+ if not isinstance(where, Iterable):
132
+ if not where:
133
+ return ()
134
+ return (where,)
135
+ return where
136
+
116
137
  @property
117
138
  def LIMIT(self) -> IQuery:
118
139
  return self._query_list.get("Limit", None)
@@ -133,8 +154,9 @@ class QueryBuilder(IQuery):
133
154
  self.SELECT.query,
134
155
  JOINS,
135
156
  Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
136
- self.ORDER.query if self.ORDER else None,
137
157
  self.GROUP_BY.query if self.GROUP_BY else None,
158
+ Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
159
+ self.ORDER.query if self.ORDER else None,
138
160
  self.LIMIT.query if self.LIMIT else None,
139
161
  self.OFFSET.query if self.OFFSET else None,
140
162
  )
@@ -154,8 +176,8 @@ class QueryBuilder(IQuery):
154
176
  joins = set()
155
177
  # Always it's gonna be a set of two
156
178
  # 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()
179
+ for fk in ForeignKey.stored_calls.copy():
180
+ fk = ForeignKey.stored_calls.pop(fk)
159
181
  self._context._add_table_alias(fk.tright, fk.alias)
160
182
  join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
161
183
  joins.add(join)
@@ -278,7 +300,7 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
278
300
 
279
301
  if GlobalChecker.is_lambda_function(selection):
280
302
  selection = selection(*self.models)
281
- return Count[T](element=selection, alias_clause=alias_clause, context=self._query_builder._context)
303
+ return Count(element=selection, alias_clause=alias_clause, context=self._query_builder._context)
282
304
 
283
305
  @override
284
306
  def where(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
@@ -291,6 +313,15 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
291
313
  self._query_builder.add_statement(Where(*conditions))
292
314
  return self
293
315
 
316
+ @override
317
+ def having(self, conditions: WhereTypes) -> IStatements_two_generic[T, MySQLConnection]:
318
+ if GlobalChecker.is_lambda_function(conditions):
319
+ conditions = GlobalChecker.resolved_callback_object(conditions, self._models)
320
+ if not isinstance(conditions, Iterable):
321
+ conditions = (conditions,)
322
+ self._query_builder.add_statement(Having(*conditions))
323
+ return self
324
+
294
325
  @override
295
326
  def order[TValue](self, columns: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
296
327
  query = GlobalChecker.resolved_callback_object(columns, self._models) if GlobalChecker.is_lambda_function(columns) else columns
@@ -299,23 +330,42 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
299
330
  return self
300
331
 
301
332
  @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)
333
+ def concat(self, selector: str | Callable[[T], str], alias: str = "CONCAT") -> IAggregate:
334
+ return func.Concat[T](
335
+ values=selector,
336
+ alias_clause=alias,
337
+ context=self._query_builder._context,
338
+ )
304
339
 
305
340
  @override
306
- def max[TProp](self, column: Callable[[T], TProp], alias: str = "max", execute: bool = False) -> TProp:
341
+ def max[TProp](
342
+ self,
343
+ column: Callable[[T], TProp],
344
+ alias: str = "max",
345
+ execute: bool = False,
346
+ ) -> TProp:
307
347
  if execute is True:
308
348
  return self.select_one(self.max(column, alias, execute=False), flavour=dict)[alias]
309
349
  return func.Max(elements=column, alias_clause=alias, context=self._query_builder._context)
310
350
 
311
351
  @override
312
- def min[TProp](self, column: Callable[[T], TProp], alias: str = "min", execute: bool = False) -> TProp:
352
+ def min[TProp](
353
+ self,
354
+ column: Callable[[T], TProp],
355
+ alias: str = "min",
356
+ execute: bool = False,
357
+ ) -> TProp:
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: Callable[[T], TProp],
366
+ alias: str = "sum",
367
+ execute: bool = False,
368
+ ) -> TProp:
319
369
  if execute is True:
320
370
  return self.select_one(self.sum(column, alias, execute=False), flavour=dict)[alias]
321
371
  return func.Sum(elements=column, alias_clause=alias, context=self._query_builder._context)
@@ -406,16 +456,17 @@ class MySQLStatements[T: Table, *Ts](BaseStatement[T, MySQLConnection]):
406
456
  )
407
457
 
408
458
  @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)
459
+ def groupby[TProp](self, column: ColumnType[TProp] | Callable[[T, *Ts], Any]):
460
+ groupby = GroupBy(column=column, context=self._query_builder._context)
414
461
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
415
462
  self._query_builder.add_statement(groupby)
416
463
  return self
417
464
 
418
465
  @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)
466
+ def alias[TProp](self, column: ColumnType[TProp], alias: str) -> ClauseInfo[T, TProp]:
467
+ return Alias(
468
+ table=column.table,
469
+ column=column,
470
+ alias_clause=alias,
471
+ context=self._query_builder._context,
472
+ )
@@ -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)