sql-testing-library 0.9.0__tar.gz → 0.10.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.
Files changed (22) hide show
  1. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/CHANGELOG.md +12 -0
  2. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/PKG-INFO +71 -9
  3. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/README.md +70 -8
  4. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/pyproject.toml +1 -1
  5. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/bigquery.py +74 -0
  6. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_exceptions.py +14 -3
  7. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_sql_utils.py +9 -1
  8. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/LICENSE +0 -0
  9. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/__init__.py +0 -0
  10. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/__init__.py +0 -0
  11. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/athena.py +0 -0
  12. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/base.py +0 -0
  13. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/presto.py +0 -0
  14. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/redshift.py +0 -0
  15. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/snowflake.py +0 -0
  16. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_adapters/trino.py +0 -0
  17. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_core.py +0 -0
  18. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_mock_table.py +0 -0
  19. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_pytest_plugin.py +0 -0
  20. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_sql_logger.py +0 -0
  21. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/_types.py +0 -0
  22. {sql_testing_library-0.9.0 → sql_testing_library-0.10.1}/src/sql_testing_library/py.typed +0 -0
@@ -5,6 +5,18 @@ 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.1 (2025-06-15)
9
+
10
+ ### Fix
11
+
12
+ - run unittests against different os/python versions (#100)
13
+
14
+ ## 0.10.0 (2025-06-15)
15
+
16
+ ### Feat
17
+
18
+ - **bigquery**: add support for map in bigquery (#99)
19
+
8
20
  ## 0.9.0 (2025-06-10)
9
21
 
10
22
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sql-testing-library
3
- Version: 0.9.0
3
+ Version: 0.10.1
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/Object** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
140
- | **Struct/Record** | `dict`/`dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
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
@@ -843,20 +842,83 @@ The adapter_type parameter will use the configuration from the corresponding sec
843
842
 
844
843
  ## Development Setup
845
844
 
845
+ ### Quick Start with Make
846
+
847
+ The project includes a Makefile for common development tasks:
848
+
849
+ ```bash
850
+ # Install all dependencies
851
+ make install
852
+
853
+ # Run unit tests
854
+ make test
855
+
856
+ # Run linting and type checking
857
+ make lint
858
+
859
+ # Format code
860
+ make format
861
+
862
+ # Run all checks (lint + format check + tests)
863
+ make check
864
+
865
+ # See all available commands
866
+ make help
867
+ ```
868
+
869
+ ### Available Make Commands
870
+
871
+ | Command | Description |
872
+ |---------|-------------|
873
+ | `make install` | Install all dependencies with poetry |
874
+ | `make test` | Run unit tests with coverage |
875
+ | `make test-unit` | Run unit tests (excludes integration tests) |
876
+ | `make test-integration` | Run integration tests (requires DB credentials) |
877
+ | `make test-all` | Run all tests (unit + integration) |
878
+ | `make test-tox` | Run tests across all Python versions (3.9-3.12) |
879
+ | `make lint` | Run ruff and mypy checks |
880
+ | `make format` | Format code with black and ruff |
881
+ | `make check` | Run all checks (lint + format + tests) |
882
+ | `make clean` | Remove build artifacts and cache files |
883
+ | `make build` | Build distribution packages |
884
+ | `make docs` | Build documentation |
885
+
886
+ ### Testing Across Python Versions
887
+
888
+ The project supports Python 3.9-3.12. You can test across all versions using:
889
+
890
+ ```bash
891
+ # Using tox (automatically tests all Python versions)
892
+ make test-tox
893
+
894
+ # Or directly with tox
895
+ tox
896
+
897
+ # Test specific Python version
898
+ tox -e py39 # Python 3.9
899
+ tox -e py310 # Python 3.10
900
+ tox -e py311 # Python 3.11
901
+ tox -e py312 # Python 3.12
902
+ ```
903
+
846
904
  ### Code Quality
847
905
 
848
906
  The project uses comprehensive tools to ensure code quality:
849
907
 
850
908
  1. **Ruff** for linting and formatting
851
- 2. **Pyright** for static type checking
852
- 3. **Pre-commit hooks** for automated checks
909
+ 2. **Black** for code formatting
910
+ 3. **Mypy** for static type checking
911
+ 4. **Pre-commit hooks** for automated checks
853
912
 
854
913
  To set up the development environment:
855
914
 
856
915
  1. Install development dependencies:
857
916
  ```bash
858
- # Install all dependencies including database adapters and dev tools
859
- poetry install --with bigquery,athena,redshift,trino,snowflake,dev
917
+ # Using make
918
+ make install
919
+
920
+ # Or directly with poetry
921
+ poetry install --all-extras
860
922
  ```
861
923
 
862
924
  2. Set up pre-commit hooks:
@@ -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/Object** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
83
- | **Struct/Record** | `dict`/`dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
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
@@ -786,20 +785,83 @@ The adapter_type parameter will use the configuration from the corresponding sec
786
785
 
787
786
  ## Development Setup
788
787
 
788
+ ### Quick Start with Make
789
+
790
+ The project includes a Makefile for common development tasks:
791
+
792
+ ```bash
793
+ # Install all dependencies
794
+ make install
795
+
796
+ # Run unit tests
797
+ make test
798
+
799
+ # Run linting and type checking
800
+ make lint
801
+
802
+ # Format code
803
+ make format
804
+
805
+ # Run all checks (lint + format check + tests)
806
+ make check
807
+
808
+ # See all available commands
809
+ make help
810
+ ```
811
+
812
+ ### Available Make Commands
813
+
814
+ | Command | Description |
815
+ |---------|-------------|
816
+ | `make install` | Install all dependencies with poetry |
817
+ | `make test` | Run unit tests with coverage |
818
+ | `make test-unit` | Run unit tests (excludes integration tests) |
819
+ | `make test-integration` | Run integration tests (requires DB credentials) |
820
+ | `make test-all` | Run all tests (unit + integration) |
821
+ | `make test-tox` | Run tests across all Python versions (3.9-3.12) |
822
+ | `make lint` | Run ruff and mypy checks |
823
+ | `make format` | Format code with black and ruff |
824
+ | `make check` | Run all checks (lint + format + tests) |
825
+ | `make clean` | Remove build artifacts and cache files |
826
+ | `make build` | Build distribution packages |
827
+ | `make docs` | Build documentation |
828
+
829
+ ### Testing Across Python Versions
830
+
831
+ The project supports Python 3.9-3.12. You can test across all versions using:
832
+
833
+ ```bash
834
+ # Using tox (automatically tests all Python versions)
835
+ make test-tox
836
+
837
+ # Or directly with tox
838
+ tox
839
+
840
+ # Test specific Python version
841
+ tox -e py39 # Python 3.9
842
+ tox -e py310 # Python 3.10
843
+ tox -e py311 # Python 3.11
844
+ tox -e py312 # Python 3.12
845
+ ```
846
+
789
847
  ### Code Quality
790
848
 
791
849
  The project uses comprehensive tools to ensure code quality:
792
850
 
793
851
  1. **Ruff** for linting and formatting
794
- 2. **Pyright** for static type checking
795
- 3. **Pre-commit hooks** for automated checks
852
+ 2. **Black** for code formatting
853
+ 3. **Mypy** for static type checking
854
+ 4. **Pre-commit hooks** for automated checks
796
855
 
797
856
  To set up the development environment:
798
857
 
799
858
  1. Install development dependencies:
800
859
  ```bash
801
- # Install all dependencies including database adapters and dev tools
802
- poetry install --with bigquery,athena,redshift,trino,snowflake,dev
860
+ # Using make
861
+ make install
862
+
863
+ # Or directly with poetry
864
+ poetry install --all-extras
803
865
  ```
804
866
 
805
867
  2. Set up pre-commit hooks:
@@ -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.9.0"
7
+ version = "0.10.1"
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
@@ -50,6 +50,17 @@ class TypeConversionError(SQLTestingError):
50
50
  self.value = value
51
51
  self.target_type = target_type
52
52
  self.column_name = column_name
53
- super().__init__(
54
- f"Cannot convert '{value}' to {target_type.__name__} for column '{column_name}'"
55
- )
53
+
54
+ # Handle type name extraction for various type forms
55
+ try:
56
+ type_name = target_type.__name__
57
+ except AttributeError:
58
+ # For types like Optional, Union, etc that don't have __name__
59
+ type_name = str(target_type)
60
+
61
+ if column_name:
62
+ message = f"Cannot convert '{value}' to {type_name} for column '{column_name}'"
63
+ else:
64
+ message = f"Cannot convert '{value}' to {type_name}"
65
+
66
+ super().__init__(message)
@@ -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