sql-testing-library 0.9.0__tar.gz → 0.10.0__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.
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/CHANGELOG.md +6 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/PKG-INFO +4 -5
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/README.md +3 -4
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/pyproject.toml +1 -1
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/bigquery.py +74 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_utils.py +9 -1
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/LICENSE +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/__init__.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/__init__.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/athena.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/base.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/presto.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/redshift.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/snowflake.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/trino.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_core.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_exceptions.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_mock_table.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_pytest_plugin.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_logger.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_types.py +0 -0
- {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/py.typed +0 -0
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 0.10.0 (2025-06-15)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- **bigquery**: add support for map in bigquery (#99)
|
|
13
|
+
|
|
8
14
|
## 0.9.0 (2025-06-10)
|
|
9
15
|
|
|
10
16
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sql-testing-library
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A powerful Python framework for unit testing SQL queries across BigQuery, Snowflake, Redshift, Athena, and Trino with mock data
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: sql,testing,unit-testing,mock-data,database-testing,bigquery,snowflake,redshift,athena,trino,data-engineering,etl-testing,sql-validation,query-testing
|
|
@@ -136,14 +136,13 @@ The library supports different data types across database engines. All checkmark
|
|
|
136
136
|
| **Integer Array** | `List[int]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
137
137
|
| **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
138
138
|
| **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
139
|
-
| **Map/
|
|
140
|
-
| **Struct/Record** | `
|
|
139
|
+
| **Map/Dict** | `Dict[K, V]` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
140
|
+
| **Struct/Record** | `dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
141
141
|
| **Nested Arrays** | `List[List[T]]` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
142
|
-
| **JSON Objects** | `JSON` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
143
142
|
|
|
144
143
|
### Database-Specific Notes
|
|
145
144
|
|
|
146
|
-
- **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals
|
|
145
|
+
- **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals; dict/map types stored as JSON strings
|
|
147
146
|
- **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax
|
|
148
147
|
- **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit
|
|
149
148
|
- **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays and maps
|
|
@@ -79,14 +79,13 @@ The library supports different data types across database engines. All checkmark
|
|
|
79
79
|
| **Integer Array** | `List[int]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
80
80
|
| **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
81
81
|
| **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
82
|
-
| **Map/
|
|
83
|
-
| **Struct/Record** | `
|
|
82
|
+
| **Map/Dict** | `Dict[K, V]` | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
83
|
+
| **Struct/Record** | `dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
84
84
|
| **Nested Arrays** | `List[List[T]]` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
85
|
-
| **JSON Objects** | `JSON` | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
86
85
|
|
|
87
86
|
### Database-Specific Notes
|
|
88
87
|
|
|
89
|
-
- **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals
|
|
88
|
+
- **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals; dict/map types stored as JSON strings
|
|
90
89
|
- **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax
|
|
91
90
|
- **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit
|
|
92
91
|
- **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays and maps
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "sql-testing-library"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
description = "A powerful Python framework for unit testing SQL queries across BigQuery, Snowflake, Redshift, Athena, and Trino with mock data"
|
|
9
9
|
authors = ["Gurmeet Saran <gurmeetx@gmail.com>", "Kushal Thakkar <kushal.thakkar@gmail.com>"]
|
|
10
10
|
maintainers = ["Gurmeet Saran <gurmeetx@gmail.com>", "Kushal Thakkar <kushal.thakkar@gmail.com>"]
|
|
@@ -30,6 +30,32 @@ class BigQueryTypeConverter(BaseTypeConverter):
|
|
|
30
30
|
|
|
31
31
|
def convert(self, value: Any, target_type: Type) -> Any:
|
|
32
32
|
"""Convert BigQuery result value to target type."""
|
|
33
|
+
# Handle None/NULL values first
|
|
34
|
+
if value is None:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
# Handle Optional types
|
|
38
|
+
if self.is_optional_type(target_type):
|
|
39
|
+
if value is None:
|
|
40
|
+
return None
|
|
41
|
+
target_type = self.get_optional_inner_type(target_type)
|
|
42
|
+
|
|
43
|
+
# Handle dict/map types from BigQuery STRING columns containing JSON
|
|
44
|
+
if hasattr(target_type, "__origin__") and target_type.__origin__ is dict:
|
|
45
|
+
# BigQuery returns JSON stored as strings, so we need to parse them
|
|
46
|
+
if isinstance(value, str):
|
|
47
|
+
import json
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
return json.loads(value)
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
return {}
|
|
53
|
+
elif isinstance(value, dict):
|
|
54
|
+
# Already a dict (shouldn't happen with STRING columns, but handle it)
|
|
55
|
+
return value
|
|
56
|
+
else:
|
|
57
|
+
return {}
|
|
58
|
+
|
|
33
59
|
# BigQuery typically returns proper Python types, so use base converter
|
|
34
60
|
return super().convert(value, target_type)
|
|
35
61
|
|
|
@@ -82,6 +108,9 @@ class BigQueryAdapter(DatabaseAdapter):
|
|
|
82
108
|
# Insert data
|
|
83
109
|
df = mock_table.to_dataframe()
|
|
84
110
|
if not df.empty:
|
|
111
|
+
# Convert dict columns to JSON strings for BigQuery
|
|
112
|
+
df = self._prepare_dataframe_for_bigquery(df, mock_table)
|
|
113
|
+
|
|
85
114
|
job_config = bigquery.LoadJobConfig()
|
|
86
115
|
job = self.client.load_table_from_dataframe(df, table, job_config=job_config)
|
|
87
116
|
job.result() # Wait for job to complete
|
|
@@ -130,6 +159,9 @@ class BigQueryAdapter(DatabaseAdapter):
|
|
|
130
159
|
|
|
131
160
|
# Insert data if any
|
|
132
161
|
if not df.empty:
|
|
162
|
+
# Convert dict columns to JSON strings for BigQuery
|
|
163
|
+
df = self._prepare_dataframe_for_bigquery(df, mock_table)
|
|
164
|
+
|
|
133
165
|
job_config = bigquery.LoadJobConfig()
|
|
134
166
|
job = self.client.load_table_from_dataframe(df, table, job_config=job_config)
|
|
135
167
|
job.result()
|
|
@@ -188,9 +220,51 @@ class BigQueryAdapter(DatabaseAdapter):
|
|
|
188
220
|
|
|
189
221
|
# Create field with mode=REPEATED for arrays
|
|
190
222
|
schema.append(bigquery.SchemaField(col_name, element_bq_type, mode="REPEATED"))
|
|
223
|
+
# Handle Dict/Map types
|
|
224
|
+
elif hasattr(col_type, "__origin__") and col_type.__origin__ is dict:
|
|
225
|
+
# BigQuery stores JSON data as STRING type
|
|
226
|
+
schema.append(bigquery.SchemaField(col_name, bigquery.enums.SqlTypeNames.STRING))
|
|
191
227
|
else:
|
|
192
228
|
# Handle scalar types
|
|
193
229
|
bq_type = type_mapping.get(col_type, bigquery.enums.SqlTypeNames.STRING)
|
|
194
230
|
schema.append(bigquery.SchemaField(col_name, bq_type))
|
|
195
231
|
|
|
196
232
|
return schema
|
|
233
|
+
|
|
234
|
+
def _prepare_dataframe_for_bigquery(
|
|
235
|
+
self, df: "pd.DataFrame", mock_table: BaseMockTable
|
|
236
|
+
) -> "pd.DataFrame":
|
|
237
|
+
"""Prepare DataFrame for BigQuery by converting dict columns to JSON strings."""
|
|
238
|
+
import json
|
|
239
|
+
|
|
240
|
+
import pandas as pd
|
|
241
|
+
|
|
242
|
+
from .._sql_utils import DecimalEncoder
|
|
243
|
+
|
|
244
|
+
# Create a copy to avoid modifying the original
|
|
245
|
+
df_copy = df.copy()
|
|
246
|
+
column_types = mock_table.get_column_types()
|
|
247
|
+
|
|
248
|
+
for col_name, col_type in column_types.items():
|
|
249
|
+
# Handle Optional types
|
|
250
|
+
if hasattr(col_type, "__origin__") and col_type.__origin__ is Union:
|
|
251
|
+
# Extract the non-None type from Optional[T]
|
|
252
|
+
non_none_types = [arg for arg in get_args(col_type) if arg is not type(None)]
|
|
253
|
+
if non_none_types:
|
|
254
|
+
col_type = non_none_types[0]
|
|
255
|
+
|
|
256
|
+
# Check if this is a dict type
|
|
257
|
+
if hasattr(col_type, "__origin__") and col_type.__origin__ is dict:
|
|
258
|
+
# Convert dict values to JSON strings
|
|
259
|
+
def convert_dict_to_json(val):
|
|
260
|
+
if pd.isna(val) or val is None:
|
|
261
|
+
return None
|
|
262
|
+
elif isinstance(val, dict):
|
|
263
|
+
# Use DecimalEncoder to handle Decimal values in dicts
|
|
264
|
+
return json.dumps(val, cls=DecimalEncoder)
|
|
265
|
+
else:
|
|
266
|
+
return val
|
|
267
|
+
|
|
268
|
+
df_copy[col_name] = df_copy[col_name].apply(convert_dict_to_json)
|
|
269
|
+
|
|
270
|
+
return df_copy
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_utils.py
RENAMED
|
@@ -167,6 +167,9 @@ def format_sql_value(value: Any, column_type: Type, dialect: str = "standard") -
|
|
|
167
167
|
elif dialect == "redshift":
|
|
168
168
|
# Redshift SUPER type handles NULL maps
|
|
169
169
|
return "NULL::SUPER"
|
|
170
|
+
elif dialect == "bigquery":
|
|
171
|
+
# BigQuery JSON type handles NULL maps
|
|
172
|
+
return "NULL"
|
|
170
173
|
else:
|
|
171
174
|
return "NULL"
|
|
172
175
|
|
|
@@ -289,9 +292,14 @@ def format_sql_value(value: Any, column_type: Type, dialect: str = "standard") -
|
|
|
289
292
|
# Redshift uses SUPER type with JSON-like syntax for maps
|
|
290
293
|
json_str = json.dumps(value, cls=DecimalEncoder)
|
|
291
294
|
return f"JSON_PARSE('{json_str}')"
|
|
295
|
+
elif dialect == "bigquery":
|
|
296
|
+
# BigQuery stores JSON as strings
|
|
297
|
+
json_str = json.dumps(value, cls=DecimalEncoder)
|
|
298
|
+
# Escape single quotes in JSON string for SQL
|
|
299
|
+
json_str = json_str.replace("'", "''")
|
|
300
|
+
return f"'{json_str}'"
|
|
292
301
|
else:
|
|
293
302
|
# Other databases don't have native map support yet
|
|
294
|
-
# Could potentially use JSON for BigQuery, Snowflake
|
|
295
303
|
raise NotImplementedError(f"Map type not yet supported for dialect: {dialect}")
|
|
296
304
|
|
|
297
305
|
# Handle string types
|
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/athena.py
RENAMED
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/base.py
RENAMED
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/presto.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/trino.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_exceptions.py
RENAMED
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_mock_table.py
RENAMED
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_pytest_plugin.py
RENAMED
|
File without changes
|
{sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_logger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|