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.
- pypomes_db-0.1.1/.gitignore +10 -0
- pypomes_db-0.1.1/LICENSE +21 -0
- pypomes_db-0.1.1/PKG-INFO +18 -0
- pypomes_db-0.1.1/README.md +0 -0
- pypomes_db-0.1.1/pyproject.toml +32 -0
- pypomes_db-0.1.1/src/pypomes_db/__init__.py +15 -0
- pypomes_db-0.1.1/src/pypomes_db/db_pomes.py +292 -0
pypomes_db-0.1.1/LICENSE
ADDED
|
@@ -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
|