sql-testing-library 0.12.0__tar.gz → 0.13.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.12.0 → sql_testing_library-0.13.0}/CHANGELOG.md +10 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/PKG-INFO +3 -6
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/README.md +2 -5
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/pyproject.toml +2 -1
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/snowflake.py +2 -2
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_core.py +23 -2
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_pytest_plugin.py +7 -1
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_sql_logger.py +45 -7
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/LICENSE +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/__init__.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/__init__.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/athena.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/base.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/bigquery.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/presto.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/redshift.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/trino.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_exceptions.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_mock_table.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_sql_utils.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_types.py +0 -0
- {sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/py.typed +0 -0
|
@@ -5,6 +5,16 @@ 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.13.0 (2025-06-27)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add pytest-xdist support for parallel test execution (#105)
|
|
13
|
+
|
|
14
|
+
### Fix
|
|
15
|
+
|
|
16
|
+
- **snowflake**: fix issue related to physical view for snowflake (#104)
|
|
17
|
+
|
|
8
18
|
## 0.12.0 (2025-06-25)
|
|
9
19
|
|
|
10
20
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sql-testing-library
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -155,7 +155,7 @@ The library supports two execution modes for mock data injection. **CTE Mode is
|
|
|
155
155
|
| Execution Mode | Description | BigQuery | Athena | Redshift | Trino | Snowflake |
|
|
156
156
|
|----------------|-------------|----------|--------|----------|-------|-----------|
|
|
157
157
|
| **CTE Mode** | Mock data injected as Common Table Expressions | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
158
|
-
| **Physical Tables** | Mock data created as temporary tables | ✅ | ✅ | ✅ | ✅ |
|
|
158
|
+
| **Physical Tables** | Mock data created as temporary tables | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
159
159
|
|
|
160
160
|
### Execution Mode Details
|
|
161
161
|
|
|
@@ -306,7 +306,7 @@ def test_physical_tables():
|
|
|
306
306
|
**Notes:**
|
|
307
307
|
- **CTE Mode**: Default mode, works with all database engines, suitable for most use cases
|
|
308
308
|
- **Physical Tables**: Used automatically when CTE queries exceed database size limits or when explicitly requested
|
|
309
|
-
-
|
|
309
|
+
- **Snowflake**: Full support for both CTE and physical table modes
|
|
310
310
|
|
|
311
311
|
## Installation
|
|
312
312
|
|
|
@@ -1172,9 +1172,6 @@ For detailed usage and configuration options, see the example files included.
|
|
|
1172
1172
|
|
|
1173
1173
|
The library has a few known limitations that are planned to be addressed in future updates:
|
|
1174
1174
|
|
|
1175
|
-
### Snowflake Support
|
|
1176
|
-
- Physical table tests for Snowflake are currently skipped due to complex mocking requirements
|
|
1177
|
-
|
|
1178
1175
|
### General Improvements
|
|
1179
1176
|
- Add support for more SQL dialects
|
|
1180
1177
|
- Improve error handling for malformed SQL
|
|
@@ -98,7 +98,7 @@ The library supports two execution modes for mock data injection. **CTE Mode is
|
|
|
98
98
|
| Execution Mode | Description | BigQuery | Athena | Redshift | Trino | Snowflake |
|
|
99
99
|
|----------------|-------------|----------|--------|----------|-------|-----------|
|
|
100
100
|
| **CTE Mode** | Mock data injected as Common Table Expressions | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
101
|
-
| **Physical Tables** | Mock data created as temporary tables | ✅ | ✅ | ✅ | ✅ |
|
|
101
|
+
| **Physical Tables** | Mock data created as temporary tables | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
102
102
|
|
|
103
103
|
### Execution Mode Details
|
|
104
104
|
|
|
@@ -249,7 +249,7 @@ def test_physical_tables():
|
|
|
249
249
|
**Notes:**
|
|
250
250
|
- **CTE Mode**: Default mode, works with all database engines, suitable for most use cases
|
|
251
251
|
- **Physical Tables**: Used automatically when CTE queries exceed database size limits or when explicitly requested
|
|
252
|
-
-
|
|
252
|
+
- **Snowflake**: Full support for both CTE and physical table modes
|
|
253
253
|
|
|
254
254
|
## Installation
|
|
255
255
|
|
|
@@ -1115,9 +1115,6 @@ For detailed usage and configuration options, see the example files included.
|
|
|
1115
1115
|
|
|
1116
1116
|
The library has a few known limitations that are planned to be addressed in future updates:
|
|
1117
1117
|
|
|
1118
|
-
### Snowflake Support
|
|
1119
|
-
- Physical table tests for Snowflake are currently skipped due to complex mocking requirements
|
|
1120
|
-
|
|
1121
1118
|
### General Improvements
|
|
1122
1119
|
- Add support for more SQL dialects
|
|
1123
1120
|
- Improve error handling for malformed SQL
|
|
@@ -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.13.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>"]
|
|
@@ -104,6 +104,7 @@ pytest = ">=7.0.0"
|
|
|
104
104
|
pytest-asyncio = ">=0.21.0"
|
|
105
105
|
pytest-mock = ">=3.10.0"
|
|
106
106
|
pytest-cov = ">=4.1.0"
|
|
107
|
+
pytest-xdist = ">=3.0.0"
|
|
107
108
|
pytest-rerunfailures = ">=12.0.0"
|
|
108
109
|
black = ">=22.0.0"
|
|
109
110
|
flake8 = ">=4.0.0"
|
|
@@ -369,7 +369,7 @@ class SnowflakeAdapter(DatabaseAdapter):
|
|
|
369
369
|
snowflake_type = "VARIANT"
|
|
370
370
|
else:
|
|
371
371
|
snowflake_type = type_mapping.get(col_type, "VARCHAR")
|
|
372
|
-
column_defs.append(f
|
|
372
|
+
column_defs.append(f"{col_name} {snowflake_type}")
|
|
373
373
|
|
|
374
374
|
columns_sql = ",\n ".join(column_defs)
|
|
375
375
|
|
|
@@ -390,7 +390,7 @@ class SnowflakeAdapter(DatabaseAdapter):
|
|
|
390
390
|
col_type = column_types.get(col_name, str)
|
|
391
391
|
value = first_row[col_name]
|
|
392
392
|
formatted_value = self.format_value_for_cte(value, col_type)
|
|
393
|
-
select_expressions.append(f
|
|
393
|
+
select_expressions.append(f"{formatted_value} AS {col_name}")
|
|
394
394
|
|
|
395
395
|
# Start with the first row in the SELECT
|
|
396
396
|
select_sql = f"SELECT {', '.join(select_expressions)}"
|
|
@@ -556,8 +556,29 @@ class SQLTestFramework:
|
|
|
556
556
|
break
|
|
557
557
|
|
|
558
558
|
if replacement_name:
|
|
559
|
-
#
|
|
560
|
-
|
|
559
|
+
# Parse the replacement name to handle schema qualification
|
|
560
|
+
parts = replacement_name.split(".")
|
|
561
|
+
# Only quote identifiers for Snowflake physical tables (which contain TEMP_)
|
|
562
|
+
should_quote = "TEMP_" in replacement_name and dialect == "snowflake"
|
|
563
|
+
|
|
564
|
+
if len(parts) == 3:
|
|
565
|
+
# catalog.schema.table format
|
|
566
|
+
new_table = exp.Table(
|
|
567
|
+
this=exp.Identifier(this=parts[2], quoted=should_quote),
|
|
568
|
+
db=exp.Identifier(this=parts[1], quoted=should_quote),
|
|
569
|
+
catalog=exp.Identifier(this=parts[0], quoted=should_quote),
|
|
570
|
+
)
|
|
571
|
+
elif len(parts) == 2:
|
|
572
|
+
# schema.table format
|
|
573
|
+
new_table = exp.Table(
|
|
574
|
+
this=exp.Identifier(this=parts[1], quoted=should_quote),
|
|
575
|
+
db=exp.Identifier(this=parts[0], quoted=should_quote),
|
|
576
|
+
)
|
|
577
|
+
else:
|
|
578
|
+
# Just table name
|
|
579
|
+
new_table = exp.Table(
|
|
580
|
+
this=exp.Identifier(this=replacement_name, quoted=should_quote)
|
|
581
|
+
)
|
|
561
582
|
|
|
562
583
|
# Preserve the table alias if it exists
|
|
563
584
|
if hasattr(node, "alias") and node.alias:
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_pytest_plugin.py
RENAMED
|
@@ -489,9 +489,15 @@ def pytest_collection_modifyitems(config: pytest.Config, items: List[Item]) -> N
|
|
|
489
489
|
|
|
490
490
|
|
|
491
491
|
def pytest_configure(config: pytest.Config) -> None:
|
|
492
|
-
"""Register custom markers."""
|
|
492
|
+
"""Register custom markers and configure xdist worker detection."""
|
|
493
493
|
config.addinivalue_line("markers", "sql_test: mark test as a SQL test")
|
|
494
494
|
|
|
495
|
+
# Set up pytest-xdist worker ID if running in parallel
|
|
496
|
+
if hasattr(config, "workerinput"):
|
|
497
|
+
# We're running with pytest-xdist
|
|
498
|
+
worker_id = config.workerinput["workerid"] # type: ignore[attr-defined]
|
|
499
|
+
os.environ["PYTEST_XDIST_WORKER"] = worker_id
|
|
500
|
+
|
|
495
501
|
|
|
496
502
|
def pytest_runtest_call(item: Item) -> None:
|
|
497
503
|
"""Custom test execution for SQL tests."""
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_sql_logger.py
RENAMED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import threading
|
|
6
|
+
import uuid
|
|
5
7
|
from datetime import datetime
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any, Dict, List, Optional
|
|
@@ -17,6 +19,7 @@ class SQLLogger:
|
|
|
17
19
|
# Class variable to store the run directory for the current test session
|
|
18
20
|
_run_directory: Optional[Path] = None
|
|
19
21
|
_run_id: Optional[str] = None
|
|
22
|
+
_lock = threading.Lock() # Thread lock for concurrent access
|
|
20
23
|
|
|
21
24
|
def __init__(self, log_dir: Optional[str] = None) -> None:
|
|
22
25
|
"""Initialize SQL logger.
|
|
@@ -66,19 +69,44 @@ class SQLLogger:
|
|
|
66
69
|
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
67
70
|
self._logged_files: List[str] = []
|
|
68
71
|
|
|
72
|
+
def _get_worker_id(self) -> Optional[str]:
|
|
73
|
+
"""Get pytest-xdist worker ID if running in parallel.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Worker ID (e.g., 'gw0', 'gw1') or None if running serially
|
|
77
|
+
"""
|
|
78
|
+
# Check for pytest-xdist worker ID in environment
|
|
79
|
+
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
|
80
|
+
if worker_id:
|
|
81
|
+
return worker_id
|
|
82
|
+
|
|
83
|
+
# Alternative: Check for pytest-xdist worker input (for older versions)
|
|
84
|
+
# This would be set in conftest.py if needed
|
|
85
|
+
return os.environ.get("PYTEST_CURRENT_TEST_WORKER")
|
|
86
|
+
|
|
69
87
|
def _ensure_run_directory(self) -> Path:
|
|
70
88
|
"""Ensure run directory exists, creating it if necessary.
|
|
71
89
|
|
|
72
90
|
Returns:
|
|
73
91
|
Path to the run directory
|
|
74
92
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
with SQLLogger._lock:
|
|
94
|
+
# Create run directory if not already created for this session
|
|
95
|
+
if SQLLogger._run_directory is None:
|
|
96
|
+
# Generate run ID with timestamp
|
|
97
|
+
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
|
98
|
+
|
|
99
|
+
# Check if running in parallel mode
|
|
100
|
+
worker_id = self._get_worker_id()
|
|
101
|
+
if worker_id:
|
|
102
|
+
# Include worker ID in run directory name for parallel execution
|
|
103
|
+
SQLLogger._run_id = f"runid_{timestamp}_{worker_id}"
|
|
104
|
+
else:
|
|
105
|
+
# Serial execution - use standard run ID
|
|
106
|
+
SQLLogger._run_id = f"runid_{timestamp}"
|
|
107
|
+
|
|
108
|
+
SQLLogger._run_directory = self.log_dir / SQLLogger._run_id
|
|
109
|
+
SQLLogger._run_directory.mkdir(parents=True, exist_ok=True)
|
|
82
110
|
return SQLLogger._run_directory
|
|
83
111
|
|
|
84
112
|
def should_log(self, log_sql: Optional[bool] = None) -> bool:
|
|
@@ -138,10 +166,20 @@ class SQLLogger:
|
|
|
138
166
|
if failed:
|
|
139
167
|
components.append("FAILED")
|
|
140
168
|
|
|
169
|
+
# Add worker ID if running in parallel
|
|
170
|
+
worker_id = self._get_worker_id()
|
|
171
|
+
if worker_id:
|
|
172
|
+
components.append(f"w{worker_id}")
|
|
173
|
+
|
|
141
174
|
# Add timestamp for uniqueness
|
|
142
175
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # Milliseconds
|
|
143
176
|
components.append(timestamp)
|
|
144
177
|
|
|
178
|
+
# Add a short UUID suffix to absolutely guarantee uniqueness
|
|
179
|
+
# This handles edge cases where tests might start at exactly the same millisecond
|
|
180
|
+
unique_suffix = str(uuid.uuid4())[:8]
|
|
181
|
+
components.append(unique_suffix)
|
|
182
|
+
|
|
145
183
|
# Join with double underscore for clarity
|
|
146
184
|
filename = "__".join(components) + ".sql"
|
|
147
185
|
|
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_adapters/trino.py
RENAMED
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_exceptions.py
RENAMED
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_mock_table.py
RENAMED
|
File without changes
|
{sql_testing_library-0.12.0 → sql_testing_library-0.13.0}/src/sql_testing_library/_sql_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|