quillsql 2.2.4__py3-none-any.whl → 2.2.6__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.
quillsql/core.py CHANGED
@@ -91,8 +91,6 @@ class Quill:
91
91
  ):
92
92
  if not tenants:
93
93
  raise ValueError("You may not pass an empty tenants array.")
94
- if flags and not flags:
95
- raise ValueError("You may not pass an empty flags array.")
96
94
 
97
95
  responseMetadata = {}
98
96
  if not metadata:
@@ -108,7 +106,6 @@ class Quill:
108
106
 
109
107
  # Handle pivot-template task
110
108
  if task == "pivot-template":
111
- # Step 1: Get pivot template and queries from server
112
109
  pivot_payload = {
113
110
  **metadata,
114
111
  "tenants": tenants,
@@ -129,16 +126,14 @@ class Quill:
129
126
  return {
130
127
  "status": "error",
131
128
  "error": pivot_template_response.get("error"),
132
- "data": (pivot_template_response.get("metadata") or {}),
129
+ "data": pivot_template_response.get("metadata") or {},
133
130
  }
134
131
 
135
- pivot_metadata = pivot_template_response.get("metadata") or {}
136
- template = pivot_metadata.get("template")
137
- config = pivot_metadata.get("config") or {}
138
- distinct_values_query = pivot_metadata.get("distinctValuesQuery")
139
- row_count_query = pivot_metadata.get("rowCountQuery")
132
+ template = pivot_template_response.get("metadata", {}).get("template")
133
+ config = pivot_template_response.get("metadata", {}).get("config")
134
+ distinct_values_query = pivot_template_response.get("metadata", {}).get("distinctValuesQuery")
135
+ row_count_query = pivot_template_response.get("metadata", {}).get("rowCountQuery")
140
136
 
141
- # Step 2: Run the distinct values query to get unique values
142
137
  distinct_values = []
143
138
  if distinct_values_query:
144
139
  distinct_value_results = self.run_queries(
@@ -149,13 +144,11 @@ class Quill:
149
144
  None
150
145
  )
151
146
 
152
- # Parse distinct values from database results
153
147
  distinct_values = parse_distinct_values(
154
148
  distinct_value_results["queryResults"][0],
155
149
  config.get("databaseType")
156
150
  )
157
151
 
158
- # Step 3: Hydrate the template with the distinct values
159
152
  try:
160
153
  final_query = hydrate_pivot_template(template, distinct_values, config)
161
154
  except Exception as err:
@@ -165,10 +158,8 @@ class Quill:
165
158
  "data": {},
166
159
  }
167
160
 
168
- # Step 4: Run queries - pivot query and optional row count query
169
161
  queries_to_run = [final_query]
170
162
  if row_count_query:
171
- # Hydrate the rowCountQuery with the same distinct values
172
163
  hydrated_row_count_query = hydrate_pivot_template(
173
164
  row_count_query,
174
165
  distinct_values,
@@ -181,11 +172,10 @@ class Quill:
181
172
  self.target_connection.database_type,
182
173
  metadata.get("databaseType"),
183
174
  metadata,
184
- pivot_metadata.get("runQueryConfig"),
175
+ pivot_template_response.get("metadata", {}).get("runQueryConfig")
185
176
  )
186
177
 
187
- responseMetadata = pivot_metadata or {}
188
- # Set rows and fields from first query result (the pivot query)
178
+ responseMetadata = pivot_template_response.get("metadata") or {}
189
179
  if final_results.get("queryResults") and len(final_results["queryResults"]) >= 1:
190
180
  query_results = final_results["queryResults"][0]
191
181
  if query_results.get("rows"):
@@ -193,7 +183,6 @@ class Quill:
193
183
  if query_results.get("fields"):
194
184
  responseMetadata["fields"] = query_results["fields"]
195
185
 
196
- # Remove internal SDK fields before returning to frontend
197
186
  if "template" in responseMetadata:
198
187
  del responseMetadata["template"]
199
188
  if "distinctValuesQuery" in responseMetadata:
@@ -229,7 +218,7 @@ class Quill:
229
218
  return {
230
219
  'status': 'error',
231
220
  'error': response.get('error'),
232
- 'data': (response.get('metadata') or {}),
221
+ 'data': response.get('metadata') or {},
233
222
  }
234
223
 
235
224
  flag_query_results = self.run_queries(
@@ -238,22 +227,34 @@ class Quill:
238
227
  )
239
228
 
240
229
  tenant_flags = []
241
- response_metadata = response.get('metadata') or {}
242
- query_order = response_metadata.get('queryOrder') or []
230
+ query_order = (response.get('metadata') or {}).get('queryOrder') or []
243
231
  query_results = (flag_query_results or {}).get('queryResults') or []
244
- for tenant_field, query_result in zip(query_order, query_results):
245
- rows = []
246
- if isinstance(query_result, dict):
247
- rows = query_result.get('rows') or []
248
- flags = {
249
- row.get('quill_flag')
250
- for row in rows
251
- if isinstance(row, dict) and row.get('quill_flag') is not None
252
- }
232
+ for index, tenant_field in enumerate(query_order):
233
+ query_result = (
234
+ query_results[index]
235
+ if index < len(query_results)
236
+ else {}
237
+ )
238
+ rows = (
239
+ query_result.get('rows')
240
+ if isinstance(query_result, dict)
241
+ else []
242
+ )
243
+ ordered_flags = []
244
+ seen = set()
245
+ for row in rows or []:
246
+ if not isinstance(row, dict):
247
+ continue
248
+ flag = row.get('quill_flag')
249
+ if flag is None or flag in seen:
250
+ continue
251
+ seen.add(flag)
252
+ ordered_flags.append(flag)
253
253
  tenant_flags.append({
254
254
  'tenantField': tenant_field,
255
- 'flags': list(flags),
255
+ 'flags': ordered_flags,
256
256
  })
257
+
257
258
  elif tenants[0] == SINGLE_TENANT and flags:
258
259
  if flags and isinstance(flags[0], dict):
259
260
  tenant_flags = [{'tenantField': SINGLE_TENANT, 'flags': flags}]
@@ -285,13 +286,14 @@ class Quill:
285
286
  and metadata.get("runQueryConfig").get("getColumns")
286
287
  else None
287
288
  )
288
- payload: dict = {
289
+ payload = {
289
290
  **metadata,
290
291
  "tenants": tenants,
291
292
  "flags": tenant_flags,
292
293
  "viewQuery": view_query,
293
- "preQueryResultsColumns": pre_query_columns,
294
294
  }
295
+ if pre_query_columns is not None:
296
+ payload["preQueryResultsColumns"] = pre_query_columns
295
297
  if admin_enabled is not None:
296
298
  payload["adminEnabled"] = admin_enabled
297
299
  if filters is not None:
@@ -338,21 +340,25 @@ class Quill:
338
340
  normalized_results.get("queryResults") or []
339
341
  )
340
342
 
341
- run_query_config = responseMetadata.get("runQueryConfig") or {}
342
- array_to_map = run_query_config.get("arrayToMap")
343
- if normalized_results.get("mapped_array") and array_to_map:
344
- target_collection = responseMetadata.get(array_to_map.get("arrayName"))
343
+ if (
344
+ normalized_results.get("mapped_array")
345
+ and metadata.get("runQueryConfig", {}).get("arrayToMap")
346
+ ):
347
+ array_to_map = metadata["runQueryConfig"]["arrayToMap"]
348
+ target_collection = responseMetadata.get(array_to_map["arrayName"])
345
349
  if isinstance(target_collection, list):
346
- for index, mapped_rows in enumerate(normalized_results["mapped_array"]):
350
+ for index, array in enumerate(normalized_results["mapped_array"]):
347
351
  if index >= len(target_collection):
348
352
  continue
349
353
  target_entry = target_collection[index]
350
- if isinstance(target_entry, dict):
351
- target_entry[array_to_map.get("field")] = mapped_rows
352
- normalized_results.pop("mapped_array", None)
354
+ if not isinstance(target_entry, dict):
355
+ target_entry = {}
356
+ target_collection[index] = target_entry
357
+ target_entry[array_to_map["field"]] = array if array is not None else []
358
+ del normalized_results["mapped_array"]
353
359
 
354
360
  query_results_list = normalized_results.get("queryResults") or []
355
- if len(query_results_list) == 1:
361
+ if len(query_results_list) == 1 and isinstance(query_results_list[0], dict):
356
362
  query_result = query_results_list[0]
357
363
  quill_results["metadata"]["rows"] = query_result.get("rows")
358
364
  quill_results["metadata"]["fields"] = query_result.get("fields")
@@ -390,13 +396,24 @@ class Quill:
390
396
  return query
391
397
  return f"{query.rstrip(';')} limit {limit}"
392
398
 
399
+ def normalize_database_type(self, db_type):
400
+ if not db_type:
401
+ return None
402
+ lowered = db_type.lower()
403
+ if lowered in ("postgresql", "postgres"):
404
+ return "postgres"
405
+ return lowered
406
+
393
407
  def run_queries(
394
408
  self, queries, pkDatabaseType, databaseType=None, metadata=None, runQueryConfig=None
395
409
  ):
396
410
  results = {}
397
411
  if not queries:
398
412
  return {"queryResults": []}
399
- if databaseType and databaseType.lower() != pkDatabaseType.lower():
413
+ normalized_pk = self.normalize_database_type(pkDatabaseType)
414
+ normalized_requested = self.normalize_database_type(databaseType)
415
+ should_enforce_match = normalized_requested is not None
416
+ if should_enforce_match and normalized_requested != normalized_pk:
400
417
  return {"dbMismatched": True, "backendDatabaseType": pkDatabaseType}
401
418
  if runQueryConfig and runQueryConfig.get("arrayToMap"):
402
419
  mapped_array = array_to_map(
quillsql/db/bigquery.py CHANGED
@@ -26,8 +26,7 @@ def format_bigquery_config(connection_string):
26
26
  "project": service_account.get("project_id"),
27
27
  "credentials": service_account,
28
28
  }
29
- except (ValueError, TypeError) as e:
30
- print("Invalid JSON string: ", e)
29
+ except (ValueError, TypeError):
31
30
  return connection_string
32
31
 
33
32
 
quillsql/db/db_helper.py CHANGED
@@ -15,47 +15,51 @@ from quillsql.db.bigquery import (
15
15
  )
16
16
 
17
17
 
18
+ def _is_postgres(database_type):
19
+ return database_type and database_type.lower() in ("postgresql", "postgres")
20
+
21
+
18
22
  def get_db_credentials(database_type, connection_string):
19
- if database_type.lower() == "postgresql":
23
+ if _is_postgres(database_type):
20
24
  return format_postgres(connection_string)
21
- elif database_type.lower() == "bigquery":
25
+ elif database_type and database_type.lower() == "bigquery":
22
26
  return format_bigquery_config(connection_string)
23
27
  return {}
24
28
 
25
29
 
26
30
  def connect_to_db(database_type, config, using_connection_string):
27
- if database_type.lower() == "postgresql":
31
+ if _is_postgres(database_type):
28
32
  return connect_to_postgres(config, using_connection_string)
29
- elif database_type.lower() == "bigquery":
33
+ elif database_type and database_type.lower() == "bigquery":
30
34
  return connect_to_bigquery(config, using_connection_string)
31
35
  return None
32
36
 
33
37
 
34
38
  def run_query_by_db(database_type, query, connection):
35
- if database_type.lower() == "postgresql":
39
+ if _is_postgres(database_type):
36
40
  return run_query_postgres(query, connection)
37
- elif database_type.lower() == "bigquery":
41
+ elif database_type and database_type.lower() == "bigquery":
38
42
  return run_query_big_query(query, connection)
39
43
  return None
40
44
 
41
45
 
42
46
  def disconnect_from_db(database_type, connection):
43
- if database_type.lower() == "postgresql":
47
+ if _is_postgres(database_type):
44
48
  return disconnect_from_postgres(connection)
45
49
  return None
46
50
 
47
51
 
48
52
  def get_schema_tables_by_db(database_type, connection, schema_name):
49
- if database_type.lower() == "postgresql":
53
+ if _is_postgres(database_type):
50
54
  return get_tables_by_schema_postgres(connection, schema_name)
51
- elif database_type.lower() == "bigquery":
55
+ elif database_type and database_type.lower() == "bigquery":
52
56
  return get_tables_by_schema_big_query(connection, schema_name)
53
57
  return None
54
58
 
55
59
 
56
60
  def get_schema_column_info_by_db(database_type, connection, schema_name, table_names):
57
- if database_type.lower() == "postgresql":
61
+ if _is_postgres(database_type):
58
62
  return get_schema_column_info_postgres(connection, schema_name, table_names)
59
- elif database_type.lower() == "bigquery":
63
+ elif database_type and database_type.lower() == "bigquery":
60
64
  return get_schema_column_info_big_query(connection, schema_name, table_names)
61
65
  return None
@@ -198,7 +198,6 @@ def parse_distinct_values(query_result: Dict[str, Any], database_type: str) -> L
198
198
  distinct_values = [v.strip() for v in row["string_values"].split(",")]
199
199
 
200
200
  else:
201
- print(f"Warning: Unknown database type: {database_type}")
202
201
  distinct_values = []
203
202
 
204
203
  # Filter out null/undefined/empty values
@@ -483,3 +482,4 @@ def validate_template(template: str, config: PivotConfig) -> Dict[str, Any]:
483
482
  "errors": errors
484
483
  }
485
484
 
485
+
@@ -1,14 +1,21 @@
1
1
  def remove_fields(query_result, fields_to_remove):
2
+ if not isinstance(query_result, dict):
3
+ return query_result
2
4
  fields = [
3
5
  {"name": field["name"], "dataTypeID": field["dataTypeID"]}
4
- for field in query_result["fields"]
5
- if field["name"] not in fields_to_remove
6
+ for field in (query_result.get("fields") or [])
7
+ if field.get("name") not in fields_to_remove
6
8
  ]
7
- rows = [row for row in query_result["rows"]]
8
- for row in rows:
9
+ rows = []
10
+ for row in query_result.get("rows") or []:
11
+ if not isinstance(row, dict):
12
+ rows.append(row)
13
+ continue
14
+ filtered = dict(row)
9
15
  for field in fields_to_remove:
10
- if field in row:
11
- del row[field]
16
+ if field in filtered:
17
+ del filtered[field]
18
+ rows.append(filtered)
12
19
  return {"fields": fields, "rows": rows}
13
20
 
14
21
 
@@ -16,5 +23,8 @@ def array_to_map(queries, array_to_map, metadata, target_pool):
16
23
  mapped_array = []
17
24
  for i in range(len(queries)):
18
25
  query_result = target_pool.query(queries[i])
19
- mapped_array.append(query_result.get("rows"))
26
+ if isinstance(query_result, dict):
27
+ mapped_array.append(query_result.get("rows"))
28
+ else:
29
+ mapped_array.append([])
20
30
  return mapped_array
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quillsql
3
- Version: 2.2.4
3
+ Version: 2.2.6
4
4
  Summary: Quill SDK for Python.
5
5
  Home-page: https://github.com/quill-sql/quill-python
6
6
  Author: Quill
@@ -1,20 +1,20 @@
1
1
  quillsql/__init__.py,sha256=FiuoxaNZveKXOPB0hkpfGNlpZKmSn3pRwcqm9HKYbCQ,180
2
- quillsql/core.py,sha256=HMmRtQQj3bWuLTIE8YuZaj6cAwh4bSui3pFe9pA9SgQ,23261
2
+ quillsql/core.py,sha256=xQbg_gBDUkujJiWvj83jK2jgGgrS3VJvkRuM4friQbQ,23699
3
3
  quillsql/error.py,sha256=n9VKHw4FAgg7ZEAz2YQ8L_8FdRG_1shwGngf2iWhUSM,175
4
4
  quillsql/assets/__init__.py,sha256=oXQ2ZS5XDXkXTYjADxNfGt55cIn_rqfgWL2EDqjTyoI,45
5
5
  quillsql/assets/pgtypes.py,sha256=-B_2wUaoAsdX7_HnJhUlx4ptZQ6x-cXwuST9ACgGFdE,33820
6
6
  quillsql/db/__init__.py,sha256=sPgYMF3eqKuc2k96i1Re8NHazLdGKCVN-rs15F1sovw,63
7
- quillsql/db/bigquery.py,sha256=xJv3WUjyDmnfwaAAeLTTezUEw_qxrxQy-CTCwj8Hmjs,4811
7
+ quillsql/db/bigquery.py,sha256=AGGlrpH1UBtN1yyluTFcrtgF53DdM9I3BIg05DK1FjU,4764
8
8
  quillsql/db/cached_connection.py,sha256=Pp0R2XGY-QuQYh8ThiDxSwNb6SEuEKfZcQRcvzjeC28,2556
9
- quillsql/db/db_helper.py,sha256=qiIP-BM7R-3PhvWBELYjNazi-92EcQB0q9eN7Ej7XUA,2111
9
+ quillsql/db/db_helper.py,sha256=nK3E9zOW9YISXBdG4kkUBotHfY2sEq3XmUEjGJAAXmU,2257
10
10
  quillsql/db/postgres.py,sha256=ZTLtUVTJHkqAj7nZkcwbmkSOwR2ySN7vS5snoxqLRN0,4156
11
11
  quillsql/utils/__init__.py,sha256=C2k9Xe0sG5XrP0XJo9K_-iej1S9PLMRKOYMeLxj7NYE,210
12
12
  quillsql/utils/filters.py,sha256=REXOLIQZDHL4EDKtConXY9_GaqUmc2uTcyUa2_4MvAg,6786
13
- quillsql/utils/pivot_template.py,sha256=NzHO7Ux8GVAKY-DUtsOmE7TUls2q6FJG2kgJxVWq-wQ,18282
14
- quillsql/utils/run_query_processes.py,sha256=FRmNvjTDLUBr7MqDKQmivdC0anwybMXUyzQbKnaZx70,698
13
+ quillsql/utils/pivot_template.py,sha256=1U1ALJHn-4K1_ps8-Z6VDpqJ9dsyOJZNYxt80NDwtlQ,18217
14
+ quillsql/utils/run_query_processes.py,sha256=QwnMr5UwXdtO_W88lv5nBaf6pJ_h5oWQnYd8K9oHQ5s,1030
15
15
  quillsql/utils/schema_conversion.py,sha256=TFfMibN9nOsxNRhHw5YIFl3jGTvipG81bxX4LFDulUY,314
16
16
  quillsql/utils/tenants.py,sha256=ZD2FuKz0gjBVSsThHDv1P8PU6EL8E009NWihE5hAH-Q,2022
17
- quillsql-2.2.4.dist-info/METADATA,sha256=wEywCChkWsaEmZezh0DCkKUXHkgGLv0RR3OzCgEEWk0,1786
18
- quillsql-2.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- quillsql-2.2.4.dist-info/top_level.txt,sha256=eU2vHnVqwpYQJ3ADl1Q-DIBzbYejZRUhcMdN_4zMCz8,9
20
- quillsql-2.2.4.dist-info/RECORD,,
17
+ quillsql-2.2.6.dist-info/METADATA,sha256=wZVOGz--kUM4xMWhXHCAsGp_kOgx8J37IeV1siy5ABs,1786
18
+ quillsql-2.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ quillsql-2.2.6.dist-info/top_level.txt,sha256=eU2vHnVqwpYQJ3ADl1Q-DIBzbYejZRUhcMdN_4zMCz8,9
20
+ quillsql-2.2.6.dist-info/RECORD,,