dapper-sqls 1.1.3__py3-none-any.whl → 1.2.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.
dapper_sqls/__init__.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from .dapper import Dapper
2
2
  from .async_dapper import AsyncDapper
3
- from .models import TableBaseModel
3
+ from .models import (TableBaseModel, SensitiveFields, StringQueryField, NumericQueryField, BoolQueryField, DateQueryField, BytesQueryField,
4
+ BaseJoinConditionField, JoinNumericCondition, JoinStringCondition, JoinBooleanCondition, JoinDateCondition, JoinBytesCondition)
5
+ from ._types import SQL_NULL, SqlErrorType
4
6
  from .builders import ModelBuilder, StpBuilder, AsyncStpBuilder
5
7
 
6
8
 
dapper_sqls/_types.py CHANGED
@@ -1,15 +1,38 @@
1
1
  from typing import TypeVar
2
2
  from enum import Enum
3
3
 
4
-
4
+ SQL_NULL = "SQL_NULL"
5
5
  T = TypeVar('T')
6
6
 
7
7
  class ExecType(Enum):
8
+ count = "count"
8
9
  send = "send"
9
10
  fetchone = "fetchone"
10
11
  fetchall = "fetchall"
11
12
 
12
-
13
+ class SqlErrorType(str, Enum):
14
+ UNIQUE_VIOLATION = "UniqueConstraintViolation"
15
+ FOREIGN_KEY_VIOLATION = "ForeignKeyViolation"
16
+ CHECK_CONSTRAINT_VIOLATION = "CheckConstraintViolation"
17
+ PERMISSION_DENIED = "PermissionDenied"
18
+ SYNTAX_ERROR = "SyntaxError"
19
+ TIMEOUT = "Timeout"
20
+ CONNECTION_ERROR = "SqlConnectionError"
21
+ UNKNOWN = "UnknownError"
22
+
23
+ SQL_ERROR_HTTP_CODES = {
24
+ SqlErrorType.UNIQUE_VIOLATION: 409, # Conflict
25
+ SqlErrorType.FOREIGN_KEY_VIOLATION: 409, # Conflict
26
+ SqlErrorType.CHECK_CONSTRAINT_VIOLATION: 400, # Bad Request
27
+ SqlErrorType.PERMISSION_DENIED: 403, # Forbidden
28
+ SqlErrorType.SYNTAX_ERROR: 400, # Bad Request
29
+ SqlErrorType.TIMEOUT: 504, # Gateway Timeout
30
+ SqlErrorType.CONNECTION_ERROR: 503, # Service Unavailable
31
+ SqlErrorType.UNKNOWN: 500 # Internal Server Error
32
+ }
33
+
34
+
35
+
13
36
 
14
37
 
15
38
 
@@ -3,17 +3,18 @@ import aioodbc, asyncio
3
3
  from typing import overload
4
4
  from datetime import datetime
5
5
  from abc import ABC, abstractmethod
6
- from ..models import ConnectionStringData, Result, UnavailableServiceException, BaseUpdate
6
+ from ..models import ConnectionStringData, Result, UnavailableServiceException, BaseUpdate, SearchTable, JoinSearchTable
7
7
  from .._types import T, ExecType
8
- from ..config import Config
9
8
  from ..builders import QueryBuilder, StoredBuilder
10
9
  from ..utils import Utils
10
+ import pyodbc
11
11
 
12
12
  class AsyncBaseExecutor(ABC):
13
13
  def __init__(self, connectionStringData : ConnectionStringData, attempts : int, wait_timeout : int, sql_version : int | None, api_environment : bool):
14
14
  self._connectionStringData = connectionStringData
15
15
  self._cursor = None
16
16
  self._connection = None
17
+ self._connection_error : Exception = None
17
18
  self._wait_timeout = wait_timeout
18
19
  self._attempts = attempts
19
20
  self._sql_version = sql_version
@@ -24,19 +25,19 @@ class AsyncBaseExecutor(ABC):
24
25
  for n in range(self._attempts):
25
26
  odbc_version = f'ODBC Driver {self._sql_version} for SQL Server'
26
27
  if not self._sql_version:
27
- all_driver_versions = Config.get_all_odbc_driver_versions()
28
- if all_driver_versions:
29
- odbc_version = all_driver_versions[0]
30
- else:
31
- raise RuntimeError("Nenhuma vers�o do driver ODBC for SQL Server foi encontrada.")
28
+ drivers = [d for d in pyodbc.drivers() if 'SQL Server' in d]
29
+ if not drivers:
30
+ raise RuntimeError("Nenhum driver ODBC do SQL Server encontrado. Instale o ODBC Driver 17 ou 18.")
31
+ odbc_version = drivers[-1]
32
32
  try:
33
33
  connection_string = f'DRIVER={{{odbc_version}}};SERVER={cs_data.server};DATABASE={cs_data.database};UID={cs_data.username};PWD={cs_data.password}'
34
34
  self._connection = await aioodbc.connect(dsn=connection_string)
35
35
  self._cursor = await self._connection.cursor()
36
36
 
37
37
  except Exception as e:
38
+ self._connection_error = e
38
39
  print(e)
39
- print(f'Erro na conex�o com a base de dados, nova tentativa em {self._wait_timeout}s')
40
+ print(f'Erro na conexão com a base de dados, nova tentativa em {self._wait_timeout}s')
40
41
  await asyncio.sleep(self._wait_timeout)
41
42
 
42
43
  return self
@@ -80,7 +81,7 @@ class AsyncBaseExecutor(ABC):
80
81
  if isinstance(value, int):
81
82
  self._attempts = value
82
83
  else:
83
- raise ValueError("O n�mero de tentativas deve ser um n�mero inteiro.")
84
+ raise ValueError("O número de tentativas deve ser um número inteiro.")
84
85
 
85
86
  @property
86
87
  def wait_timeout(self):
@@ -91,7 +92,7 @@ class AsyncBaseExecutor(ABC):
91
92
  if isinstance(value, int):
92
93
  self._wait_timeout = value
93
94
  else:
94
- raise ValueError("O tempo de espera deve ser um n�mero inteiro.")
95
+ raise ValueError("O tempo de espera deve ser um número inteiro.")
95
96
 
96
97
  @abstractmethod
97
98
  async def _exec_(self, connection, operation_sql, exec_type):
@@ -119,22 +120,53 @@ class AsyncQuery(AsyncBaseExecutor):
119
120
  def __init__(self, connectionStringData : ConnectionStringData, attempts : int, wait_timeout : int, sql_version : int | None, api_environment : bool):
120
121
  super().__init__(connectionStringData, attempts, wait_timeout, sql_version, api_environment)
121
122
 
123
+ @overload
124
+ async def count(self, query : str) -> Result.Count:
125
+ pass
126
+
127
+ @overload
128
+ async def count(self, model : T, additional_sql : str = "", select_top : int = None) -> Result.Count:
129
+ pass
130
+
131
+ async def count(self, *args, **kwargs) -> T | Result.Count:
132
+ args = Utils.args_query(*args, **kwargs)
133
+ if args.model:
134
+ args.query = QueryBuilder.select(args.model, args.additional_sql, args.select_top)
135
+
136
+ count_query = f"""
137
+ SELECT COUNT(*) AS Count FROM (
138
+ {args.query}
139
+ ) AS count_subquery
140
+ """
141
+
142
+ result : Result.Fetchone = await self._exec_(self._connection, count_query, ExecType.fetchone)
143
+ if result.success:
144
+ return Result.Count(count_query, result.dict.get('Count', 0), result.status_code, result.error)
145
+ return Result.Count(count_query, 0, result.status_code, result.error)
146
+
122
147
  @overload
123
148
  async def fetchone(self, query : str) -> Result.Fetchone:
124
149
  pass
125
150
 
126
151
  @overload
127
- async def fetchone(self, model : T, additional_sql : str = "") -> T:
152
+ async def fetchone(self, model : T, additional_sql : str = "") -> Result.FetchoneModel[T]:
128
153
  pass
129
154
 
130
- async def fetchone(self, *args, **kwargs) -> T | Result.Fetchone:
155
+ async def fetchone(self, *args, **kwargs) -> Result.FetchoneModel[T] | Result.Fetchone:
131
156
  args = Utils.args_query(*args, **kwargs)
132
157
  if args.model:
133
158
  args.query = QueryBuilder.select(args.model, args.additional_sql, args.select_top)
134
159
 
135
160
  result = await self._exec_(self._connection, args.query, ExecType.fetchone)
136
161
  if args.model:
137
- return args.model.__class__(**result.dict) if result.success else args.model.__class__()
162
+ model_instance = args.model.__class__(**result.dict) if result.success else args.model.__class__()
163
+ return Result.FetchoneModel(model_instance, result)
164
+ return result
165
+
166
+ async def fetchone_with_joins(self, main_search: SearchTable, joins: list[JoinSearchTable] = [], additional_sql: str = "", select_top: int = None) -> Result.Fetchone:
167
+ query = QueryBuilder.select_with_joins(main_search, joins, additional_sql, select_top)
168
+ result = await self._exec_(self._connection, query, ExecType.fetchone)
169
+ result._organize_joined_tables(joins)
138
170
  return result
139
171
 
140
172
  @overload
@@ -142,17 +174,24 @@ class AsyncQuery(AsyncBaseExecutor):
142
174
  pass
143
175
 
144
176
  @overload
145
- async def fetchall(self, model : T, additional_sql : str = "", select_top : int = None) -> list[T]:
177
+ async def fetchall(self, model : T, additional_sql : str = "", select_top : int = None) -> Result.FetchallModel[T]:
146
178
  pass
147
179
 
148
- async def fetchall(self, *args, **kwargs) -> list[T] | Result.Fetchall:
180
+ async def fetchall(self, *args, **kwargs) -> Result.FetchallModel[T] | Result.Fetchall:
149
181
  args = Utils.args_query(*args, **kwargs)
150
182
  if args.model:
151
183
  args.query = QueryBuilder.select(args.model, args.additional_sql, args.select_top)
152
184
 
153
185
  result = await self._exec_(self._connection, args.query, ExecType.fetchall)
154
186
  if args.model:
155
- return [args.model.__class__(**r) for r in result.list_dict] if result.success else []
187
+ models = [args.model.__class__(**r) for r in result.list_dict] if result.success else []
188
+ return Result.FetchallModel(models, result)
189
+ return result
190
+
191
+ async def fetchall_with_joins(self, main_search: SearchTable, joins: list[JoinSearchTable] = [], additional_sql: str = "", select_top: int = None) -> Result.Fetchall:
192
+ query = QueryBuilder.select_with_joins(main_search, joins, additional_sql, select_top)
193
+ result = await self._exec_(self._connection, query, ExecType.fetchall)
194
+ result._organize_joined_tables(joins)
156
195
  return result
157
196
 
158
197
  async def execute(self, query : str) -> Result.Send:
@@ -173,8 +212,8 @@ class AsyncQuery(AsyncBaseExecutor):
173
212
  query = QueryBuilder.insert(model, name_column_id)
174
213
  result : Result.Fetchone = await self._exec_(self._connection, query, ExecType.fetchone)
175
214
  if result.success:
176
- return Result.Insert(result.dict.get('Id', 0), result.status_code, result.message)
177
- return Result.Insert(0, result.status_code, result.message)
215
+ return Result.Insert(query, result.dict.get('Id', 0), result.status_code, result.error)
216
+ return Result.Insert(query, 0, result.status_code, result.error)
178
217
 
179
218
  async def _exec_(self, connection, query_sql : str, exec_type : ExecType):
180
219
 
@@ -183,26 +222,35 @@ class AsyncQuery(AsyncBaseExecutor):
183
222
  raise UnavailableServiceException()
184
223
 
185
224
  if exec_type == ExecType.fetchone:
186
- return Result.Fetchone(None, None, 503)
225
+ return Result.Fetchone(query_sql, None, None, self._connection_error)
187
226
  elif exec_type == ExecType.fetchall:
188
- return Result.Fetchall(None, None, 503)
227
+ return Result.Fetchall(query_sql, None, None, self._connection_error)
189
228
  elif exec_type == ExecType.send:
190
- return Result.Send(False, 503)
229
+ return Result.Send(query_sql, False, self._connection_error)
191
230
 
192
- # executar
193
- response = await self._cursor.execute(query_sql)
231
+ try:
232
+ # executar
233
+ response = await self._cursor.execute(query_sql)
234
+
235
+ # ober resultado se nessesario
236
+ if exec_type == ExecType.fetchone:
237
+ result = Result.Fetchone(query_sql, self._cursor, await response.fetchone())
194
238
 
195
- # ober resultado se nessesario
196
- if exec_type == ExecType.fetchone:
197
- result = Result.Fetchone(self._cursor, await response.fetchone())
239
+ elif exec_type == ExecType.fetchall:
240
+ result = Result.Fetchall(query_sql, self._cursor, await response.fetchall())
241
+ elif exec_type == ExecType.send:
242
+ result = Result.Send(query_sql, True)
198
243
 
199
- elif exec_type == ExecType.fetchall:
200
- result = Result.Fetchall(self._cursor, await response.fetchall())
201
- elif exec_type == ExecType.send:
202
- result = Result.Send(True)
244
+ # fazer o commit
245
+ await connection.commit()
203
246
 
204
- # fazer o commit
205
- await connection.commit()
247
+ except Exception as ex:
248
+ if exec_type == ExecType.fetchone:
249
+ return Result.Fetchone(query_sql, None, None, ex)
250
+ elif exec_type == ExecType.fetchall:
251
+ return Result.Fetchall(query_sql, None, None, ex)
252
+ elif exec_type == ExecType.send:
253
+ return Result.Send(query_sql, False, ex)
206
254
 
207
255
  # retorna o resultado
208
256
  return result
@@ -222,6 +270,30 @@ class AsyncStored(AsyncBaseExecutor):
222
270
  def __init__(self, connectionStringData : ConnectionStringData, attempts : int, wait_timeout : int,sql_version : int | None, api_environment : bool):
223
271
  super().__init__(connectionStringData, attempts, wait_timeout, sql_version, api_environment)
224
272
 
273
+ @overload
274
+ async def count(self, query : str) -> Result.Count:
275
+ pass
276
+
277
+ @overload
278
+ async def count(self, model : T, additional_sql : str = "", select_top : int = None) -> Result.Count:
279
+ pass
280
+
281
+ async def count(self, *args, **kwargs) -> T | Result.Count:
282
+ args = Utils.args_query(*args, **kwargs)
283
+ if args.model:
284
+ args.query = StoredBuilder.select(args.model, args.additional_sql, args.select_top)
285
+
286
+ count_query = f"""
287
+ SELECT COUNT(*) AS Count FROM (
288
+ {args.query}
289
+ ) AS count_subquery
290
+ """
291
+
292
+ result : Result.Fetchone = await self._exec_(self._connection, count_query, ExecType.fetchone)
293
+ if result.success:
294
+ return Result.Count(count_query, result.dict.get('Count', 0), result.status_code, result.error)
295
+ return Result.Count(count_query, 0, result.status_code, result.error)
296
+
225
297
  @overload
226
298
  async def fetchone(self, query : str, params : list | tuple) -> Result.Fetchone:
227
299
  pass
@@ -231,17 +303,18 @@ class AsyncStored(AsyncBaseExecutor):
231
303
  pass
232
304
 
233
305
  @overload
234
- async def fetchone(self, model : T, additional_sql : str = "") -> T:
306
+ async def fetchone(self, model : T, additional_sql : str = "") -> Result.FetchoneModel[T]:
235
307
  pass
236
308
 
237
- async def fetchone(self, *args, **kwargs) -> T | Result.Fetchone:
309
+ async def fetchone(self, *args, **kwargs) -> Result.FetchoneModel[T] | Result.Fetchone:
238
310
  args = Utils.args_stored(*args, **kwargs)
239
311
  if args.model:
240
312
  args.query, args.params = StoredBuilder.select(args.model, args.additional_sql, args.select_top)
241
313
 
242
- result = await self._exec_(self._connection, (args.query, *args.params), ExecType.fetchone)
314
+ result : Result.Fetchone = await self._exec_(self._connection, (args.query, *args.params), ExecType.fetchone)
243
315
  if args.model:
244
- return args.model.__class__(**result.dict) if result.success else args.model.__class__()
316
+ model_instance = args.model.__class__(**result.dict) if result.success else args.model.__class__()
317
+ return Result.FetchoneModel(model_instance, result)
245
318
  return result
246
319
 
247
320
  @overload
@@ -253,17 +326,18 @@ class AsyncStored(AsyncBaseExecutor):
253
326
  pass
254
327
 
255
328
  @overload
256
- async def fetchall(self, model : T, additional_sql : str = "", select_top : int = None) -> list[T]:
329
+ async def fetchall(self, model : T, additional_sql : str = "", select_top : int = None) -> Result.FetchallModel[T]:
257
330
  pass
258
331
 
259
- async def fetchall(self, *args, **kwargs) -> list[T] | Result.Fetchall:
332
+ async def fetchall(self, *args, **kwargs) -> Result.FetchallModel[T] | Result.Fetchall:
260
333
  args = Utils.args_stored(*args, **kwargs)
261
334
  if args.model:
262
335
  args.query, args.params = StoredBuilder.select(args.model, args.additional_sql, args.select_top)
263
336
 
264
- result = await self._exec_(self._connection, (args.query, *args.params), ExecType.fetchall)
337
+ result : Result.Fetchall = await self._exec_(self._connection, (args.query, *args.params), ExecType.fetchall)
265
338
  if args.model:
266
- return [args.model.__class__(**r) for r in result.list_dict] if result.success else []
339
+ models = [args.model.__class__(**r) for r in result.list_dict] if result.success else []
340
+ return Result.FetchallModel(models, result)
267
341
  return result
268
342
 
269
343
  @overload
@@ -294,10 +368,11 @@ class AsyncStored(AsyncBaseExecutor):
294
368
  name_column_id = next(iter(insert_data.keys()))
295
369
 
296
370
  query, params = StoredBuilder.insert(model, name_column_id)
297
- result : Result.Fetchone = await self._exec_(self._connection, (query, *params), ExecType.fetchone)
371
+ stored_procedure = (query, *params)
372
+ result : Result.Fetchone = await self._exec_(self._connection, stored_procedure, ExecType.fetchone)
298
373
  if result.success:
299
- return Result.Insert(result.dict.get('Id', 0), result.status_code, result.message)
300
- return Result.Insert(0, result.status_code, result.message)
374
+ return Result.Insert(stored_procedure, result.dict.get('Id', 0), result.status_code, result.error)
375
+ return Result.Insert(stored_procedure, 0, result.status_code, result.error)
301
376
 
302
377
  async def _exec_(self, connection , stored_procedure : tuple, exec_type : ExecType):
303
378
 
@@ -306,11 +381,11 @@ class AsyncStored(AsyncBaseExecutor):
306
381
  raise UnavailableServiceException()
307
382
 
308
383
  if exec_type == ExecType.fetchone:
309
- return Result.Fetchone(None, None, 503)
384
+ return Result.Fetchone(stored_procedure, None, None, self._connection_error)
310
385
  elif exec_type == ExecType.fetchall:
311
- return Result.Fetchall(None, None, 503)
386
+ return Result.Fetchall(stored_procedure, None, None, self._connection_error)
312
387
  elif exec_type == ExecType.send:
313
- return Result.Send(False, 503)
388
+ return Result.Send(stored_procedure, False, self._connection_error)
314
389
 
315
390
  try:
316
391
  # executar
@@ -318,22 +393,22 @@ class AsyncStored(AsyncBaseExecutor):
318
393
 
319
394
  # ober resultado se nessesario
320
395
  if exec_type == ExecType.fetchone:
321
- result = Result.Fetchone(self._cursor, await response.fetchone())
396
+ result = Result.Fetchone(stored_procedure, self._cursor, await response.fetchone())
322
397
  elif exec_type == ExecType.fetchall:
323
- result = Result.Fetchall(self._cursor, await response.fetchall())
398
+ result = Result.Fetchall(stored_procedure, self._cursor, await response.fetchall())
324
399
  elif exec_type == ExecType.send:
325
- result = Result.Send(True)
400
+ result = Result.Send(stored_procedure, True)
326
401
 
327
402
  # fazer o commit
328
403
  await connection.commit()
329
404
 
330
405
  except Exception as ex:
331
406
  if exec_type == ExecType.fetchone:
332
- return Result.Fetchone(None, None, 503, str(ex))
407
+ return Result.Fetchone(stored_procedure, None, None, ex)
333
408
  elif exec_type == ExecType.fetchall:
334
- return Result.Fetchall(None, None, 503, str(ex))
409
+ return Result.Fetchall(stored_procedure, None, None, ex)
335
410
  elif exec_type == ExecType.send:
336
- return Result.Send(False, 503, str(ex))
411
+ return Result.Send(stored_procedure, False, ex)
337
412
 
338
413
  # retorna o resultado
339
414
  return result