ormlambda 3.12.2__py3-none-any.whl → 3.34.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/caster/__init__.py +1 -1
- ormlambda/caster/caster.py +29 -12
- ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
- ormlambda/common/abstract_classes/decomposition_query.py +17 -2
- ormlambda/common/abstract_classes/non_query_base.py +9 -7
- ormlambda/common/abstract_classes/query_base.py +3 -1
- ormlambda/common/errors/__init__.py +29 -0
- ormlambda/common/interfaces/IQueryCommand.py +6 -2
- ormlambda/databases/__init__.py +0 -1
- ormlambda/databases/my_sql/__init__.py +0 -1
- ormlambda/databases/my_sql/caster/caster.py +23 -19
- ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
- ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
- ormlambda/databases/my_sql/caster/types/date.py +34 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
- ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
- ormlambda/databases/my_sql/caster/types/float.py +7 -7
- ormlambda/databases/my_sql/caster/types/int.py +7 -7
- ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
- ormlambda/databases/my_sql/caster/types/none.py +8 -7
- ormlambda/databases/my_sql/caster/types/point.py +4 -4
- ormlambda/databases/my_sql/caster/types/string.py +7 -7
- ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
- ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
- ormlambda/databases/my_sql/clauses/__init__.py +4 -10
- ormlambda/databases/my_sql/clauses/count.py +5 -15
- ormlambda/databases/my_sql/clauses/delete.py +3 -50
- ormlambda/databases/my_sql/clauses/group_by.py +3 -16
- ormlambda/databases/my_sql/clauses/having.py +2 -6
- ormlambda/databases/my_sql/clauses/insert.py +4 -92
- ormlambda/databases/my_sql/clauses/joins.py +5 -140
- ormlambda/databases/my_sql/clauses/limit.py +4 -15
- ormlambda/databases/my_sql/clauses/offset.py +4 -15
- ormlambda/databases/my_sql/clauses/order.py +4 -61
- ormlambda/databases/my_sql/clauses/update.py +4 -67
- ormlambda/databases/my_sql/clauses/upsert.py +3 -66
- ormlambda/databases/my_sql/clauses/where.py +4 -42
- ormlambda/databases/my_sql/repository.py +217 -0
- ormlambda/dialects/__init__.py +39 -0
- ormlambda/dialects/default/__init__.py +1 -0
- ormlambda/dialects/default/base.py +39 -0
- ormlambda/dialects/interface/__init__.py +1 -0
- ormlambda/dialects/interface/dialect.py +78 -0
- ormlambda/dialects/mysql/__init__.py +8 -0
- ormlambda/dialects/mysql/base.py +387 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -0
- ormlambda/dialects/mysql/types.py +732 -0
- ormlambda/dialects/sqlite/__init__.py +5 -0
- ormlambda/dialects/sqlite/base.py +47 -0
- ormlambda/dialects/sqlite/pysqlite.py +32 -0
- ormlambda/engine/__init__.py +1 -0
- ormlambda/engine/base.py +58 -0
- ormlambda/engine/create.py +9 -23
- ormlambda/engine/url.py +31 -19
- ormlambda/env.py +30 -0
- ormlambda/errors.py +17 -0
- ormlambda/model/base_model.py +7 -9
- ormlambda/repository/base_repository.py +36 -5
- ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
- ormlambda/repository/response.py +134 -0
- ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
- ormlambda/sql/clause_info/clause_info.py +34 -17
- ormlambda/sql/clauses/__init__.py +14 -0
- ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
- ormlambda/sql/clauses/count.py +57 -0
- ormlambda/sql/clauses/delete.py +71 -0
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/sql/clauses/having.py +21 -0
- ormlambda/sql/clauses/insert.py +104 -0
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
- ormlambda/sql/clauses/joins.py +159 -0
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/sql/clauses/order.py +55 -0
- ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
- ormlambda/sql/clauses/update.py +84 -0
- ormlambda/sql/clauses/upsert.py +77 -0
- ormlambda/sql/clauses/where.py +65 -0
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/{column.py → column/column.py} +82 -22
- ormlambda/sql/comparer.py +51 -37
- ormlambda/sql/compiler.py +427 -0
- ormlambda/sql/ddl.py +68 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +43 -39
- ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
- ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
- ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
- ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
- ormlambda/sql/sqltypes.py +647 -0
- ormlambda/sql/table/__init__.py +1 -1
- ormlambda/sql/table/table.py +179 -0
- ormlambda/sql/table/table_constructor.py +1 -208
- ormlambda/sql/type_api.py +35 -0
- ormlambda/sql/types.py +3 -1
- ormlambda/sql/visitors.py +74 -0
- ormlambda/statements/__init__.py +1 -0
- ormlambda/statements/base_statement.py +28 -38
- ormlambda/statements/interfaces/IStatements.py +5 -4
- ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
- ormlambda/{databases/my_sql → statements}/statements.py +50 -60
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +88 -0
- ormlambda/util/load_module.py +21 -0
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.1.dist-info/AUTHORS +32 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
- ormlambda-3.34.1.dist-info/RECORD +157 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
- ormlambda/components/__init__.py +0 -4
- ormlambda/components/delete/__init__.py +0 -2
- ormlambda/components/delete/abstract_delete.py +0 -17
- ormlambda/components/insert/__init__.py +0 -2
- ormlambda/components/insert/abstract_insert.py +0 -25
- ormlambda/components/select/__init__.py +0 -1
- ormlambda/components/update/__init__.py +0 -2
- ormlambda/components/update/abstract_update.py +0 -29
- ormlambda/components/upsert/__init__.py +0 -2
- ormlambda/components/upsert/abstract_upsert.py +0 -25
- ormlambda/databases/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
- ormlambda/databases/my_sql/repository/__init__.py +0 -1
- ormlambda/databases/my_sql/repository/repository.py +0 -351
- ormlambda/engine/template.py +0 -47
- ormlambda/sql/dtypes.py +0 -94
- ormlambda/utils/__init__.py +0 -1
- ormlambda-3.12.2.dist-info/RECORD +0 -125
- /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
- /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
- /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
- /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
- /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
- /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
- /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
- /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
- /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -1,71 +1,8 @@
|
|
1
|
-
from
|
1
|
+
from mysql.connector import MySQLConnection
|
2
2
|
|
3
|
-
from ormlambda.
|
4
|
-
from ormlambda import Table, Column
|
5
|
-
from ormlambda.repository import IRepositoryBase
|
6
|
-
from ormlambda.caster.caster import Caster
|
7
|
-
from .where import Where
|
8
|
-
from ormlambda.sql.types import ColumnType
|
3
|
+
from ormlambda.sql.clauses import Update
|
9
4
|
|
10
5
|
|
11
|
-
class
|
12
|
-
def __init__(self,
|
13
|
-
super().__init__(*args)
|
14
|
-
self._table: Type[Table] = table
|
15
|
-
self._key: str | ColumnType = key
|
16
|
-
|
17
|
-
def __str__(self):
|
18
|
-
if isinstance(self._key, Column):
|
19
|
-
return f"The column '{self._key.column_name}' does not belong to the table '{self._table.__table_name__}'; it belongs to the table '{self._key.table.__table_name__}'. Please check the columns in the query."
|
20
|
-
return f"The column '{self._key}' does not belong to the table '{self._table.__table_name__}'. Please check the columns in the query."
|
21
|
-
|
22
|
-
|
23
|
-
class UpdateQuery[T: Type[Table]](UpdateQueryBase[T, IRepositoryBase]):
|
24
|
-
def __init__(self, model: T, repository: Any, where: list[Where]) -> None:
|
6
|
+
class Update[T](Update[T, MySQLConnection]):
|
7
|
+
def __init__(self, model, repository, where):
|
25
8
|
super().__init__(model, repository, where)
|
26
|
-
|
27
|
-
@override
|
28
|
-
@property
|
29
|
-
def CLAUSE(self) -> str:
|
30
|
-
return "UPDATE"
|
31
|
-
|
32
|
-
@override
|
33
|
-
def execute(self) -> None:
|
34
|
-
if self._where:
|
35
|
-
for where in self._where:
|
36
|
-
query_with_table = where.query
|
37
|
-
for x in where._comparer:
|
38
|
-
# TODOH []: Refactor this part. We need to get only the columns withouth __table_name__ preffix
|
39
|
-
self._query += " " + query_with_table.replace(x.left_condition.table.__table_name__ + ".", "")
|
40
|
-
return self._repository.execute_with_values(self._query, self._values)
|
41
|
-
|
42
|
-
@override
|
43
|
-
def update[TProp](self, dicc: dict[str | ColumnType[TProp], Any]) -> None:
|
44
|
-
if not isinstance(dicc, dict):
|
45
|
-
raise TypeError
|
46
|
-
|
47
|
-
col_names: list[Column] = []
|
48
|
-
CASTER = Caster(self._repository)
|
49
|
-
for col, value in dicc.items():
|
50
|
-
if isinstance(col, str):
|
51
|
-
if not hasattr(self._model, col):
|
52
|
-
raise UpdateKeyError(self._model, col)
|
53
|
-
col = getattr(self._model, col)
|
54
|
-
if not isinstance(col, Column):
|
55
|
-
raise ValueError
|
56
|
-
|
57
|
-
if self.__is_valid__(col):
|
58
|
-
clean_data = CASTER.for_value(value)
|
59
|
-
col_names.append((col.column_name, clean_data.wildcard_to_insert()))
|
60
|
-
self._values.append(clean_data.to_database)
|
61
|
-
|
62
|
-
set_query: str = ",".join(["=".join(col_data) for col_data in col_names])
|
63
|
-
|
64
|
-
self._query = f"{self.CLAUSE} {self._model.__table_name__} SET {set_query}"
|
65
|
-
self._values = tuple(self._values)
|
66
|
-
return None
|
67
|
-
|
68
|
-
def __is_valid__(self, col: Column) -> bool:
|
69
|
-
if self._model is not col.table:
|
70
|
-
raise UpdateKeyError(self._model, col)
|
71
|
-
return not col.is_auto_generated
|
@@ -1,72 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import override, TYPE_CHECKING
|
3
2
|
|
4
3
|
from ormlambda import Table
|
5
|
-
from ormlambda.
|
6
|
-
from ormlambda.repository import IRepositoryBase, BaseRepository
|
4
|
+
from ormlambda.sql.clauses.upsert import Upsert
|
7
5
|
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from mysql.connector import MySQLConnection
|
13
|
-
|
14
|
-
|
15
|
-
class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
|
16
|
-
def __init__(self, model: T, repository: BaseRepository[MySQLConnection]) -> None:
|
7
|
+
class UpsertQuery[T: Table](Upsert):
|
8
|
+
def __init__(self, model, repository):
|
17
9
|
super().__init__(model, repository)
|
18
|
-
|
19
|
-
@override
|
20
|
-
@property
|
21
|
-
def CLAUSE(self) -> str:
|
22
|
-
return "ON DUPLICATE KEY UPDATE"
|
23
|
-
|
24
|
-
@override
|
25
|
-
def execute(self) -> None:
|
26
|
-
return self._repository.executemany_with_values(self._query, self._values)
|
27
|
-
|
28
|
-
@override
|
29
|
-
def upsert(self, instances: T | list[T]) -> None:
|
30
|
-
"""
|
31
|
-
Esta funcion se enfoca para trabajar con listas, aunque el argumneto changes sea un unico diccionario.
|
32
|
-
|
33
|
-
Accedemos a la primera posicion de la lista 'changes[0]' porque en la query solo estamos poniendo marcadores de posicion, alias y nombres de columnas
|
34
|
-
|
35
|
-
EXAMPLE
|
36
|
-
------
|
37
|
-
|
38
|
-
MySQL
|
39
|
-
-----
|
40
|
-
|
41
|
-
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
42
|
-
VALUES
|
43
|
-
(1,'PABLO'),
|
44
|
-
(2,'MARINA') AS _val
|
45
|
-
ON DUPLICATE KEY UPDATE
|
46
|
-
COL2 = _val.COL2;
|
47
|
-
|
48
|
-
Python
|
49
|
-
-----
|
50
|
-
|
51
|
-
INSERT INTO NAME_TABLE(PK_COL,COL2)
|
52
|
-
VALUES (%s, %s') AS _val
|
53
|
-
ON DUPLICATE KEY UPDATE
|
54
|
-
COL2 = _val.COL2;
|
55
|
-
|
56
|
-
"""
|
57
|
-
insert = InsertQuery[T](self._model, self._repository)
|
58
|
-
insert.insert(instances)
|
59
|
-
|
60
|
-
if isinstance(instances, Table):
|
61
|
-
instances = tuple([instances])
|
62
|
-
ALIAS = "VALUES"
|
63
|
-
|
64
|
-
cols = instances[0].get_columns()
|
65
|
-
pk_key = instances[0].get_pk().column_name
|
66
|
-
|
67
|
-
alternative = ", ".join([f"{col}={ALIAS}({col})" for col in cols if col != pk_key])
|
68
|
-
query = f"{insert._query} {self.CLAUSE} {alternative};"
|
69
|
-
|
70
|
-
self._query = query
|
71
|
-
self._values = insert.values
|
72
|
-
return None
|
@@ -1,45 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
3
|
-
from ormlambda.sql.comparer import Comparer
|
4
|
-
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
2
|
+
from ormlambda.sql.clauses import Where
|
6
3
|
|
7
4
|
|
8
|
-
class Where(
|
9
|
-
|
10
|
-
|
11
|
-
"""
|
12
|
-
|
13
|
-
def __init__(self, *comparer: Comparer, restrictive: bool = True, context: ClauseContextType = None) -> None:
|
14
|
-
self._comparer: tuple[Comparer] = comparer
|
15
|
-
self._restrictive: bool = restrictive
|
16
|
-
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
17
|
-
|
18
|
-
@staticmethod
|
19
|
-
def FUNCTION_NAME() -> str:
|
20
|
-
return "WHERE"
|
21
|
-
|
22
|
-
@property
|
23
|
-
def query(self) -> str:
|
24
|
-
if isinstance(self._comparer, tp.Iterable):
|
25
|
-
context = ClauseInfoContext(table_context=self._context._table_context)
|
26
|
-
comparer = Comparer.join_comparers(self._comparer, restrictive=self._restrictive, context=context)
|
27
|
-
else:
|
28
|
-
comparer = self._comparer
|
29
|
-
return f"{self.FUNCTION_NAME()} {comparer}"
|
30
|
-
|
31
|
-
@property
|
32
|
-
def alias_clause(self) -> None:
|
33
|
-
return None
|
34
|
-
|
35
|
-
@classmethod
|
36
|
-
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext) -> str:
|
37
|
-
if not isinstance(wheres, tp.Iterable):
|
38
|
-
wheres = (wheres,)
|
39
|
-
|
40
|
-
comparers: list[Comparer] = []
|
41
|
-
for where in wheres:
|
42
|
-
for c in where._comparer:
|
43
|
-
c.set_context(context)
|
44
|
-
comparers.append(c)
|
45
|
-
return cls(*comparers, restrictive=restrictive, context=context).query
|
5
|
+
class Where(Where):
|
6
|
+
def __init__(self, *comparer, restrictive=True, context=None):
|
7
|
+
super().__init__(*comparer, restrictive=restrictive, context=context)
|
@@ -0,0 +1,217 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import contextlib
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Generator, Iterable, Optional, Type, override, TYPE_CHECKING, Unpack
|
5
|
+
import uuid
|
6
|
+
|
7
|
+
# from mysql.connector.pooling import MySQLConnectionPool
|
8
|
+
from mysql.connector import MySQLConnection # noqa: F401
|
9
|
+
from mysql.connector.pooling import MySQLConnectionPool # noqa: F401
|
10
|
+
from ormlambda.repository import BaseRepository
|
11
|
+
|
12
|
+
# Custom libraries
|
13
|
+
from .clauses import DropTable
|
14
|
+
from ormlambda.repository.response import Response
|
15
|
+
from ormlambda.caster import Caster
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from ormlambda import URL as _URL
|
19
|
+
from ormlambda.sql.clauses import Select
|
20
|
+
from .pool_types import MySQLArgs
|
21
|
+
|
22
|
+
|
23
|
+
class MySQLRepository(BaseRepository[MySQLConnectionPool]):
|
24
|
+
# def get_connection[**P, TReturn](func: Callable[Concatenate[MySQLRepository, MySQLConnection, P], TReturn]) -> Callable[P, TReturn]:
|
25
|
+
# def wrapper(self: MySQLRepository, *args: P.args, **kwargs: P.kwargs):
|
26
|
+
# with self.get_connection() as cnx:
|
27
|
+
# try:
|
28
|
+
# return func(self, cnx._cnx, *args, **kwargs)
|
29
|
+
# except Exception as e:
|
30
|
+
# cnx._cnx.rollback()
|
31
|
+
# raise e
|
32
|
+
|
33
|
+
# return wrapper
|
34
|
+
|
35
|
+
#
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
/,
|
40
|
+
url: Optional[_URL] = None,
|
41
|
+
*,
|
42
|
+
user: Optional[str] = None,
|
43
|
+
password: Optional[str] = None,
|
44
|
+
host: Optional[str] = None,
|
45
|
+
database: Optional[str] = None,
|
46
|
+
**kwargs: Unpack[MySQLArgs],
|
47
|
+
):
|
48
|
+
timeout = self.__add_connection_timeout(kwargs)
|
49
|
+
name = self.__add_pool_name(kwargs)
|
50
|
+
size = self.__add_pool_size(kwargs)
|
51
|
+
attr = kwargs.copy()
|
52
|
+
attr["connection_timeout"] = timeout
|
53
|
+
attr["pool_name"] = name
|
54
|
+
attr["pool_size"] = size
|
55
|
+
|
56
|
+
super().__init__(
|
57
|
+
user=user if not url else url.username,
|
58
|
+
password=password if not url else url.password,
|
59
|
+
host=host if not url else url.host,
|
60
|
+
database=database if not url else url.database,
|
61
|
+
pool=MySQLConnectionPool,
|
62
|
+
**attr,
|
63
|
+
)
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def __add_connection_timeout(kwargs: MySQLArgs) -> int:
|
67
|
+
if "connection_timeout" not in kwargs.keys():
|
68
|
+
return 60
|
69
|
+
return int(kwargs.pop("connection_timeout"))
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def __add_pool_name(kwargs: MySQLArgs) -> str:
|
73
|
+
if "pool_name" not in kwargs.keys():
|
74
|
+
return str(uuid.uuid4())
|
75
|
+
|
76
|
+
return kwargs.pop("pool_name")
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def __add_pool_size(kwargs: MySQLArgs) -> int:
|
80
|
+
if "pool_size" not in kwargs.keys():
|
81
|
+
return 5
|
82
|
+
return int(kwargs.pop("pool_size"))
|
83
|
+
|
84
|
+
@contextlib.contextmanager
|
85
|
+
def get_connection(self) -> Generator[MySQLConnection, None, None]:
|
86
|
+
with self._pool.get_connection() as cnx:
|
87
|
+
try:
|
88
|
+
yield cnx._cnx
|
89
|
+
cnx._cnx.commit()
|
90
|
+
except Exception as exc:
|
91
|
+
cnx._cnx.rollback()
|
92
|
+
raise exc
|
93
|
+
|
94
|
+
@override
|
95
|
+
def read_sql[TFlavour: Iterable](
|
96
|
+
self,
|
97
|
+
query: str,
|
98
|
+
flavour: tuple | Type[TFlavour] = tuple,
|
99
|
+
**kwargs,
|
100
|
+
) -> tuple[TFlavour]:
|
101
|
+
"""
|
102
|
+
Return tuple of tuples by default.
|
103
|
+
|
104
|
+
ATTRIBUTE
|
105
|
+
-
|
106
|
+
- query:str: string of request to the server
|
107
|
+
- flavour: Type[TFlavour]: Useful to return tuple of any Iterable type as dict,set,list...
|
108
|
+
"""
|
109
|
+
|
110
|
+
select: Select = kwargs.pop("select", None)
|
111
|
+
|
112
|
+
with self.get_connection() as cnx:
|
113
|
+
with cnx.cursor(buffered=True) as cursor:
|
114
|
+
cursor.execute(query)
|
115
|
+
values: list[tuple] = cursor.fetchall()
|
116
|
+
columns: tuple[str] = cursor.column_names
|
117
|
+
return Response(
|
118
|
+
dialect=self._dialect,
|
119
|
+
response_values=values,
|
120
|
+
columns=columns,
|
121
|
+
flavour=flavour,
|
122
|
+
select=select,
|
123
|
+
).response(**kwargs)
|
124
|
+
|
125
|
+
# FIXME [ ]: this method does not comply with the implemented interface
|
126
|
+
def create_tables_code_first(self, path: str | Path) -> None:
|
127
|
+
return
|
128
|
+
from ormlambda.utils.module_tree.dynamic_module import ModuleTree
|
129
|
+
|
130
|
+
if not isinstance(path, Path | str):
|
131
|
+
raise ValueError
|
132
|
+
|
133
|
+
if isinstance(path, str):
|
134
|
+
path = Path(path).resolve()
|
135
|
+
|
136
|
+
if not path.exists():
|
137
|
+
raise FileNotFoundError
|
138
|
+
|
139
|
+
module_tree: ModuleTree = ModuleTree(path)
|
140
|
+
|
141
|
+
queries_list: list[str] = module_tree.get_queries()
|
142
|
+
|
143
|
+
for query in queries_list:
|
144
|
+
with self.get_connection() as cnx:
|
145
|
+
with cnx.cursor(buffered=True) as cursor:
|
146
|
+
cursor.execute(query)
|
147
|
+
return None
|
148
|
+
|
149
|
+
@override
|
150
|
+
def executemany_with_values(self, query: str, values) -> None:
|
151
|
+
with self.get_connection() as cnx:
|
152
|
+
with cnx.cursor(buffered=True) as cursor:
|
153
|
+
cursor.executemany(query, values)
|
154
|
+
return None
|
155
|
+
|
156
|
+
@override
|
157
|
+
def execute_with_values(self, query: str, values) -> None:
|
158
|
+
with self.get_connection() as cnx:
|
159
|
+
with cnx.cursor(buffered=True) as cursor:
|
160
|
+
cursor.execute(query, values)
|
161
|
+
return None
|
162
|
+
|
163
|
+
@override
|
164
|
+
def execute(self, query: str) -> None:
|
165
|
+
with self.get_connection() as cnx:
|
166
|
+
with cnx.cursor(buffered=True) as cursor:
|
167
|
+
cursor.execute(query)
|
168
|
+
return None
|
169
|
+
|
170
|
+
@override
|
171
|
+
def drop_table(self, name: str) -> None:
|
172
|
+
return DropTable(self).execute(name)
|
173
|
+
|
174
|
+
@override
|
175
|
+
def database_exists(self, name: str) -> bool:
|
176
|
+
temp_config = self._pool._cnx_config
|
177
|
+
|
178
|
+
config_without_db = temp_config.copy()
|
179
|
+
|
180
|
+
if "database" in config_without_db:
|
181
|
+
config_without_db.pop("database")
|
182
|
+
self._pool.set_config(**config_without_db)
|
183
|
+
|
184
|
+
with self.get_connection() as cnx:
|
185
|
+
with cnx.cursor(buffered=True) as cursor:
|
186
|
+
cursor.execute(f"SHOW DATABASES LIKE {Caster.PLACEHOLDER};", (name,))
|
187
|
+
res = cursor.fetchmany(1)
|
188
|
+
|
189
|
+
self._pool.set_config(**temp_config)
|
190
|
+
return len(res) > 0
|
191
|
+
|
192
|
+
@override
|
193
|
+
def table_exists(self, name: str) -> bool:
|
194
|
+
with self.get_connection() as cnx:
|
195
|
+
if not cnx.database:
|
196
|
+
raise Exception("No database selected")
|
197
|
+
with cnx.cursor(buffered=True) as cursor:
|
198
|
+
cursor.execute(f"SHOW TABLES LIKE {Caster.PLACEHOLDER};", (name,))
|
199
|
+
res = cursor.fetchmany(1)
|
200
|
+
return len(res) > 0
|
201
|
+
|
202
|
+
@property
|
203
|
+
def database(self) -> Optional[str]:
|
204
|
+
return self._pool._cnx_config.get("database", None)
|
205
|
+
|
206
|
+
@database.setter
|
207
|
+
def database(self, value: str) -> None:
|
208
|
+
"""Change the current database using USE statement"""
|
209
|
+
|
210
|
+
if not self.database_exists(value):
|
211
|
+
raise ValueError(f"You cannot set the non-existent '{value}' database.")
|
212
|
+
|
213
|
+
old_config: MySQLArgs = self._pool._cnx_config.copy()
|
214
|
+
old_config["database"] = value
|
215
|
+
|
216
|
+
self._pool._remove_connections()
|
217
|
+
self._pool = type(self)(**old_config)._pool
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Callable, Optional, Type, TYPE_CHECKING
|
3
|
+
from ormlambda import util
|
4
|
+
import importlib
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from .interface import Dialect
|
8
|
+
|
9
|
+
|
10
|
+
__all__ = ("mysql", "sqlite")
|
11
|
+
|
12
|
+
|
13
|
+
def _auto_fn(name: str) -> Optional[Callable[[], Type[Dialect]]]:
|
14
|
+
"""default dialect importer.
|
15
|
+
|
16
|
+
plugs into the :class:`.PluginLoader`
|
17
|
+
as a first-hit system.
|
18
|
+
|
19
|
+
"""
|
20
|
+
if "." in name:
|
21
|
+
dialect, driver = name.split(".")
|
22
|
+
else:
|
23
|
+
dialect = name
|
24
|
+
driver = "base"
|
25
|
+
|
26
|
+
try:
|
27
|
+
module = importlib.import_module(f"ormlambda.dialects.{dialect}")
|
28
|
+
|
29
|
+
except ImportError:
|
30
|
+
return None
|
31
|
+
|
32
|
+
if hasattr(module, driver):
|
33
|
+
module = getattr(module, driver)
|
34
|
+
return lambda: module.dialect
|
35
|
+
else:
|
36
|
+
return None
|
37
|
+
|
38
|
+
|
39
|
+
registry = util.PluginLoader("ormlambda.dialects", auto_fn=_auto_fn)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .base import DefaultDialect
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from ormlambda.dialects.interface import Dialect
|
2
|
+
from ormlambda.sql import compiler
|
3
|
+
from typing import Optional, Any
|
4
|
+
from types import ModuleType
|
5
|
+
from ormlambda import BaseRepository
|
6
|
+
|
7
|
+
|
8
|
+
class DefaultDialect(Dialect):
|
9
|
+
"""Default implementation of Dialect"""
|
10
|
+
|
11
|
+
statement_compiler = compiler.SQLCompiler
|
12
|
+
ddl_compiler = compiler.DDLCompiler
|
13
|
+
type_compiler_cls = compiler.GenericTypeCompiler
|
14
|
+
repository_cls = BaseRepository
|
15
|
+
default_paramstyle = "named"
|
16
|
+
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
dbapi: Optional[ModuleType] = None, # type: ignore
|
20
|
+
**kwargs: Any,
|
21
|
+
):
|
22
|
+
self.dbapi = dbapi
|
23
|
+
|
24
|
+
if self.dbapi is not None:
|
25
|
+
self.paramstyle = self.dbapi.paramstyle
|
26
|
+
else:
|
27
|
+
self.paramstyle = self.default_paramstyle
|
28
|
+
self.positional = self.paramstyle in (
|
29
|
+
"qmark",
|
30
|
+
"format",
|
31
|
+
"numeric",
|
32
|
+
"numeric_dollar",
|
33
|
+
)
|
34
|
+
|
35
|
+
tt_callable = self.type_compiler_cls
|
36
|
+
|
37
|
+
self.type_compiler_instance = self.type_compiler = tt_callable(self)
|
38
|
+
|
39
|
+
super().__init__(**kwargs)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .dialect import Dialect # noqa: F401
|
@@ -0,0 +1,78 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
from typing import ClassVar, Optional, Type, TYPE_CHECKING
|
4
|
+
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from ormlambda.caster.caster import Caster
|
8
|
+
from ormlambda.repository.interfaces.IRepositoryBase import DBAPIConnection
|
9
|
+
from ormlambda.sql.types import DDLCompiler, SQLCompiler, TypeCompiler
|
10
|
+
from ormlambda import BaseRepository
|
11
|
+
|
12
|
+
|
13
|
+
class Dialect(abc.ABC):
|
14
|
+
"""
|
15
|
+
Abstract base class for all database dialects.
|
16
|
+
"""
|
17
|
+
|
18
|
+
dbapi: Optional[DBAPIConnection]
|
19
|
+
"""A reference to the DBAPI module object itself.
|
20
|
+
|
21
|
+
Ormlambda dialects import DBAPI modules using the classmethod
|
22
|
+
:meth:`.Dialect.import_dbapi`. The rationale is so that any dialect
|
23
|
+
module can be imported and used to generate SQL statements without the
|
24
|
+
need for the actual DBAPI driver to be installed. Only when an
|
25
|
+
:class:`.Engine` is constructed using :func:`.create_engine` does the
|
26
|
+
DBAPI get imported; at that point, the creation process will assign
|
27
|
+
the DBAPI module to this attribute.
|
28
|
+
|
29
|
+
Dialects should therefore implement :meth:`.Dialect.import_dbapi`
|
30
|
+
which will import the necessary module and return it, and then refer
|
31
|
+
to ``self.dbapi`` in dialect code in order to refer to the DBAPI module
|
32
|
+
contents.
|
33
|
+
|
34
|
+
.. versionchanged:: The :attr:`.Dialect.dbapi` attribute is exclusively
|
35
|
+
used as the per-:class:`.Dialect`-instance reference to the DBAPI
|
36
|
+
module. The previous not-fully-documented ``.Dialect.dbapi()``
|
37
|
+
classmethod is deprecated and replaced by :meth:`.Dialect.import_dbapi`.
|
38
|
+
|
39
|
+
"""
|
40
|
+
|
41
|
+
name: ClassVar[str]
|
42
|
+
"""The name of the dialect, e.g. 'sqlite', 'postgresql', etc."""
|
43
|
+
driver: ClassVar[str]
|
44
|
+
"""The driver used by the dialect, e.g. 'sqlite3', 'psycopg2', etc."""
|
45
|
+
|
46
|
+
ddl_compiler: ClassVar[Type[DDLCompiler]]
|
47
|
+
"""The DDL compiler class used by the dialect."""
|
48
|
+
|
49
|
+
statement_compiler: ClassVar[Type[SQLCompiler]]
|
50
|
+
"""The statement compiler class used by the dialect."""
|
51
|
+
|
52
|
+
type_compiler_cls: ClassVar[Type[TypeCompiler]]
|
53
|
+
"""The type compiler class used by the dialect."""
|
54
|
+
|
55
|
+
type_compiler_instance: ClassVar[TypeCompiler]
|
56
|
+
"""The instance of the type compiler class used by the dialect."""
|
57
|
+
|
58
|
+
repository_cls: ClassVar[Type[BaseRepository]]
|
59
|
+
"""The repository class used by the dialect."""
|
60
|
+
|
61
|
+
caster: ClassVar[Type[Caster]]
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def get_dialect_cls(cls) -> Type[Dialect]:
|
65
|
+
return cls
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
@abc.abstractmethod
|
69
|
+
def import_dbapi(cls) -> DBAPIConnection:
|
70
|
+
"""
|
71
|
+
Import the DB API module for the dialect.
|
72
|
+
This method should be implemented by subclasses to import the
|
73
|
+
appropriate DB API module for the dialect.
|
74
|
+
"""
|
75
|
+
...
|
76
|
+
|
77
|
+
def __repr__(self):
|
78
|
+
return f"{Dialect.__name__}: {type(self).__name__}"
|