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.
Files changed (145) hide show
  1. ormlambda/__init__.py +2 -0
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
  5. ormlambda/common/abstract_classes/decomposition_query.py +17 -2
  6. ormlambda/common/abstract_classes/non_query_base.py +9 -7
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  10. ormlambda/databases/__init__.py +0 -1
  11. ormlambda/databases/my_sql/__init__.py +0 -1
  12. ormlambda/databases/my_sql/caster/caster.py +23 -19
  13. ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
  14. ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
  15. ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
  16. ormlambda/databases/my_sql/caster/types/date.py +34 -0
  17. ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
  18. ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
  19. ormlambda/databases/my_sql/caster/types/float.py +7 -7
  20. ormlambda/databases/my_sql/caster/types/int.py +7 -7
  21. ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
  22. ormlambda/databases/my_sql/caster/types/none.py +8 -7
  23. ormlambda/databases/my_sql/caster/types/point.py +4 -4
  24. ormlambda/databases/my_sql/caster/types/string.py +7 -7
  25. ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
  26. ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
  27. ormlambda/databases/my_sql/clauses/__init__.py +4 -10
  28. ormlambda/databases/my_sql/clauses/count.py +5 -15
  29. ormlambda/databases/my_sql/clauses/delete.py +3 -50
  30. ormlambda/databases/my_sql/clauses/group_by.py +3 -16
  31. ormlambda/databases/my_sql/clauses/having.py +2 -6
  32. ormlambda/databases/my_sql/clauses/insert.py +4 -92
  33. ormlambda/databases/my_sql/clauses/joins.py +5 -140
  34. ormlambda/databases/my_sql/clauses/limit.py +4 -15
  35. ormlambda/databases/my_sql/clauses/offset.py +4 -15
  36. ormlambda/databases/my_sql/clauses/order.py +4 -61
  37. ormlambda/databases/my_sql/clauses/update.py +4 -67
  38. ormlambda/databases/my_sql/clauses/upsert.py +3 -66
  39. ormlambda/databases/my_sql/clauses/where.py +4 -42
  40. ormlambda/databases/my_sql/repository.py +217 -0
  41. ormlambda/dialects/__init__.py +39 -0
  42. ormlambda/dialects/default/__init__.py +1 -0
  43. ormlambda/dialects/default/base.py +39 -0
  44. ormlambda/dialects/interface/__init__.py +1 -0
  45. ormlambda/dialects/interface/dialect.py +78 -0
  46. ormlambda/dialects/mysql/__init__.py +8 -0
  47. ormlambda/dialects/mysql/base.py +387 -0
  48. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  49. ormlambda/dialects/mysql/types.py +732 -0
  50. ormlambda/dialects/sqlite/__init__.py +5 -0
  51. ormlambda/dialects/sqlite/base.py +47 -0
  52. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  53. ormlambda/engine/__init__.py +1 -0
  54. ormlambda/engine/base.py +58 -0
  55. ormlambda/engine/create.py +9 -23
  56. ormlambda/engine/url.py +31 -19
  57. ormlambda/env.py +30 -0
  58. ormlambda/errors.py +17 -0
  59. ormlambda/model/base_model.py +7 -9
  60. ormlambda/repository/base_repository.py +36 -5
  61. ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
  62. ormlambda/repository/response.py +134 -0
  63. ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
  64. ormlambda/sql/clause_info/clause_info.py +34 -17
  65. ormlambda/sql/clauses/__init__.py +14 -0
  66. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  67. ormlambda/sql/clauses/count.py +57 -0
  68. ormlambda/sql/clauses/delete.py +71 -0
  69. ormlambda/sql/clauses/group_by.py +30 -0
  70. ormlambda/sql/clauses/having.py +21 -0
  71. ormlambda/sql/clauses/insert.py +104 -0
  72. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  73. ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
  74. ormlambda/sql/clauses/joins.py +159 -0
  75. ormlambda/sql/clauses/limit.py +15 -0
  76. ormlambda/sql/clauses/offset.py +15 -0
  77. ormlambda/sql/clauses/order.py +55 -0
  78. ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
  79. ormlambda/sql/clauses/update.py +84 -0
  80. ormlambda/sql/clauses/upsert.py +77 -0
  81. ormlambda/sql/clauses/where.py +65 -0
  82. ormlambda/sql/column/__init__.py +1 -0
  83. ormlambda/sql/{column.py → column/column.py} +82 -22
  84. ormlambda/sql/comparer.py +51 -37
  85. ormlambda/sql/compiler.py +427 -0
  86. ormlambda/sql/ddl.py +68 -0
  87. ormlambda/sql/elements.py +36 -0
  88. ormlambda/sql/foreign_key.py +43 -39
  89. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  90. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  91. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  92. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  93. ormlambda/sql/sqltypes.py +647 -0
  94. ormlambda/sql/table/__init__.py +1 -1
  95. ormlambda/sql/table/table.py +179 -0
  96. ormlambda/sql/table/table_constructor.py +1 -208
  97. ormlambda/sql/type_api.py +35 -0
  98. ormlambda/sql/types.py +3 -1
  99. ormlambda/sql/visitors.py +74 -0
  100. ormlambda/statements/__init__.py +1 -0
  101. ormlambda/statements/base_statement.py +28 -38
  102. ormlambda/statements/interfaces/IStatements.py +5 -4
  103. ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
  104. ormlambda/{databases/my_sql → statements}/statements.py +50 -60
  105. ormlambda/statements/types.py +2 -2
  106. ormlambda/types/__init__.py +24 -0
  107. ormlambda/types/metadata.py +42 -0
  108. ormlambda/util/__init__.py +88 -0
  109. ormlambda/util/load_module.py +21 -0
  110. ormlambda/util/plugin_loader.py +32 -0
  111. ormlambda/util/typing.py +6 -0
  112. ormlambda-3.34.1.dist-info/AUTHORS +32 -0
  113. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
  114. ormlambda-3.34.1.dist-info/RECORD +157 -0
  115. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
  116. ormlambda/components/__init__.py +0 -4
  117. ormlambda/components/delete/__init__.py +0 -2
  118. ormlambda/components/delete/abstract_delete.py +0 -17
  119. ormlambda/components/insert/__init__.py +0 -2
  120. ormlambda/components/insert/abstract_insert.py +0 -25
  121. ormlambda/components/select/__init__.py +0 -1
  122. ormlambda/components/update/__init__.py +0 -2
  123. ormlambda/components/update/abstract_update.py +0 -29
  124. ormlambda/components/upsert/__init__.py +0 -2
  125. ormlambda/components/upsert/abstract_upsert.py +0 -25
  126. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  127. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  128. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  129. ormlambda/databases/my_sql/repository/repository.py +0 -351
  130. ormlambda/engine/template.py +0 -47
  131. ormlambda/sql/dtypes.py +0 -94
  132. ormlambda/utils/__init__.py +0 -1
  133. ormlambda-3.12.2.dist-info/RECORD +0 -125
  134. /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
  135. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  136. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  137. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  138. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  139. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  140. /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
  141. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  142. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  143. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  144. /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
  145. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -1,71 +1,8 @@
1
- from typing import Type, override, Any
1
+ from mysql.connector import MySQLConnection
2
2
 
3
- from ormlambda.components.update import UpdateQueryBase
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 UpdateKeyError(KeyError):
12
- def __init__(self, table: Type[Table], key: str | ColumnType, *args):
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.components.upsert import UpsertQueryBase
6
- from ormlambda.repository import IRepositoryBase, BaseRepository
4
+ from ormlambda.sql.clauses.upsert import Upsert
7
5
 
8
6
 
9
- from .insert import InsertQuery
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
- import typing as tp
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(AggregateFunctionBase):
9
- """
10
- The purpose of this class is to create 'WHERE' condition queries properly.
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__}"
@@ -0,0 +1,8 @@
1
+ from . import base
2
+ from . import mysqlconnector
3
+
4
+ # default dialect
5
+ base.dialect = dialect = mysqlconnector.dialect
6
+
7
+
8
+ from .types import * # noqa: F403