sparkless-testing 0.1.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.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Odos Matthews
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,445 @@
1
+ Metadata-Version: 2.4
2
+ Name: sparkless-testing
3
+ Version: 0.1.0
4
+ Summary: Easy testing with sparkless or PySpark on demand
5
+ Author-email: Odos Matthews <odosmatthews@gmail.com>
6
+ Maintainer-email: Odos Matthews <odosmatthews@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/eddiethedean/sparkless-testing
9
+ Project-URL: Repository, https://github.com/eddiethedean/sparkless-testing
10
+ Project-URL: Issues, https://github.com/eddiethedean/sparkless-testing/issues
11
+ Keywords: spark,pyspark,testing,pytest,sparkless,data-engineering,unit-testing,fixtures
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pytest>=7.0.0
26
+ Provides-Extra: mock
27
+ Requires-Dist: sparkless>=3.19.0; extra == "mock"
28
+ Provides-Extra: pyspark
29
+ Requires-Dist: pyspark<3.6.0,>=3.5.0; extra == "pyspark"
30
+ Provides-Extra: delta
31
+ Requires-Dist: delta-spark<4.0.0,>=3.0.0; extra == "delta"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
37
+ Requires-Dist: sparkless>=3.19.0; extra == "dev"
38
+ Requires-Dist: pyspark<3.6.0,>=3.5.0; extra == "dev"
39
+ Requires-Dist: delta-spark<4.0.0,>=3.0.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # sparkless-testing
43
+
44
+ > **Easy testing with sparkless or PySpark on demand**
45
+
46
+ A Python package that simplifies running tests with either **sparkless** (mock) or **PySpark** (real) engines on demand. Write tests once, run them with either engine seamlessly.
47
+
48
+ ## Why sparkless-testing?
49
+
50
+ - 🚀 **Fast Mock Testing**: Use `sparkless` for lightning-fast unit tests without JVM overhead
51
+ - 🔄 **Real Engine Validation**: Test against real PySpark to catch integration issues
52
+ - 🎯 **Single Test Suite**: Write tests once, run with both engines automatically
53
+ - 🔧 **Zero Configuration**: Automatic engine detection and session management
54
+ - ⚡ **Parallel Ready**: Optimized for parallel test execution with pytest-xdist
55
+
56
+ ## Features
57
+
58
+ - **Automatic Engine Detection** - Automatically detects and configures available engines
59
+ - **Pytest Fixtures** - Ready-to-use fixtures for both engines
60
+ - **Session Management** - Automatic session creation, cleanup, and isolation
61
+ - **Test Utilities** - Helpers for common test patterns
62
+ - **Parametrization Support** - Run tests with both engines automatically
63
+ - **Parallel Testing Support** - Optimized for parallel test execution with pytest-xdist
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install sparkless-testing
69
+ ```
70
+
71
+ For specific engines:
72
+
73
+ ```bash
74
+ # Install with mock engine (sparkless)
75
+ pip install sparkless-testing[mock]
76
+
77
+ # Install with PySpark engine
78
+ pip install sparkless-testing[pyspark]
79
+
80
+ # Install with Delta Lake support
81
+ pip install sparkless-testing[delta]
82
+
83
+ # Install with all engines
84
+ pip install sparkless-testing[dev]
85
+ ```
86
+
87
+ ## Quick Start
88
+
89
+ ### Basic Usage
90
+
91
+ ```python
92
+ import pytest
93
+ from sparkless_testing import pytest_fixtures
94
+
95
+ # Use the spark_session fixture
96
+ def test_my_function(spark_session):
97
+ df = spark_session.createDataFrame([{"id": 1, "name": "Alice"}])
98
+ assert df.count() == 1
99
+ assert df.collect()[0]["name"] == "Alice"
100
+ ```
101
+
102
+ ### Explicit Engine Selection
103
+
104
+ ```python
105
+ def test_mock_only(mock_spark_session):
106
+ # Only runs with sparkless
107
+ df = mock_spark_session.createDataFrame([{"id": 1}])
108
+ assert df.count() == 1
109
+
110
+ def test_pyspark_only(pyspark_session):
111
+ # Only runs with PySpark
112
+ df = pyspark_session.createDataFrame([{"id": 1}])
113
+ assert df.count() == 1
114
+ ```
115
+
116
+ ### Using Functions and Types
117
+
118
+ ```python
119
+ def test_with_functions(spark_session, spark_functions, spark_types):
120
+ F = spark_functions
121
+ Types = spark_types
122
+
123
+ df = spark_session.createDataFrame([{"name": "Alice"}])
124
+ result = df.select(F.upper(F.col("name"))).collect()
125
+ assert result[0][0] == "ALICE"
126
+ ```
127
+
128
+ **Note**: Always use `spark_functions` together with `spark_session`. PySpark functions like `F.col()` require an active SparkContext, which is provided by the `spark_session` fixture.
129
+
130
+ ### Parametrized Tests (Both Engines)
131
+
132
+ You can run tests with both engines in two ways:
133
+
134
+ **Option 1: Using the decorator**
135
+ ```python
136
+ @pytest.mark.parametrize_engines
137
+ def test_both_engines(spark_session):
138
+ # Runs with both engines automatically
139
+ df = spark_session.createDataFrame([{"id": 1}])
140
+ assert df.count() == 1
141
+ ```
142
+
143
+ **Option 2: Using the `both_engines` fixture**
144
+ ```python
145
+ def test_both_engines(both_engines):
146
+ spark, engine_type = both_engines
147
+ # Runs with both engines automatically
148
+ df = spark.createDataFrame([{"id": 1}])
149
+ assert df.count() == 1
150
+ # engine_type will be EngineType.MOCK or EngineType.PYSPARK
151
+ ```
152
+
153
+ ### Environment-Based Configuration
154
+
155
+ ```bash
156
+ # Run tests with mock engine
157
+ SPARK_MODE=mock pytest tests/
158
+
159
+ # Run tests with PySpark engine
160
+ SPARK_MODE=pyspark pytest tests/
161
+
162
+ # Auto-detect (default)
163
+ SPARK_MODE=auto pytest tests/
164
+ ```
165
+
166
+ ### Pytest Markers
167
+
168
+ ```python
169
+ @pytest.mark.spark_engine("mock")
170
+ def test_mock_specific(spark_session):
171
+ # Forces mock engine
172
+ pass
173
+
174
+ @pytest.mark.spark_engine("pyspark")
175
+ def test_pyspark_specific(spark_session):
176
+ # Forces PySpark engine
177
+ pass
178
+ ```
179
+
180
+ ## Configuration
181
+
182
+ ### Environment Variables
183
+
184
+ - `SPARK_MODE`: `mock`, `pyspark`, or `auto` (default: `auto`)
185
+ - `SPARK_TEST_WAREHOUSE_DIR`: Custom warehouse directory for PySpark tests
186
+ - `SPARK_TEST_APP_NAME`: Custom app name prefix (default: `sparkless-testing`)
187
+
188
+ ### Programmatic Configuration
189
+
190
+ ```python
191
+ from sparkless_testing import (
192
+ EngineType,
193
+ SessionConfig,
194
+ auto_configure_engine,
195
+ create_session,
196
+ create_pyspark_session,
197
+ )
198
+
199
+ # Auto-configure based on availability
200
+ auto_configure_engine()
201
+
202
+ # Or specify preferred engine
203
+ auto_configure_engine(EngineType.MOCK)
204
+
205
+ # Create session manually
206
+ spark = create_session(engine_type=EngineType.MOCK)
207
+
208
+ # Create session with custom configuration
209
+ config = SessionConfig(
210
+ app_name="my-test",
211
+ enable_delta=True,
212
+ shuffle_partitions=2,
213
+ )
214
+ spark = create_pyspark_session(config=config)
215
+ # ... use spark ...
216
+ spark.stop()
217
+ ```
218
+
219
+ ## API Reference
220
+
221
+ ### Engine Configuration
222
+
223
+ - `EngineType`: Enum for engine types (`MOCK`, `PYSPARK`, `AUTO`)
224
+ - `configure_engine()`: Manually configure engine components
225
+ - `get_engine()`: Get current engine configuration
226
+ - `detect_available_engines()`: Check which engines are installed
227
+ - `auto_configure_engine()`: Automatically configure based on availability
228
+
229
+ ### Session Factory
230
+
231
+ - `create_mock_session()`: Create sparkless session
232
+ - `create_pyspark_session()`: Create PySpark session
233
+ - `create_session()`: Factory function that creates appropriate session
234
+ - `SessionConfig`: Configuration dataclass for session creation
235
+ - `app_name`: Application name for the Spark session
236
+ - `warehouse_dir`: Custom warehouse directory (optional)
237
+ - `enable_delta`: Enable Delta Lake support (default: `True` for programmatic use, `False` in test fixtures)
238
+ - `enable_ui`: Enable Spark UI (default: `False`)
239
+ - `shuffle_partitions`: Number of shuffle partitions (default: `1`)
240
+ - `parallelism`: Default parallelism (default: `1`)
241
+ - `adaptive_enabled`: Enable adaptive query execution (default: `False`)
242
+
243
+ ### Test Utilities
244
+
245
+ - `detect_spark_type(spark)`: Detect if session is PySpark or mock
246
+ - `create_test_dataframe(spark, data, schema)`: Compatibility wrapper for DataFrame creation
247
+ - `is_dataframe_like(obj)`: Check if object is DataFrame-like
248
+
249
+ ### Pytest Fixtures
250
+
251
+ - `spark_session`: Main fixture (auto-detects engine based on `SPARK_MODE` or markers)
252
+ - `mock_spark_session`: Explicitly use sparkless (mock) engine
253
+ - `pyspark_session`: Explicitly use PySpark (real) engine
254
+ - `both_engines`: Fixture that yields `(spark, engine_type)` for both engines (parametrized)
255
+ - `spark_functions`: Functions module (F) for current engine
256
+ - `spark_types`: Types module for current engine
257
+ - `spark_engine_type`: Current engine type as string (`"mock"` or `"pyspark"`)
258
+
259
+ **Note**: Test fixtures automatically disable Delta Lake for parallel testing compatibility. To use Delta Lake, create sessions programmatically with `enable_delta=True` in `SessionConfig`.
260
+
261
+ ## Advanced Usage
262
+
263
+ ### Custom Session Configuration
264
+
265
+ ```python
266
+ from sparkless_testing import SessionConfig, create_pyspark_session
267
+
268
+ def test_with_custom_config():
269
+ config = SessionConfig(
270
+ app_name="custom-test",
271
+ warehouse_dir="/tmp/my-warehouse",
272
+ enable_delta=True,
273
+ enable_ui=True,
274
+ shuffle_partitions=4,
275
+ parallelism=4,
276
+ )
277
+ spark = create_pyspark_session(config=config)
278
+ # ... your test code ...
279
+ spark.stop()
280
+ ```
281
+
282
+ ### Parallel Test Execution
283
+
284
+ ```bash
285
+ # Run tests in parallel with pytest-xdist
286
+ pytest -n 2 # Use 2 workers
287
+ pytest -n auto # Auto-detect worker count
288
+ ```
289
+
290
+ **Best Practices for Parallel Testing:**
291
+ - Mock (sparkless) tests can run with high parallelism (`-n 10+`)
292
+ - PySpark tests work best with fewer workers (`-n 2` or `-n 4`)
293
+ - Test fixtures automatically disable Delta Lake to avoid conflicts
294
+ - Each test gets a unique session name and warehouse directory
295
+
296
+ ### Using Test Utilities
297
+
298
+ ```python
299
+ from sparkless_testing.utils import create_test_dataframe, detect_spark_type
300
+
301
+ def test_with_utilities(spark_session):
302
+ # Create DataFrame with schema handling
303
+ data = [("Alice", 25), ("Bob", 30)]
304
+ schema = ["name", "age"]
305
+ df = create_test_dataframe(spark_session, data, schema)
306
+
307
+ # Detect engine type
308
+ engine = detect_spark_type(spark_session)
309
+ assert engine in ("mock", "pyspark")
310
+ ```
311
+
312
+ ## Examples
313
+
314
+ ### Complete Test Example
315
+
316
+ ```python
317
+ import pytest
318
+ from sparkless_testing.pytest_fixtures import spark_session, spark_functions
319
+
320
+ def test_data_transformation(spark_session, spark_functions):
321
+ F = spark_functions
322
+
323
+ # Create test data
324
+ data = [
325
+ {"id": 1, "name": "Alice", "age": 25},
326
+ {"id": 2, "name": "Bob", "age": 30},
327
+ ]
328
+ df = spark_session.createDataFrame(data)
329
+
330
+ # Transform data
331
+ result = (
332
+ df.filter(F.col("age") > 25)
333
+ .select("name", "age")
334
+ .collect()
335
+ )
336
+
337
+ # Assertions
338
+ assert len(result) == 1
339
+ assert result[0]["name"] == "Bob"
340
+ ```
341
+
342
+ ### Using with Both Engines
343
+
344
+ ```python
345
+ @pytest.mark.parametrize_engines
346
+ def test_compatibility(spark_session, spark_engine_type):
347
+ # This test runs with both engines
348
+ df = spark_session.createDataFrame([{"value": 42}])
349
+
350
+ # Engine-specific logic if needed
351
+ if spark_engine_type == "mock":
352
+ # Mock-specific assertions
353
+ pass
354
+ else:
355
+ # PySpark-specific assertions
356
+ pass
357
+
358
+ assert df.count() == 1
359
+ ```
360
+
361
+ ## Troubleshooting
362
+
363
+ ### Tests Hang with Parallel Execution
364
+
365
+ If tests hang when using `pytest -n`, try:
366
+ - Use fewer workers: `pytest -n 2` instead of `pytest -n 10`
367
+ - Run PySpark tests sequentially: `pytest -n 0` for PySpark-only tests
368
+ - Use mock engine for faster parallel execution: `SPARK_MODE=mock pytest -n 10`
369
+
370
+ ### Delta Lake Not Available
371
+
372
+ Delta Lake is disabled by default in test fixtures. To enable:
373
+ ```python
374
+ from sparkless_testing import SessionConfig, create_pyspark_session
375
+
376
+ config = SessionConfig(app_name="test", enable_delta=True)
377
+ spark = create_pyspark_session(config=config)
378
+ ```
379
+
380
+ ### SparkContext Errors
381
+
382
+ Always use `spark_functions` together with `spark_session`:
383
+ ```python
384
+ def test_correct(spark_session, spark_functions): # ✅ Correct
385
+ F = spark_functions
386
+ df = spark_session.createDataFrame([{"x": 1}])
387
+ result = df.select(F.col("x")).collect()
388
+
389
+ def test_incorrect(spark_functions): # ❌ May fail with PySpark
390
+ F = spark_functions
391
+ # Missing active SparkContext
392
+ ```
393
+
394
+ ## Migration Guide
395
+
396
+ ### From Manual Engine Switching
397
+
398
+ **Before:**
399
+ ```python
400
+ import os
401
+ from sparkless import SparkSession as MockSparkSession
402
+ from pyspark.sql import SparkSession as PySparkSession
403
+
404
+ def test_my_function():
405
+ if os.environ.get("SPARK_MODE") == "mock":
406
+ spark = MockSparkSession("test")
407
+ else:
408
+ spark = PySparkSession.builder.appName("test").getOrCreate()
409
+ # ... test code ...
410
+ spark.stop()
411
+ ```
412
+
413
+ **After:**
414
+ ```python
415
+ from sparkless_testing.pytest_fixtures import spark_session
416
+
417
+ def test_my_function(spark_session):
418
+ # ... test code ...
419
+ # spark_session is automatically cleaned up
420
+ ```
421
+
422
+ ## Requirements
423
+
424
+ - Python 3.9+
425
+ - pytest 7.0+
426
+
427
+ Optional:
428
+ - sparkless>=3.19.0 (for mock engine)
429
+ - pyspark>=3.5.0 (for real engine)
430
+ - delta-spark>=3.0.0 (for Delta Lake support)
431
+ - pytest-xdist (for parallel test execution)
432
+
433
+ ## License
434
+
435
+ MIT License - see [LICENSE](LICENSE) file for details.
436
+
437
+ ## Links
438
+
439
+ - **GitHub**: [github.com/eddiethedean/sparkless-testing](https://github.com/eddiethedean/sparkless-testing)
440
+ - **Issues**: [github.com/eddiethedean/sparkless-testing/issues](https://github.com/eddiethedean/sparkless-testing/issues)
441
+
442
+ ---
443
+
444
+ **Made with ❤️ for the data engineering community**
445
+