dapper-sqls 0.9.7__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.
Files changed (37) hide show
  1. dapper_sqls/__init__.py +4 -2
  2. dapper_sqls/_types.py +25 -2
  3. dapper_sqls/async_dapper/async_dapper.py +1 -1
  4. dapper_sqls/async_dapper/async_executors.py +128 -53
  5. dapper_sqls/builders/model/model.py +421 -36
  6. dapper_sqls/builders/model/utils.py +337 -45
  7. dapper_sqls/builders/query.py +165 -44
  8. dapper_sqls/builders/stored.py +16 -10
  9. dapper_sqls/builders/stp.py +6 -2
  10. dapper_sqls/config.py +41 -32
  11. dapper_sqls/dapper/dapper.py +1 -1
  12. dapper_sqls/dapper/executors.py +131 -56
  13. dapper_sqls/decorators.py +5 -3
  14. dapper_sqls/http/__init__.py +4 -0
  15. dapper_sqls/http/aiohttp.py +155 -0
  16. dapper_sqls/http/decorators.py +123 -0
  17. dapper_sqls/http/models.py +58 -0
  18. dapper_sqls/http/request.py +140 -0
  19. dapper_sqls/models/__init__.py +3 -5
  20. dapper_sqls/models/base.py +246 -20
  21. dapper_sqls/models/connection.py +2 -2
  22. dapper_sqls/models/query_field.py +214 -0
  23. dapper_sqls/models/result.py +315 -45
  24. dapper_sqls/sqlite/__init__.py +5 -1
  25. dapper_sqls/sqlite/async_local_database.py +168 -0
  26. dapper_sqls/sqlite/decorators.py +69 -0
  27. dapper_sqls/sqlite/installer.py +97 -0
  28. dapper_sqls/sqlite/local_database.py +67 -185
  29. dapper_sqls/sqlite/models.py +51 -1
  30. dapper_sqls/sqlite/utils.py +9 -0
  31. dapper_sqls/utils.py +18 -6
  32. dapper_sqls-1.2.0.dist-info/METADATA +41 -0
  33. dapper_sqls-1.2.0.dist-info/RECORD +40 -0
  34. {dapper_sqls-0.9.7.dist-info → dapper_sqls-1.2.0.dist-info}/WHEEL +1 -1
  35. dapper_sqls-0.9.7.dist-info/METADATA +0 -19
  36. dapper_sqls-0.9.7.dist-info/RECORD +0 -30
  37. {dapper_sqls-0.9.7.dist-info → dapper_sqls-1.2.0.dist-info}/top_level.txt +0 -0
@@ -1,55 +1,120 @@
1
- # -*- coding: latin -*-
2
- from typing import Type, Union
3
- from pydantic import BaseModel
1
+ # coding: utf-8
2
+ from typing import Union
4
3
  from datetime import datetime, date
5
- import json
6
-
7
- class Value:
8
- def __init__(self, value : Union[str, int, bytes, float, datetime, date], prefix : str, suffix : str):
9
- self.value = value
10
- self.prefix = prefix
11
- self.suffix = suffix
4
+ from ..models import TableBaseModel, QueryFieldBase, BaseJoinConditionField, SearchTable, JoinSearchTable
12
5
 
13
6
  class QueryBuilder(object):
14
7
 
15
- def value(value : Union[str, int, bytes, float, datetime, date], prefix = "=", suffix = ""):
16
- json_value = json.dumps({'prefix': prefix, 'value': value, 'suffix': suffix})
17
- return f"#QueryBuilderValue#{json_value}#QueryBuilderValue#"
18
-
19
8
  @classmethod
20
- def _build_where_clause(cls, **kwargs):
21
- conditions = []
22
- for field, value in kwargs.items():
23
- if value is not None:
24
- if isinstance(value, str):
25
- if '#QueryBuilderValue#' in value:
26
- value = value.replace('#QueryBuilderValue#', '')
27
- value = Value(**json.loads(value.strip()))
28
- if isinstance(value.value, str):
29
- conditions.append(f"{field} {value.prefix} '{value.value}' {value.suffix}")
30
- else:
31
- conditions.append(f"{field} {value.prefix} {value.value} {value.suffix}")
32
- else:
33
- conditions.append(f"{field} = '{value}'")
9
+ def build_where_clause(cls, model: TableBaseModel, table_alias: str = "", base_table_alias : str = "") -> str:
10
+ clause_parts = []
11
+
12
+ for field_name, field_value in model:
13
+
14
+ if field_value is None:
15
+ continue
16
+
17
+ # Constrói o nome qualificado com alias, se houver
18
+ qualified_field = f"{table_alias}.{field_name}" if table_alias.strip() else field_name
19
+
20
+ if isinstance(field_value, BaseJoinConditionField):
21
+ sql = field_value.to_sql(base_table_alias, qualified_field)
22
+ if sql:
23
+ clause_parts.append(sql)
24
+
25
+ elif isinstance(field_value, QueryFieldBase):
26
+ sql = field_value.to_sql(qualified_field)
27
+ if sql:
28
+ clause_parts.append(sql)
29
+
30
+ else:
31
+ if isinstance(field_value, str):
32
+ escaped_value = field_value.replace("'", "''")
33
+ clause_parts.append(f"{qualified_field} = '{escaped_value}'")
34
+ elif isinstance(field_value, bool):
35
+ clause_parts.append(f"{qualified_field} = {'1' if field_value else '0'}")
36
+ elif isinstance(field_value, datetime):
37
+ clause_parts.append(f"{qualified_field} = '{field_value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}'")
38
+ elif isinstance(field_value, date):
39
+ clause_parts.append(f"{qualified_field} = '{field_value.strftime('%Y-%m-%d')}'")
40
+ elif isinstance(field_value, bytes):
41
+ # Converte os bytes em uma string hexadecimal para SQL (ex: 0x4E6574)
42
+ hex_value = field_value.hex()
43
+ clause_parts.append(f"{qualified_field} = 0x{hex_value}")
34
44
  else:
35
- conditions.append(f"{field} = {value}")
36
- return " AND ".join(conditions)
45
+ clause_parts.append(f"{qualified_field} = {field_value}")
46
+
47
+ return " AND ".join(clause_parts)
48
+
49
+ @staticmethod
50
+ def format_sql_value(value):
51
+ if isinstance(value, str):
52
+ value = value.replace("'", "''")
53
+ return f"'{value}'"
54
+ elif isinstance(value, bool):
55
+ return '1' if value else '0'
56
+ elif isinstance(value, datetime):
57
+ return f"'{value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}'"
58
+ elif isinstance(value, date):
59
+ return f"'{value.strftime('%Y-%m-%d')}'"
60
+ elif isinstance(value, bytes):
61
+ return f"0x{value.hex()}"
62
+ elif value is None:
63
+ return "NULL"
64
+ else:
65
+ return str(value)
66
+
67
+ @staticmethod
68
+ def build_select_fields(search_table: SearchTable, table_alias: str, rename_fields: bool = True) -> str:
69
+ model_fields = search_table.model.model_fields.keys()
70
+
71
+ if not rename_fields:
72
+ # Se include estiver presente, listar colunas com alias normal
73
+ if search_table.include:
74
+ fields = [f"{table_alias}.{f}" for f in search_table.include]
75
+ else:
76
+ # Nenhuma regra definida, usar '*'
77
+ return f"{table_alias}.*"
78
+ return ", ".join(fields)
79
+
80
+ # Caso rename_fields=True
81
+ if search_table.include:
82
+ fields = search_table.include
83
+ else:
84
+ fields = list(model_fields)
85
+
86
+ select_parts = [
87
+ f"{table_alias}.{field} AS {table_alias}{field}" for field in fields
88
+ ]
89
+ return ", ".join(select_parts)
37
90
 
38
91
  @classmethod
39
- def update(cls, model: Type[BaseModel], where : Union[str , Type[BaseModel]]):
40
- update_data = model.model_dump(exclude_none=True)
92
+ def update(cls, model: TableBaseModel, where : Union[str , TableBaseModel]):
93
+ model._reset_defaults()
94
+ update_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
41
95
  if not isinstance(where, str):
42
- where_data = where.model_dump(exclude_none=True)
43
- where = cls._build_where_clause(**where_data)
96
+ where._reset_defaults()
97
+ for key in where.model_dump(mode="json", exclude_none=True):
98
+ if key in update_data:
99
+ update_data.pop(key, None)
100
+ where = cls.build_where_clause(where)
44
101
 
45
- set_clause = ", ".join([f"{key} = '{value}'" if isinstance(value, str) else f"{key} = {value}" for key, value in update_data.items()])
102
+ set_clause = ", ".join(
103
+ f"{key} = {cls.format_sql_value(value)}"
104
+ for key, value in update_data.items()
105
+ )
46
106
  sql_query = f"UPDATE {model.TABLE_NAME} SET {set_clause} WHERE {where}"
47
107
  return sql_query
48
108
 
49
- def insert(model: Type[BaseModel], name_column_id = 'Id'):
50
- insert_data = model.model_dump(exclude_none=True)
109
+ @classmethod
110
+ def insert(cls, model: TableBaseModel, name_column_id = 'Id'):
111
+ model._reset_defaults()
112
+ insert_data = {
113
+ k: int(v) if isinstance(v, bool) else v
114
+ for k, v in model.model_dump(mode="json", exclude_none=True).items()
115
+ }
51
116
  columns = ", ".join(insert_data.keys())
52
- values = ", ".join([f"'{value}'" if isinstance(value, str) else str(value) for value in insert_data.values()])
117
+ values = ", ".join(cls.format_sql_value(v) for v in insert_data.values())
53
118
  sql_query = f"""
54
119
  INSERT INTO {model.TABLE_NAME} ({columns})
55
120
  OUTPUT INSERTED.{name_column_id} AS Id
@@ -58,21 +123,77 @@ class QueryBuilder(object):
58
123
  return sql_query
59
124
 
60
125
  @classmethod
61
- def select(cls, model: Type[BaseModel], additional_sql : str = "" ,select_top : int= None):
126
+ def select(cls, model: TableBaseModel, additional_sql : str = "" ,select_top : int= None):
127
+ model._reset_defaults()
62
128
  top_clause = f"TOP ({select_top}) * " if select_top else "*"
63
- select_data = model.model_dump(exclude_none=True)
64
- where_clause = cls._build_where_clause(**select_data)
129
+ where_clause = cls.build_where_clause(model)
65
130
 
66
131
  sql_query = f"SELECT {top_clause} FROM {model.TABLE_NAME}"
67
132
  if where_clause:
68
133
  sql_query += f" WHERE {where_clause}"
69
134
  sql_query = f'{sql_query} {additional_sql}'
70
135
  return sql_query
136
+
137
+ @staticmethod
138
+ def build_on_clause(join_model: TableBaseModel, join_alias: str, base_alias: str) -> str:
139
+ clause_parts = []
140
+ for field_name, field_value in join_model:
141
+ if isinstance(field_value, BaseJoinConditionField):
142
+ sql = field_value.to_sql(base_alias, f"{join_alias}.{field_name}")
143
+ if sql:
144
+ clause_parts.append(sql)
145
+ return " AND ".join(clause_parts)
146
+
147
+ @classmethod
148
+ def select_with_joins(cls, main_search: SearchTable, joins: list[JoinSearchTable] = [], additional_sql: str = "", select_top: int = None) -> str:
149
+ main_model = main_search.model
150
+ main_model._reset_defaults()
151
+ main_table = main_model.TABLE_NAME
152
+ main_table_alias = main_model.TABLE_ALIAS
153
+ top_clause = f"TOP ({select_top})" if select_top else ""
154
+
155
+ # Campos SELECT da tabela principal
156
+ select_fields = cls.build_select_fields(main_search, main_table_alias, False)
157
+
158
+ # JOINs
159
+ join_clauses = []
160
+ for join_search in joins:
161
+ join_model = join_search.model
162
+ join_model._reset_defaults()
163
+ join_table = join_model.TABLE_NAME
164
+ join_table_alias = join_model.TABLE_ALIAS
165
+
166
+ join_type = join_search.join_type.upper()
167
+ on_conditions = cls.build_on_clause(join_model, join_table_alias, main_table_alias)
71
168
 
169
+ # Usa o alias no JOIN
170
+ join_clause = (
171
+ f"{join_type} JOIN {join_table} AS {join_table_alias} "
172
+ f"ON {on_conditions}"
173
+ )
174
+ join_clauses.append(join_clause)
175
+
176
+ # Campos SELECT do JOIN
177
+ select_fields += ", " + cls.build_select_fields(join_search, join_table_alias)
178
+
179
+ # WHERE principal
180
+ where_clause = cls.build_where_clause(main_model, main_table_alias)
181
+ where_part = f"WHERE {where_clause}" if where_clause else ""
182
+
183
+ # SQL final
184
+ sql_query = (
185
+ f"SELECT {top_clause} {select_fields} "
186
+ f"FROM {main_table} AS {main_table_alias} "
187
+ + " ".join(join_clauses)
188
+ + f" {where_part} {additional_sql}"
189
+ ).strip()
190
+
191
+ return sql_query
192
+
72
193
  @classmethod
73
- def delete(cls, model: Type[BaseModel]):
74
- delete_data = model.model_dump(exclude_none=True)
75
- where_clause = cls._build_where_clause(**delete_data)
194
+ def delete(cls, model: TableBaseModel):
195
+ model._reset_defaults()
196
+ where_clause = cls.build_where_clause(model)
76
197
  if not where_clause:
77
198
  raise ValueError("DELETE operation requires at least one condition.")
78
199
  sql_query = f"DELETE FROM {model.TABLE_NAME} WHERE {where_clause}"
@@ -1,4 +1,4 @@
1
- # -*- coding: latin -*-
1
+ # coding: utf-8
2
2
  from typing import Type
3
3
  from pydantic import BaseModel
4
4
 
@@ -9,18 +9,21 @@ class StoredBuilder:
9
9
  parameters = []
10
10
  for field, value in kwargs.items():
11
11
  if value is not None:
12
- if isinstance(value, str):
13
- conditions.append(f"{field} = ?")
14
- parameters.append(value)
15
- else:
16
12
  conditions.append(f"{field} = ?")
17
13
  parameters.append(value)
18
14
  return " AND ".join(conditions), tuple(parameters)
19
15
 
20
16
  @classmethod
21
17
  def update(cls, model: Type[BaseModel], where: Type[BaseModel]):
22
- update_data = model.model_dump(exclude_none=True)
23
- where_data = where.model_dump(exclude_none=True)
18
+ model._reset_defaults()
19
+ where._reset_defaults()
20
+ update_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
21
+ where_data = {k: int(v) if isinstance(v, bool) else v for k, v in where.model_dump(mode="json", exclude_none=True).items()}
22
+
23
+ for key in where_data:
24
+ if key in update_data:
25
+ update_data.pop(key, None)
26
+
24
27
  where_clause, where_params = cls._build_where_clause(**where_data)
25
28
 
26
29
  set_clause = ", ".join([f"{key} = ?" for key in update_data.keys()])
@@ -30,7 +33,8 @@ class StoredBuilder:
30
33
 
31
34
  @classmethod
32
35
  def insert(cls, model : Type[BaseModel], name_column_id = 'Id'):
33
- insert_data = model.model_dump(exclude_none=True)
36
+ model._reset_defaults()
37
+ insert_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
34
38
  columns = ", ".join(insert_data.keys())
35
39
  values = ", ".join(["?" for _ in insert_data.values()])
36
40
  sql_query = f"""
@@ -42,8 +46,9 @@ class StoredBuilder:
42
46
 
43
47
  @classmethod
44
48
  def select(cls, model : Type[BaseModel], additional_sql : str = "" ,select_top : int= None):
49
+ model._reset_defaults()
45
50
  top_clause = f"TOP ({select_top}) * " if select_top else "*"
46
- select_data = model.model_dump(exclude_none=True)
51
+ select_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
47
52
  where_clause, parameters = cls._build_where_clause(**select_data)
48
53
 
49
54
  sql_query = f"SELECT {top_clause} FROM {model.TABLE_NAME}"
@@ -54,7 +59,8 @@ class StoredBuilder:
54
59
 
55
60
  @classmethod
56
61
  def delete(cls, model : Type[BaseModel]):
57
- delete_data = model.model_dump(exclude_none=True)
62
+ model._reset_defaults()
63
+ delete_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
58
64
  where_clause, parameters = cls._build_where_clause(**delete_data)
59
65
  if not where_clause:
60
66
  raise ValueError("DELETE operation requires at least one condition.")
@@ -61,6 +61,7 @@ class StpBuilder(StpBaseBuilder):
61
61
  with self.dapper.stored(args.attempts, args.wait_timeout) as db:
62
62
  result = db.fetchone(self.query, self.params)
63
63
  if args.model:
64
+ args.model._reset_defaults()
64
65
  return self.dapper.load(args.model, result)
65
66
  return result
66
67
 
@@ -78,7 +79,8 @@ class StpBuilder(StpBaseBuilder):
78
79
  with self.dapper.stored(args.attempts, args.wait_timeout) as db:
79
80
  result = db.fetchall(self.query, self.params)
80
81
  if args.model:
81
- return self.dapper.load(args.model, result)
82
+ args.model._reset_defaults()
83
+ return self.dapper.load(args.model, result)
82
84
  return result
83
85
 
84
86
 
@@ -110,6 +112,7 @@ class AsyncStpBuilder(StpBaseBuilder):
110
112
  async with await self.async_dapper.stored(args.attempts, args.wait_timeout) as db:
111
113
  result = await db.fetchone(self.query, self.params)
112
114
  if args.model:
115
+ args.model._reset_defaults()
113
116
  return self.async_dapper.load(args.model, result)
114
117
  return result
115
118
 
@@ -127,7 +130,8 @@ class AsyncStpBuilder(StpBaseBuilder):
127
130
  async with await self.async_dapper.stored(args.attempts, args.wait_timeout) as db:
128
131
  result = await db.fetchall(self.query, self.params)
129
132
  if args.model:
130
- return self.async_dapper.load(args.model, result)
133
+ args.model._reset_defaults()
134
+ return self.async_dapper.load(args.model, result)
131
135
  return result
132
136
 
133
137
 
dapper_sqls/config.py CHANGED
@@ -1,6 +1,7 @@
1
- # -*- coding: latin -*-
1
+ # coding: utf-8
2
2
  from .models import ConnectionStringData
3
3
  import sys
4
+ import subprocess
4
5
 
5
6
  class Config(object):
6
7
  def __init__(self, server: str, database: str, username: str, password: str, sql_version: int = None, api_environment=False, default_attempts=1, default_wait_timeout=2):
@@ -72,41 +73,49 @@ class Config(object):
72
73
 
73
74
  @staticmethod
74
75
  def get_all_odbc_driver_versions():
75
- driver_versions = []
76
76
 
77
+ driver_versions = []
77
78
  # Verificar se o sistema é Windows e o módulo winreg está disponível
78
- if sys.platform == "win32":
79
+ platform : str = sys.platform
80
+ if platform:
81
+ try:
82
+ import winreg # Importar o módulo winreg se estiver disponível
83
+
84
+ # Abrir a chave onde as informações sobre os drivers ODBC estão armazenadas
85
+ key_path = r"SOFTWARE\ODBC\ODBCINST.INI"
86
+ key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
87
+
88
+ # Iterar sobre as subchaves para encontrar os drivers específicos
89
+ i = 0
90
+ while True:
91
+ try:
92
+ subkey_name = winreg.EnumKey(key, i)
93
+ subkey = winreg.OpenKey(key, subkey_name)
94
+
95
+ # Verificar se a subchave contém o valor 'Driver'
96
+ try:
97
+ driver_name, _ = winreg.QueryValueEx(subkey, "Driver")
98
+ if subkey_name.startswith('ODBC Driver'):
99
+ driver_versions.append(subkey_name)
100
+
101
+ except FileNotFoundError:
102
+ pass # A subchave não possui a entrada 'Driver'
103
+
104
+ i += 1
105
+ except OSError:
106
+ break
107
+
108
+ except Exception as e:
109
+ print(f"Erro ao acessar o registro: {e}")
110
+ else:
79
111
  try:
80
- import winreg # Importar o módulo winreg se estiver disponível
81
-
82
- # Abrir a chave onde as informações sobre os drivers ODBC estão armazenadas
83
- key_path = r"SOFTWARE\ODBC\ODBCINST.INI"
84
- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
85
-
86
- # Iterar sobre as subchaves para encontrar os drivers específicos
87
- i = 0
88
- while True:
89
- try:
90
- subkey_name = winreg.EnumKey(key, i)
91
- subkey = winreg.OpenKey(key, subkey_name)
92
-
93
- # Verificar se a subchave contém o valor 'Driver'
94
- try:
95
- driver_name, _ = winreg.QueryValueEx(subkey, "Driver")
96
- if subkey_name.startswith('ODBC Driver'):
97
- driver_versions.append(subkey_name)
98
-
99
- except FileNotFoundError:
100
- pass # A subchave não possui a entrada 'Driver'
101
-
102
- i += 1
103
- except OSError:
104
- break
105
-
112
+ result = subprocess.run(['odbcinst', '-q', '-d'], stdout=subprocess.PIPE, text=True)
113
+ for line in result.stdout.splitlines():
114
+ line = line.strip()
115
+ if line.startswith("[ODBC Driver") and "SQL Server" in line:
116
+ driver_versions.append(line.strip("[]"))
106
117
  except Exception as e:
107
- print(f"Erro ao acessar o registro: {e}")
108
- else:
109
- print("A funcionalidade de obtenção de versões de drivers ODBC não é suportada neste sistema operacional.")
118
+ print(f"Erro ao detectar drivers ODBC no Linux: {e}")
110
119
 
111
120
  return sorted(driver_versions, key=lambda x: int(x.split()[-4]), reverse=True) if driver_versions else []
112
121
 
@@ -1,4 +1,4 @@
1
- # -*- coding: latin -*-
1
+ # coding: utf-8
2
2
  from .._types import T
3
3
  from ..config import Config
4
4
  from ..models import Result