pypomes-db 0.1.1__tar.gz

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.
@@ -0,0 +1,10 @@
1
+ .idea
2
+ .env
3
+ temp
4
+ tmp
5
+ **/__pycache__
6
+ /dist
7
+ /env
8
+ .vscode
9
+ *.py[cod]
10
+ deploy.txt
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 GT Nunes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: pypomes_db
3
+ Version: 0.1.1
4
+ Summary: A collection of Python pomes, pennyeach (DB modules)
5
+ Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-DB
6
+ Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-DB/issues
7
+ Author-email: GT Nunes <wisecoder01@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: pip>=23.1.2
14
+ Requires-Dist: psycopg2-binary>=2.9.6
15
+ Requires-Dist: pyodbc>=4.0.39
16
+ Requires-Dist: pypomes-core>=0.1.1
17
+ Requires-Dist: setuptools>=68.0.0
18
+ Requires-Dist: wheel>=0.40.0
File without changes
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = [
3
+ "hatchling"
4
+ ]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "pypomes_db"
9
+ version = "0.1.1"
10
+ authors = [
11
+ { name="GT Nunes", email="wisecoder01@gmail.com" },
12
+ ]
13
+ description = "A collection of Python pomes, pennyeach (DB modules)"
14
+ readme = "README.md"
15
+ requires-python = ">=3.10"
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ dependencies = [
22
+ "pip>=23.1.2",
23
+ "psycopg2-binary>=2.9.6",
24
+ "pyodbc>=4.0.39",
25
+ "pypomes_core>=0.1.1",
26
+ "setuptools>=68.0.0",
27
+ "wheel>=0.40.0"
28
+ ]
29
+
30
+ [project.urls]
31
+ "Homepage" = "https://github.com/TheWiseCoder/PyPomes-DB"
32
+ "Bug Tracker" = "https://github.com/TheWiseCoder/PyPomes-DB/issues"
@@ -0,0 +1,15 @@
1
+ from .db_pomes import (
2
+ DB_HOST, DB_PWD, DB_NAME, DB_PORT, DB_USER, DB_DRIVER,
3
+ db_delete, db_insert, db_update, db_connect, db_exists,
4
+ db_select_all, db_select_one, db_row_to_dict, db_exec_stored_procedure
5
+ )
6
+
7
+ __all__ = [
8
+ # db_pomes
9
+ DB_HOST, DB_PWD, DB_NAME, DB_PORT, DB_USER, DB_DRIVER,
10
+ db_delete, db_insert, db_update, db_connect, db_exists,
11
+ db_select_all, db_select_one, db_row_to_dict, db_exec_stored_procedure,
12
+ ]
13
+
14
+ __version__ = "0.1.1"
15
+ __version_info__ = (0, 1, 1)
@@ -0,0 +1,292 @@
1
+ # noinspection PyProtectedMember
2
+ from pyodbc import connect, Connection, Row
3
+ from typing import Final
4
+ from pypomes_core.env_pomes import APP_PREFIX, env_get_int, env_get_str
5
+
6
+ # dados para acesso ao BD
7
+ DB_DRIVER: Final[str] = env_get_str(f"{APP_PREFIX}_DB_DRIVER")
8
+ DB_NAME: Final[str] = env_get_str(f"{APP_PREFIX}_DB_NAME")
9
+ DB_HOST: Final[str] = env_get_str(f"{APP_PREFIX}_DB_HOST")
10
+ DB_PORT: Final[int] = env_get_int(f"{APP_PREFIX}_DB_PORT")
11
+ DB_PWD: Final[str] = env_get_str(f"{APP_PREFIX}_DB_PWD")
12
+ DB_USER: Final[str] = env_get_str(f"{APP_PREFIX}_DB_USER")
13
+
14
+ __CONNECTION_KWARGS: Final[str] = f"DRIVER={{{DB_DRIVER}}};SERVER={DB_HOST},{DB_PORT};" \
15
+ f"DATABASE={DB_NAME};UID={DB_USER};PWD={DB_PWD};TrustServerCertificate=yes;"
16
+
17
+
18
+ def db_connect(errors: list[str]) -> Connection:
19
+ """
20
+ Obtém e retorna uma conexão ao banco de dados, ou *None* se a conexão não pode ser obtida.
21
+
22
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
23
+ :return: a conexão ao banco de dados
24
+ """
25
+ # inicializa a variável de retorno
26
+ result: Connection | None = None
27
+
28
+ # Obtém a conexão com o BD
29
+ try:
30
+ result = connect(__CONNECTION_KWARGS)
31
+ except Exception as e:
32
+ errors.append(__db_except_msg(e))
33
+
34
+ return result
35
+
36
+
37
+ def db_exists(errors: list[str], table: str, where_attrs: list[str], where_vals: tuple) -> bool:
38
+ """
39
+ Determina se a tabela *table* no banco de dados contem pelo menos uma tupla onde *attrs* são iguais a
40
+ *values*, respectivamente. Se mais de um, os atributos são concatenados pelo conector lógico *AND*.
41
+ Retorna *None* se houver erro na consulta ao banco de dados.
42
+
43
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
44
+ :param table: a tabela a ser pesquisada
45
+ :param where_attrs: os atributos para a busca
46
+ :param where_vals: lista de valores a serem atribuídos aos atributos
47
+ :return: True se não houve erro, e se pelo menos uma tupla existir
48
+ """
49
+ sel_stmt: str = f"SELECT * FROM {table}" # noqa
50
+ if len(where_attrs) > 0:
51
+ sel_stmt += " WHERE " + "".join(f"{attr} = ? AND " for attr in where_attrs)[0:-5]
52
+ rec: tuple = db_select_one(errors, sel_stmt, where_vals)
53
+ result: bool = None if len(errors) > 0 else rec is not None
54
+
55
+ return result
56
+
57
+
58
+ def db_select_one(errors: list[str], sel_stmt: str,
59
+ where_vals: tuple, required: bool = False) -> tuple:
60
+ """
61
+ Busca no banco de dados e retorna a primeira tupla que satisfaça o comando de busca *sel_stmt*.
62
+ O comando pode opcionalmente conter critérios de busca, com valores respectivos fornecidos
63
+ em *where_vals*. A lista de valores para um atributo com a cláusula *IN* deve estar contida
64
+ em tupla específica. Na hipótese de erro, ou se a busca resultar vazia, *None* é retornado.
65
+
66
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
67
+ :param sel_stmt: comando SELECT para a busca
68
+ :param where_vals: lista de valores a serem associados aos critérios de busca
69
+ :param required: define se busca vazia deve ser considerada erro
70
+ :return: tupla contendo o resultado da busca, ou None se houve erro ou se a busca resultar vazia
71
+ """
72
+ # inicializa a variável de retorno
73
+ result: tuple | None = None
74
+
75
+ exc: bool = False
76
+ try:
77
+ with connect(__CONNECTION_KWARGS) as conn:
78
+ # obtem o cursor e executa a operação
79
+ with conn.cursor() as cursor:
80
+ sel_stmt = sel_stmt.replace("SELECT", "SELECT TOP 1")
81
+ cursor.execute(sel_stmt, where_vals)
82
+ # obtem a primeira tupla retornada pelo SELECT (None se nenhuma foi retornada)
83
+ result = cursor.fetchone()
84
+ except Exception as e:
85
+ exc = True
86
+ errors.append(__db_except_msg(e))
87
+
88
+ # o parâmetro 'required' foi definido e nenhum registro foi obtido?
89
+ if required and not exc and result is None:
90
+ # sim, reporte o erro
91
+ errors.append(__db_required_msg(sel_stmt, where_vals))
92
+
93
+ return result
94
+
95
+
96
+ def db_exec_stored_procedure(errors: list[str], nm_proced: str, param_vals: tuple) -> list[Row]:
97
+ """
98
+ Executa o stored procedure no banco de dados com os parâmetros informados e retorna os erros que ocorrerem
99
+
100
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
101
+ :param nm_proced: nome do stored procedure
102
+ :param param_vals: lista de valores dos parâmetros
103
+ :return: lista de tuplas contendo o resultado da busca, ou [] se a busca resultar vazia
104
+ """
105
+ # inicializa a variável de retorno
106
+ result: list[Row] = []
107
+
108
+ try:
109
+ with connect(__CONNECTION_KWARGS) as conn:
110
+ # obtém o cursor e executa a operação
111
+ with conn.cursor() as cursor:
112
+ sql = f"SET NOCOUNT ON; EXEC {nm_proced} {','.join(('?',) * len(param_vals))}"
113
+ cursor.execute(sql, param_vals)
114
+ # obtem as tuplas retornadas
115
+ for record in cursor:
116
+ result.append(record)
117
+
118
+ except Exception as e:
119
+ errors.append(__db_except_msg(e))
120
+
121
+ return result
122
+
123
+
124
+ def db_select_all(errors: list[str], sel_stmt: str,
125
+ where_vals: tuple, required: bool = False) -> list[Row]:
126
+ """
127
+ Busca no banco de dados e retorna todas as tuplas que satisfaçam o comando de busca *sele_stmt*.
128
+ O comando pode opcionalmente conter critérios de busca, com valores respectivos fornecidos
129
+ em *where_vals*. A lista de valores para um atributo com a cláusula *IN* deve estar contida
130
+ em tupla específica. Se a busca resultar vazia, uma lista vazia é retornado.
131
+
132
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
133
+ :param sel_stmt: comando SELECT para a busca
134
+ :param where_vals: lista de valores a serem associados aos critérios de busca
135
+ :param required: define se busca vazia deve ser considerada erro
136
+ :return: lista de tuplas contendo o resultado da busca, ou [] se a busca resultar vazia
137
+ """
138
+ # inicializa a variável de retorno
139
+ result: list[Row] = []
140
+
141
+ exc: bool = False
142
+ try:
143
+ with connect(__CONNECTION_KWARGS) as conn:
144
+ # obtem o cursor e executa a operação
145
+ with conn.cursor() as cursor:
146
+ cursor.execute(sel_stmt, where_vals)
147
+ # obtem as tuplas retornadas
148
+ for record in cursor:
149
+ result.append(record)
150
+ except Exception as e:
151
+ exc = True
152
+ errors.append(__db_except_msg(e))
153
+
154
+ # o parâmetro 'required' foi definido, não houve erros, e nenhum registro foi obtido ?
155
+ if required and not exc and len(result) == 0:
156
+ # sim, reporte o erro
157
+ errors.append(__db_required_msg(sel_stmt, where_vals))
158
+
159
+ return result
160
+
161
+
162
+ def db_insert(errors: list[str], insert_stmt: str, insert_vals: tuple) -> int:
163
+ """
164
+ Insere no banco de dados uma tupla com valores definidos em *insert_vals*.
165
+ Retorna o id da tupla inserida.
166
+
167
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
168
+ :param insert_stmt: comando INSERT
169
+ :param insert_vals: lista de valores a serem inseridos
170
+ :return: o id da tupla inserida, ou None em caso de erro
171
+ """
172
+ result: int | None = None
173
+ try:
174
+ with connect(__CONNECTION_KWARGS) as conn:
175
+ with conn.cursor() as cursor:
176
+ insert_stmt = insert_stmt.replace("VALUES", "OUTPUT INSERTED.id VALUES")
177
+ cursor.execute(insert_stmt, insert_vals)
178
+ result = cursor.fetchone()[0]
179
+ except Exception as e:
180
+ errors.append(str(e))
181
+
182
+ return result
183
+
184
+
185
+ def db_update(errors: list[str], update_stmt: str,
186
+ update_vals: tuple, where_vals: tuple) -> int:
187
+ """
188
+ Atualiza uma ou mais tuplas no banco de dados, segundo as definições do comando
189
+ *update_stmt*. Os valores para essa atualização estão em *update_vals*.
190
+ Os valores para a seleção das tuplas a serem atualizadas estão em *where_vals*.
191
+ Retorna o número de tuplas modificadas.
192
+
193
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
194
+ :param update_stmt: comando UPDATE
195
+ :param update_vals: lista de valores para a atualização
196
+ :param where_vals: lista de valores para os critérios de seleção de tuplas
197
+ :return: o número de tuplas atualizadas
198
+ """
199
+ values: tuple = update_vals + where_vals
200
+ return __db_modify(errors, update_stmt, values)
201
+
202
+
203
+ def db_delete(errors: list[str], delete_stmt: str, where_vals: tuple) -> int:
204
+ """
205
+ Exclui uma ou mais tuplas no banco de dados, segundo as definições do comando *delete_stmt*.
206
+ Os valores para a seleção das tuplas a serem excluídas estão em *where_vals*.
207
+
208
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
209
+ :param delete_stmt: comando DELETE
210
+ :param where_vals: lista de valores para os critérios de seleção de tuplas
211
+ :return: o número de tuplas excluídas
212
+ """
213
+ return __db_modify(errors, delete_stmt, where_vals)
214
+
215
+
216
+ def db_row_to_dict(row):
217
+ """
218
+ Converts a pyodbc.Row object to a dictionary.
219
+
220
+ :param row: A pyodbc.Row object.
221
+ :return: A dictionary where keys are column names and values are the corresponding values in the row.
222
+ """
223
+ return {description[0]: row[i] for i, description in enumerate(row.cursor_description)}
224
+
225
+
226
+ def __db_modify(errors: list[str], modify_stmt: str, bind_vals: tuple) -> int:
227
+ """
228
+ Modifica o banco de dados, inserindo, atualizando ou excluindo tuplas, segundo as
229
+ definições do comando *modify_stmt*. Os valores para essa modificação, seguidos dos
230
+ valores para a seleção das tuplas, estão em *bind_vals*.
231
+
232
+ :param errors: lista a ser apensada com mensagem apropriada, em caso de erro
233
+ :param modify_stmt: comando INSERT, UPDATE ou DELETE
234
+ :param bind_vals: lista de valores para modificação e seleção de tuplas
235
+ :return: o número de tuplas inseridas, atualizadas ou excluídas, ou None em caso de erro
236
+ """
237
+ result: int | None = None
238
+
239
+ try:
240
+ with connect(__CONNECTION_KWARGS) as conn:
241
+ # obtem o cursor e executa a operação
242
+ with conn.cursor() as cursor:
243
+ cursor.execute(modify_stmt, bind_vals)
244
+ result = cursor.rowcount
245
+ conn.commit()
246
+ except Exception as e:
247
+ errors.append(__db_except_msg(e))
248
+
249
+ return result
250
+
251
+
252
+ # TODO
253
+ def __db_except_msg(exception: Exception) -> str:
254
+ """
255
+ Formata e retorna a mensagem de erro correspondente à exceção levantada no acesso
256
+ ao banco de dados.
257
+
258
+ :param exception: A exceção levantada
259
+ :return: A mensagem de erro formatada
260
+ """
261
+ exc_msg: str = f"{exception}"
262
+ exc_msg = exc_msg.replace('"', "'") \
263
+ .replace('\n', " ") \
264
+ .replace('\t', " ") \
265
+ .replace("\\", "")
266
+ result = f"Error accessing {DB_NAME} at {DB_HOST}: {exc_msg}"
267
+
268
+ return result
269
+
270
+
271
+ def __db_required_msg(sel_stmt: str, where_vals: tuple) -> str:
272
+ """
273
+ Formata e retorna a mensagem indicativa de busca vazia.
274
+
275
+ :param sel_stmt: O comando de busca utilizado
276
+ :param where_vals: a lista de valores constituindo os critérios de busca
277
+ :return: mensagem indicativa de busca vazia
278
+ """
279
+ stmt: str = sel_stmt.replace('"', "'") \
280
+ .replace('\n', " ") \
281
+ .replace('\t', " ") \
282
+ .replace("\\", "")
283
+ result: str = f"No record found in {DB_NAME} at {DB_HOST}, for {stmt}"
284
+
285
+ for val in where_vals:
286
+ if isinstance(val, str):
287
+ val = f"'{val}'"
288
+ else:
289
+ val = str(val)
290
+ result = result.replace("%s", val, 1)
291
+
292
+ return result