From 2ff246ce1605222fb834a6f39feeea3d25d45d3d Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Mon, 19 Jan 2026 22:13:10 -0500 Subject: [PATCH 1/7] V2 DuckDB engine --- .gitignore | 4 +- BENCHMARK.md | 185 +++++ Engines.md | 344 +++++++++ benchmark/__init__.py | 28 + benchmark/config.py | 107 +++ benchmark/experiments.py | 547 +++++++++++++++ benchmark/report.py | 210 ++++++ benchmark/results.py | 286 ++++++++ benchmark/spark_server.py | 197 ++++++ benchmark/visualize.py | 288 ++++++++ benchmark/worker.py | 189 +++++ benchmark_cli.py | 354 ++++++++++ docs/architecture.md | 214 ++++++ imgs/benchmark_chart.png | Bin 0 -> 180620 bytes pydeequ/__init__.py | 53 ++ pydeequ/configs.py | 2 +- pydeequ/engines/__init__.py | 389 +++++++++++ pydeequ/engines/constraints/__init__.py | 125 ++++ pydeequ/engines/constraints/base.py | 271 ++++++++ pydeequ/engines/constraints/evaluators.py | 494 +++++++++++++ pydeequ/engines/constraints/factory.py | 136 ++++ pydeequ/engines/constraints/protocols.py | 73 ++ pydeequ/engines/duckdb.py | 427 ++++++++++++ pydeequ/engines/operators/__init__.py | 132 ++++ pydeequ/engines/operators/base.py | 178 +++++ pydeequ/engines/operators/factory.py | 277 ++++++++ .../engines/operators/grouping_operators.py | 334 +++++++++ .../engines/operators/metadata_operators.py | 145 ++++ pydeequ/engines/operators/mixins.py | 188 +++++ .../engines/operators/profiling_operators.py | 413 +++++++++++ pydeequ/engines/operators/protocols.py | 100 +++ pydeequ/engines/operators/scan_operators.py | 502 ++++++++++++++ pydeequ/engines/spark.py | 264 +++++++ pydeequ/engines/suggestions/__init__.py | 67 ++ pydeequ/engines/suggestions/registry.py | 98 +++ pydeequ/engines/suggestions/rules.py | 380 ++++++++++ pydeequ/engines/suggestions/runner.py | 178 +++++ pydeequ/v2/analyzers.py | 2 +- pydeequ/v2/checks.py | 2 +- pydeequ/v2/predicates.py | 46 ++ pydeequ/v2/profiles.py | 130 +++- pydeequ/v2/suggestions.py | 140 +++- pydeequ/v2/verification.py | 254 ++++++- pyproject.toml | 1 + tests/engines/__init__.py | 19 + tests/engines/comparison/__init__.py | 19 + tests/engines/comparison/conftest.py | 241 +++++++ .../comparison/test_analyzer_parity.py | 393 +++++++++++ .../comparison/test_constraint_parity.py | 334 +++++++++ .../engines/comparison/test_profile_parity.py | 142 ++++ .../comparison/test_suggestion_parity.py | 222 ++++++ tests/engines/comparison/utils.py | 420 +++++++++++ tests/engines/conftest.py | 330 +++++++++ tests/engines/fixtures/__init__.py | 36 + tests/engines/fixtures/datasets.py | 529 ++++++++++++++ tests/engines/test_constraint_evaluators.py | 473 +++++++++++++ tests/engines/test_duckdb_analyzers.py | 650 ++++++++++++++++++ tests/engines/test_duckdb_constraints.py | 640 +++++++++++++++++ tests/engines/test_duckdb_profiles.py | 266 +++++++ tests/engines/test_duckdb_suggestions.py | 287 ++++++++ tests/engines/test_operators.py | 484 +++++++++++++ tests/engines/test_suggestion_rules.py | 462 +++++++++++++ tutorials/data_quality_example_duckdb.py | 258 +++++++ 63 files changed, 14922 insertions(+), 37 deletions(-) create mode 100644 BENCHMARK.md create mode 100644 Engines.md create mode 100644 benchmark/__init__.py create mode 100644 benchmark/config.py create mode 100644 benchmark/experiments.py create mode 100644 benchmark/report.py create mode 100644 benchmark/results.py create mode 100644 benchmark/spark_server.py create mode 100644 benchmark/visualize.py create mode 100644 benchmark/worker.py create mode 100644 benchmark_cli.py create mode 100644 docs/architecture.md create mode 100644 imgs/benchmark_chart.png create mode 100644 pydeequ/engines/__init__.py create mode 100644 pydeequ/engines/constraints/__init__.py create mode 100644 pydeequ/engines/constraints/base.py create mode 100644 pydeequ/engines/constraints/evaluators.py create mode 100644 pydeequ/engines/constraints/factory.py create mode 100644 pydeequ/engines/constraints/protocols.py create mode 100644 pydeequ/engines/duckdb.py create mode 100644 pydeequ/engines/operators/__init__.py create mode 100644 pydeequ/engines/operators/base.py create mode 100644 pydeequ/engines/operators/factory.py create mode 100644 pydeequ/engines/operators/grouping_operators.py create mode 100644 pydeequ/engines/operators/metadata_operators.py create mode 100644 pydeequ/engines/operators/mixins.py create mode 100644 pydeequ/engines/operators/profiling_operators.py create mode 100644 pydeequ/engines/operators/protocols.py create mode 100644 pydeequ/engines/operators/scan_operators.py create mode 100644 pydeequ/engines/spark.py create mode 100644 pydeequ/engines/suggestions/__init__.py create mode 100644 pydeequ/engines/suggestions/registry.py create mode 100644 pydeequ/engines/suggestions/rules.py create mode 100644 pydeequ/engines/suggestions/runner.py create mode 100644 tests/engines/__init__.py create mode 100644 tests/engines/comparison/__init__.py create mode 100644 tests/engines/comparison/conftest.py create mode 100644 tests/engines/comparison/test_analyzer_parity.py create mode 100644 tests/engines/comparison/test_constraint_parity.py create mode 100644 tests/engines/comparison/test_profile_parity.py create mode 100644 tests/engines/comparison/test_suggestion_parity.py create mode 100644 tests/engines/comparison/utils.py create mode 100644 tests/engines/conftest.py create mode 100644 tests/engines/fixtures/__init__.py create mode 100644 tests/engines/fixtures/datasets.py create mode 100644 tests/engines/test_constraint_evaluators.py create mode 100644 tests/engines/test_duckdb_analyzers.py create mode 100644 tests/engines/test_duckdb_constraints.py create mode 100644 tests/engines/test_duckdb_profiles.py create mode 100644 tests/engines/test_duckdb_suggestions.py create mode 100644 tests/engines/test_operators.py create mode 100644 tests/engines/test_suggestion_rules.py create mode 100644 tutorials/data_quality_example_duckdb.py diff --git a/.gitignore b/.gitignore index b4b68d0..1c912e4 100644 --- a/.gitignore +++ b/.gitignore @@ -148,5 +148,7 @@ dmypy.json # Cython debug symbols cython_debug/ -# DS_STORE +# DS_STORE .DS_Store + +benchmark_results diff --git a/BENCHMARK.md b/BENCHMARK.md new file mode 100644 index 0000000..856bb20 --- /dev/null +++ b/BENCHMARK.md @@ -0,0 +1,185 @@ +# PyDeequ Benchmark + +Benchmark harness for comparing DuckDB and Spark engine performance. + +## Design Overview + +### Architecture + +``` +benchmark_cli.py # CLI entry point +benchmark/ +├── config.py # Configuration dataclasses +├── experiments.py # Experiment logic (data gen, checks, profiling) +├── worker.py # Subprocess worker for process isolation +├── spark_server.py # Auto Spark Connect server management +├── results.py # Results storage and merging +├── report.py # Markdown report generation +└── visualize.py # PNG chart generation +``` + +### Process Isolation + +Each engine runs in a separate subprocess to ensure: +- Clean JVM state for Spark +- Independent memory allocation +- No cross-contamination between engines + +### Data Pipeline + +1. **Generate** synthetic mixed-type data (strings, floats, ints) +2. **Cache** as Parquet files with optimized row groups +3. **Load** from same Parquet files for both engines (fair comparison) + +## Experiments + +### 1. Varying Rows +- Fixed: 10 columns, 16 data quality checks +- Variable: 100K to 130M rows +- Measures: Validation time scaling with data size + +### 2. Varying Columns +- Fixed: 1M rows +- Variable: 10 to 80 columns (16 to 226 checks) +- Measures: Validation time scaling with schema complexity + +### 3. Column Profiling +- Fixed: 10 columns +- Variable: 100K to 10M rows +- Measures: Full column profiling performance + +## Results + +Benchmark run on Apple M3 Max (14 cores), macOS Darwin 25.2.0. + +![Benchmark Results](imgs/benchmark_chart.png) + +### Experiment 1: Varying Rows + +| Rows | DuckDB (s) | Spark (s) | Speedup | +|------|------------|-----------|---------| +| 100K | 0.080 | 1.159 | **14.6x** | +| 1M | 0.114 | 1.824 | **16.0x** | +| 5M | 0.243 | 2.491 | **10.3x** | +| 10M | 0.354 | 3.276 | **9.2x** | +| 50M | 1.153 | 10.959 | **9.5x** | +| 130M | 2.792 | 27.385 | **9.8x** | + +### Experiment 2: Varying Columns + +| Cols | Checks | DuckDB (s) | Spark (s) | Speedup | +|------|--------|------------|-----------|---------| +| 10 | 16 | 0.108 | 1.572 | **14.5x** | +| 20 | 46 | 0.280 | 2.049 | **7.3x** | +| 40 | 106 | 0.824 | 2.760 | **3.3x** | +| 80 | 226 | 2.320 | 4.425 | **1.9x** | + +### Experiment 3: Column Profiling + +| Rows | DuckDB (s) | Spark (s) | Speedup | +|------|------------|-----------|---------| +| 100K | 0.097 | 0.651 | **6.7x** | +| 1M | 0.372 | 0.778 | **2.1x** | +| 5M | 1.446 | 1.898 | **1.3x** | +| 10M | 2.614 | 3.450 | **1.3x** | + +### Key Takeaways + +1. **DuckDB is 9-16x faster** for row-scaling validation workloads +2. **Speedup decreases with complexity** - more columns/checks narrow the gap +3. **Profiling converges** - at 10M rows, both engines perform similarly +4. **No JVM overhead** - DuckDB runs natively in Python, no startup cost + +## Quick Start + +### Run DuckDB Only (No Spark Required) + +```bash +python benchmark_cli.py run --engine duckdb +``` + +### Run Both Engines + +```bash +python benchmark_cli.py run --engine all +``` + +Auto-spark is enabled by default. The harness will: +1. Start a Spark Connect server +2. Run DuckDB benchmarks +3. Run Spark benchmarks +4. Stop the server +5. Merge results + +### Run with External Spark Server + +```bash +# Start server manually first, then: +python benchmark_cli.py run --engine spark --no-auto-spark +``` + +## Output Structure + +Each run creates a timestamped folder: + +``` +benchmark_results/ +└── benchmark_2024-01-19T14-30-45/ + ├── results.json # Raw timing data + └── BENCHMARK_RESULTS.md # Markdown report +``` + +## Visualize Results + +Generate a PNG chart comparing engine performance: + +```bash +# From run folder +python benchmark_cli.py visualize benchmark_results/benchmark_2024-01-19T14-30-45/ + +# Custom output path +python benchmark_cli.py visualize benchmark_results/benchmark_2024-01-19T14-30-45/ -o comparison.png +``` + +The chart shows: +- **Top row**: Time comparisons (DuckDB vs Spark) for each experiment +- **Bottom row**: Speedup ratios (how many times faster DuckDB is) + +## Regenerate Report + +```bash +python benchmark_cli.py report benchmark_results/benchmark_2024-01-19T14-30-45/ +``` + +## Configuration + +Default experiment parameters (see `benchmark/config.py`): + +| Parameter | Default | +|-----------|---------| +| Row counts | 100K, 1M, 5M, 10M, 50M, 130M | +| Column counts | 10, 20, 40, 80 | +| Profiling rows | 100K, 1M, 5M, 10M | +| Validation runs | 3 (averaged) | +| Cache directory | `~/.deequ_benchmark_data` | + +## Requirements + +- **DuckDB**: No additional setup +- **Spark**: Requires `SPARK_HOME` and `JAVA_HOME` environment variables (or use `--spark-home`/`--java-home` flags) + +## Example Workflow + +```bash +# 1. Run full benchmark +python benchmark_cli.py run --engine all + +# 2. View results +cat benchmark_results/benchmark_*/BENCHMARK_RESULTS.md + +# 3. Generate chart +python benchmark_cli.py visualize benchmark_results/benchmark_*/ + +# 4. Open chart +open benchmark_results/benchmark_*/benchmark_chart.png +``` diff --git a/Engines.md b/Engines.md new file mode 100644 index 0000000..4a08feb --- /dev/null +++ b/Engines.md @@ -0,0 +1,344 @@ +# Engine Parity Analysis Report + +## Executive Summary + +This report documents 18 remaining parity test failures between the Spark and DuckDB engines in python-deequ. The failures are grouped by root cause to facilitate systematic resolution. + +### Summary by Category + +| Category | Test Failures | Root Cause | +|----------|---------------|------------| +| Standard Deviation | 2 tests | Population vs Sample formula | +| Approximate Algorithms | 3 tests | Different HyperLogLog/quantile implementations | +| Information Theory | 3 tests | Floating-point precision in log calculations | +| Output Format | 2 tests | JSON string vs structured Map | +| Profile Statistics | 8 tests | Combination of above issues | + +### Impact Assessment + +- **Critical**: Standard deviation and output format differences affect basic analyzer functionality +- **Moderate**: Approximate algorithm differences are inherent to probabilistic data structures +- **Low**: Information theory precision differences are minor numerical variations + +### Recommended Priority Order + +1. Output format differences (quick fix, high impact) +2. Standard deviation formula alignment (configuration option) +3. Tolerance adjustments for approximate metrics +4. Information theory numerical stability (long-term) + +--- + +## Detailed Analysis by Category + +### 2.1 Standard Deviation Differences + +**Affected Tests:** +- `test_standard_deviation` +- `test_numeric_statistics` (profile) +- `test_all_basic_analyzers` + +**Root Cause:** + +DuckDB uses `STDDEV_SAMP()` which implements the sample standard deviation formula with N-1 denominator (Bessel's correction). Spark may use the population formula (N denominator) or a different algorithm. + +**Evidence:** + +``` +Spark: std_dev(price) = 11.18 (population formula) +DuckDB: std_dev(price) = 12.91 (sample formula) +``` + +The relationship between these values follows the expected formula: +- Sample std = Population std × √(N/(N-1)) +- For small datasets, this difference is significant + +**Options to Bridge:** + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| 1. Config option | Add setting to switch between `STDDEV_SAMP`/`STDDEV_POP` | Flexible, user choice | More complexity | +| 2. Match Spark | Determine which formula Spark uses and replicate | True parity | May not match DuckDB conventions | +| 3. Increase tolerance | Relax test assertions for statistical metrics | Quick fix | Doesn't address root cause | + +**Recommended Fix:** Option 1 - Add configuration option with Spark's formula as default for parity mode. + +--- + +### 2.2 Approximate Count Distinct + +**Affected Tests:** +- `test_approx_count_distinct` +- `test_distinct_values` (profile) + +**Root Cause:** + +Different HyperLogLog implementations with different precision parameters: +- Spark uses HyperLogLog++ with configurable precision (default p=14) +- DuckDB uses its own HyperLogLog implementation + +**Evidence:** + +Approximate distinct counts can vary by several percent between engines due to: +- Different hash functions +- Different precision parameters +- Different merging algorithms + +**Options to Bridge:** + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| 1. Exact count for small data | Use `COUNT(DISTINCT)` when dataset is small | Accurate for tests | Performance impact at scale | +| 2. Increase tolerance | Allow >10% variance for approximate metrics | Realistic expectation | Less precise tests | +| 3. Document as expected | Mark as known variance in documentation | Honest about limitations | Doesn't "fix" tests | + +**Recommended Fix:** Option 1 for test datasets, Option 2 for production with appropriate tolerance. + +--- + +### 2.3 Entropy & Mutual Information + +**Affected Tests:** +- `test_entropy_uniform` +- `test_mutual_information` +- `test_has_entropy` (constraint) + +**Root Cause:** + +Floating-point precision differences in logarithmic calculations: +- Log base conversions (ln vs log2) +- Numerical stability in probability calculations +- Handling of edge cases (p=0, p=1) + +**Evidence:** + +``` +Spark entropy: 2.3219280948873626 +DuckDB entropy: 2.321928094887362 (may differ in last digits) +``` + +Differences in the 15th-16th decimal places are common due to: +- Different order of floating-point operations +- Different handling of -0 × log(0) = 0 convention + +**Options to Bridge:** + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| 1. Round before compare | Round to fewer decimal places | Simple fix | Loses precision info | +| 2. Log-sum-exp trick | Use numerically stable algorithms | Mathematically correct | Implementation effort | +| 3. Increase tolerance | Use relative tolerance ~1e-10 | Acknowledges FP limits | May mask real bugs | + +**Recommended Fix:** Option 3 - Use relative tolerance appropriate for floating-point comparison. + +--- + +### 2.4 Quantile Calculations + +**Affected Tests:** +- `test_approx_quantile_median` +- `test_approx_quantile_quartiles` + +**Root Cause:** + +Different interpolation methods for percentile calculation: +- Spark uses T-Digest algorithm for approximate quantiles +- DuckDB uses `QUANTILE_CONT` (continuous) or `QUANTILE_DISC` (discrete) + +**Evidence:** + +For a dataset [1, 2, 3, 4, 5]: +``` +Median with interpolation: 3.0 +Median discrete: 3 +Q1 with interpolation: 1.75 or 2.0 (depends on method) +``` + +There are 9 different interpolation methods defined in statistics literature. + +**Options to Bridge:** + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| 1. Use PERCENTILE_DISC | Exact percentiles, no interpolation | Deterministic | Different semantics | +| 2. Increase tolerance | Allow small variance in quantiles | Pragmatic | Imprecise tests | +| 3. Document methods | Specify interpolation method used | Clear expectations | Doesn't achieve parity | + +**Recommended Fix:** Option 2 with documentation of method differences (Option 3). + +--- + +### 2.5 Output Format Differences + +**Affected Tests:** +- `test_histogram` +- `test_data_type` + +**Root Cause:** + +Different output formats for complex types: +- DuckDB returns JSON strings for maps/structs: `'{"bin1": 10, "bin2": 20}'` +- Spark returns native structured Maps: `Map(bin1 -> 10, bin2 -> 20)` + +**Evidence:** + +```python +# DuckDB histogram output +'{"[0,10)": 5, "[10,20)": 3, "[20,30)": 2}' + +# Spark histogram output +{'[0,10)': 5, '[10,20)': 3, '[20,30)': 2} +``` + +**Options to Bridge:** + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| 1. Parse JSON in comparison | Convert JSON strings to dicts before compare | Quick fix | Only fixes tests | +| 2. Standardize output | Return same format from both engines | Consistent API | Breaking change | +| 3. Format-agnostic comparison | Utility that handles both formats | Flexible | More code | + +**Recommended Fix:** Option 1 (immediate) + Option 2 (future standardization). + +--- + +### 2.6 Profile Edge Cases + +**Affected Tests:** +- `test_profile_all_columns` +- `test_profile_specific_columns` +- `test_completeness_partial` +- `test_numeric_with_nulls` +- `test_single_row` +- `test_mixed_types` + +**Root Causes:** + +These tests fail due to combinations of the above issues: + +| Test | Primary Issues | +|------|----------------| +| `test_profile_all_columns` | std_dev + distinct count | +| `test_profile_specific_columns` | std_dev formula | +| `test_completeness_partial` | NULL counting edge case | +| `test_numeric_with_nulls` | std_dev with NULLs | +| `test_single_row` | std_dev undefined (0/0) | +| `test_mixed_types` | Type inference + formatting | + +**Single Row Edge Case:** + +Standard deviation of a single value is mathematically undefined (sample) or 0 (population): +- `STDDEV_SAMP([42])` → NULL or NaN +- `STDDEV_POP([42])` → 0 + +**Options to Bridge:** + +Fixing the underlying issues (std_dev, distinct count, output format) will resolve most profile test failures. + +--- + +## Recommended Fixes + +| Priority | Fix | Impact | Effort | Tests Fixed | +|----------|-----|--------|--------|-------------| +| **High** | Parse JSON in comparison utility | 2 tests | Low | histogram, data_type | +| **High** | Add STDDEV_POP configuration option | 4+ tests | Low | std_dev, profile tests | +| **Medium** | Increase tolerances for approximate metrics | 5+ tests | Low | approx_count, quantiles, entropy | +| **Medium** | Use exact COUNT DISTINCT for small datasets | 2 tests | Medium | approx_count_distinct, distinct_values | +| **Low** | Implement numerical stability improvements | 3 tests | High | entropy, mutual_info | + +### Implementation Roadmap + +**Phase 1: Quick Wins (1-2 days)** +1. Add JSON parsing to test comparison utilities +2. Add configuration option for standard deviation formula +3. Update test tolerances for approximate metrics + +**Phase 2: Standardization (1 week)** +1. Implement exact distinct count fallback for small datasets +2. Standardize output formats across engines +3. Document expected variances in analyzer docstrings + +**Phase 3: Long-term (future)** +1. Implement numerically stable entropy calculations +2. Add configurable interpolation methods for quantiles +3. Create comprehensive engine compatibility matrix + +--- + +## Acceptance Criteria + +Tests can be categorized into three resolution states: + +### 1. Fixed +Implementation changed to match Spark behavior: +- Standard deviation formula alignment +- Output format standardization +- Exact counts for small datasets + +### 2. Relaxed +Tolerance increased with documented reasoning: +- Approximate count distinct (inherent HLL variance) +- Quantile calculations (interpolation method differences) +- Entropy calculations (floating-point precision limits) + +### 3. Skipped +Marked as known difference with `pytest.mark.xfail`: +- Engine-specific features not portable +- Fundamental algorithmic differences + +--- + +## Appendix: Test Failure Details + +### Full List of Failing Tests + +1. `test_standard_deviation` - Sample vs population formula +2. `test_approx_count_distinct` - HyperLogLog implementation +3. `test_approx_quantile_median` - Interpolation method +4. `test_approx_quantile_quartiles` - Interpolation method +5. `test_entropy_uniform` - Floating-point precision +6. `test_mutual_information` - Floating-point precision +7. `test_has_entropy` - Floating-point precision +8. `test_histogram` - JSON vs Map output +9. `test_data_type` - JSON vs Map output +10. `test_profile_all_columns` - Combined issues +11. `test_profile_specific_columns` - Combined issues +12. `test_completeness_partial` - NULL handling +13. `test_numeric_with_nulls` - std_dev with NULLs +14. `test_single_row` - Edge case handling +15. `test_mixed_types` - Type inference +16. `test_numeric_statistics` - std_dev formula +17. `test_all_basic_analyzers` - std_dev formula +18. `test_distinct_values` - Approximate count + +### Reference: Standard Deviation Formulas + +**Population Standard Deviation:** +``` +σ = √(Σ(xi - μ)² / N) +``` + +**Sample Standard Deviation:** +``` +s = √(Σ(xi - x̄)² / (N-1)) +``` + +**Relationship:** +``` +s = σ × √(N / (N-1)) +``` + +For N=5: s ≈ 1.118 × σ + +--- + +## Conclusion + +The 18 test failures between Spark and DuckDB engines stem from five root causes, all of which have viable resolution paths. The recommended approach prioritizes: + +1. Quick fixes that achieve parity (output format, std_dev config) +2. Appropriate tolerance adjustments for probabilistic algorithms +3. Documentation of inherent differences + +With the high-priority fixes implemented, the test suite should achieve >90% parity. The remaining differences reflect fundamental algorithmic choices that should be documented rather than hidden. diff --git a/benchmark/__init__.py b/benchmark/__init__.py new file mode 100644 index 0000000..7e9dfac --- /dev/null +++ b/benchmark/__init__.py @@ -0,0 +1,28 @@ +"""Benchmark package for PyDeequ engine comparison.""" + +from .config import ExperimentConfig, SparkServerConfig, BenchmarkConfig +from .results import ( + ExperimentResult, + EnvironmentInfo, + BenchmarkRun, + generate_run_id, + save_results, + load_results, + collect_environment_info, +) +from .spark_server import SparkConnectServer, managed_spark_server + +__all__ = [ + "ExperimentConfig", + "SparkServerConfig", + "BenchmarkConfig", + "ExperimentResult", + "EnvironmentInfo", + "BenchmarkRun", + "generate_run_id", + "save_results", + "load_results", + "collect_environment_info", + "SparkConnectServer", + "managed_spark_server", +] diff --git a/benchmark/config.py b/benchmark/config.py new file mode 100644 index 0000000..1829876 --- /dev/null +++ b/benchmark/config.py @@ -0,0 +1,107 @@ +"""Configuration dataclasses for benchmark.""" + +import os +from dataclasses import dataclass, field, asdict +from typing import List, Optional + + +# Default experiment configurations (from original script) +DEFAULT_ROW_COUNTS = [100_000, 1_000_000, 5_000_000, 10_000_000, 50_000_000, 130_000_000] +DEFAULT_COLUMN_COUNTS = [10, 20, 40, 80] +DEFAULT_PROFILING_ROW_COUNTS = [100_000, 1_000_000, 5_000_000, 10_000_000] +DEFAULT_FIXED_ROWS = 1_000_000 +DEFAULT_BASE_COLS = 10 +DEFAULT_N_RUNS = 3 + +@dataclass +class ExperimentConfig: + """Configuration for benchmark experiments.""" + + n_runs: int = DEFAULT_N_RUNS + row_counts: List[int] = field(default_factory=lambda: DEFAULT_ROW_COUNTS.copy()) + column_counts: List[int] = field(default_factory=lambda: DEFAULT_COLUMN_COUNTS.copy()) + profiling_row_counts: List[int] = field( + default_factory=lambda: DEFAULT_PROFILING_ROW_COUNTS.copy() + ) + fixed_rows: int = DEFAULT_FIXED_ROWS + base_cols: int = DEFAULT_BASE_COLS + cache_dir: str = field( + default_factory=lambda: os.path.expanduser("~/.deequ_benchmark_data") + ) + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization.""" + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "ExperimentConfig": + """Create from dictionary.""" + return cls(**data) + + +@dataclass +class SparkServerConfig: + """Configuration for Spark Connect server.""" + + java_home: str = field( + default_factory=lambda: os.environ.get( + "JAVA_HOME", + "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home", + ) + ) + spark_home: str = field( + default_factory=lambda: os.environ.get( + "SPARK_HOME", "/Volumes/workplace/deequ_rewrite/spark-3.5.0-bin-hadoop3" + ) + ) + port: int = 15002 + startup_timeout: int = 60 + poll_interval: float = 1.0 + driver_memory: str = "16g" + executor_memory: str = "16g" + deequ_jar: str = field( + default_factory=lambda: "/Volumes/workplace/deequ_rewrite/deequ/target/deequ_2.12-2.1.0b-spark-3.5.jar" + ) + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization.""" + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "SparkServerConfig": + """Create from dictionary.""" + return cls(**data) + + +@dataclass +class BenchmarkConfig: + """Overall benchmark configuration.""" + + engine: str = "all" # "all", "duckdb", or "spark" + output_dir: str = "benchmark_results" + experiment: ExperimentConfig = field(default_factory=ExperimentConfig) + spark_server: SparkServerConfig = field(default_factory=SparkServerConfig) + spark_remote: str = field( + default_factory=lambda: os.environ.get("SPARK_REMOTE", "sc://localhost:15002") + ) + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization.""" + return { + "engine": self.engine, + "output_dir": self.output_dir, + "experiment": self.experiment.to_dict(), + "spark_server": self.spark_server.to_dict(), + "spark_remote": self.spark_remote, + } + + @classmethod + def from_dict(cls, data: dict) -> "BenchmarkConfig": + """Create from dictionary.""" + return cls( + engine=data.get("engine", "all"), + output_dir=data.get("output_dir", "benchmark_results"), + experiment=ExperimentConfig.from_dict(data.get("experiment", {})), + spark_server=SparkServerConfig.from_dict(data.get("spark_server", {})), + spark_remote=data.get("spark_remote", "sc://localhost:15002"), + ) diff --git a/benchmark/experiments.py b/benchmark/experiments.py new file mode 100644 index 0000000..3424807 --- /dev/null +++ b/benchmark/experiments.py @@ -0,0 +1,547 @@ +"""Benchmark experiment logic extracted from original benchmark_cli.py.""" + +import os +import time +from typing import List, Dict, Any, Optional, Tuple + +import duckdb +import numpy as np +import pandas as pd +import pydeequ + +from pydeequ.v2.verification import VerificationSuite +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.predicates import gte, lte, between +from pydeequ.v2.profiles import ColumnProfilerRunner + +from .config import ExperimentConfig + + +# ============================================================================= +# Data Generation +# ============================================================================= + + +def generate_rich_data(n_rows: int, n_extra_cols: int = 0) -> pd.DataFrame: + """ + Generate mixed-type data for benchmarking with optional extra numeric columns. + + Base schema (10 columns): + - id: string (unique identifier) + - category: string (5 categorical values) + - status: string (3 categorical values) + - email: string (email-like pattern) + - amount: float [0, 10000] + - quantity: int [0, 1000] + - score: float [0, 100] (normal distribution) + - rating: int [1, 5] + - price: float [0.01, 9999.99] + - discount: float [0, 0.5] + + Args: + n_rows: Number of rows to generate + n_extra_cols: Number of additional numeric columns + + Returns: + DataFrame with mixed-type columns + optional extra numeric columns + """ + np.random.seed(42) + + data = { + "id": [f"ID{i:012d}" for i in range(n_rows)], + "category": np.random.choice( + ["electronics", "clothing", "food", "books", "toys"], n_rows + ), + "status": np.random.choice(["active", "inactive", "pending"], n_rows), + "email": [f"user{i}@example.com" for i in range(n_rows)], + "amount": np.random.uniform(0, 10000, n_rows), + "quantity": np.random.randint(0, 1001, n_rows), + "score": np.random.normal(50, 15, n_rows).clip(0, 100), + "rating": np.random.randint(1, 6, n_rows), + "price": np.random.uniform(0.01, 9999.99, n_rows), + "discount": np.random.uniform(0, 0.5, n_rows), + } + + for i in range(n_extra_cols): + data[f"extra_{i}"] = np.random.uniform(-10000, 10000, n_rows) + + return pd.DataFrame(data) + + +def save_to_parquet(df: pd.DataFrame, cache_dir: str, name: str, target_row_groups: int = 64) -> str: + """ + Save DataFrame to Parquet with dynamic row group size for Spark parallelism. + + Args: + df: DataFrame to save + cache_dir: Cache directory path + name: Cache file name + target_row_groups: Target number of row groups + + Returns: + Path to the saved Parquet file + """ + import pyarrow as pa + import pyarrow.parquet as pq + + os.makedirs(cache_dir, exist_ok=True) + path = os.path.join(cache_dir, f"{name}.parquet") + + if not os.path.exists(path): + n_rows = len(df) + row_group_size = max(10_000, n_rows // target_row_groups) + table = pa.Table.from_pandas(df) + pq.write_table(table, path, compression="snappy", row_group_size=row_group_size) + + return path + + +def get_cached_parquet(cache_dir: str, name: str) -> Optional[str]: + """Get path to cached Parquet file, or None if not cached.""" + path = os.path.join(cache_dir, f"{name}.parquet") + return path if os.path.exists(path) else None + + +# ============================================================================= +# Check Building +# ============================================================================= + + +def build_rich_check(n_extra_cols: int = 0) -> Check: + """ + Build a Check suite with rich validations on base columns + simple checks on extras. + + Args: + n_extra_cols: Number of extra numeric columns to add checks for + + Returns: + Check instance with all constraints configured + """ + check = ( + Check(CheckLevel.Warning, "Rich Benchmark Check") + # Completeness checks (3) + .isComplete("id") + .isComplete("category") + .hasCompleteness("email", gte(0.95)) + # Uniqueness checks (2) + .isUnique("id") + .hasDistinctness(["category"], gte(0.001)) + # Numeric range checks (6) + .hasMin("amount", gte(0)) + .hasMax("amount", lte(10000)) + .hasMean("score", between(0, 100)) + .hasStandardDeviation("score", lte(50)) + .isNonNegative("quantity") + .isPositive("price") + # String checks (5) + .hasMinLength("id", gte(8)) + .hasMaxLength("id", lte(20)) + .hasPattern("email", r".*@.*\..*", gte(0.9)) + .isContainedIn("status", ["active", "inactive", "pending"]) + .isContainedIn("rating", ["1", "2", "3", "4", "5"]) + ) + + for i in range(n_extra_cols): + col = f"extra_{i}" + check = check.isComplete(col).hasMin(col, gte(-10000)).hasMax(col, lte(10000)) + + return check + + +def count_checks(n_extra_cols: int = 0) -> int: + """Return total number of checks for given extra columns.""" + base_checks = 16 + extra_checks = n_extra_cols * 3 + return base_checks + extra_checks + + +# ============================================================================= +# DuckDB Setup and Benchmarking +# ============================================================================= + + +def setup_duckdb_from_parquet(parquet_path: str) -> Tuple[Any, duckdb.DuckDBPyConnection]: + """Setup DuckDB engine to read from Parquet file.""" + con = duckdb.connect() + engine = pydeequ.connect(con, table=f"read_parquet('{parquet_path}')") + return engine, con + + +def setup_duckdb_for_profiling(parquet_path: str) -> Tuple[Any, duckdb.DuckDBPyConnection]: + """ + Setup DuckDB engine for profiling by creating a view from parquet. + This is needed because PRAGMA table_info() doesn't work with read_parquet(). + """ + con = duckdb.connect() + con.execute(f"CREATE VIEW benchmark_data AS SELECT * FROM read_parquet('{parquet_path}')") + engine = pydeequ.connect(con, table="benchmark_data") + return engine, con + + +def benchmark_duckdb_validation(engine: Any, check: Check, n_runs: int) -> float: + """Time DuckDB VerificationSuite.run() over N runs, return average.""" + times = [] + for _ in range(n_runs): + start = time.perf_counter() + result = VerificationSuite().on_engine(engine).addCheck(check).run() + _ = len(result) + elapsed = time.perf_counter() - start + times.append(elapsed) + return sum(times) / len(times) + + +def benchmark_duckdb_profiling(engine: Any, n_runs: int) -> float: + """Time DuckDB ColumnProfilerRunner.run() over N runs, return average.""" + times = [] + for _ in range(n_runs): + start = time.perf_counter() + result = ColumnProfilerRunner().on_engine(engine).run() + _ = len(result) + elapsed = time.perf_counter() - start + times.append(elapsed) + return sum(times) / len(times) + + +# ============================================================================= +# Spark Setup and Benchmarking +# ============================================================================= + + +def setup_spark(spark_remote: str) -> Tuple[Any, float]: + """Create SparkSession for Spark Connect. Returns (spark, startup_time).""" + from pyspark.sql import SparkSession + + start = time.perf_counter() + spark = SparkSession.builder.remote(spark_remote).getOrCreate() + startup_time = time.perf_counter() - start + + return spark, startup_time + + +def load_spark_from_parquet(spark: Any, parquet_path: str) -> Tuple[Any, float]: + """Load Parquet file into Spark. Returns (spark_df, load_time).""" + start = time.perf_counter() + spark_df = spark.read.parquet(parquet_path) + spark_df.count() # Force materialization + load_time = time.perf_counter() - start + + return spark_df, load_time + + +def benchmark_spark_validation(spark: Any, spark_df: Any, check: Check, n_runs: int) -> float: + """Time Spark VerificationSuite.run() over N runs, return average.""" + times = [] + for _ in range(n_runs): + start = time.perf_counter() + result = VerificationSuite(spark).onData(spark_df).addCheck(check).run() + _ = result.collect() + elapsed = time.perf_counter() - start + times.append(elapsed) + return sum(times) / len(times) + + +def benchmark_spark_profiling(spark: Any, spark_df: Any, n_runs: int) -> float: + """Time Spark ColumnProfilerRunner.run() over N runs, return average.""" + times = [] + for _ in range(n_runs): + start = time.perf_counter() + result = ColumnProfilerRunner(spark).onData(spark_df).run() + _ = result.collect() + elapsed = time.perf_counter() - start + times.append(elapsed) + return sum(times) / len(times) + + +# ============================================================================= +# DuckDB Experiment Runners +# ============================================================================= + + +def run_varying_rows_experiment_duckdb(config: ExperimentConfig) -> List[Dict[str, Any]]: + """Run varying rows experiment for DuckDB engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 1 (DuckDB): VARYING ROWS (Fixed Columns = {config.base_cols})") + print("=" * 70) + + results = [] + + for n_rows in config.row_counts: + n_checks = count_checks(0) + print(f"\n--- {n_rows:,} rows x {config.base_cols} cols ({n_checks} checks) ---") + + cache_name = f"rich_rows_{n_rows}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(n_rows, n_extra_cols=0) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + check = build_rich_check(n_extra_cols=0) + + print("Setting up DuckDB (from Parquet)...") + duck_engine, duck_con = setup_duckdb_from_parquet(parquet_path) + + print(f"Running DuckDB validation ({config.n_runs} runs)...") + duck_validation = benchmark_duckdb_validation(duck_engine, check, config.n_runs) + print(f" DuckDB Validation: {duck_validation:.3f}s (avg)") + + duck_con.close() + + results.append({ + "rows": n_rows, + "cols": config.base_cols, + "checks": n_checks, + "duckdb_validation": duck_validation, + }) + + return results + + +def run_varying_cols_experiment_duckdb(config: ExperimentConfig) -> List[Dict[str, Any]]: + """Run varying columns experiment for DuckDB engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 2 (DuckDB): VARYING COLUMNS (Fixed Rows = {config.fixed_rows:,})") + print("=" * 70) + + results = [] + + for n_cols in config.column_counts: + n_extra_cols = n_cols - config.base_cols + n_checks = count_checks(n_extra_cols) + print(f"\n--- {config.fixed_rows:,} rows x {n_cols} cols ({n_checks} checks) ---") + + cache_name = f"rich_cols_{n_cols}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(config.fixed_rows, n_extra_cols=n_extra_cols) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + check = build_rich_check(n_extra_cols=n_extra_cols) + + print("Setting up DuckDB (from Parquet)...") + duck_engine, duck_con = setup_duckdb_from_parquet(parquet_path) + + print(f"Running DuckDB validation ({config.n_runs} runs)...") + duck_validation = benchmark_duckdb_validation(duck_engine, check, config.n_runs) + print(f" DuckDB Validation: {duck_validation:.3f}s (avg)") + + duck_con.close() + + results.append({ + "rows": config.fixed_rows, + "cols": n_cols, + "checks": n_checks, + "duckdb_validation": duck_validation, + }) + + return results + + +def run_profiling_experiment_duckdb(config: ExperimentConfig) -> List[Dict[str, Any]]: + """Run column profiling experiment for DuckDB engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 3 (DuckDB): COLUMN PROFILING (Fixed Columns = {config.base_cols})") + print("=" * 70) + + results = [] + + for n_rows in config.profiling_row_counts: + print(f"\n--- {n_rows:,} rows x {config.base_cols} cols (profiling) ---") + + cache_name = f"rich_rows_{n_rows}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(n_rows, n_extra_cols=0) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + print("Setting up DuckDB for profiling...") + duck_engine, duck_con = setup_duckdb_for_profiling(parquet_path) + + print(f"Running DuckDB profiling ({config.n_runs} runs)...") + duck_profiling = benchmark_duckdb_profiling(duck_engine, config.n_runs) + print(f" DuckDB Profiling: {duck_profiling:.3f}s (avg)") + + duck_con.close() + + results.append({ + "rows": n_rows, + "cols": config.base_cols, + "duckdb_profiling": duck_profiling, + }) + + return results + + +# ============================================================================= +# Spark Experiment Runners +# ============================================================================= + + +def run_varying_rows_experiment_spark( + spark: Any, spark_startup_time: float, config: ExperimentConfig +) -> List[Dict[str, Any]]: + """Run varying rows experiment for Spark engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 1 (Spark): VARYING ROWS (Fixed Columns = {config.base_cols})") + print("=" * 70) + + results = [] + + for n_rows in config.row_counts: + n_checks = count_checks(0) + print(f"\n--- {n_rows:,} rows x {config.base_cols} cols ({n_checks} checks) ---") + + cache_name = f"rich_rows_{n_rows}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(n_rows, n_extra_cols=0) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + check = build_rich_check(n_extra_cols=0) + + spark_load = None + spark_validation = None + + try: + print("Loading Parquet into Spark...") + spark_df, spark_load = load_spark_from_parquet(spark, parquet_path) + print(f" Spark Data Load: {spark_load:.3f}s") + + print(f"Running Spark validation ({config.n_runs} runs)...") + spark_validation = benchmark_spark_validation(spark, spark_df, check, config.n_runs) + print(f" Spark Validation: {spark_validation:.3f}s (avg)") + except Exception as e: + print(f" Spark error: {str(e)[:80]}") + + results.append({ + "rows": n_rows, + "cols": config.base_cols, + "checks": n_checks, + "spark_startup": spark_startup_time, + "spark_load": spark_load, + "spark_validation": spark_validation, + }) + + return results + + +def run_varying_cols_experiment_spark( + spark: Any, spark_startup_time: float, config: ExperimentConfig +) -> List[Dict[str, Any]]: + """Run varying columns experiment for Spark engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 2 (Spark): VARYING COLUMNS (Fixed Rows = {config.fixed_rows:,})") + print("=" * 70) + + results = [] + + for n_cols in config.column_counts: + n_extra_cols = n_cols - config.base_cols + n_checks = count_checks(n_extra_cols) + print(f"\n--- {config.fixed_rows:,} rows x {n_cols} cols ({n_checks} checks) ---") + + cache_name = f"rich_cols_{n_cols}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(config.fixed_rows, n_extra_cols=n_extra_cols) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + check = build_rich_check(n_extra_cols=n_extra_cols) + + spark_load = None + spark_validation = None + + try: + print("Loading Parquet into Spark...") + spark_df, spark_load = load_spark_from_parquet(spark, parquet_path) + print(f" Spark Data Load: {spark_load:.3f}s") + + print(f"Running Spark validation ({config.n_runs} runs)...") + spark_validation = benchmark_spark_validation(spark, spark_df, check, config.n_runs) + print(f" Spark Validation: {spark_validation:.3f}s (avg)") + except Exception as e: + print(f" Spark error: {str(e)[:80]}") + + results.append({ + "rows": config.fixed_rows, + "cols": n_cols, + "checks": n_checks, + "spark_startup": spark_startup_time, + "spark_load": spark_load, + "spark_validation": spark_validation, + }) + + return results + + +def run_profiling_experiment_spark( + spark: Any, spark_startup_time: float, config: ExperimentConfig +) -> List[Dict[str, Any]]: + """Run column profiling experiment for Spark engine.""" + print("\n" + "=" * 70) + print(f"EXPERIMENT 3 (Spark): COLUMN PROFILING (Fixed Columns = {config.base_cols})") + print("=" * 70) + + results = [] + + for n_rows in config.profiling_row_counts: + print(f"\n--- {n_rows:,} rows x {config.base_cols} cols (profiling) ---") + + cache_name = f"rich_rows_{n_rows}" + parquet_path = get_cached_parquet(config.cache_dir, cache_name) + + if parquet_path: + print(f"Using cached data: {parquet_path}") + else: + print("Generating rich mixed-type data and saving to Parquet...") + df = generate_rich_data(n_rows, n_extra_cols=0) + parquet_path = save_to_parquet(df, config.cache_dir, cache_name) + del df + + spark_load = None + spark_profiling = None + + try: + print("Loading Parquet into Spark...") + spark_df, spark_load = load_spark_from_parquet(spark, parquet_path) + print(f" Spark Data Load: {spark_load:.3f}s") + + print(f"Running Spark profiling ({config.n_runs} runs)...") + spark_profiling = benchmark_spark_profiling(spark, spark_df, config.n_runs) + print(f" Spark Profiling: {spark_profiling:.3f}s (avg)") + except Exception as e: + print(f" Spark error: {str(e)[:80]}") + + results.append({ + "rows": n_rows, + "cols": config.base_cols, + "spark_startup": spark_startup_time, + "spark_load": spark_load, + "spark_profiling": spark_profiling, + }) + + return results diff --git a/benchmark/report.py b/benchmark/report.py new file mode 100644 index 0000000..48b4366 --- /dev/null +++ b/benchmark/report.py @@ -0,0 +1,210 @@ +"""Markdown report generation for benchmark results.""" + +import os +from typing import Optional + +from .results import BenchmarkRun +from .experiments import count_checks + + +def format_value(value: Optional[float], precision: int = 3) -> str: + """Format a numeric value or return 'N/A' if None.""" + if value is None: + return "N/A" + return f"{value:.{precision}f}" + + +def calculate_speedup(spark_time: Optional[float], duckdb_time: Optional[float]) -> str: + """Calculate speedup ratio (spark/duckdb) or return 'N/A'.""" + if spark_time is None or duckdb_time is None or duckdb_time <= 0: + return "N/A" + return f"{spark_time / duckdb_time:.1f}x" + + +def generate_markdown_report(run: BenchmarkRun) -> str: + """ + Generate a markdown report from benchmark results. + + Args: + run: BenchmarkRun instance with results + + Returns: + Markdown string + """ + # Extract config values + config = run.config + exp_config = config.get("experiment", {}) + n_runs = exp_config.get("n_runs", 3) + base_cols = exp_config.get("base_cols", 10) + fixed_rows = exp_config.get("fixed_rows", 1_000_000) + row_counts = exp_config.get("row_counts", []) + column_counts = exp_config.get("column_counts", []) + + spark_startup = run.spark_startup_time or 0.0 + + report = f"""# PyDeequ Engine Benchmark Results + +## Run Information + +| Field | Value | +|-------|-------| +| Run ID | `{run.run_id}` | +| Timestamp | {run.timestamp} | +| Engine | {run.engine} | +| Total Duration | {format_value(run.total_duration_seconds, 1)}s | + +## Environment + +| Component | Version | +|-----------|---------| +| Python | {run.environment.python_version} | +| Platform | {run.environment.platform_system} {run.environment.platform_release} ({run.environment.platform_machine}) | +| CPU Count | {run.environment.cpu_count} | +| DuckDB | {run.environment.duckdb_version or 'N/A'} | +| PySpark | {run.environment.pyspark_version or 'N/A'} | +| PyDeequ | {run.environment.pydeequ_version or 'N/A'} | +| Pandas | {run.environment.pandas_version or 'N/A'} | +| NumPy | {run.environment.numpy_version or 'N/A'} | +| PyArrow | {run.environment.pyarrow_version or 'N/A'} | + +## Methodology + +Based on duckdq-exp experiments: + +- **Data Source**: Both engines read from the same Parquet files +- **Rich Dataset**: Mixed-type columns (strings + numerics) with realistic data patterns +- **Validation Runs**: {n_runs} iterations, reporting average +- **Base Checks**: {count_checks(0)} rich checks on {base_cols} mixed-type columns + +### Rich Dataset Schema ({base_cols} base columns) + +| Column | Type | Description | +|--------|------|-------------| +| `id` | string | Unique identifier (ID000000000000) | +| `category` | string | Categorical (5 values) | +| `status` | string | Categorical (3 values) | +| `email` | string | Email pattern | +| `amount` | float | Numeric value [0, 10000] | +| `quantity` | int | Non-negative integer [0, 1000] | +| `score` | float | Normal distribution [0, 100] | +| `rating` | int | Star rating [1, 5] | +| `price` | float | Positive numeric [0.01, 9999.99] | +| `discount` | float | Percentage [0, 0.5] | + +## Experiment 1: Varying Rows (Fixed Columns = {base_cols}, {count_checks(0)} checks) + +| Rows | Cols | Checks | DuckDB (s) | Spark (s) | Speedup | +|------|------|--------|------------|-----------|---------| +""" + + for r in run.varying_rows_results: + duck_s = r.get("duckdb_validation") + spark_s = r.get("spark_validation") + checks = r.get("checks", count_checks(0)) + speedup = calculate_speedup(spark_s, duck_s) + report += f"| {r['rows']:,} | {r['cols']} | {checks} | {format_value(duck_s)} | {format_value(spark_s)} | {speedup} |\n" + + report += f""" +## Experiment 2: Varying Columns (Fixed Rows = {fixed_rows:,}) + +Column counts: {column_counts} (base {base_cols} mixed-type + extra numeric columns) + +| Rows | Cols | Checks | DuckDB (s) | Spark (s) | Speedup | +|------|------|--------|------------|-----------|---------| +""" + + for r in run.varying_cols_results: + duck_s = r.get("duckdb_validation") + spark_s = r.get("spark_validation") + checks = r.get("checks", "N/A") + speedup = calculate_speedup(spark_s, duck_s) + report += f"| {r['rows']:,} | {r['cols']} | {checks} | {format_value(duck_s)} | {format_value(spark_s)} | {speedup} |\n" + + report += f""" +## Experiment 3: Column Profiling (Fixed Columns = {base_cols}) + +Uses `ColumnProfilerRunner` to profile all columns. + +| Rows | Cols | DuckDB (s) | Spark (s) | Speedup | +|------|------|------------|-----------|---------| +""" + + for r in run.profiling_results: + duck_s = r.get("duckdb_profiling") + spark_s = r.get("spark_profiling") + speedup = calculate_speedup(spark_s, duck_s) + report += f"| {r['rows']:,} | {r['cols']} | {format_value(duck_s)} | {format_value(spark_s)} | {speedup} |\n" + + report += f""" +## Timing Details + +### Spark Overhead (Excluded from Validation Time) + +| Phase | Time (s) | +|-------|----------| +| Startup (SparkSession) | {format_value(spark_startup)} | + +**Note**: Data load time varies per experiment and is not included in validation/profiling time. + +## Key Findings + +1. **DuckDB is significantly faster** for single-node data quality validation +2. **No JVM overhead**: DuckDB runs natively in Python process +3. **Rich type support**: Both engines handle mixed string/numeric data effectively +4. **Parquet files**: Both engines read from the same files, eliminating gRPC serialization bottleneck +5. **Column profiling**: Full profiling available on both engines + +## Running the Benchmark + +```bash +# Run DuckDB only (no Spark server needed) +python benchmark_cli.py run --engine duckdb + +# Run Spark only (auto-spark is enabled by default) +python benchmark_cli.py run --engine spark + +# Run both engines +python benchmark_cli.py run --engine all + +# Generate report from saved results (folder or file path) +python benchmark_cli.py report benchmark_results/{run.run_id}/ +python benchmark_cli.py report benchmark_results/{run.run_id}/results.json + +# Generate PNG visualization +python benchmark_cli.py visualize benchmark_results/{run.run_id}/ +``` + +## Notes + +- Both engines read from the same Parquet files, ensuring fair comparison +- Memory configuration (16GB+) prevents OOM errors for large datasets +- For distributed workloads across multiple nodes, Spark scales horizontally +- DuckDB is optimized for single-node analytical workloads +""" + + if run.errors: + report += "\n## Errors\n\n" + for error in run.errors: + report += f"- {error}\n" + + return report + + +def save_report(run: BenchmarkRun, output_path: str) -> str: + """ + Generate and save markdown report. + + Args: + run: BenchmarkRun instance + output_path: Path to save the report + + Returns: + Path to the saved report + """ + report = generate_markdown_report(run) + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "w") as f: + f.write(report) + + return output_path diff --git a/benchmark/results.py b/benchmark/results.py new file mode 100644 index 0000000..dc4e179 --- /dev/null +++ b/benchmark/results.py @@ -0,0 +1,286 @@ +"""Results dataclasses and JSON I/O for benchmark.""" + +import json +import os +import platform +from dataclasses import dataclass, field, asdict +from datetime import datetime +from typing import List, Optional, Dict, Any + + +@dataclass +class ExperimentResult: + """Result from a single experiment run.""" + + rows: int + cols: int + checks: Optional[int] = None + duckdb_validation: Optional[float] = None + duckdb_profiling: Optional[float] = None + spark_startup: Optional[float] = None + spark_load: Optional[float] = None + spark_validation: Optional[float] = None + spark_profiling: Optional[float] = None + error: Optional[str] = None + + def to_dict(self) -> dict: + """Convert to dictionary, excluding None values.""" + return {k: v for k, v in asdict(self).items() if v is not None} + + @classmethod + def from_dict(cls, data: dict) -> "ExperimentResult": + """Create from dictionary.""" + return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__}) + + +@dataclass +class EnvironmentInfo: + """Environment information for reproducibility.""" + + python_version: str = "" + platform_system: str = "" + platform_release: str = "" + platform_machine: str = "" + cpu_count: int = 0 + duckdb_version: str = "" + pyspark_version: str = "" + pydeequ_version: str = "" + pandas_version: str = "" + numpy_version: str = "" + pyarrow_version: str = "" + + def to_dict(self) -> dict: + """Convert to dictionary.""" + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "EnvironmentInfo": + """Create from dictionary.""" + return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__}) + + +@dataclass +class BenchmarkRun: + """Complete benchmark run with all results.""" + + run_id: str + timestamp: str + engine: str + config: Dict[str, Any] = field(default_factory=dict) + environment: EnvironmentInfo = field(default_factory=EnvironmentInfo) + varying_rows_results: List[Dict[str, Any]] = field(default_factory=list) + varying_cols_results: List[Dict[str, Any]] = field(default_factory=list) + profiling_results: List[Dict[str, Any]] = field(default_factory=list) + spark_startup_time: Optional[float] = None + total_duration_seconds: Optional[float] = None + errors: List[str] = field(default_factory=list) + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization.""" + return { + "run_id": self.run_id, + "timestamp": self.timestamp, + "engine": self.engine, + "config": self.config, + "environment": self.environment.to_dict(), + "varying_rows_results": self.varying_rows_results, + "varying_cols_results": self.varying_cols_results, + "profiling_results": self.profiling_results, + "spark_startup_time": self.spark_startup_time, + "total_duration_seconds": self.total_duration_seconds, + "errors": self.errors, + } + + @classmethod + def from_dict(cls, data: dict) -> "BenchmarkRun": + """Create from dictionary.""" + return cls( + run_id=data.get("run_id", ""), + timestamp=data.get("timestamp", ""), + engine=data.get("engine", ""), + config=data.get("config", {}), + environment=EnvironmentInfo.from_dict(data.get("environment", {})), + varying_rows_results=data.get("varying_rows_results", []), + varying_cols_results=data.get("varying_cols_results", []), + profiling_results=data.get("profiling_results", []), + spark_startup_time=data.get("spark_startup_time"), + total_duration_seconds=data.get("total_duration_seconds"), + errors=data.get("errors", []), + ) + + +def generate_run_id() -> str: + """Generate a unique run ID with timestamp.""" + return f"benchmark_{datetime.now().strftime('%Y-%m-%dT%H-%M-%S')}" + + +def save_results(run: BenchmarkRun, run_dir: str) -> str: + """ + Save benchmark results to JSON file in run directory. + + Args: + run: BenchmarkRun instance + run_dir: Directory for this benchmark run (e.g., benchmark_results/benchmark_2024-01-19T14-30-45/) + + Returns: + Path to the saved JSON file + """ + os.makedirs(run_dir, exist_ok=True) + path = os.path.join(run_dir, "results.json") + + with open(path, "w") as f: + json.dump(run.to_dict(), f, indent=2) + + return path + + +def load_results(path: str) -> BenchmarkRun: + """ + Load benchmark results from JSON file. + + Args: + path: Path to JSON file or run directory containing results.json + + Returns: + BenchmarkRun instance + """ + # If path is a directory, look for results.json inside + if os.path.isdir(path): + path = os.path.join(path, "results.json") + + with open(path) as f: + data = json.load(f) + return BenchmarkRun.from_dict(data) + + +def collect_environment_info() -> EnvironmentInfo: + """Collect environment information for reproducibility.""" + info = EnvironmentInfo( + python_version=platform.python_version(), + platform_system=platform.system(), + platform_release=platform.release(), + platform_machine=platform.machine(), + cpu_count=os.cpu_count() or 0, + ) + + # Try to get package versions + try: + import duckdb + + info.duckdb_version = duckdb.__version__ + except (ImportError, AttributeError): + pass + + try: + import pyspark + + info.pyspark_version = pyspark.__version__ + except (ImportError, AttributeError): + pass + + try: + import pydeequ + + info.pydeequ_version = getattr(pydeequ, "__version__", "unknown") + except (ImportError, AttributeError): + pass + + try: + import pandas + + info.pandas_version = pandas.__version__ + except (ImportError, AttributeError): + pass + + try: + import numpy + + info.numpy_version = numpy.__version__ + except (ImportError, AttributeError): + pass + + try: + import pyarrow + + info.pyarrow_version = pyarrow.__version__ + except (ImportError, AttributeError): + pass + + return info + + +def merge_results(duckdb_run: Optional[BenchmarkRun], spark_run: Optional[BenchmarkRun]) -> BenchmarkRun: + """ + Merge results from separate DuckDB and Spark runs into a single combined result. + + Args: + duckdb_run: Results from DuckDB worker (may be None) + spark_run: Results from Spark worker (may be None) + + Returns: + Combined BenchmarkRun + """ + # Use whichever run is available as the base + base = duckdb_run or spark_run + if base is None: + raise ValueError("At least one run must be provided") + + merged = BenchmarkRun( + run_id=base.run_id, + timestamp=base.timestamp, + engine="all" if duckdb_run and spark_run else base.engine, + config=base.config, + environment=base.environment, + errors=base.errors.copy(), + ) + + # Merge varying rows results + rows_by_key = {} + for run in [duckdb_run, spark_run]: + if run: + for r in run.varying_rows_results: + key = (r.get("rows"), r.get("cols")) + if key not in rows_by_key: + rows_by_key[key] = r.copy() + else: + rows_by_key[key].update({k: v for k, v in r.items() if v is not None}) + merged.errors.extend(e for e in run.errors if e not in merged.errors) + merged.varying_rows_results = list(rows_by_key.values()) + + # Merge varying cols results + cols_by_key = {} + for run in [duckdb_run, spark_run]: + if run: + for r in run.varying_cols_results: + key = (r.get("rows"), r.get("cols")) + if key not in cols_by_key: + cols_by_key[key] = r.copy() + else: + cols_by_key[key].update({k: v for k, v in r.items() if v is not None}) + merged.varying_cols_results = list(cols_by_key.values()) + + # Merge profiling results + prof_by_key = {} + for run in [duckdb_run, spark_run]: + if run: + for r in run.profiling_results: + key = (r.get("rows"), r.get("cols")) + if key not in prof_by_key: + prof_by_key[key] = r.copy() + else: + prof_by_key[key].update({k: v for k, v in r.items() if v is not None}) + merged.profiling_results = list(prof_by_key.values()) + + # Take Spark startup time from Spark run + if spark_run and spark_run.spark_startup_time: + merged.spark_startup_time = spark_run.spark_startup_time + + # Sum total durations + total = 0.0 + if duckdb_run and duckdb_run.total_duration_seconds: + total += duckdb_run.total_duration_seconds + if spark_run and spark_run.total_duration_seconds: + total += spark_run.total_duration_seconds + merged.total_duration_seconds = total if total > 0 else None + + return merged diff --git a/benchmark/spark_server.py b/benchmark/spark_server.py new file mode 100644 index 0000000..eac0020 --- /dev/null +++ b/benchmark/spark_server.py @@ -0,0 +1,197 @@ +"""Spark Connect server management for benchmarks.""" + +import os +import signal +import socket +import subprocess +import time +from contextlib import contextmanager +from typing import Optional + +from .config import SparkServerConfig + + +class SparkConnectServer: + """Manages Spark Connect server lifecycle.""" + + def __init__(self, config: Optional[SparkServerConfig] = None): + """ + Initialize Spark Connect server manager. + + Args: + config: Server configuration (uses defaults if not provided) + """ + self.config = config or SparkServerConfig() + self._process: Optional[subprocess.Popen] = None + self._started_by_us = False + + def is_running(self) -> bool: + """Check if Spark Connect server is running by attempting to connect.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex(("localhost", self.config.port)) + sock.close() + return result == 0 + except (socket.error, OSError): + return False + + def start(self) -> float: + """ + Start Spark Connect server if not already running. + + Returns: + Time taken to start the server (0 if already running) + + Raises: + RuntimeError: If server fails to start within timeout + """ + if self.is_running(): + print(f"Spark Connect server already running on port {self.config.port}") + return 0.0 + + start_time = time.time() + + # Build the startup command + start_script = os.path.join(self.config.spark_home, "sbin", "start-connect-server.sh") + + if not os.path.exists(start_script): + raise RuntimeError(f"Spark Connect start script not found: {start_script}") + + cmd = [ + start_script, + "--conf", f"spark.driver.memory={self.config.driver_memory}", + "--conf", f"spark.executor.memory={self.config.executor_memory}", + "--packages", "org.apache.spark:spark-connect_2.12:3.5.0", + "--jars", self.config.deequ_jar, + "--conf", "spark.connect.extensions.relation.classes=com.amazon.deequ.connect.DeequRelationPlugin", + ] + + # Set up environment + env = os.environ.copy() + env["JAVA_HOME"] = self.config.java_home + env["SPARK_HOME"] = self.config.spark_home + + print(f"Starting Spark Connect server on port {self.config.port}...") + print(f" JAVA_HOME: {self.config.java_home}") + print(f" SPARK_HOME: {self.config.spark_home}") + + # Start the server + self._process = subprocess.Popen( + cmd, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self._started_by_us = True + + # Wait for server to be ready + deadline = time.time() + self.config.startup_timeout + while time.time() < deadline: + if self.is_running(): + elapsed = time.time() - start_time + print(f"Spark Connect server started in {elapsed:.1f}s") + return elapsed + time.sleep(self.config.poll_interval) + + # Timeout - try to get error output + if self._process: + self._process.terminate() + _, stderr = self._process.communicate(timeout=5) + error_msg = stderr.decode() if stderr else "Unknown error" + self._process = None + self._started_by_us = False + raise RuntimeError( + f"Spark Connect server failed to start within {self.config.startup_timeout}s: {error_msg[:500]}" + ) + + raise RuntimeError( + f"Spark Connect server failed to start within {self.config.startup_timeout}s" + ) + + def stop(self) -> None: + """Stop Spark Connect server if we started it.""" + if not self._started_by_us: + print("Spark Connect server was not started by us, skipping stop") + return + + stop_script = os.path.join(self.config.spark_home, "sbin", "stop-connect-server.sh") + + if os.path.exists(stop_script): + print("Stopping Spark Connect server...") + env = os.environ.copy() + env["JAVA_HOME"] = self.config.java_home + env["SPARK_HOME"] = self.config.spark_home + + try: + subprocess.run( + [stop_script], + env=env, + timeout=30, + capture_output=True, + ) + print("Spark Connect server stopped") + except subprocess.TimeoutExpired: + print("Warning: stop script timed out") + except Exception as e: + print(f"Warning: Error stopping server: {e}") + else: + # Fall back to killing the process directly + if self._process: + print("Terminating Spark Connect server process...") + self._process.terminate() + try: + self._process.wait(timeout=10) + except subprocess.TimeoutExpired: + self._process.kill() + print("Spark Connect server process terminated") + + self._started_by_us = False + self._process = None + + +@contextmanager +def managed_spark_server(config: Optional[SparkServerConfig] = None): + """ + Context manager for Spark Connect server with signal handling. + + Ensures the server is stopped on exit, including on SIGINT/SIGTERM. + + Args: + config: Server configuration + + Yields: + SparkConnectServer instance + """ + server = SparkConnectServer(config) + original_sigint = signal.getsignal(signal.SIGINT) + original_sigterm = signal.getsignal(signal.SIGTERM) + + def signal_handler(signum, frame): + """Handle interrupt signals by stopping the server.""" + print(f"\nReceived signal {signum}, stopping Spark server...") + server.stop() + # Re-raise the signal to trigger default behavior + if signum == signal.SIGINT: + signal.signal(signal.SIGINT, original_sigint) + if callable(original_sigint): + original_sigint(signum, frame) + elif signum == signal.SIGTERM: + signal.signal(signal.SIGTERM, original_sigterm) + if callable(original_sigterm): + original_sigterm(signum, frame) + + try: + # Install signal handlers + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + yield server + + finally: + # Restore original signal handlers + signal.signal(signal.SIGINT, original_sigint) + signal.signal(signal.SIGTERM, original_sigterm) + + # Stop the server + server.stop() diff --git a/benchmark/visualize.py b/benchmark/visualize.py new file mode 100644 index 0000000..f239fa9 --- /dev/null +++ b/benchmark/visualize.py @@ -0,0 +1,288 @@ +""" +Benchmark Visualization for PyDeequ Engine Comparison. + +Generates PNG charts comparing DuckDB vs Spark performance from benchmark results. +""" + +import os +from typing import List, Dict, Any, Optional + +import matplotlib.pyplot as plt +import numpy as np + +from .results import BenchmarkRun + + +def _format_row_count(n: int) -> str: + """Format row count for display (e.g., 1000000 -> '1M').""" + if n >= 1_000_000: + return f"{n // 1_000_000}M" + elif n >= 1_000: + return f"{n // 1_000}K" + return str(n) + + +def _extract_validation_data( + results: List[Dict[str, Any]], x_key: str +) -> Dict[str, List]: + """Extract validation timing data from results.""" + data = { + "x_values": [], + "x_labels": [], + "duckdb": [], + "spark": [], + "checks": [], + } + + for r in sorted(results, key=lambda x: x.get(x_key, 0)): + x_val = r.get(x_key) + if x_val is None: + continue + + data["x_values"].append(x_val) + if x_key == "rows": + data["x_labels"].append(_format_row_count(x_val)) + else: + checks = r.get("checks", "") + data["x_labels"].append(f"{x_val}\n({checks})" if checks else str(x_val)) + + data["duckdb"].append(r.get("duckdb_validation")) + data["spark"].append(r.get("spark_validation")) + data["checks"].append(r.get("checks")) + + return data + + +def _extract_profiling_data(results: List[Dict[str, Any]]) -> Dict[str, List]: + """Extract profiling timing data from results.""" + data = { + "x_values": [], + "x_labels": [], + "duckdb": [], + "spark": [], + } + + for r in sorted(results, key=lambda x: x.get("rows", 0)): + rows = r.get("rows") + if rows is None: + continue + + data["x_values"].append(rows) + data["x_labels"].append(_format_row_count(rows)) + data["duckdb"].append(r.get("duckdb_profiling")) + data["spark"].append(r.get("spark_profiling")) + + return data + + +def _calculate_speedup(spark_times: List, duckdb_times: List) -> List[Optional[float]]: + """Calculate speedup ratios (Spark time / DuckDB time).""" + speedups = [] + for s, d in zip(spark_times, duckdb_times): + if s is not None and d is not None and d > 0: + speedups.append(s / d) + else: + speedups.append(None) + return speedups + + +def _plot_comparison( + ax: plt.Axes, + x_labels: List[str], + duckdb_times: List, + spark_times: List, + xlabel: str, + ylabel: str, + title: str, + duckdb_color: str, + spark_color: str, + use_log_scale: bool = False, +) -> None: + """Plot a side-by-side bar comparison chart.""" + # Filter out None values + valid_indices = [ + i for i in range(len(x_labels)) + if duckdb_times[i] is not None or spark_times[i] is not None + ] + + if not valid_indices: + ax.text(0.5, 0.5, "No data available", ha="center", va="center", transform=ax.transAxes) + ax.set_title(title, fontsize=12, fontweight="bold") + return + + labels = [x_labels[i] for i in valid_indices] + duckdb = [duckdb_times[i] if duckdb_times[i] is not None else 0 for i in valid_indices] + spark = [spark_times[i] if spark_times[i] is not None else 0 for i in valid_indices] + + x = np.arange(len(labels)) + width = 0.35 + + has_duckdb = any(d > 0 for d in duckdb) + has_spark = any(s > 0 for s in spark) + + if has_duckdb: + bars1 = ax.bar( + x - width / 2, duckdb, width, label="DuckDB", + color=duckdb_color, edgecolor="black", linewidth=0.5 + ) + for bar in bars1: + height = bar.get_height() + if height > 0: + ax.annotate( + f"{height:.2f}s", + xy=(bar.get_x() + bar.get_width() / 2, height), + xytext=(0, 3), textcoords="offset points", + ha="center", va="bottom", fontsize=7 + ) + + if has_spark: + bars2 = ax.bar( + x + width / 2, spark, width, label="Spark", + color=spark_color, edgecolor="black", linewidth=0.5 + ) + for bar in bars2: + height = bar.get_height() + if height > 0: + ax.annotate( + f"{height:.1f}s", + xy=(bar.get_x() + bar.get_width() / 2, height), + xytext=(0, 3), textcoords="offset points", + ha="center", va="bottom", fontsize=7 + ) + + ax.set_xlabel(xlabel, fontsize=11) + ax.set_ylabel(ylabel, fontsize=11) + ax.set_title(title, fontsize=12, fontweight="bold") + ax.set_xticks(x) + ax.set_xticklabels(labels) + ax.legend(loc="upper left") + + if use_log_scale and has_duckdb and has_spark: + ax.set_yscale("log") + + +def _plot_speedup( + ax: plt.Axes, + x_labels: List[str], + speedups: List[Optional[float]], + xlabel: str, + title: str, + speedup_color: str, +) -> None: + """Plot a speedup bar chart.""" + valid_indices = [i for i in range(len(x_labels)) if speedups[i] is not None] + + if not valid_indices: + ax.text(0.5, 0.5, "No speedup data\n(need both engines)", ha="center", va="center", transform=ax.transAxes) + ax.set_title(title, fontsize=12, fontweight="bold") + return + + labels = [x_labels[i] for i in valid_indices] + values = [speedups[i] for i in valid_indices] + + x = np.arange(len(labels)) + bars = ax.bar(x, values, color=speedup_color, edgecolor="black", linewidth=0.5) + + ax.axhline(y=1, color="gray", linestyle="--", alpha=0.7) + ax.set_xlabel(xlabel, fontsize=11) + ax.set_ylabel("Speedup (x times faster)", fontsize=11) + ax.set_title(title, fontsize=12, fontweight="bold") + ax.set_xticks(x) + ax.set_xticklabels(labels) + + for bar in bars: + height = bar.get_height() + ax.annotate( + f"{height:.1f}x", + xy=(bar.get_x() + bar.get_width() / 2, height), + xytext=(0, 3), textcoords="offset points", + ha="center", va="bottom", fontsize=10, fontweight="bold" + ) + + +def generate_visualization(run: BenchmarkRun, output_path: str) -> str: + """ + Generate benchmark visualization PNG from results. + + Args: + run: BenchmarkRun instance with results + output_path: Path to save the PNG file + + Returns: + Path to the saved PNG file + """ + # Extract data from results + rows_data = _extract_validation_data(run.varying_rows_results, "rows") + cols_data = _extract_validation_data(run.varying_cols_results, "cols") + profiling_data = _extract_profiling_data(run.profiling_results) + + # Calculate speedups + rows_speedup = _calculate_speedup(rows_data["spark"], rows_data["duckdb"]) + cols_speedup = _calculate_speedup(cols_data["spark"], cols_data["duckdb"]) + profiling_speedup = _calculate_speedup(profiling_data["spark"], profiling_data["duckdb"]) + + # Color scheme + duckdb_color = "#FFA500" # Orange + spark_color = "#E25A1C" # Spark orange/red + speedup_color = "#2E86AB" # Blue + + # Set style and create figure + plt.style.use("seaborn-v0_8-whitegrid") + fig, axes = plt.subplots(2, 3, figsize=(16, 10)) + + # Row 1: Time comparisons + _plot_comparison( + axes[0, 0], rows_data["x_labels"], rows_data["duckdb"], rows_data["spark"], + "Dataset Size (rows)", "Validation Time (seconds)", + "Exp 1: Varying Rows (10 cols)", + duckdb_color, spark_color, use_log_scale=True + ) + + _plot_comparison( + axes[0, 1], cols_data["x_labels"], cols_data["duckdb"], cols_data["spark"], + "Columns (Checks)", "Validation Time (seconds)", + "Exp 2: Varying Columns (1M rows)", + duckdb_color, spark_color + ) + + _plot_comparison( + axes[0, 2], profiling_data["x_labels"], profiling_data["duckdb"], profiling_data["spark"], + "Dataset Size (rows)", "Profiling Time (seconds)", + "Exp 3: Column Profiling (10 cols)", + duckdb_color, spark_color + ) + + # Row 2: Speedup charts + _plot_speedup( + axes[1, 0], rows_data["x_labels"], rows_speedup, + "Dataset Size (rows)", "DuckDB Speedup: Varying Rows", + speedup_color + ) + + _plot_speedup( + axes[1, 1], cols_data["x_labels"], cols_speedup, + "Columns (Checks)", "DuckDB Speedup: Varying Columns", + speedup_color + ) + + _plot_speedup( + axes[1, 2], profiling_data["x_labels"], profiling_speedup, + "Dataset Size (rows)", "DuckDB Speedup: Column Profiling", + speedup_color + ) + + # Title + engine_label = run.engine.upper() if run.engine != "all" else "DuckDB vs Spark" + fig.suptitle( + f"PyDeequ Benchmark: {engine_label}\n{run.run_id}", + fontsize=14, fontweight="bold", y=1.02 + ) + + plt.tight_layout() + + # Save the figure + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + plt.savefig(output_path, dpi=150, bbox_inches="tight", facecolor="white", edgecolor="none") + plt.close(fig) + + return output_path diff --git a/benchmark/worker.py b/benchmark/worker.py new file mode 100644 index 0000000..3724731 --- /dev/null +++ b/benchmark/worker.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +Subprocess worker for running benchmarks in isolated process. + +This module is designed to be run as: + python -m benchmark.worker --config --engine {duckdb,spark} --output + +Each engine runs in a fresh subprocess to ensure clean JVM/Python state. +""" + +import argparse +import json +import os +import sys +import time +from datetime import datetime +from typing import Optional + +from .config import BenchmarkConfig, ExperimentConfig +from .results import ( + BenchmarkRun, + collect_environment_info, + generate_run_id, +) +from .experiments import ( + run_varying_rows_experiment_duckdb, + run_varying_cols_experiment_duckdb, + run_profiling_experiment_duckdb, + run_varying_rows_experiment_spark, + run_varying_cols_experiment_spark, + run_profiling_experiment_spark, + setup_spark, +) + + +def run_duckdb_worker(config: BenchmarkConfig, run_id: str) -> BenchmarkRun: + """Run all DuckDB experiments.""" + start_time = time.time() + + run = BenchmarkRun( + run_id=run_id, + timestamp=datetime.now().isoformat(), + engine="duckdb", + config=config.to_dict(), + environment=collect_environment_info(), + ) + + exp_config = config.experiment + + try: + run.varying_rows_results = run_varying_rows_experiment_duckdb(exp_config) + except Exception as e: + run.errors.append(f"DuckDB varying rows experiment failed: {e}") + print(f"Error in varying rows experiment: {e}") + + try: + run.varying_cols_results = run_varying_cols_experiment_duckdb(exp_config) + except Exception as e: + run.errors.append(f"DuckDB varying cols experiment failed: {e}") + print(f"Error in varying cols experiment: {e}") + + try: + run.profiling_results = run_profiling_experiment_duckdb(exp_config) + except Exception as e: + run.errors.append(f"DuckDB profiling experiment failed: {e}") + print(f"Error in profiling experiment: {e}") + + run.total_duration_seconds = time.time() - start_time + return run + + +def run_spark_worker(config: BenchmarkConfig, run_id: str) -> BenchmarkRun: + """Run all Spark experiments.""" + start_time = time.time() + + run = BenchmarkRun( + run_id=run_id, + timestamp=datetime.now().isoformat(), + engine="spark", + config=config.to_dict(), + environment=collect_environment_info(), + ) + + exp_config = config.experiment + + # Setup Spark + spark = None + spark_startup_time = 0.0 + + try: + print("\nSetting up Spark Connect...") + spark, spark_startup_time = setup_spark(config.spark_remote) + print(f" Spark Startup: {spark_startup_time:.3f}s") + run.spark_startup_time = spark_startup_time + except Exception as e: + error_msg = f"Spark setup failed: {e}" + run.errors.append(error_msg) + print(f"Error: {error_msg}") + run.total_duration_seconds = time.time() - start_time + return run + + try: + run.varying_rows_results = run_varying_rows_experiment_spark( + spark, spark_startup_time, exp_config + ) + except Exception as e: + run.errors.append(f"Spark varying rows experiment failed: {e}") + print(f"Error in varying rows experiment: {e}") + + try: + run.varying_cols_results = run_varying_cols_experiment_spark( + spark, spark_startup_time, exp_config + ) + except Exception as e: + run.errors.append(f"Spark varying cols experiment failed: {e}") + print(f"Error in varying cols experiment: {e}") + + try: + run.profiling_results = run_profiling_experiment_spark( + spark, spark_startup_time, exp_config + ) + except Exception as e: + run.errors.append(f"Spark profiling experiment failed: {e}") + print(f"Error in profiling experiment: {e}") + + run.total_duration_seconds = time.time() - start_time + return run + + +def main(): + parser = argparse.ArgumentParser(description="Benchmark worker subprocess") + parser.add_argument( + "--config", + required=True, + help="Path to JSON config file", + ) + parser.add_argument( + "--engine", + choices=["duckdb", "spark"], + required=True, + help="Engine to benchmark", + ) + parser.add_argument( + "--output", + required=True, + help="Path to write JSON results", + ) + parser.add_argument( + "--run-id", + help="Run ID to use (optional, generated if not provided)", + ) + + args = parser.parse_args() + + # Load config from JSON file + with open(args.config) as f: + config_data = json.load(f) + config = BenchmarkConfig.from_dict(config_data) + + # Use provided run ID or generate new one + run_id = args.run_id or generate_run_id() + + print(f"Benchmark Worker: {args.engine}") + print(f"Run ID: {run_id}") + print("=" * 70) + + # Run the appropriate engine + if args.engine == "duckdb": + result = run_duckdb_worker(config, run_id) + else: + result = run_spark_worker(config, run_id) + + # Write results to output file + os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) + with open(args.output, "w") as f: + json.dump(result.to_dict(), f, indent=2) + + print(f"\nResults written to: {args.output}") + print(f"Total duration: {result.total_duration_seconds:.1f}s") + + if result.errors: + print(f"Errors encountered: {len(result.errors)}") + for error in result.errors: + print(f" - {error}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/benchmark_cli.py b/benchmark_cli.py new file mode 100644 index 0000000..418e401 --- /dev/null +++ b/benchmark_cli.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +""" +PyDeequ Engine Benchmark CLI + +Orchestrates benchmark runs with process isolation and auto Spark server management. + +Usage: + # Run DuckDB only (no Spark server needed) + python benchmark_cli.py run --engine duckdb + + # Run Spark only (auto-spark is enabled by default) + python benchmark_cli.py run --engine spark + + # Run both engines + python benchmark_cli.py run --engine all + + # Run without auto Spark server management (assumes server is running) + python benchmark_cli.py run --engine spark --no-auto-spark + + # Generate report from saved results (folder or file path) + python benchmark_cli.py report benchmark_results/benchmark_2024-01-19T14-30-45/ + python benchmark_cli.py report benchmark_results/benchmark_2024-01-19T14-30-45/results.json + + # Generate report to custom location + python benchmark_cli.py report benchmark_results/benchmark_2024-01-19T14-30-45/ -o MY_RESULTS.md + + # Generate visualization PNG from results + python benchmark_cli.py visualize benchmark_results/benchmark_2024-01-19T14-30-45/ + python benchmark_cli.py visualize benchmark_results/benchmark_2024-01-19T14-30-45/ -o charts.png +""" + +import argparse +import json +import os +import subprocess +import sys +import tempfile +import time +from typing import Optional + +from benchmark.config import BenchmarkConfig, ExperimentConfig, SparkServerConfig +from benchmark.results import ( + BenchmarkRun, + generate_run_id, + save_results, + load_results, + merge_results, + collect_environment_info, +) +from benchmark.spark_server import managed_spark_server +from benchmark.report import save_report +from benchmark.visualize import generate_visualization + + +def run_engine_in_subprocess( + engine: str, + config: BenchmarkConfig, + run_id: str, +) -> Optional[BenchmarkRun]: + """ + Run benchmark for a single engine in an isolated subprocess. + + Args: + engine: Engine to run ("duckdb" or "spark") + config: Benchmark configuration + run_id: Run ID to use + + Returns: + BenchmarkRun result, or None on failure + """ + print(f"\n{'=' * 70}") + print(f"Running {engine.upper()} benchmarks in subprocess...") + print("=" * 70) + + # Write config to temp file + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: + json.dump(config.to_dict(), f, indent=2) + config_path = f.name + + # Temp output file for results (will be cleaned up after loading) + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: + output_path = f.name + + try: + # Run worker subprocess + cmd = [ + sys.executable, + "-m", + "benchmark.worker", + "--config", config_path, + "--engine", engine, + "--output", output_path, + "--run-id", run_id, + ] + + print(f"Command: {' '.join(cmd)}") + print() + + result = subprocess.run( + cmd, + cwd=os.path.dirname(os.path.abspath(__file__)), + ) + + if result.returncode != 0: + print(f"\n{engine.upper()} worker exited with code {result.returncode}") + # Try to load partial results if they exist + if os.path.exists(output_path): + return load_results(output_path) + return None + + # Load and return results + return load_results(output_path) + + except Exception as e: + print(f"Error running {engine} worker: {e}") + return None + + finally: + # Clean up temp files + if os.path.exists(config_path): + os.unlink(config_path) + if os.path.exists(output_path): + os.unlink(output_path) + + +def cmd_run(args: argparse.Namespace) -> int: + """Execute the 'run' subcommand.""" + # Build configuration + exp_config = ExperimentConfig() + if args.n_runs: + exp_config.n_runs = args.n_runs + + spark_config = SparkServerConfig() + if args.spark_home: + spark_config.spark_home = args.spark_home + if args.java_home: + spark_config.java_home = args.java_home + + config = BenchmarkConfig( + engine=args.engine, + output_dir=args.output_dir, + experiment=exp_config, + spark_server=spark_config, + ) + + # Generate run ID and create run directory + run_id = generate_run_id() + run_dir = os.path.join(args.output_dir, run_id) + os.makedirs(run_dir, exist_ok=True) + + auto_spark = not args.no_auto_spark + + print("PyDeequ Engine Benchmark") + print("=" * 70) + print(f"Run ID: {run_id}") + print(f"Engine: {args.engine}") + print(f"Output directory: {run_dir}") + print(f"Auto Spark: {auto_spark}") + print(f"Validation runs: {exp_config.n_runs}") + print(f"Row counts: {exp_config.row_counts}") + print(f"Column counts: {exp_config.column_counts}") + print(f"Cache directory: {exp_config.cache_dir}") + + start_time = time.time() + + run_duckdb = args.engine in ("all", "duckdb") + run_spark = args.engine in ("all", "spark") + + duckdb_result: Optional[BenchmarkRun] = None + spark_result: Optional[BenchmarkRun] = None + + # Run DuckDB (doesn't need Spark server) + if run_duckdb: + duckdb_result = run_engine_in_subprocess("duckdb", config, run_id) + + # Run Spark (may need server management) + if run_spark: + if auto_spark: + # Use managed server context + with managed_spark_server(spark_config) as server: + startup_time = server.start() + if server.is_running(): + spark_result = run_engine_in_subprocess("spark", config, run_id) + else: + print("Spark server failed to start, skipping Spark benchmarks") + else: + # Assume server is already running + spark_result = run_engine_in_subprocess("spark", config, run_id) + + # Merge results if both engines ran + if duckdb_result and spark_result: + final_result = merge_results(duckdb_result, spark_result) + elif duckdb_result: + final_result = duckdb_result + elif spark_result: + final_result = spark_result + else: + print("\nNo benchmark results produced!") + return 1 + + # Update total duration + final_result.total_duration_seconds = time.time() - start_time + + # Save combined results to run directory + results_path = save_results(final_result, run_dir) + print(f"\n{'=' * 70}") + print(f"Results saved to: {results_path}") + + # Generate markdown report in run directory + report_path = os.path.join(run_dir, "BENCHMARK_RESULTS.md") + save_report(final_result, report_path) + print(f"Report saved to: {report_path}") + + print(f"Total duration: {final_result.total_duration_seconds:.1f}s") + + if final_result.errors: + print(f"\nErrors encountered: {len(final_result.errors)}") + for error in final_result.errors: + print(f" - {error}") + return 1 + + return 0 + + +def cmd_report(args: argparse.Namespace) -> int: + """Execute the 'report' subcommand.""" + if not os.path.exists(args.json_file): + print(f"Error: File not found: {args.json_file}") + return 1 + + try: + run = load_results(args.json_file) + except Exception as e: + print(f"Error loading results: {e}") + return 1 + + output_path = args.output or "BENCHMARK_RESULTS.md" + save_report(run, output_path) + print(f"Report generated: {output_path}") + + return 0 + + +def cmd_visualize(args: argparse.Namespace) -> int: + """Execute the 'visualize' subcommand.""" + if not os.path.exists(args.results_path): + print(f"Error: Path not found: {args.results_path}") + return 1 + + try: + run = load_results(args.results_path) + except Exception as e: + print(f"Error loading results: {e}") + return 1 + + # Determine output path + if args.output: + output_path = args.output + else: + # Default: save in the same directory as results + if os.path.isdir(args.results_path): + output_path = os.path.join(args.results_path, "benchmark_chart.png") + else: + output_path = os.path.join(os.path.dirname(args.results_path), "benchmark_chart.png") + + try: + generate_visualization(run, output_path) + print(f"Visualization saved to: {output_path}") + except Exception as e: + print(f"Error generating visualization: {e}") + return 1 + + return 0 + + +def main(): + parser = argparse.ArgumentParser( + description="PyDeequ Engine Benchmark CLI", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # 'run' subcommand + run_parser = subparsers.add_parser("run", help="Run benchmark experiments") + run_parser.add_argument( + "--engine", + choices=["all", "duckdb", "spark"], + default="all", + help="Engine to benchmark (default: all)", + ) + run_parser.add_argument( + "--output-dir", + default="benchmark_results", + help="Output directory for results (default: benchmark_results/)", + ) + run_parser.add_argument( + "--no-auto-spark", + action="store_true", + dest="no_auto_spark", + help="Disable automatic Spark Connect server management (assumes server is already running)", + ) + run_parser.add_argument( + "--spark-home", + help="Path to Spark installation", + ) + run_parser.add_argument( + "--java-home", + help="Path to Java installation", + ) + run_parser.add_argument( + "--n-runs", + type=int, + help="Number of validation runs for averaging", + ) + + # 'report' subcommand + report_parser = subparsers.add_parser("report", help="Generate markdown report from JSON results") + report_parser.add_argument( + "json_file", + help="Path to JSON results file or run directory containing results.json", + ) + report_parser.add_argument( + "-o", "--output", + help="Output path for markdown report (default: BENCHMARK_RESULTS.md)", + ) + + # 'visualize' subcommand + visualize_parser = subparsers.add_parser("visualize", help="Generate PNG visualization from results") + visualize_parser.add_argument( + "results_path", + help="Path to JSON results file or run directory containing results.json", + ) + visualize_parser.add_argument( + "-o", "--output", + help="Output path for PNG file (default: benchmark_chart.png in results directory)", + ) + + args = parser.parse_args() + + if args.command == "run": + sys.exit(cmd_run(args)) + elif args.command == "report": + sys.exit(cmd_report(args)) + elif args.command == "visualize": + sys.exit(cmd_visualize(args)) + else: + parser.print_help() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..6304e20 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,214 @@ +# PyDeequ v2 Architecture + +## Overview + +PyDeequ v2 introduces a multi-engine architecture enabling data quality checks on different backends. The code is the source of truth - this document provides a high-level map to help you navigate the codebase. + +**Supported backends:** +- **DuckDB**: Local development, small-medium datasets (`pip install duckdb`) +- **Spark Connect**: Large-scale distributed processing (requires Spark cluster) + +## Design Philosophy + +The architecture is inspired by [DuckDQ](https://github.com/tdoehmen/duckdq), which demonstrated a key insight: + +> **Decouple state computation (engine-dependent) from state merging (engine-independent)** + +- **State computation** = expensive, engine-dependent (SQL queries, Spark jobs) +- **State merging** = cheap, pure Python (addition, max/min, Welford's algorithm) + +This separation enables multiple backends, incremental validation, and distributed processing. + +## Architecture Diagram + +``` + ┌──────────────────────────────────────┐ + │ User API │ + │ VerificationSuite, AnalysisRunner │ + │ ColumnProfilerRunner, Suggestions │ + └─────────────────┬────────────────────┘ + │ + ┌─────────────────▼────────────────────┐ + │ Engine Abstraction │ + │ BaseEngine ABC │ + │ compute_metrics(), run_checks() │ + │ profile_columns(), suggest_...() │ + └─────────────────┬────────────────────┘ + │ + ┌───────────────────────┼───────────────────────┐ + │ │ │ + ┌─────────▼─────────┐ ┌─────────▼─────────┐ ┌─────────▼─────────┐ + │ DuckDBEngine │ │ SparkEngine │ │ Future Engines │ + │ (Direct SQL) │ │ (Spark Connect) │ │ (Polars, etc.) │ + └───────────────────┘ └───────────────────┘ └───────────────────┘ +``` + +## Module Structure + +``` +pydeequ/ +├── __init__.py # connect() with auto-detection +├── engines/ +│ ├── __init__.py # BaseEngine ABC, result types +│ ├── duckdb.py # DuckDBEngine implementation +│ ├── spark.py # SparkEngine wrapper +│ ├── operators/ +│ │ ├── base.py # ScanOperator, GroupingOperator ABCs +│ │ ├── factory.py # OperatorFactory registry +│ │ ├── mixins.py # WhereClauseMixin, SafeExtractMixin +│ │ ├── scan_operators.py # 15 single-pass operators +│ │ ├── grouping_operators.py # 6 GROUP BY operators +│ │ ├── metadata_operators.py # Schema-based operators +│ │ └── profiling_operators.py # Column profiling operators +│ ├── constraints/ +│ │ ├── base.py # BaseEvaluator hierarchy +│ │ ├── factory.py # ConstraintEvaluatorFactory (27 types) +│ │ └── evaluators.py # 23 concrete evaluators +│ └── suggestions/ +│ ├── runner.py # Suggestion generation +│ ├── rules.py # Rule implementations +│ └── registry.py # Rule registry +└── v2/ # User-facing API + ├── analyzers.py # Analyzer definitions + ├── checks.py # Check/Constraint definitions + ├── predicates.py # Predicate classes + ├── verification.py # VerificationSuite, AnalysisRunner + ├── profiles.py # ColumnProfilerRunner + └── suggestions.py # ConstraintSuggestionRunner +``` + +## Key Abstractions + +### BaseEngine (`pydeequ/engines/__init__.py`) + +Abstract base class defining the engine interface. All engines implement: +- `compute_metrics(analyzers)` - Run analyzers and return `MetricResult` list +- `run_checks(checks)` - Evaluate constraints and return `ConstraintResult` list +- `profile_columns(columns)` - Return `ColumnProfile` for each column +- `suggest_constraints(rules)` - Generate `ConstraintSuggestion` list + +### Operators (`pydeequ/engines/operators/`) + +Operators translate analyzers into engine-specific queries: + +| Type | Description | Examples | +|------|-------------|----------| +| **ScanOperator** | Single-pass SQL aggregations, batched together | Size, Completeness, Mean, Sum, Min, Max | +| **GroupingOperator** | Requires GROUP BY, runs individually | Distinctness, Uniqueness, Entropy | +| **MetadataOperator** | Schema-based, no query needed | DataType | + +See `base.py` for ABCs, `factory.py` for the registry pattern. + +### OperatorFactory (`pydeequ/engines/operators/factory.py`) + +Registry mapping analyzer names to operator classes. Use `OperatorFactory.create(analyzer)` to instantiate operators. The factory determines query batching strategy. + +### Constraint Evaluators (`pydeequ/engines/constraints/`) + +Evaluators check if computed metrics satisfy constraints: +- **AnalyzerBasedEvaluator**: Delegates to an analyzer operator (hasMean, hasMin) +- **RatioCheckEvaluator**: Computes matches/total ratio (isPositive, isContainedIn) + +The `ConstraintEvaluatorFactory` maps 27 constraint types to evaluator classes. + +### Result Types (`pydeequ/engines/__init__.py`) + +Standardized dataclasses returned by all engines: +- `MetricResult`: Analyzer output (name, column, value, success) +- `ConstraintResult`: Check output (constraint, status, message) +- `ColumnProfile`: Profiling output (column, stats, histogram) + +All convert to pandas DataFrames via `results_to_dataframe()`. + +## Quick Start Examples + +### Analysis + +```python +import duckdb +import pydeequ +from pydeequ.v2.analyzers import Size, Completeness, Mean +from pydeequ.v2.verification import AnalysisRunner + +con = duckdb.connect() +con.execute("CREATE TABLE sales AS SELECT * FROM 'sales.parquet'") +engine = pydeequ.connect(con, table="sales") + +result = (AnalysisRunner() + .on_engine(engine) + .addAnalyzer(Size()) + .addAnalyzer(Completeness("customer_id")) + .addAnalyzer(Mean("amount")) + .run()) +``` + +### Verification + +```python +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.verification import VerificationSuite +from pydeequ.v2.predicates import gte + +result = (VerificationSuite() + .on_engine(engine) + .addCheck( + Check(CheckLevel.Error, "Data Quality") + .isComplete("id") + .hasCompleteness("email", gte(0.95)) + .isUnique("transaction_id") + ) + .run()) +``` + +### Profiling + +```python +from pydeequ.v2.profiles import ColumnProfilerRunner + +profiles = (ColumnProfilerRunner() + .on_engine(engine) + .run()) +``` + +### Suggestions + +```python +from pydeequ.v2.suggestions import ConstraintSuggestionRunner, Rules + +suggestions = (ConstraintSuggestionRunner() + .on_engine(engine) + .addConstraintRules(Rules.DEFAULT) + .run()) +``` + +## Engine Comparison + +| Aspect | DuckDB | Spark | +|--------|--------|-------| +| **Use case** | Local dev, CI/CD, files < 10GB | Distributed data, data lakes | +| **Setup** | `pip install duckdb` | Spark cluster + Deequ plugin | +| **Latency** | Low (in-process) | Higher (network overhead) | +| **Scaling** | Single-node, memory-bound | Distributed, scales horizontally | +| **Approximate metrics** | HyperLogLog, exact quantiles | HLL++, KLL sketches | + +Both engines aim for functional parity. Minor differences exist in approximate algorithms and histogram formats - see test suite for tolerances. + +## Benchmarks + +Performance comparisons between DuckDB and Spark engines are documented in [BENCHMARK.md](../BENCHMARK.md), including: +- Varying row counts (100K to 130M rows) +- Varying column counts (10 to 80 columns) +- Column profiling performance + +## Future Enhancements + +- State persistence for incremental validation +- Additional backends (Polars, SQLAlchemy, BigQuery) +- Anomaly detection on metrics +- Data lineage for constraint violations + +## References + +- [DuckDQ](https://github.com/tdoehmen/duckdq) - Inspiration for engine abstraction +- [AWS Deequ](https://github.com/awslabs/deequ) - Original Scala implementation +- [Ibis](https://ibis-project.org/) - Multi-backend design patterns diff --git a/imgs/benchmark_chart.png b/imgs/benchmark_chart.png new file mode 100644 index 0000000000000000000000000000000000000000..9283677829ea8448927c85417cecaa06b5e40182 GIT binary patch literal 180620 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ĸ{3*UneCuvwa8u!=AViJ* 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 ZP_8Qp=_go5eIjqXUqfeilDduW{{oEWcWVFu literal 0 HcmV?d00001 diff --git a/pydeequ/__init__.py b/pydeequ/__init__.py index 6d2202f..f31b47f 100644 --- a/pydeequ/__init__.py +++ b/pydeequ/__init__.py @@ -14,6 +14,15 @@ """ PyDeequ - Python API for Deequ data quality library. +For PyDeequ 2.0 with DuckDB (no Spark required): + import duckdb + import pydeequ + from pydeequ.v2.analyzers import Size, Completeness + + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id") + engine = pydeequ.connect(con, table="test") + For PyDeequ 2.0 (Spark Connect), use: from pydeequ.v2 import VerificationSuite, Check, CheckLevel from pydeequ.v2.predicates import eq, gte @@ -22,8 +31,52 @@ from pydeequ import deequ_maven_coord from pydeequ.checks import Check, CheckLevel """ +from typing import Any, Optional + __version__ = "2.0.0b1" + +def connect( + connection: Any, + table: Optional[str] = None, + dataframe: Optional[Any] = None, +): + """ + Create an engine from a connection object with auto-detection. + + This function inspects the connection type and creates the appropriate + engine backend. It supports: + - DuckDB connections (duckdb.DuckDBPyConnection) - runs locally + - Spark sessions (pyspark.sql.SparkSession) - uses Spark Connect + + Args: + connection: A database connection or Spark session + table: Table name for SQL-based backends (required for DuckDB) + dataframe: DataFrame for Spark backend (alternative to table) + + Returns: + An engine instance appropriate for the connection type + + Raises: + ValueError: If connection type is not supported + + Example: + # DuckDB (local, no Spark required) + import duckdb + import pydeequ + + con = duckdb.connect() + con.execute("CREATE TABLE reviews AS SELECT * FROM 'reviews.csv'") + engine = pydeequ.connect(con, table="reviews") + + # Spark Connect + from pyspark.sql import SparkSession + spark = SparkSession.builder.remote("sc://localhost:15002").getOrCreate() + engine = pydeequ.connect(spark, dataframe=df) + """ + from pydeequ.engines import connect as engines_connect + return engines_connect(connection, table=table, dataframe=dataframe) + # Legacy imports are deferred to avoid requiring SPARK_VERSION for V2 users. # V2 users should import from pydeequ.v2 directly. diff --git a/pydeequ/configs.py b/pydeequ/configs.py index e56c97d..ba5e378 100644 --- a/pydeequ/configs.py +++ b/pydeequ/configs.py @@ -41,4 +41,4 @@ def _get_deequ_maven_config(): SPARK_VERSION = _get_spark_version() DEEQU_MAVEN_COORD = _get_deequ_maven_config() -IS_DEEQU_V1 = re.search("com\.amazon\.deequ\:deequ\:1.*", DEEQU_MAVEN_COORD) is not None +IS_DEEQU_V1 = re.search(r"com\.amazon\.deequ\:deequ\:1.*", DEEQU_MAVEN_COORD) is not None diff --git a/pydeequ/engines/__init__.py b/pydeequ/engines/__init__.py new file mode 100644 index 0000000..b24a697 --- /dev/null +++ b/pydeequ/engines/__init__.py @@ -0,0 +1,389 @@ +# -*- coding: utf-8 -*- +""" +Engine abstraction for PyDeequ. + +This module provides the engine abstraction layer that enables PyDeequ +to work with different execution backends (Spark, DuckDB, etc.). + +Key design principles (inspired by DuckDQ): +1. State computation is engine-dependent (SQL queries, Spark jobs) +2. State merging is engine-independent (pure Python) +3. This separation enables incremental validation and easy backend additions + +Example usage: + import duckdb + import pydeequ + + # Auto-detection from connection type + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id, 2 as value") + engine = pydeequ.connect(con, table="test") + + # Direct import + from pydeequ.engines.duckdb import DuckDBEngine + engine = DuckDBEngine(con, table="test") +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from enum import Enum +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Optional, + Sequence, + Tuple, + Union, +) + +import pandas as pd + +if TYPE_CHECKING: + from pydeequ.v2.analyzers import _ConnectAnalyzer + from pydeequ.v2.checks import Check + + +class ConstraintStatus(Enum): + """Status of a constraint evaluation.""" + SUCCESS = "Success" + FAILURE = "Failure" + + # Aliases for backwards compatibility + Success = "Success" + Failure = "Failure" + + +class CheckStatus(Enum): + """Status of a check evaluation.""" + SUCCESS = "Success" + WARNING = "Warning" + ERROR = "Error" + + # Aliases for backwards compatibility + Success = "Success" + Warning = "Warning" + Error = "Error" + + +@dataclass +class MetricResult: + """Result of computing a metric.""" + name: str + instance: str + entity: str + value: Optional[float] + success: bool = True + message: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for DataFrame creation.""" + return { + "name": self.name, + "instance": self.instance, + "entity": self.entity, + "value": self.value, + } + + +@dataclass +class ConstraintResult: + """Result of evaluating a constraint.""" + check_description: str + check_level: str + check_status: Union[str, "CheckStatus"] + constraint: str + constraint_status: Union[str, "ConstraintStatus"] + constraint_message: Optional[str] = None + + def __post_init__(self): + """Convert string status values to enum values.""" + # Handle check_status + if isinstance(self.check_status, str): + for status in CheckStatus: + if status.value == self.check_status: + self.check_status = status + break + # Handle constraint_status + if isinstance(self.constraint_status, str): + for status in ConstraintStatus: + if status.value == self.constraint_status: + self.constraint_status = status + break + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for DataFrame creation.""" + check_status_val = self.check_status.value if isinstance(self.check_status, CheckStatus) else self.check_status + constraint_status_val = self.constraint_status.value if isinstance(self.constraint_status, ConstraintStatus) else self.constraint_status + return { + "check": self.check_description, + "check_level": self.check_level, + "check_status": check_status_val, + "constraint": self.constraint, + "constraint_status": constraint_status_val, + "constraint_message": self.constraint_message or "", + } + + +@dataclass +class ColumnProfile: + """Profile of a single column.""" + column: str + completeness: float + approx_distinct_values: int + data_type: str + is_data_type_inferred: bool = True + type_counts: Optional[str] = None + histogram: Optional[str] = None + mean: Optional[float] = None + minimum: Optional[float] = None + maximum: Optional[float] = None + sum: Optional[float] = None + std_dev: Optional[float] = None + approx_percentiles: Optional[str] = None + kll_buckets: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for DataFrame creation.""" + return { + "column": self.column, + "completeness": self.completeness, + "approx_distinct_values": self.approx_distinct_values, + "data_type": self.data_type, + "is_data_type_inferred": self.is_data_type_inferred, + "type_counts": self.type_counts, + "histogram": self.histogram, + "mean": self.mean, + "minimum": self.minimum, + "maximum": self.maximum, + "sum": self.sum, + "std_dev": self.std_dev, + "approx_percentiles": self.approx_percentiles, + "kll_buckets": self.kll_buckets, + } + + +@dataclass +class ConstraintSuggestion: + """A suggested constraint.""" + column_name: str + constraint_name: str + current_value: Optional[str] + description: str + suggesting_rule: str + code_for_constraint: str + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for DataFrame creation.""" + return { + "column_name": self.column_name, + "constraint_name": self.constraint_name, + "current_value": self.current_value, + "description": self.description, + "suggesting_rule": self.suggesting_rule, + "code_for_constraint": self.code_for_constraint, + } + + +class BaseEngine(ABC): + """ + Abstract base class for execution engines. + + Engines are responsible for: + 1. Computing metrics from data (engine-dependent) + 2. Evaluating constraints against computed metrics + 3. Profiling columns + 4. Suggesting constraints + + Subclasses must implement the core computation methods for their + specific backend (DuckDB, Spark, etc.). + """ + + @abstractmethod + def compute_metrics( + self, analyzers: Sequence["_ConnectAnalyzer"] + ) -> List[MetricResult]: + """ + Compute metrics for the given analyzers. + + Args: + analyzers: Sequence of analyzers to compute metrics for + + Returns: + List of MetricResult objects + """ + pass + + @abstractmethod + def run_checks(self, checks: Sequence["Check"]) -> List[ConstraintResult]: + """ + Run verification checks and return constraint results. + + Args: + checks: Sequence of Check objects to evaluate + + Returns: + List of ConstraintResult objects + """ + pass + + @abstractmethod + def profile_columns( + self, + columns: Optional[Sequence[str]] = None, + low_cardinality_threshold: int = 0, + ) -> List[ColumnProfile]: + """ + Profile columns in the data source. + + Args: + columns: Optional list of columns to profile. If None, profile all. + low_cardinality_threshold: Threshold for histogram computation + + Returns: + List of ColumnProfile objects + """ + pass + + @abstractmethod + def suggest_constraints( + self, + columns: Optional[Sequence[str]] = None, + rules: Optional[Sequence[str]] = None, + ) -> List[ConstraintSuggestion]: + """ + Suggest constraints based on data characteristics. + + Args: + columns: Optional list of columns to analyze + rules: Optional list of rule sets to apply + + Returns: + List of ConstraintSuggestion objects + """ + pass + + @abstractmethod + def get_schema(self) -> Dict[str, str]: + """ + Get the schema of the data source. + + Returns: + Dictionary mapping column names to data types + """ + pass + + def metrics_to_dataframe(self, metrics: List[MetricResult]) -> pd.DataFrame: + """Convert metrics to a pandas DataFrame.""" + if not metrics: + return pd.DataFrame(columns=["name", "instance", "entity", "value"]) + return pd.DataFrame([m.to_dict() for m in metrics]) + + def constraints_to_dataframe( + self, results: List[ConstraintResult] + ) -> pd.DataFrame: + """Convert constraint results to a pandas DataFrame.""" + if not results: + return pd.DataFrame( + columns=[ + "check", "check_level", "check_status", + "constraint", "constraint_status", "constraint_message" + ] + ) + return pd.DataFrame([r.to_dict() for r in results]) + + def profiles_to_dataframe(self, profiles: List[ColumnProfile]) -> pd.DataFrame: + """Convert column profiles to a pandas DataFrame.""" + if not profiles: + return pd.DataFrame(columns=["column", "completeness", "data_type"]) + return pd.DataFrame([p.to_dict() for p in profiles]) + + def suggestions_to_dataframe( + self, suggestions: List[ConstraintSuggestion] + ) -> pd.DataFrame: + """Convert suggestions to a pandas DataFrame.""" + if not suggestions: + return pd.DataFrame( + columns=[ + "column_name", "constraint_name", "current_value", + "description", "suggesting_rule", "code_for_constraint" + ] + ) + return pd.DataFrame([s.to_dict() for s in suggestions]) + + +def connect( + connection: Any, + table: Optional[str] = None, + dataframe: Optional[Any] = None, +) -> BaseEngine: + """ + Create an engine from a connection object with auto-detection. + + This function inspects the connection type and creates the appropriate + engine backend. It supports: + - DuckDB connections (duckdb.DuckDBPyConnection) + - Spark sessions (pyspark.sql.SparkSession) - wraps existing v2 API + + Args: + connection: A database connection or Spark session + table: Table name for SQL-based backends + dataframe: DataFrame for Spark backend (alternative to table) + + Returns: + An engine instance appropriate for the connection type + + Raises: + ValueError: If connection type is not supported + + Example: + import duckdb + import pydeequ + + con = duckdb.connect() + con.execute("CREATE TABLE reviews AS SELECT * FROM 'reviews.csv'") + engine = pydeequ.connect(con, table="reviews") + """ + # Try DuckDB + try: + import duckdb + if isinstance(connection, duckdb.DuckDBPyConnection): + if table is None: + raise ValueError("table parameter is required for DuckDB connections") + from pydeequ.engines.duckdb import DuckDBEngine + return DuckDBEngine(connection, table) + except ImportError: + pass + + # Try Spark + try: + from pyspark.sql import SparkSession + if isinstance(connection, SparkSession): + from pydeequ.engines.spark import SparkEngine + return SparkEngine(connection, table=table, dataframe=dataframe) + except ImportError: + pass + + raise ValueError( + f"Unsupported connection type: {type(connection).__name__}. " + "Supported types: duckdb.DuckDBPyConnection, pyspark.sql.SparkSession" + ) + + +# Export public API +__all__ = [ + # Base classes + "BaseEngine", + # Result types + "MetricResult", + "ConstraintResult", + "ConstraintStatus", + "CheckStatus", + "ColumnProfile", + "ConstraintSuggestion", + # Factory function + "connect", +] diff --git a/pydeequ/engines/constraints/__init__.py b/pydeequ/engines/constraints/__init__.py new file mode 100644 index 0000000..d475279 --- /dev/null +++ b/pydeequ/engines/constraints/__init__.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" +Constraint evaluator abstractions for data quality checks. + +This module provides a constraint evaluator pattern that: +1. Encapsulates constraint evaluation logic in self-contained classes +2. Separates value computation from assertion evaluation +3. Provides consistent WHERE clause handling +4. Enables easy addition of new constraint types + +Architecture: + Protocols (Contracts) + └── ConstraintEvaluatorProtocol - Defines evaluator contract + + Base Classes (Hierarchy) + ├── BaseEvaluator - Base with WHERE clause and assertion handling + ├── RatioCheckEvaluator - For match/total ratio constraints + └── AnalyzerBasedEvaluator - Delegates to analyzer operators + + Evaluator Implementations + ├── Analyzer-based (SizeEvaluator, CompletenessEvaluator, etc.) + ├── Ratio-check (IsPositiveEvaluator, IsContainedInEvaluator, etc.) + ├── Comparison (ColumnComparisonEvaluator) + └── Multi-column (MultiColumnCompletenessEvaluator) + + Factory + └── ConstraintEvaluatorFactory - Creates evaluators from protobufs + +Example usage: + from pydeequ.engines.constraints import ConstraintEvaluatorFactory + + # Create evaluator from constraint protobuf + evaluator = ConstraintEvaluatorFactory.create(constraint_proto) + + if evaluator: + # Compute the metric value + value = evaluator.compute_value(table, execute_fn) + + # Evaluate the assertion + passed = evaluator.evaluate(value) + + # Get human-readable description + description = evaluator.to_string() +""" + +from pydeequ.engines.constraints.base import ( + AnalyzerBasedEvaluator, + BaseEvaluator, + RatioCheckEvaluator, +) +from pydeequ.engines.constraints.evaluators import ( + ApproxCountDistinctEvaluator, + ApproxQuantileEvaluator, + ColumnComparisonEvaluator, + CompletenessEvaluator, + ComplianceEvaluator, + ContainsCreditCardEvaluator, + ContainsEmailEvaluator, + ContainsSSNEvaluator, + ContainsURLEvaluator, + CorrelationEvaluator, + DistinctnessEvaluator, + EntropyEvaluator, + IsContainedInEvaluator, + IsNonNegativeEvaluator, + IsPositiveEvaluator, + MaximumEvaluator, + MaxLengthEvaluator, + MeanEvaluator, + MinimumEvaluator, + MinLengthEvaluator, + MultiColumnCompletenessEvaluator, + MutualInformationEvaluator, + PatternMatchEvaluator, + SizeEvaluator, + StandardDeviationEvaluator, + SumEvaluator, + UniquenessEvaluator, + UniqueValueRatioEvaluator, +) +from pydeequ.engines.constraints.factory import ConstraintEvaluatorFactory +from pydeequ.engines.constraints.protocols import ConstraintEvaluatorProtocol + +__all__ = [ + # Protocols + "ConstraintEvaluatorProtocol", + # Base classes + "BaseEvaluator", + "RatioCheckEvaluator", + "AnalyzerBasedEvaluator", + # Analyzer-based evaluators + "SizeEvaluator", + "CompletenessEvaluator", + "MeanEvaluator", + "MinimumEvaluator", + "MaximumEvaluator", + "SumEvaluator", + "StandardDeviationEvaluator", + "UniquenessEvaluator", + "DistinctnessEvaluator", + "UniqueValueRatioEvaluator", + "CorrelationEvaluator", + "EntropyEvaluator", + "MutualInformationEvaluator", + "PatternMatchEvaluator", + "MinLengthEvaluator", + "MaxLengthEvaluator", + "ApproxCountDistinctEvaluator", + "ApproxQuantileEvaluator", + "ComplianceEvaluator", + # Ratio-check evaluators + "IsPositiveEvaluator", + "IsNonNegativeEvaluator", + "IsContainedInEvaluator", + "ContainsEmailEvaluator", + "ContainsURLEvaluator", + "ContainsCreditCardEvaluator", + "ContainsSSNEvaluator", + # Comparison evaluators + "ColumnComparisonEvaluator", + # Multi-column evaluators + "MultiColumnCompletenessEvaluator", + # Factory + "ConstraintEvaluatorFactory", +] diff --git a/pydeequ/engines/constraints/base.py b/pydeequ/engines/constraints/base.py new file mode 100644 index 0000000..abcd32e --- /dev/null +++ b/pydeequ/engines/constraints/base.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +""" +Base classes for constraint evaluators. + +This module provides the abstract base classes that combine mixins +to create the foundation for concrete evaluator implementations. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Callable, List, Optional + +from pydeequ.engines.operators.mixins import SafeExtractMixin, WhereClauseMixin + +if TYPE_CHECKING: + import pandas as pd + from pydeequ.v2.predicates import Predicate + + +class BaseEvaluator(WhereClauseMixin, SafeExtractMixin, ABC): + """ + Base class for all constraint evaluators. + + Provides shared functionality for WHERE clause handling, + assertion parsing, and predicate evaluation. + + Attributes: + column: Optional column name for single-column constraints + columns: List of column names for multi-column constraints + where: Optional SQL WHERE clause for filtering + assertion: Parsed predicate for evaluation + """ + + def __init__(self, constraint_proto): + """ + Initialize evaluator from constraint protobuf. + + Args: + constraint_proto: Protobuf message containing constraint definition + """ + self.column = constraint_proto.column if constraint_proto.column else None + self.columns = list(constraint_proto.columns) if constraint_proto.columns else [] + self.where = constraint_proto.where if constraint_proto.where else None + self.assertion = self._parse_assertion(constraint_proto) + self._constraint_type = constraint_proto.type + + @property + def constraint_type(self) -> str: + """Return the constraint type identifier.""" + return self._constraint_type + + def _parse_assertion(self, constraint_proto) -> Optional["Predicate"]: + """ + Parse assertion predicate from constraint protobuf. + + Args: + constraint_proto: Protobuf message containing constraint definition + + Returns: + Parsed predicate or None if no assertion specified + """ + from pydeequ.v2.proto import deequ_connect_pb2 as proto + + if not constraint_proto.HasField("assertion"): + return None + + pred_msg = constraint_proto.assertion + + if pred_msg.operator == proto.PredicateMessage.Operator.BETWEEN: + from pydeequ.v2.predicates import Between + return Between(pred_msg.lower_bound, pred_msg.upper_bound) + else: + from pydeequ.v2.predicates import Comparison + return Comparison(pred_msg.operator, pred_msg.value) + + def _evaluate_predicate(self, value: float, assertion: "Predicate") -> bool: + """ + Evaluate a predicate against a value. + + Args: + value: The value to check + assertion: The predicate to evaluate + + Returns: + True if the value satisfies the predicate + """ + from pydeequ.v2.predicates import Between, Comparison + from pydeequ.v2.proto import deequ_connect_pb2 as proto + + if isinstance(assertion, Comparison): + op = assertion.operator + target = assertion.value + + if op == proto.PredicateMessage.Operator.EQ: + return abs(value - target) < 1e-9 + elif op == proto.PredicateMessage.Operator.NE: + return abs(value - target) >= 1e-9 + elif op == proto.PredicateMessage.Operator.GT: + return value > target + elif op == proto.PredicateMessage.Operator.GE: + return value >= target + elif op == proto.PredicateMessage.Operator.LT: + return value < target + elif op == proto.PredicateMessage.Operator.LE: + return value <= target + + elif isinstance(assertion, Between): + return assertion.lower <= value <= assertion.upper + + return False + + def evaluate(self, value: Optional[float]) -> bool: + """ + Evaluate whether the computed value satisfies the constraint. + + Args: + value: The computed metric value + + Returns: + True if the constraint is satisfied, False otherwise + """ + if value is None: + return False + + if self.assertion: + return self._evaluate_predicate(value, self.assertion) + + # Default: value must equal 1.0 (for completeness-like constraints) + return value == 1.0 + + @abstractmethod + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + """ + Compute the metric value for this constraint. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Computed metric value, or None if computation fails + """ + raise NotImplementedError + + @abstractmethod + def to_string(self) -> str: + """ + Return a human-readable string representation of the constraint. + + Returns: + Description of what the constraint checks + """ + raise NotImplementedError + + +class RatioCheckEvaluator(BaseEvaluator): + """ + Base class for constraints that compute matches/total ratio. + + These constraints check what fraction of rows satisfy some condition, + such as isPositive, isNonNegative, isContainedIn, etc. + """ + + @abstractmethod + def get_condition(self) -> str: + """ + Get the SQL condition that defines a 'match'. + + Returns: + SQL boolean expression for the match condition + """ + raise NotImplementedError + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + """ + Compute the fraction of rows matching the condition. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Ratio of matching rows to total rows + """ + condition = self.get_condition() + + if self.where: + query = f""" + SELECT + SUM(CASE WHEN {self.where} THEN 1 ELSE 0 END) as total, + SUM(CASE WHEN ({self.where}) AND ({condition}) THEN 1 ELSE 0 END) as matches + FROM {table} + """ + else: + query = f""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN {condition} THEN 1 ELSE 0 END) as matches + FROM {table} + """ + + result = execute_fn(query) + total = self.safe_float(result, "total") or 0 + matches = self.safe_float(result, "matches") or 0 + + if total == 0: + return 1.0 + return matches / total + + +class AnalyzerBasedEvaluator(BaseEvaluator): + """ + Base class for constraints that delegate to an analyzer operator. + + These constraints compute their value by creating and running + the corresponding analyzer operator. + """ + + @abstractmethod + def get_operator(self): + """ + Get the operator instance to compute the metric. + + Returns: + Operator instance (ScanOperator or GroupingOperator) + """ + raise NotImplementedError + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + """ + Compute the metric value using the analyzer operator. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Computed metric value + """ + operator = self.get_operator() + + # Check if it's a scan or grouping operator + if hasattr(operator, "get_aggregations"): + # Scan operator + aggregations = operator.get_aggregations() + query = f"SELECT {', '.join(aggregations)} FROM {table}" + result = execute_fn(query) + metric_result = operator.extract_result(result) + return metric_result.value + elif hasattr(operator, "build_query"): + # Grouping operator + query = operator.build_query(table) + result = execute_fn(query) + metric_result = operator.extract_result(result) + return metric_result.value + + return None + + +__all__ = [ + "BaseEvaluator", + "RatioCheckEvaluator", + "AnalyzerBasedEvaluator", +] diff --git a/pydeequ/engines/constraints/evaluators.py b/pydeequ/engines/constraints/evaluators.py new file mode 100644 index 0000000..2ac5650 --- /dev/null +++ b/pydeequ/engines/constraints/evaluators.py @@ -0,0 +1,494 @@ +# -*- coding: utf-8 -*- +""" +Constraint evaluator implementations. + +This module contains all concrete evaluator classes that implement +specific constraint types. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Optional + +from pydeequ.engines.constraints.base import ( + AnalyzerBasedEvaluator, + BaseEvaluator, + RatioCheckEvaluator, +) +from pydeequ.engines.operators import ( + ApproxCountDistinctOperator, + ApproxQuantileOperator, + CompletenessOperator, + ComplianceOperator, + CorrelationOperator, + DistinctnessOperator, + EntropyOperator, + MaximumOperator, + MaxLengthOperator, + MeanOperator, + MinimumOperator, + MinLengthOperator, + MutualInformationOperator, + PatternMatchOperator, + SizeOperator, + StandardDeviationOperator, + SumOperator, + UniqueValueRatioOperator, + UniquenessOperator, +) + +if TYPE_CHECKING: + import pandas as pd + + +# ============================================================================= +# Analyzer-based evaluators +# ============================================================================= + + +class SizeEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasSize constraint.""" + + def get_operator(self): + return SizeOperator(where=self.where) + + def to_string(self) -> str: + if self.assertion: + return f"hasSize(assertion)" + return "hasSize()" + + +class CompletenessEvaluator(AnalyzerBasedEvaluator): + """Evaluator for isComplete and hasCompleteness constraints.""" + + def get_operator(self): + return CompletenessOperator(self.column, where=self.where) + + def to_string(self) -> str: + if self.assertion: + return f"hasCompleteness({self.column}, assertion)" + return f"isComplete({self.column})" + + +class MeanEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMean constraint.""" + + def get_operator(self): + return MeanOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasMean({self.column}, assertion)" + + +class MinimumEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMin constraint.""" + + def get_operator(self): + return MinimumOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasMin({self.column}, assertion)" + + +class MaximumEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMax constraint.""" + + def get_operator(self): + return MaximumOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasMax({self.column}, assertion)" + + +class SumEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasSum constraint.""" + + def get_operator(self): + return SumOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasSum({self.column}, assertion)" + + +class StandardDeviationEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasStandardDeviation constraint.""" + + def get_operator(self): + return StandardDeviationOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasStandardDeviation({self.column}, assertion)" + + +class UniquenessEvaluator(AnalyzerBasedEvaluator): + """Evaluator for isUnique and hasUniqueness constraints.""" + + def get_operator(self): + cols = self.columns if self.columns else [self.column] + return UniquenessOperator(cols, where=self.where) + + def to_string(self) -> str: + cols = self.columns if self.columns else [self.column] + col_str = ", ".join(cols) + if self.assertion: + return f"hasUniqueness({col_str}, assertion)" + return f"isUnique({col_str})" + + +class DistinctnessEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasDistinctness constraint.""" + + def get_operator(self): + cols = self.columns if self.columns else [self.column] + return DistinctnessOperator(cols, where=self.where) + + def to_string(self) -> str: + cols = self.columns if self.columns else [self.column] + col_str = ", ".join(cols) + return f"hasDistinctness({col_str}, assertion)" + + +class UniqueValueRatioEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasUniqueValueRatio constraint.""" + + def get_operator(self): + cols = self.columns if self.columns else [self.column] + return UniqueValueRatioOperator(cols, where=self.where) + + def to_string(self) -> str: + cols = self.columns if self.columns else [self.column] + col_str = ", ".join(cols) + return f"hasUniqueValueRatio({col_str}, assertion)" + + +class CorrelationEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasCorrelation constraint.""" + + def get_operator(self): + if len(self.columns) >= 2: + return CorrelationOperator(self.columns[0], self.columns[1], where=self.where) + return None + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + if len(self.columns) < 2: + return None + return super().compute_value(table, execute_fn) + + def to_string(self) -> str: + if len(self.columns) >= 2: + return f"hasCorrelation({self.columns[0]}, {self.columns[1]}, assertion)" + return "hasCorrelation()" + + +class EntropyEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasEntropy constraint.""" + + def get_operator(self): + return EntropyOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasEntropy({self.column}, assertion)" + + +class MutualInformationEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMutualInformation constraint.""" + + def get_operator(self): + if len(self.columns) >= 2: + return MutualInformationOperator(self.columns, where=self.where) + return None + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + if len(self.columns) < 2: + return None + return super().compute_value(table, execute_fn) + + def to_string(self) -> str: + col_str = ", ".join(self.columns) + return f"hasMutualInformation({col_str}, assertion)" + + +class PatternMatchEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasPattern constraint.""" + + def __init__(self, constraint_proto): + super().__init__(constraint_proto) + self.pattern = constraint_proto.pattern if constraint_proto.pattern else "" + + def get_operator(self): + return PatternMatchOperator(self.column, self.pattern, where=self.where) + + def to_string(self) -> str: + return f"hasPattern({self.column}, '{self.pattern}')" + + +class MinLengthEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMinLength constraint.""" + + def get_operator(self): + return MinLengthOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasMinLength({self.column}, assertion)" + + +class MaxLengthEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasMaxLength constraint.""" + + def get_operator(self): + return MaxLengthOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasMaxLength({self.column}, assertion)" + + +class ApproxCountDistinctEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasApproxCountDistinct constraint.""" + + def get_operator(self): + return ApproxCountDistinctOperator(self.column, where=self.where) + + def to_string(self) -> str: + return f"hasApproxCountDistinct({self.column}, assertion)" + + +class ApproxQuantileEvaluator(AnalyzerBasedEvaluator): + """Evaluator for hasApproxQuantile constraint.""" + + def __init__(self, constraint_proto): + super().__init__(constraint_proto) + self.quantile = constraint_proto.quantile if constraint_proto.quantile else 0.5 + + def get_operator(self): + return ApproxQuantileOperator(self.column, self.quantile, where=self.where) + + def to_string(self) -> str: + return f"hasApproxQuantile({self.column}, {self.quantile}, assertion)" + + +class ComplianceEvaluator(AnalyzerBasedEvaluator): + """Evaluator for satisfies constraint.""" + + def __init__(self, constraint_proto): + super().__init__(constraint_proto) + self.predicate = constraint_proto.column_condition if constraint_proto.column_condition else "" + self.name = constraint_proto.constraint_name if constraint_proto.constraint_name else "satisfies" + + def get_operator(self): + return ComplianceOperator(self.name, self.predicate, where=self.where) + + def to_string(self) -> str: + return f"satisfies({self.name}, '{self.predicate}')" + + +# ============================================================================= +# Ratio-check evaluators +# ============================================================================= + + +class IsPositiveEvaluator(RatioCheckEvaluator): + """Evaluator for isPositive constraint.""" + + def get_condition(self) -> str: + return f"{self.column} > 0" + + def to_string(self) -> str: + return f"isPositive({self.column})" + + +class IsNonNegativeEvaluator(RatioCheckEvaluator): + """Evaluator for isNonNegative constraint.""" + + def get_condition(self) -> str: + return f"{self.column} >= 0" + + def to_string(self) -> str: + return f"isNonNegative({self.column})" + + +class IsContainedInEvaluator(RatioCheckEvaluator): + """Evaluator for isContainedIn constraint.""" + + def __init__(self, constraint_proto): + super().__init__(constraint_proto) + self.allowed_values = list(constraint_proto.allowed_values) if constraint_proto.allowed_values else [] + + def get_condition(self) -> str: + # Escape single quotes in values + escaped_values = [v.replace("'", "''") for v in self.allowed_values] + values_str = ", ".join([f"'{v}'" for v in escaped_values]) + return f"{self.column} IN ({values_str})" + + def to_string(self) -> str: + values_str = ", ".join([f"'{v}'" for v in self.allowed_values]) + return f"isContainedIn({self.column}, [{values_str}])" + + +class ContainsEmailEvaluator(RatioCheckEvaluator): + """Evaluator for containsEmail constraint.""" + + EMAIL_PATTERN = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" + + def get_condition(self) -> str: + return f"REGEXP_MATCHES({self.column}, '{self.EMAIL_PATTERN}')" + + def to_string(self) -> str: + return f"containsEmail({self.column})" + + +class ContainsURLEvaluator(RatioCheckEvaluator): + """Evaluator for containsURL constraint.""" + + URL_PATTERN = r"^https?://[^\s]+$" + + def get_condition(self) -> str: + return f"REGEXP_MATCHES({self.column}, '{self.URL_PATTERN}')" + + def to_string(self) -> str: + return f"containsURL({self.column})" + + +class ContainsCreditCardEvaluator(RatioCheckEvaluator): + """Evaluator for containsCreditCardNumber constraint.""" + + CC_PATTERN = r"^\d{13,19}$" + + def get_condition(self) -> str: + return f"REGEXP_MATCHES({self.column}, '{self.CC_PATTERN}')" + + def to_string(self) -> str: + return f"containsCreditCardNumber({self.column})" + + +class ContainsSSNEvaluator(RatioCheckEvaluator): + """Evaluator for containsSocialSecurityNumber constraint.""" + + SSN_PATTERN = r"^\d{3}-\d{2}-\d{4}$" + + def get_condition(self) -> str: + return f"REGEXP_MATCHES({self.column}, '{self.SSN_PATTERN}')" + + def to_string(self) -> str: + return f"containsSocialSecurityNumber({self.column})" + + +# ============================================================================= +# Comparison evaluators +# ============================================================================= + + +class ColumnComparisonEvaluator(RatioCheckEvaluator): + """Evaluator for column comparison constraints.""" + + def __init__(self, constraint_proto): + super().__init__(constraint_proto) + self._comparison_type = constraint_proto.type + + def get_condition(self) -> str: + if len(self.columns) < 2: + return "1=0" # Always false if not enough columns + + col_a, col_b = self.columns[0], self.columns[1] + + if self._comparison_type == "isLessThan": + return f"{col_a} < {col_b}" + elif self._comparison_type == "isLessThanOrEqualTo": + return f"{col_a} <= {col_b}" + elif self._comparison_type == "isGreaterThan": + return f"{col_a} > {col_b}" + elif self._comparison_type == "isGreaterThanOrEqualTo": + return f"{col_a} >= {col_b}" + + return "1=0" + + def to_string(self) -> str: + if len(self.columns) >= 2: + return f"{self._comparison_type}({self.columns[0]}, {self.columns[1]})" + return f"{self._comparison_type}()" + + +# ============================================================================= +# Multi-column evaluators +# ============================================================================= + + +class MultiColumnCompletenessEvaluator(BaseEvaluator): + """Evaluator for areComplete and haveCompleteness constraints.""" + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + if not self.columns: + return 1.0 + + # All columns must be non-null for a row to be "complete" + null_conditions = " OR ".join([f"{col} IS NULL" for col in self.columns]) + + if self.where: + query = f""" + SELECT + SUM(CASE WHEN {self.where} THEN 1 ELSE 0 END) as total, + SUM(CASE WHEN ({self.where}) AND ({null_conditions}) THEN 1 ELSE 0 END) as any_null + FROM {table} + """ + else: + query = f""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN {null_conditions} THEN 1 ELSE 0 END) as any_null + FROM {table} + """ + + result = execute_fn(query) + total = self.safe_float(result, "total") or 0 + any_null = self.safe_float(result, "any_null") or 0 + + if total == 0: + return 1.0 + return (total - any_null) / total + + def to_string(self) -> str: + col_str = ", ".join(self.columns) + if self.assertion: + return f"haveCompleteness({col_str}, assertion)" + return f"areComplete({col_str})" + + +__all__ = [ + # Analyzer-based evaluators + "SizeEvaluator", + "CompletenessEvaluator", + "MeanEvaluator", + "MinimumEvaluator", + "MaximumEvaluator", + "SumEvaluator", + "StandardDeviationEvaluator", + "UniquenessEvaluator", + "DistinctnessEvaluator", + "UniqueValueRatioEvaluator", + "CorrelationEvaluator", + "EntropyEvaluator", + "MutualInformationEvaluator", + "PatternMatchEvaluator", + "MinLengthEvaluator", + "MaxLengthEvaluator", + "ApproxCountDistinctEvaluator", + "ApproxQuantileEvaluator", + "ComplianceEvaluator", + # Ratio-check evaluators + "IsPositiveEvaluator", + "IsNonNegativeEvaluator", + "IsContainedInEvaluator", + "ContainsEmailEvaluator", + "ContainsURLEvaluator", + "ContainsCreditCardEvaluator", + "ContainsSSNEvaluator", + # Comparison evaluators + "ColumnComparisonEvaluator", + # Multi-column evaluators + "MultiColumnCompletenessEvaluator", +] diff --git a/pydeequ/engines/constraints/factory.py b/pydeequ/engines/constraints/factory.py new file mode 100644 index 0000000..c04e60c --- /dev/null +++ b/pydeequ/engines/constraints/factory.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +Factory for creating constraint evaluators. + +This module provides a registry-based factory pattern for creating +evaluator instances from constraint protobufs. +""" + +from __future__ import annotations + +from typing import Dict, Optional, Type + +from pydeequ.engines.constraints.base import BaseEvaluator +from pydeequ.engines.constraints.evaluators import ( + ApproxCountDistinctEvaluator, + ApproxQuantileEvaluator, + ColumnComparisonEvaluator, + CompletenessEvaluator, + ComplianceEvaluator, + ContainsCreditCardEvaluator, + ContainsEmailEvaluator, + ContainsSSNEvaluator, + ContainsURLEvaluator, + CorrelationEvaluator, + DistinctnessEvaluator, + EntropyEvaluator, + IsContainedInEvaluator, + IsNonNegativeEvaluator, + IsPositiveEvaluator, + MaximumEvaluator, + MaxLengthEvaluator, + MeanEvaluator, + MinimumEvaluator, + MinLengthEvaluator, + MultiColumnCompletenessEvaluator, + MutualInformationEvaluator, + PatternMatchEvaluator, + SizeEvaluator, + StandardDeviationEvaluator, + SumEvaluator, + UniquenessEvaluator, + UniqueValueRatioEvaluator, +) + +class ConstraintEvaluatorFactory: + """ + Factory for creating constraint evaluators from protobufs. + + This factory uses a registry pattern to map constraint type strings + to their corresponding evaluator classes. + """ + + _registry: Dict[str, Type[BaseEvaluator]] = { + # Analyzer-based evaluators + "hasSize": SizeEvaluator, + "isComplete": CompletenessEvaluator, + "hasCompleteness": CompletenessEvaluator, + "hasMean": MeanEvaluator, + "hasMin": MinimumEvaluator, + "hasMax": MaximumEvaluator, + "hasSum": SumEvaluator, + "hasStandardDeviation": StandardDeviationEvaluator, + "isUnique": UniquenessEvaluator, + "hasUniqueness": UniquenessEvaluator, + "hasDistinctness": DistinctnessEvaluator, + "hasUniqueValueRatio": UniqueValueRatioEvaluator, + "hasCorrelation": CorrelationEvaluator, + "hasEntropy": EntropyEvaluator, + "hasMutualInformation": MutualInformationEvaluator, + "hasPattern": PatternMatchEvaluator, + "hasMinLength": MinLengthEvaluator, + "hasMaxLength": MaxLengthEvaluator, + "hasApproxCountDistinct": ApproxCountDistinctEvaluator, + "hasApproxQuantile": ApproxQuantileEvaluator, + "satisfies": ComplianceEvaluator, + # Ratio-check evaluators + "isPositive": IsPositiveEvaluator, + "isNonNegative": IsNonNegativeEvaluator, + "isContainedIn": IsContainedInEvaluator, + "containsEmail": ContainsEmailEvaluator, + "containsURL": ContainsURLEvaluator, + "containsCreditCardNumber": ContainsCreditCardEvaluator, + "containsSocialSecurityNumber": ContainsSSNEvaluator, + # Comparison evaluators + "isLessThan": ColumnComparisonEvaluator, + "isLessThanOrEqualTo": ColumnComparisonEvaluator, + "isGreaterThan": ColumnComparisonEvaluator, + "isGreaterThanOrEqualTo": ColumnComparisonEvaluator, + # Multi-column evaluators + "areComplete": MultiColumnCompletenessEvaluator, + "haveCompleteness": MultiColumnCompletenessEvaluator, + } + + @classmethod + def create(cls, constraint_proto) -> Optional[BaseEvaluator]: + """ + Create an evaluator instance from a constraint protobuf. + + Args: + constraint_proto: Protobuf message containing constraint definition + + Returns: + Evaluator instance or None if constraint type not supported + """ + evaluator_class = cls._registry.get(constraint_proto.type) + if evaluator_class: + return evaluator_class(constraint_proto) + return None + + @classmethod + def is_supported(cls, constraint_type: str) -> bool: + """ + Check if a constraint type is supported by the factory. + + Args: + constraint_type: The constraint type string to check + + Returns: + True if the constraint type is supported + """ + return constraint_type in cls._registry + + @classmethod + def supported_types(cls) -> list: + """ + Get list of all supported constraint types. + + Returns: + List of supported constraint type strings + """ + return list(cls._registry.keys()) + + +__all__ = [ + "ConstraintEvaluatorFactory", +] diff --git a/pydeequ/engines/constraints/protocols.py b/pydeequ/engines/constraints/protocols.py new file mode 100644 index 0000000..87a7e67 --- /dev/null +++ b/pydeequ/engines/constraints/protocols.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" +Protocol definitions for constraint evaluators. + +This module defines the structural typing contracts that all constraint +evaluators must satisfy. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Optional, Protocol, runtime_checkable + +if TYPE_CHECKING: + from pydeequ.v2.predicates import Predicate + + +@runtime_checkable +class ConstraintEvaluatorProtocol(Protocol): + """ + Contract for constraint evaluators. + + Constraint evaluators compute values from data and evaluate + assertions against those values to determine pass/fail status. + """ + + @property + def constraint_type(self) -> str: + """Return the constraint type identifier.""" + ... + + def compute_value( + self, table: str, execute_fn: Callable[[str], "pd.DataFrame"] + ) -> Optional[float]: + """ + Compute the metric value for this constraint. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Computed metric value, or None if computation fails + """ + ... + + def evaluate( + self, value: Optional[float], assertion: Optional["Predicate"] = None + ) -> bool: + """ + Evaluate whether the computed value satisfies the constraint. + + Args: + value: The computed metric value + assertion: Optional predicate to evaluate against + + Returns: + True if the constraint is satisfied, False otherwise + """ + ... + + def to_string(self) -> str: + """ + Return a human-readable string representation of the constraint. + + Returns: + Description of what the constraint checks + """ + ... + + +__all__ = [ + "ConstraintEvaluatorProtocol", +] diff --git a/pydeequ/engines/duckdb.py b/pydeequ/engines/duckdb.py new file mode 100644 index 0000000..ef6ce92 --- /dev/null +++ b/pydeequ/engines/duckdb.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +""" +DuckDB execution engine for PyDeequ. + +This module provides a DuckDB-based execution engine that runs data quality +checks directly via SQL queries, without requiring a Spark cluster. + +Example usage: + import duckdb + from pydeequ.engines.duckdb import DuckDBEngine + from pydeequ.v2.analyzers import Size, Completeness, Mean + + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id, 2 as value") + + engine = DuckDBEngine(con, table="test") + metrics = engine.compute_metrics([Size(), Completeness("id"), Mean("value")]) +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence + +import pandas as pd + +from pydeequ.engines import ( + BaseEngine, + ColumnProfile, + ConstraintResult, + ConstraintSuggestion, + ConstraintStatus, + CheckStatus, + MetricResult, +) +from pydeequ.engines.operators import OperatorFactory + +if TYPE_CHECKING: + import duckdb + from pydeequ.v2.analyzers import _ConnectAnalyzer + from pydeequ.v2.checks import Check + from pydeequ.v2.predicates import Predicate + + +class DuckDBEngine(BaseEngine): + """ + DuckDB-based execution engine. + + This engine executes data quality checks using DuckDB SQL queries. + It supports most analyzers through standard SQL aggregations. + + Attributes: + con: DuckDB connection + table: Name of the table to analyze + """ + + def __init__(self, con: "duckdb.DuckDBPyConnection", table: str): + """ + Create a new DuckDBEngine. + + Args: + con: DuckDB connection object + table: Name of the table to analyze + """ + self.con = con + self.table = table + self._schema: Optional[Dict[str, str]] = None + + def get_schema(self) -> Dict[str, str]: + """Get the schema of the table.""" + if self._schema is None: + df = self.con.execute(f"PRAGMA table_info('{self.table}')").fetchdf() + self._schema = {} + for _, row in df.iterrows(): + # Normalize type names to uppercase for consistency + col_type = str(row["type"]).upper() + # Extract base type (e.g., "DECIMAL(10,2)" -> "DECIMAL") + base_type = col_type.split("(")[0] + self._schema[row["name"]] = base_type + return self._schema + + def _execute_query(self, query: str) -> pd.DataFrame: + """Execute a SQL query and return results as DataFrame.""" + return self.con.execute(query).fetchdf() + + def _get_row_count(self, where: Optional[str] = None) -> int: + """Get the row count, optionally filtered.""" + if where: + query = f"SELECT COUNT(*) as cnt FROM {self.table} WHERE {where}" + else: + query = f"SELECT COUNT(*) as cnt FROM {self.table}" + result = self._execute_query(query) + return int(result["cnt"].iloc[0]) + + # ========================================================================= + # Main compute_metrics implementation using operators + # ========================================================================= + + def compute_metrics( + self, analyzers: Sequence["_ConnectAnalyzer"] + ) -> List[MetricResult]: + """ + Compute metrics for the given analyzers. + + This method uses the operator abstraction to: + 1. Create operators from analyzers via OperatorFactory + 2. Batch scan operators into a single SQL query + 3. Execute grouping operators individually + 4. Handle metadata operators using schema access + 5. Extract results using operator-specific logic + """ + results: List[MetricResult] = [] + + # Separate analyzers by operator type + scan_operators = [] + grouping_operators = [] + metadata_operators = [] + + for analyzer in analyzers: + if OperatorFactory.is_scan_operator(analyzer): + operator = OperatorFactory.create(analyzer) + if operator: + scan_operators.append(operator) + elif OperatorFactory.is_grouping_operator(analyzer): + operator = OperatorFactory.create(analyzer) + if operator: + grouping_operators.append(operator) + elif OperatorFactory.is_metadata_operator(analyzer): + operator = OperatorFactory.create(analyzer) + if operator: + metadata_operators.append(operator) + else: + # Unsupported analyzer + results.append(MetricResult( + name=type(analyzer).__name__, + instance=getattr(analyzer, 'column', '*'), + entity="Column" if hasattr(analyzer, 'column') else "Dataset", + value=None, + success=False, + message=f"Analyzer {type(analyzer).__name__} not implemented" + )) + + # Execute batched scan query + if scan_operators: + try: + # Collect all aggregations + aggregations = [] + for operator in scan_operators: + aggregations.extend(operator.get_aggregations()) + + # Build and execute single query + query = f"SELECT {', '.join(aggregations)} FROM {self.table}" + scan_result = self._execute_query(query) + + # Extract results from each operator + for operator in scan_operators: + try: + result = operator.extract_result(scan_result) + results.append(result) + except Exception as e: + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=str(e) + )) + + except Exception as e: + # If batch query fails, report error for all scan operators + for operator in scan_operators: + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=f"Batch query failed: {str(e)}" + )) + + # Execute grouping operators individually + for operator in grouping_operators: + try: + query = operator.build_query(self.table) + df = self._execute_query(query) + result = operator.extract_result(df) + results.append(result) + except Exception as e: + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=str(e) + )) + + # Execute metadata operators using schema + schema = self.get_schema() + for operator in metadata_operators: + try: + result = operator.compute_from_schema(schema) + results.append(result) + except Exception as e: + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=str(e) + )) + + return results + + # ========================================================================= + # Constraint checking + # ========================================================================= + + def run_checks(self, checks: Sequence["Check"]) -> List[ConstraintResult]: + """Run verification checks and return constraint results.""" + from pydeequ.v2.checks import CheckLevel + from pydeequ.engines.constraints import ConstraintEvaluatorFactory + + results: List[ConstraintResult] = [] + + for check in checks: + check_description = check.description + check_level = check.level.value + + # Track overall check status + check_has_failure = False + + for constraint in check._constraints: + constraint_message = None + constraint_passed = False + + try: + # Create evaluator for this constraint + evaluator = ConstraintEvaluatorFactory.create(constraint) + + if evaluator: + # Compute the metric value + value = evaluator.compute_value(self.table, self._execute_query) + + # Evaluate the constraint + constraint_passed = evaluator.evaluate(value) + + # Get constraint description + constraint_str = evaluator.to_string() + + if not constraint_passed: + if value is not None: + constraint_message = f"Value: {value:.6g}" + else: + constraint_message = "Could not compute metric" + else: + constraint_str = constraint.type + constraint_message = f"Unknown constraint type: {constraint.type}" + + except Exception as e: + constraint_str = constraint.type + constraint_message = f"Error: {str(e)}" + constraint_passed = False + + if not constraint_passed: + check_has_failure = True + + results.append(ConstraintResult( + check_description=check_description, + check_level=check_level, + check_status=CheckStatus.ERROR.value if check_has_failure else CheckStatus.SUCCESS.value, + constraint=constraint_str, + constraint_status=ConstraintStatus.SUCCESS.value if constraint_passed else ConstraintStatus.FAILURE.value, + constraint_message=constraint_message, + )) + + # Update check status for all constraints in this check + final_status = CheckStatus.ERROR.value if check_has_failure else CheckStatus.SUCCESS.value + if check.level == CheckLevel.Warning and check_has_failure: + final_status = CheckStatus.WARNING.value + + for i in range(len(results) - len(check._constraints), len(results)): + results[i] = ConstraintResult( + check_description=results[i].check_description, + check_level=results[i].check_level, + check_status=final_status, + constraint=results[i].constraint, + constraint_status=results[i].constraint_status, + constraint_message=results[i].constraint_message, + ) + + return results + + # ========================================================================= + # Column profiling + # ========================================================================= + + def profile_columns( + self, + columns: Optional[Sequence[str]] = None, + low_cardinality_threshold: int = 0, + ) -> List[ColumnProfile]: + """ + Profile columns in the table. + + Uses ColumnProfileOperator to compute statistics for each column + including completeness, distinct values, and (for numeric columns) + min, max, mean, sum, stddev, and percentiles. + + Args: + columns: Optional list of columns to profile. If None, profile all. + low_cardinality_threshold: Threshold for histogram computation. + If > 0 and distinct values <= threshold, compute histogram. + + Returns: + List of ColumnProfile objects + """ + from pydeequ.engines.operators.profiling_operators import ColumnProfileOperator + + schema = self.get_schema() + + # Determine which columns to profile + if columns: + cols_to_profile = [c for c in columns if c in schema] + else: + cols_to_profile = list(schema.keys()) + + profiles: List[ColumnProfile] = [] + + for col in cols_to_profile: + col_type = schema[col] + + # Create operator for this column + operator = ColumnProfileOperator( + column=col, + column_type=col_type, + compute_percentiles=True, + compute_histogram=(low_cardinality_threshold > 0), + histogram_limit=low_cardinality_threshold, + ) + + # Execute base query and extract stats + base_query = operator.build_base_query(self.table) + base_result = self._execute_query(base_query) + base_stats = operator.extract_base_result(base_result) + + # Execute percentile query for numeric columns + percentiles = None + if operator.compute_percentiles: + try: + percentile_query = operator.build_percentile_query(self.table) + percentile_result = self._execute_query(percentile_query) + percentiles = operator.extract_percentile_result(percentile_result) + except Exception: + # Percentile computation may fail for some types + pass + + # Execute histogram query for low cardinality columns + histogram = None + if operator.compute_histogram and base_stats["distinct_count"] <= low_cardinality_threshold: + hist_query = operator.build_histogram_query(self.table) + hist_result = self._execute_query(hist_query) + histogram = operator.extract_histogram_result(hist_result) + + # Build and append profile + profile = operator.build_profile(base_stats, percentiles, histogram) + profiles.append(profile) + + return profiles + + # ========================================================================= + # Constraint suggestions + # ========================================================================= + + def suggest_constraints( + self, + columns: Optional[Sequence[str]] = None, + rules: Optional[Sequence[str]] = None, + ) -> List[ConstraintSuggestion]: + """ + Suggest constraints based on data characteristics. + + Uses the SuggestionRunner to apply modular suggestion rules against + column profiles. Rules are organized into sets: + - DEFAULT: completeness, non-negative, categorical + - NUMERICAL: min, max, mean + - STRING: min/max length + - COMMON: uniqueness + - EXTENDED: all rules + + Args: + columns: Optional list of columns to analyze. If None, analyze all. + rules: Optional list of rule sets to apply. Defaults to ["DEFAULT"]. + + Returns: + List of ConstraintSuggestion objects + """ + from pydeequ.engines.suggestions import SuggestionRunner + from pydeequ.v2.suggestions import Rules + + # Default rules - normalize to strings for SuggestionRunner + if rules is None: + rule_strings = ["DEFAULT"] + else: + # Accept both Rules enum and string values + rule_strings = [] + for rule in rules: + if isinstance(rule, Rules): + rule_strings.append(rule.value) + else: + rule_strings.append(rule) + + # Profile columns with histograms for categorical detection + profiles = self.profile_columns(columns, low_cardinality_threshold=100) + + # Get row count for uniqueness checks + row_count = self._get_row_count() + + # Run suggestion rules + runner = SuggestionRunner(rule_sets=rule_strings) + return runner.run( + profiles, + execute_fn=self._execute_query, + table=self.table, + row_count=row_count, + ) diff --git a/pydeequ/engines/operators/__init__.py b/pydeequ/engines/operators/__init__.py new file mode 100644 index 0000000..93b3957 --- /dev/null +++ b/pydeequ/engines/operators/__init__.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +""" +SQL Operator abstractions for data quality metrics. + +This module provides a hierarchical operator abstraction pattern that: +1. Eliminates code duplication across analyzer implementations +2. Separates SQL generation from result extraction +3. Enables efficient batch execution of scan operators +4. Provides consistent WHERE clause handling + +Architecture: + Protocols (Contracts) + ├── ScanOperatorProtocol - Single-pass aggregation operators + └── GroupingOperatorProtocol - GROUP BY-based operators + + Mixins (Shared Behaviors) + ├── WhereClauseMixin - Conditional aggregation wrapping + ├── SafeExtractMixin - Safe value extraction from DataFrames + └── ColumnAliasMixin - Consistent alias generation + + Base Classes (Hierarchy) + ├── ScanOperator - Base for single-pass operators + └── GroupingOperator - Base for GROUP BY operators + + Factory + └── OperatorFactory - Creates operators from analyzers + +Example usage: + from pydeequ.engines.operators import OperatorFactory + + # Create operator from analyzer + operator = OperatorFactory.create(Mean("price")) + + # Get SQL aggregations for scan operators + aggregations = operator.get_aggregations() + # ["AVG(price) AS mean_price"] + + # Execute query and extract result + df = engine._execute_query(f"SELECT {', '.join(aggregations)} FROM table") + result = operator.extract_result(df) +""" + +from pydeequ.engines.operators.base import GroupingOperator, ScanOperator +from pydeequ.engines.operators.factory import OperatorFactory +from pydeequ.engines.operators.grouping_operators import ( + DistinctnessOperator, + EntropyOperator, + HistogramOperator, + MutualInformationOperator, + UniqueValueRatioOperator, + UniquenessOperator, +) +from pydeequ.engines.operators.metadata_operators import ( + DataTypeOperator, +) +from pydeequ.engines.operators.profiling_operators import ( + ColumnProfileOperator, + MultiColumnProfileOperator, + NUMERIC_TYPES, + STRING_TYPES, +) +from pydeequ.engines.operators.mixins import ( + ColumnAliasMixin, + SafeExtractMixin, + WhereClauseMixin, +) +from pydeequ.engines.operators.protocols import ( + GroupingOperatorProtocol, + ScanOperatorProtocol, +) +from pydeequ.engines.operators.scan_operators import ( + ApproxCountDistinctOperator, + ApproxQuantileOperator, + ComplianceOperator, + CompletenessOperator, + CorrelationOperator, + CountDistinctOperator, + MaximumOperator, + MaxLengthOperator, + MeanOperator, + MinimumOperator, + MinLengthOperator, + PatternMatchOperator, + SizeOperator, + StandardDeviationOperator, + SumOperator, +) + +__all__ = [ + # Protocols + "ScanOperatorProtocol", + "GroupingOperatorProtocol", + # Mixins + "WhereClauseMixin", + "SafeExtractMixin", + "ColumnAliasMixin", + # Base classes + "ScanOperator", + "GroupingOperator", + # Scan operators + "SizeOperator", + "CompletenessOperator", + "MeanOperator", + "SumOperator", + "MinimumOperator", + "MaximumOperator", + "StandardDeviationOperator", + "MaxLengthOperator", + "MinLengthOperator", + "PatternMatchOperator", + "ComplianceOperator", + "CorrelationOperator", + "CountDistinctOperator", + "ApproxCountDistinctOperator", + "ApproxQuantileOperator", + # Grouping operators + "DistinctnessOperator", + "UniquenessOperator", + "UniqueValueRatioOperator", + "EntropyOperator", + "MutualInformationOperator", + "HistogramOperator", + # Metadata operators + "DataTypeOperator", + # Profiling operators + "ColumnProfileOperator", + "MultiColumnProfileOperator", + "NUMERIC_TYPES", + "STRING_TYPES", + # Factory + "OperatorFactory", +] diff --git a/pydeequ/engines/operators/base.py b/pydeequ/engines/operators/base.py new file mode 100644 index 0000000..88ef664 --- /dev/null +++ b/pydeequ/engines/operators/base.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +""" +Base classes for SQL operators. + +This module provides the abstract base classes that combine protocols +and mixins to create the foundation for concrete operator implementations. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, List, Optional + +from pydeequ.engines.operators.mixins import ( + ColumnAliasMixin, + SafeExtractMixin, + WhereClauseMixin, +) + +if TYPE_CHECKING: + import pandas as pd + from pydeequ.engines import MetricResult + + +class ScanOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin, ABC): + """ + Base class for single-pass aggregation operators. + + Scan operators compute metrics via SQL aggregations that can be + combined into a single SELECT statement. This enables efficient + batch execution where multiple metrics are computed in one query. + + Subclasses must implement: + - get_aggregations(): Return SQL aggregation expressions + - extract_result(): Parse query results into MetricResult + + Attributes: + column: Column name to analyze + where: Optional SQL WHERE clause for filtering + """ + + def __init__(self, column: str, where: Optional[str] = None): + """ + Initialize scan operator. + + Args: + column: Column name to analyze + where: Optional SQL WHERE clause for filtering + """ + self.column = column + self.where = where + + @abstractmethod + def get_aggregations(self) -> List[str]: + """ + Return SQL aggregation expressions. + + Returns: + List of SQL aggregation expressions with AS alias clauses + """ + raise NotImplementedError + + @abstractmethod + def extract_result(self, df: "pd.DataFrame") -> "MetricResult": + """ + Extract metric from query result DataFrame. + + Args: + df: DataFrame containing query results + + Returns: + MetricResult with extracted value + """ + raise NotImplementedError + + @property + def instance(self) -> str: + """Return the instance identifier for this operator.""" + return self.column + + @property + def entity(self) -> str: + """Return the entity type for this operator.""" + return "Column" + + @property + @abstractmethod + def metric_name(self) -> str: + """Return the metric name for this operator.""" + raise NotImplementedError + + +class GroupingOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin, ABC): + """ + Base class for operators requiring GROUP BY queries. + + Grouping operators need to compute intermediate aggregations + via GROUP BY before computing the final metric. They cannot + be batched with scan operators and require separate queries. + + Subclasses must implement: + - get_grouping_columns(): Return columns to GROUP BY + - build_query(): Build complete CTE-based query + - extract_result(): Parse query results into MetricResult + + Attributes: + columns: Column name(s) to analyze + where: Optional SQL WHERE clause for filtering + """ + + def __init__(self, columns: List[str], where: Optional[str] = None): + """ + Initialize grouping operator. + + Args: + columns: Column name(s) to analyze + where: Optional SQL WHERE clause for filtering + """ + self.columns = columns + self.where = where + + @abstractmethod + def get_grouping_columns(self) -> List[str]: + """ + Return columns to GROUP BY. + + Returns: + List of column names for the GROUP BY clause + """ + raise NotImplementedError + + @abstractmethod + def build_query(self, table: str) -> str: + """ + Build complete CTE-based query. + + Args: + table: Name of the table to query + + Returns: + Complete SQL query string + """ + raise NotImplementedError + + @abstractmethod + def extract_result(self, df: "pd.DataFrame") -> "MetricResult": + """ + Extract metric from query result DataFrame. + + Args: + df: DataFrame containing query results + + Returns: + MetricResult with extracted value + """ + raise NotImplementedError + + @property + def instance(self) -> str: + """Return the instance identifier for this operator.""" + return ",".join(self.columns) + + @property + def entity(self) -> str: + """Return the entity type for this operator.""" + return "Multicolumn" if len(self.columns) > 1 else "Column" + + @property + @abstractmethod + def metric_name(self) -> str: + """Return the metric name for this operator.""" + raise NotImplementedError + + +__all__ = [ + "ScanOperator", + "GroupingOperator", +] diff --git a/pydeequ/engines/operators/factory.py b/pydeequ/engines/operators/factory.py new file mode 100644 index 0000000..ed3c2ce --- /dev/null +++ b/pydeequ/engines/operators/factory.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +""" +Operator factory for creating operators from analyzers. + +This module provides a registry-based factory pattern that eliminates +isinstance() chains when creating operators from analyzers. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, Optional, Type, Union + +from pydeequ.engines.operators.grouping_operators import ( + DistinctnessOperator, + EntropyOperator, + HistogramOperator, + MutualInformationOperator, + UniqueValueRatioOperator, + UniquenessOperator, +) +from pydeequ.engines.operators.metadata_operators import ( + DataTypeOperator, +) +from pydeequ.engines.operators.scan_operators import ( + ApproxCountDistinctOperator, + ApproxQuantileOperator, + ComplianceOperator, + CompletenessOperator, + CorrelationOperator, + CountDistinctOperator, + MaximumOperator, + MaxLengthOperator, + MeanOperator, + MinimumOperator, + MinLengthOperator, + PatternMatchOperator, + SizeOperator, + StandardDeviationOperator, + SumOperator, +) + +if TYPE_CHECKING: + from pydeequ.engines.operators.base import GroupingOperator, ScanOperator + from pydeequ.v2.analyzers import _ConnectAnalyzer + +# Type alias for operator types +OperatorType = Union["ScanOperator", "GroupingOperator", "DataTypeOperator"] + + +class OperatorFactory: + """ + Creates operators from analyzers using registry pattern. + + This factory eliminates isinstance() chains by mapping analyzer + types to their corresponding operator classes. + """ + + # Registry mapping analyzer type names to operator classes + _scan_registry: Dict[str, Type] = {} + _grouping_registry: Dict[str, Type] = {} + _metadata_registry: Dict[str, Type] = {} + + @classmethod + def register_scan(cls, analyzer_name: str): + """ + Decorator to register a scan operator for an analyzer type. + + Args: + analyzer_name: Name of the analyzer class (e.g., "Mean", "Sum") + + Returns: + Decorator function + """ + def decorator(operator_class: Type): + cls._scan_registry[analyzer_name] = operator_class + return operator_class + return decorator + + @classmethod + def register_grouping(cls, analyzer_name: str): + """ + Decorator to register a grouping operator for an analyzer type. + + Args: + analyzer_name: Name of the analyzer class + + Returns: + Decorator function + """ + def decorator(operator_class: Type): + cls._grouping_registry[analyzer_name] = operator_class + return operator_class + return decorator + + @classmethod + def register_metadata(cls, analyzer_name: str): + """ + Decorator to register a metadata operator for an analyzer type. + + Metadata operators compute metrics from schema information rather + than SQL queries. They are used for type inference and similar + schema-based analysis. + + Args: + analyzer_name: Name of the analyzer class + + Returns: + Decorator function + """ + def decorator(operator_class: Type): + cls._metadata_registry[analyzer_name] = operator_class + return operator_class + return decorator + + @classmethod + def create(cls, analyzer: "_ConnectAnalyzer") -> Optional[OperatorType]: + """ + Create operator instance for given analyzer. + + Args: + analyzer: Analyzer instance to create operator for + + Returns: + Operator instance or None if analyzer type not supported + """ + analyzer_name = type(analyzer).__name__ + + # Try scan registry first + if analyzer_name in cls._scan_registry: + return cls._create_scan_operator(analyzer_name, analyzer) + + # Try grouping registry + if analyzer_name in cls._grouping_registry: + return cls._create_grouping_operator(analyzer_name, analyzer) + + # Try metadata registry + if analyzer_name in cls._metadata_registry: + return cls._create_metadata_operator(analyzer_name, analyzer) + + return None + + @classmethod + def _create_scan_operator( + cls, analyzer_name: str, analyzer: "_ConnectAnalyzer" + ) -> "ScanOperator": + """Create a scan operator from an analyzer.""" + operator_class = cls._scan_registry[analyzer_name] + + # Extract common attributes + column = getattr(analyzer, "column", None) + where = getattr(analyzer, "where", None) + + # Handle special cases + if analyzer_name == "Size": + return operator_class(where=where) + elif analyzer_name == "Compliance": + instance = getattr(analyzer, "instance", "compliance") + predicate = getattr(analyzer, "predicate", "") + return operator_class(instance, predicate, where=where) + elif analyzer_name == "PatternMatch": + pattern = getattr(analyzer, "pattern", "") + return operator_class(column, pattern, where=where) + elif analyzer_name == "Correlation": + column1 = getattr(analyzer, "column1", "") + column2 = getattr(analyzer, "column2", "") + return operator_class(column1, column2, where=where) + elif analyzer_name == "CountDistinct": + columns = list(getattr(analyzer, "columns", [])) + return operator_class(columns, where=where) + elif analyzer_name == "ApproxQuantile": + quantile = getattr(analyzer, "quantile", 0.5) + return operator_class(column, quantile, where=where) + else: + # Standard single-column operators + return operator_class(column, where=where) + + @classmethod + def _create_grouping_operator( + cls, analyzer_name: str, analyzer: "_ConnectAnalyzer" + ) -> "GroupingOperator": + """Create a grouping operator from an analyzer.""" + operator_class = cls._grouping_registry[analyzer_name] + + where = getattr(analyzer, "where", None) + + if analyzer_name == "Entropy": + column = getattr(analyzer, "column", "") + return operator_class(column, where=where) + elif analyzer_name == "Histogram": + column = getattr(analyzer, "column", "") + max_bins = getattr(analyzer, "max_detail_bins", 100) or 100 + return operator_class(column, max_bins, where=where) + else: + # Multi-column operators (Distinctness, Uniqueness, etc.) + columns = getattr(analyzer, "columns", []) + if isinstance(columns, str): + columns = [columns] + return operator_class(list(columns), where=where) + + @classmethod + def _create_metadata_operator( + cls, analyzer_name: str, analyzer: "_ConnectAnalyzer" + ) -> "DataTypeOperator": + """Create a metadata operator from an analyzer.""" + operator_class = cls._metadata_registry[analyzer_name] + + # Extract common attributes + column = getattr(analyzer, "column", None) + where = getattr(analyzer, "where", None) + + # Standard single-column metadata operators + return operator_class(column, where=where) + + @classmethod + def is_scan_operator(cls, analyzer: "_ConnectAnalyzer") -> bool: + """Check if analyzer maps to a scan operator.""" + return type(analyzer).__name__ in cls._scan_registry + + @classmethod + def is_grouping_operator(cls, analyzer: "_ConnectAnalyzer") -> bool: + """Check if analyzer maps to a grouping operator.""" + return type(analyzer).__name__ in cls._grouping_registry + + @classmethod + def is_metadata_operator(cls, analyzer: "_ConnectAnalyzer") -> bool: + """Check if analyzer maps to a metadata operator.""" + return type(analyzer).__name__ in cls._metadata_registry + + @classmethod + def is_supported(cls, analyzer: "_ConnectAnalyzer") -> bool: + """Check if analyzer type is supported by the factory.""" + analyzer_name = type(analyzer).__name__ + return ( + analyzer_name in cls._scan_registry + or analyzer_name in cls._grouping_registry + or analyzer_name in cls._metadata_registry + ) + + +# Register all scan operators +OperatorFactory._scan_registry = { + "Size": SizeOperator, + "Completeness": CompletenessOperator, + "Mean": MeanOperator, + "Sum": SumOperator, + "Minimum": MinimumOperator, + "Maximum": MaximumOperator, + "StandardDeviation": StandardDeviationOperator, + "MaxLength": MaxLengthOperator, + "MinLength": MinLengthOperator, + "PatternMatch": PatternMatchOperator, + "Compliance": ComplianceOperator, + "Correlation": CorrelationOperator, + "CountDistinct": CountDistinctOperator, + "ApproxCountDistinct": ApproxCountDistinctOperator, + "ApproxQuantile": ApproxQuantileOperator, +} + +# Register all grouping operators +OperatorFactory._grouping_registry = { + "Distinctness": DistinctnessOperator, + "Uniqueness": UniquenessOperator, + "UniqueValueRatio": UniqueValueRatioOperator, + "Entropy": EntropyOperator, + "MutualInformation": MutualInformationOperator, + "Histogram": HistogramOperator, +} + +# Register all metadata operators +OperatorFactory._metadata_registry = { + "DataType": DataTypeOperator, +} + + +__all__ = [ + "OperatorFactory", +] diff --git a/pydeequ/engines/operators/grouping_operators.py b/pydeequ/engines/operators/grouping_operators.py new file mode 100644 index 0000000..d4c58f6 --- /dev/null +++ b/pydeequ/engines/operators/grouping_operators.py @@ -0,0 +1,334 @@ +# -*- coding: utf-8 -*- +""" +Grouping operator implementations. + +Grouping operators require GROUP BY queries and cannot be batched with +scan operators. They require separate query execution. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from pydeequ.engines import MetricResult +from pydeequ.engines.operators.base import GroupingOperator + +if TYPE_CHECKING: + import pandas as pd + + +class DistinctnessOperator(GroupingOperator): + """ + Computes distinctness = count_distinct / total_count. + + Distinctness measures what fraction of the total rows have + unique value combinations in the specified columns. + """ + + def __init__(self, columns: List[str], where: Optional[str] = None): + super().__init__(columns, where) + + @property + def metric_name(self) -> str: + return "Distinctness" + + def get_grouping_columns(self) -> List[str]: + return self.columns + + def build_query(self, table: str) -> str: + cols_str = ", ".join(self.columns) + where_clause = self.get_where_clause() + + return f""" + WITH freq AS ( + SELECT {cols_str}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {cols_str} + ) + SELECT + COUNT(*) AS distinct_count, + SUM(cnt) AS total_count + FROM freq + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + distinct = self.safe_float(df, "distinct_count") or 0 + total = self.safe_float(df, "total_count") or 0 + value = distinct / total if total > 0 else 0.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class UniquenessOperator(GroupingOperator): + """ + Computes uniqueness = count_unique (count=1) / total_count. + + Uniqueness measures what fraction of the total rows have + value combinations that appear exactly once. + """ + + def __init__(self, columns: List[str], where: Optional[str] = None): + super().__init__(columns, where) + + @property + def metric_name(self) -> str: + return "Uniqueness" + + def get_grouping_columns(self) -> List[str]: + return self.columns + + def build_query(self, table: str) -> str: + cols_str = ", ".join(self.columns) + where_clause = self.get_where_clause() + + return f""" + WITH freq AS ( + SELECT {cols_str}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {cols_str} + ) + SELECT + SUM(CASE WHEN cnt = 1 THEN 1 ELSE 0 END) AS unique_count, + SUM(cnt) AS total_count + FROM freq + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + unique = self.safe_float(df, "unique_count") or 0 + total = self.safe_float(df, "total_count") or 0 + value = unique / total if total > 0 else 0.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class UniqueValueRatioOperator(GroupingOperator): + """ + Computes unique value ratio = count_unique / count_distinct. + + This measures what fraction of distinct value combinations + appear exactly once. + """ + + def __init__(self, columns: List[str], where: Optional[str] = None): + super().__init__(columns, where) + + @property + def metric_name(self) -> str: + return "UniqueValueRatio" + + def get_grouping_columns(self) -> List[str]: + return self.columns + + def build_query(self, table: str) -> str: + cols_str = ", ".join(self.columns) + where_clause = self.get_where_clause() + + return f""" + WITH freq AS ( + SELECT {cols_str}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {cols_str} + ) + SELECT + COUNT(*) AS distinct_count, + SUM(CASE WHEN cnt = 1 THEN 1 ELSE 0 END) AS unique_count + FROM freq + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + distinct = self.safe_float(df, "distinct_count") or 0 + unique = self.safe_float(df, "unique_count") or 0 + value = unique / distinct if distinct > 0 else 0.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class EntropyOperator(GroupingOperator): + """ + Computes entropy = -SUM(p * log2(p)). + + Entropy measures the information content of a column's + value distribution. + """ + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__([column], where) + self.column = column + + @property + def metric_name(self) -> str: + return "Entropy" + + @property + def instance(self) -> str: + return self.column + + @property + def entity(self) -> str: + return "Column" + + def get_grouping_columns(self) -> List[str]: + return [self.column] + + def build_query(self, table: str) -> str: + where_clause = self.get_where_clause() + + return f""" + WITH freq AS ( + SELECT {self.column}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {self.column} + ), + total AS ( + SELECT SUM(cnt) AS total_cnt FROM freq + ) + SELECT + -SUM((cnt * 1.0 / total_cnt) * LOG2(cnt * 1.0 / total_cnt)) AS entropy + FROM freq, total + WHERE cnt > 0 + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, "entropy") + if value is None: + value = 0.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MutualInformationOperator(GroupingOperator): + """Computes mutual information between two columns.""" + + def __init__(self, columns: List[str], where: Optional[str] = None): + if len(columns) != 2: + raise ValueError("MutualInformation requires exactly 2 columns") + super().__init__(columns, where) + + @property + def metric_name(self) -> str: + return "MutualInformation" + + def get_grouping_columns(self) -> List[str]: + return self.columns + + def build_query(self, table: str) -> str: + col1, col2 = self.columns + where_clause = self.get_where_clause() + + return f""" + WITH + joint AS ( + SELECT {col1}, {col2}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {col1}, {col2} + ), + total AS (SELECT SUM(cnt) AS n FROM joint), + marginal1 AS ( + SELECT {col1}, SUM(cnt) AS cnt1 FROM joint GROUP BY {col1} + ), + marginal2 AS ( + SELECT {col2}, SUM(cnt) AS cnt2 FROM joint GROUP BY {col2} + ) + SELECT SUM( + (j.cnt * 1.0 / t.n) * + LOG2((j.cnt * 1.0 / t.n) / ((m1.cnt1 * 1.0 / t.n) * (m2.cnt2 * 1.0 / t.n))) + ) AS mi + FROM joint j, total t, marginal1 m1, marginal2 m2 + WHERE j.{col1} = m1.{col1} AND j.{col2} = m2.{col2} AND j.cnt > 0 + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, "mi") + if value is None: + value = 0.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class HistogramOperator(GroupingOperator): + """ + Computes histogram of value distribution in a column. + + Returns a JSON-serialized dict mapping values to their counts. + """ + + def __init__(self, column: str, max_bins: int = 100, where: Optional[str] = None): + super().__init__([column], where) + self.column = column + self.max_bins = max_bins + + @property + def metric_name(self) -> str: + return "Histogram" + + @property + def instance(self) -> str: + return self.column + + @property + def entity(self) -> str: + return "Column" + + def get_grouping_columns(self) -> List[str]: + return [self.column] + + def build_query(self, table: str) -> str: + where_clause = self.get_where_clause() + if where_clause: + where_clause += f" AND {self.column} IS NOT NULL" + else: + where_clause = f"WHERE {self.column} IS NOT NULL" + + return f""" + SELECT {self.column} as value, COUNT(*) as count + FROM {table} + {where_clause} + GROUP BY {self.column} + ORDER BY count DESC + LIMIT {self.max_bins} + """ + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + import json + histogram = {str(row["value"]): int(row["count"]) for _, row in df.iterrows()} + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=json.dumps(histogram), + ) + + +__all__ = [ + "DistinctnessOperator", + "UniquenessOperator", + "UniqueValueRatioOperator", + "EntropyOperator", + "MutualInformationOperator", + "HistogramOperator", +] diff --git a/pydeequ/engines/operators/metadata_operators.py b/pydeequ/engines/operators/metadata_operators.py new file mode 100644 index 0000000..963a2a1 --- /dev/null +++ b/pydeequ/engines/operators/metadata_operators.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +""" +Metadata operator implementations. + +Metadata operators compute metrics using schema information rather than +SQL aggregations. They are useful for type inference and schema-based +analysis that don't require scanning data. +""" + +from __future__ import annotations + +import json +from typing import Dict, Optional + +from pydeequ.engines import MetricResult +from pydeequ.engines.operators.mixins import ( + ColumnAliasMixin, + SafeExtractMixin, +) + +class DataTypeOperator(SafeExtractMixin, ColumnAliasMixin): + """ + Computes data type information from schema metadata. + + Unlike scan operators that require SQL queries, DataTypeOperator + infers type information directly from the table schema, making it + more efficient for type analysis. + + Type Mapping: + DuckDB types are mapped to Deequ-compatible type categories: + - Integral: TINYINT, SMALLINT, INTEGER, BIGINT, HUGEINT, etc. + - Fractional: FLOAT, DOUBLE, REAL, DECIMAL, NUMERIC + - String: VARCHAR, CHAR, TEXT, etc. + - Boolean: BOOLEAN, BOOL + + Attributes: + column: Column name to analyze + where: Optional WHERE clause (ignored for schema-based inference) + """ + + # Mapping from DuckDB SQL types to Deequ type categories + TYPE_MAPPING: Dict[str, str] = { + # Integral types + "TINYINT": "Integral", + "SMALLINT": "Integral", + "INTEGER": "Integral", + "BIGINT": "Integral", + "HUGEINT": "Integral", + "UTINYINT": "Integral", + "USMALLINT": "Integral", + "UINTEGER": "Integral", + "UBIGINT": "Integral", + "INT": "Integral", + "INT1": "Integral", + "INT2": "Integral", + "INT4": "Integral", + "INT8": "Integral", + # Fractional types + "FLOAT": "Fractional", + "DOUBLE": "Fractional", + "REAL": "Fractional", + "DECIMAL": "Fractional", + "NUMERIC": "Fractional", + "FLOAT4": "Fractional", + "FLOAT8": "Fractional", + # String types + "VARCHAR": "String", + "CHAR": "String", + "BPCHAR": "String", + "TEXT": "String", + "STRING": "String", + # Boolean types + "BOOLEAN": "Boolean", + "BOOL": "Boolean", + # Date/Time types (mapped to String for Deequ compatibility) + "DATE": "String", + "TIMESTAMP": "String", + "TIME": "String", + "TIMESTAMPTZ": "String", + "TIMETZ": "String", + "INTERVAL": "String", + # Binary types + "BLOB": "Unknown", + "BYTEA": "Unknown", + # UUID + "UUID": "String", + } + + def __init__(self, column: str, where: Optional[str] = None): + """ + Initialize DataTypeOperator. + + Args: + column: Column name to analyze + where: Optional WHERE clause (ignored for schema-based inference) + """ + self.column = column + self.where = where # Stored but ignored for schema-based type inference + + @property + def metric_name(self) -> str: + """Return the metric name for this operator.""" + return "DataType" + + @property + def instance(self) -> str: + """Return the instance identifier for this operator.""" + return self.column + + @property + def entity(self) -> str: + """Return the entity type for this operator.""" + return "Column" + + def compute_from_schema(self, schema: Dict[str, str]) -> MetricResult: + """ + Compute data type information from schema. + + Args: + schema: Dictionary mapping column names to SQL type names + + Returns: + MetricResult with JSON-encoded type information + """ + sql_type = schema.get(self.column, "Unknown") + mapped_type = self.TYPE_MAPPING.get(sql_type, "Unknown") + + # Build result compatible with Spark Deequ format + result = { + "dtype": sql_type, + "mapped_type": mapped_type, + "type_counts": {mapped_type: 1.0} # DuckDB has strict typing + } + + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=json.dumps(result), + ) + + +__all__ = [ + "DataTypeOperator", +] diff --git a/pydeequ/engines/operators/mixins.py b/pydeequ/engines/operators/mixins.py new file mode 100644 index 0000000..c68dfad --- /dev/null +++ b/pydeequ/engines/operators/mixins.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +""" +Mixin classes providing shared behaviors for SQL operators. + +These mixins provide reusable functionality that eliminates code duplication +across operator implementations. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + import pandas as pd + + +class WhereClauseMixin: + """ + Provides WHERE clause wrapping for conditional aggregations. + + This mixin eliminates the repeated if/else pattern for handling + optional WHERE clauses in aggregations. Expects the class to + have a `where` attribute. + """ + + where: Optional[str] + + def wrap_agg_with_where(self, agg_func: str, column: str) -> str: + """ + Wrap an aggregation with optional WHERE filter using CASE WHEN. + + Args: + agg_func: SQL aggregation function name (e.g., "AVG", "SUM", "MIN") + column: Column name to aggregate + + Returns: + SQL expression with conditional aggregation if where is set, + otherwise standard aggregation + + Example: + >>> op = SomeOperator(column="price", where="status='active'") + >>> op.wrap_agg_with_where("AVG", "price") + "AVG(CASE WHEN status='active' THEN price ELSE NULL END)" + """ + if self.where: + return f"{agg_func}(CASE WHEN {self.where} THEN {column} ELSE NULL END)" + return f"{agg_func}({column})" + + def wrap_count_with_where(self, condition: str = "1") -> str: + """ + Wrap COUNT with optional WHERE filter. + + Args: + condition: SQL condition to count (default "1" counts all rows) + + Returns: + SQL expression for conditional count + + Example: + >>> op = SomeOperator(where="status='active'") + >>> op.wrap_count_with_where() + "SUM(CASE WHEN status='active' THEN 1 ELSE 0 END)" + >>> op.wrap_count_with_where("price > 0") + "SUM(CASE WHEN status='active' AND (price > 0) THEN 1 ELSE 0 END)" + """ + if self.where: + if condition == "1": + return f"SUM(CASE WHEN {self.where} THEN 1 ELSE 0 END)" + return f"SUM(CASE WHEN ({self.where}) AND ({condition}) THEN 1 ELSE 0 END)" + if condition == "1": + return "COUNT(*)" + return f"SUM(CASE WHEN {condition} THEN 1 ELSE 0 END)" + + def get_where_clause(self) -> str: + """ + Get WHERE clause for standalone queries. + + Returns: + "WHERE {condition}" if where is set, otherwise empty string + """ + if self.where: + return f"WHERE {self.where}" + return "" + + +class SafeExtractMixin: + """ + Provides safe value extraction from DataFrames. + + This mixin standardizes the pattern of safely extracting values + from query result DataFrames, handling NULL and NaN values. + """ + + def safe_float(self, df: "pd.DataFrame", column: str) -> Optional[float]: + """ + Extract float value from DataFrame, handling NULL/NaN. + + Args: + df: DataFrame containing query results + column: Column name to extract + + Returns: + Float value or None if not present/invalid + """ + import pandas as pd + + if column not in df.columns: + return None + val = df[column].iloc[0] + if val is not None and not pd.isna(val): + return float(val) + return None + + def safe_int(self, df: "pd.DataFrame", column: str) -> Optional[int]: + """ + Extract int value from DataFrame, handling NULL/NaN. + + Args: + df: DataFrame containing query results + column: Column name to extract + + Returns: + Integer value or None if not present/invalid + """ + val = self.safe_float(df, column) + return int(val) if val is not None else None + + def safe_string(self, df: "pd.DataFrame", column: str) -> Optional[str]: + """ + Extract string value from DataFrame, handling NULL/NaN. + + Args: + df: DataFrame containing query results + column: Column name to extract + + Returns: + String value or None if not present/invalid + """ + import pandas as pd + + if column not in df.columns: + return None + val = df[column].iloc[0] + if val is not None and not pd.isna(val): + return str(val) + return None + + +class ColumnAliasMixin: + """ + Provides consistent column alias generation. + + This mixin ensures all operators generate unique and predictable + column aliases for their SQL expressions. + """ + + def make_alias(self, prefix: str, *parts: str) -> str: + """ + Generate unique column alias from prefix and parts. + + Args: + prefix: Alias prefix (e.g., "mean", "count", "sum") + *parts: Additional parts to include (e.g., column names) + + Returns: + Underscore-separated alias with sanitized column names + + Example: + >>> op = SomeOperator() + >>> op.make_alias("mean", "price") + "mean_price" + >>> op.make_alias("corr", "price", "quantity") + "corr_price_quantity" + """ + # Sanitize parts: replace dots and other special chars + sanitized = [] + for p in parts: + if p: + sanitized.append(p.replace(".", "_").replace(" ", "_")) + suffix = "_".join(sanitized) + return f"{prefix}_{suffix}" if suffix else prefix + + +__all__ = [ + "WhereClauseMixin", + "SafeExtractMixin", + "ColumnAliasMixin", +] diff --git a/pydeequ/engines/operators/profiling_operators.py b/pydeequ/engines/operators/profiling_operators.py new file mode 100644 index 0000000..a69f722 --- /dev/null +++ b/pydeequ/engines/operators/profiling_operators.py @@ -0,0 +1,413 @@ +# -*- coding: utf-8 -*- +""" +Profiling operator implementations. + +Profiling operators compute column profile statistics including completeness, +distinct values, min, max, mean, sum, stddev, percentiles, and histograms. +""" + +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, Dict, List, Optional, Set + +from pydeequ.engines import ColumnProfile +from pydeequ.engines.operators.mixins import ( + ColumnAliasMixin, + SafeExtractMixin, + WhereClauseMixin, +) + +if TYPE_CHECKING: + import pandas as pd + + +# SQL types that are considered numeric +NUMERIC_TYPES: Set[str] = { + "TINYINT", "SMALLINT", "INTEGER", "BIGINT", "HUGEINT", + "UTINYINT", "USMALLINT", "UINTEGER", "UBIGINT", + "FLOAT", "DOUBLE", "REAL", "DECIMAL", "NUMERIC", + "INT", "INT1", "INT2", "INT4", "INT8", + "FLOAT4", "FLOAT8", +} + +# SQL types that are considered string +STRING_TYPES: Set[str] = {"VARCHAR", "CHAR", "BPCHAR", "TEXT", "STRING"} + + +class ColumnProfileOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """ + Computes all profile statistics for a column. + + This operator generates SQL queries to compute completeness, distinct values, + and (for numeric columns) min, max, mean, sum, stddev, and percentiles. + + Attributes: + column: Column name to profile + column_type: SQL type of the column (e.g., "INTEGER", "VARCHAR") + is_numeric: Whether the column is a numeric type + compute_percentiles: Whether to compute percentile statistics + compute_histogram: Whether to compute value histogram + histogram_limit: Maximum number of histogram buckets + where: Optional WHERE clause for filtering + """ + + def __init__( + self, + column: str, + column_type: str, + compute_percentiles: bool = True, + compute_histogram: bool = False, + histogram_limit: int = 100, + where: Optional[str] = None, + ): + """ + Initialize ColumnProfileOperator. + + Args: + column: Column name to profile + column_type: SQL type of the column + compute_percentiles: Whether to compute percentile statistics + compute_histogram: Whether to compute value histogram + histogram_limit: Maximum number of histogram buckets + where: Optional WHERE clause for filtering + """ + self.column = column + self.column_type = column_type + self.is_numeric = column_type in NUMERIC_TYPES + self.compute_percentiles = compute_percentiles and self.is_numeric + self.compute_histogram = compute_histogram + self.histogram_limit = histogram_limit + self.where = where + + def build_base_query(self, table: str) -> str: + """ + Build query for basic statistics. + + Args: + table: Table name to query + + Returns: + SQL query string for base statistics + """ + col = self.column + if self.is_numeric: + query = f""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN {col} IS NULL THEN 1 ELSE 0 END) as null_count, + APPROX_COUNT_DISTINCT({col}) as distinct_count, + MIN({col}) as min_val, + MAX({col}) as max_val, + AVG({col}) as mean_val, + SUM({col}) as sum_val, + STDDEV_SAMP({col}) as stddev_val + FROM {table} + """ + else: + query = f""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN {col} IS NULL THEN 1 ELSE 0 END) as null_count, + APPROX_COUNT_DISTINCT({col}) as distinct_count + FROM {table} + """ + return query.strip() + + def build_percentile_query(self, table: str) -> str: + """ + Build query for percentiles (separate query). + + Args: + table: Table name to query + + Returns: + SQL query string for percentile statistics + """ + col = self.column + return f""" + SELECT + QUANTILE_CONT({col}, 0.25) as p25, + QUANTILE_CONT({col}, 0.50) as p50, + QUANTILE_CONT({col}, 0.75) as p75 + FROM {table} + """.strip() + + def build_histogram_query(self, table: str) -> str: + """ + Build query for histogram (separate query). + + Args: + table: Table name to query + + Returns: + SQL query string for histogram + """ + col = self.column + return f""" + SELECT {col} as value, COUNT(*) as count + FROM {table} + WHERE {col} IS NOT NULL + GROUP BY {col} + ORDER BY count DESC + LIMIT {self.histogram_limit} + """.strip() + + def extract_base_result(self, df: "pd.DataFrame") -> Dict: + """ + Extract base statistics from query result. + + Args: + df: DataFrame containing query results + + Returns: + Dictionary of extracted statistics + """ + import pandas as pd + + total = int(df["total"].iloc[0]) + + # Handle NaN for empty datasets + null_count_raw = df["null_count"].iloc[0] + null_count = int(null_count_raw) if not pd.isna(null_count_raw) else 0 + + distinct_count_raw = df["distinct_count"].iloc[0] + distinct_count = int(distinct_count_raw) if not pd.isna(distinct_count_raw) else 0 + + completeness = (total - null_count) / total if total > 0 else 1.0 + + result = { + "total": total, + "null_count": null_count, + "distinct_count": distinct_count, + "completeness": completeness, + } + + if self.is_numeric: + result["minimum"] = self.safe_float(df, "min_val") + result["maximum"] = self.safe_float(df, "max_val") + result["mean"] = self.safe_float(df, "mean_val") + result["sum"] = self.safe_float(df, "sum_val") + result["std_dev"] = self.safe_float(df, "stddev_val") + + return result + + def extract_percentile_result(self, df: "pd.DataFrame") -> Optional[str]: + """ + Extract percentile statistics from query result. + + Args: + df: DataFrame containing percentile query results + + Returns: + JSON string of percentile values or None + """ + p25 = self.safe_float(df, "p25") + p50 = self.safe_float(df, "p50") + p75 = self.safe_float(df, "p75") + + return json.dumps({ + "0.25": p25, + "0.50": p50, + "0.75": p75, + }) + + def extract_histogram_result(self, df: "pd.DataFrame") -> Optional[str]: + """ + Extract histogram from query result. + + Args: + df: DataFrame containing histogram query results + + Returns: + JSON string of histogram or None + """ + histogram = { + str(row["value"]): int(row["count"]) + for _, row in df.iterrows() + } + return json.dumps(histogram) + + def build_profile( + self, + base_stats: Dict, + percentiles: Optional[str] = None, + histogram: Optional[str] = None, + ) -> ColumnProfile: + """ + Build ColumnProfile from extracted statistics. + + Args: + base_stats: Dictionary of base statistics + percentiles: JSON string of percentile values + histogram: JSON string of histogram + + Returns: + ColumnProfile object + """ + profile = ColumnProfile( + column=self.column, + completeness=base_stats["completeness"], + approx_distinct_values=base_stats["distinct_count"], + data_type=self.column_type, + is_data_type_inferred=True, + ) + + if self.is_numeric: + profile.minimum = base_stats.get("minimum") + profile.maximum = base_stats.get("maximum") + profile.mean = base_stats.get("mean") + profile.sum = base_stats.get("sum") + profile.std_dev = base_stats.get("std_dev") + + if percentiles: + profile.approx_percentiles = percentiles + + if histogram: + profile.histogram = histogram + + return profile + + +class MultiColumnProfileOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """ + Profiles multiple columns in minimal queries. + + This operator batches profile statistics for multiple columns to reduce + the number of SQL queries needed for profiling. + + Attributes: + columns: List of column names to profile + schema: Dictionary mapping column names to SQL types + numeric_columns: List of numeric column names + string_columns: List of string column names + where: Optional WHERE clause for filtering + """ + + def __init__( + self, + columns: List[str], + schema: Dict[str, str], + where: Optional[str] = None, + ): + """ + Initialize MultiColumnProfileOperator. + + Args: + columns: List of column names to profile + schema: Dictionary mapping column names to SQL types + where: Optional WHERE clause for filtering + """ + self.columns = columns + self.schema = schema + self.where = where + + # Categorize columns by type + self.numeric_columns = [c for c in columns if schema.get(c) in NUMERIC_TYPES] + self.string_columns = [c for c in columns if schema.get(c) in STRING_TYPES] + self.other_columns = [ + c for c in columns + if c not in self.numeric_columns and c not in self.string_columns + ] + + def build_completeness_query(self, table: str) -> str: + """ + Build query for completeness of all columns. + + Args: + table: Table name to query + + Returns: + SQL query string + """ + aggregations = ["COUNT(*) as total"] + for col in self.columns: + aggregations.append( + f"SUM(CASE WHEN {col} IS NULL THEN 1 ELSE 0 END) as null_{col}" + ) + aggregations.append(f"APPROX_COUNT_DISTINCT({col}) as distinct_{col}") + + return f"SELECT {', '.join(aggregations)} FROM {table}" + + def build_numeric_stats_query(self, table: str) -> str: + """ + Build query for numeric column statistics. + + Args: + table: Table name to query + + Returns: + SQL query string + """ + if not self.numeric_columns: + return "" + + aggregations = [] + for col in self.numeric_columns: + aggregations.extend([ + f"MIN({col}) as min_{col}", + f"MAX({col}) as max_{col}", + f"AVG({col}) as mean_{col}", + f"SUM({col}) as sum_{col}", + f"STDDEV_SAMP({col}) as stddev_{col}", + ]) + + return f"SELECT {', '.join(aggregations)} FROM {table}" + + def extract_profiles( + self, + completeness_df: "pd.DataFrame", + numeric_df: Optional["pd.DataFrame"] = None, + ) -> List[ColumnProfile]: + """ + Extract column profiles from query results. + + Args: + completeness_df: DataFrame with completeness statistics + numeric_df: DataFrame with numeric statistics (optional) + + Returns: + List of ColumnProfile objects + """ + import pandas as pd + + profiles = [] + total = int(completeness_df["total"].iloc[0]) + + for col in self.columns: + # Extract completeness stats + null_count_raw = completeness_df[f"null_{col}"].iloc[0] + null_count = int(null_count_raw) if not pd.isna(null_count_raw) else 0 + + distinct_count_raw = completeness_df[f"distinct_{col}"].iloc[0] + distinct_count = int(distinct_count_raw) if not pd.isna(distinct_count_raw) else 0 + + completeness = (total - null_count) / total if total > 0 else 1.0 + + profile = ColumnProfile( + column=col, + completeness=completeness, + approx_distinct_values=distinct_count, + data_type=self.schema.get(col, "Unknown"), + is_data_type_inferred=True, + ) + + # Add numeric stats if applicable + if col in self.numeric_columns and numeric_df is not None: + profile.minimum = self.safe_float(numeric_df, f"min_{col}") + profile.maximum = self.safe_float(numeric_df, f"max_{col}") + profile.mean = self.safe_float(numeric_df, f"mean_{col}") + profile.sum = self.safe_float(numeric_df, f"sum_{col}") + profile.std_dev = self.safe_float(numeric_df, f"stddev_{col}") + + profiles.append(profile) + + return profiles + + +__all__ = [ + "ColumnProfileOperator", + "MultiColumnProfileOperator", + "NUMERIC_TYPES", + "STRING_TYPES", +] diff --git a/pydeequ/engines/operators/protocols.py b/pydeequ/engines/operators/protocols.py new file mode 100644 index 0000000..3d36c5a --- /dev/null +++ b/pydeequ/engines/operators/protocols.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +""" +Protocol definitions for SQL operators. + +This module defines the structural typing contracts that operators must +implement. Using Protocol from typing allows for duck typing while still +providing IDE support and type checking. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Protocol, runtime_checkable + +if TYPE_CHECKING: + import pandas as pd + from pydeequ.engines import MetricResult + + +@runtime_checkable +class ScanOperatorProtocol(Protocol): + """ + Contract for single-pass aggregation operators. + + Scan operators compute metrics via SQL aggregations that can be + combined into a single SELECT statement, enabling efficient + batch execution. + """ + + def get_aggregations(self) -> List[str]: + """ + Return SQL aggregation expressions. + + Returns: + List of SQL aggregation expressions with AS alias clauses, + e.g., ["AVG(col) AS mean_col", "COUNT(*) AS count_col"] + """ + ... + + def extract_result(self, df: "pd.DataFrame") -> "MetricResult": + """ + Extract metric from query result DataFrame. + + Args: + df: DataFrame containing query results with columns + matching the aliases from get_aggregations() + + Returns: + MetricResult with extracted value + """ + ... + + +@runtime_checkable +class GroupingOperatorProtocol(Protocol): + """ + Contract for operators requiring GROUP BY queries. + + Grouping operators need to compute intermediate aggregations + via GROUP BY before computing the final metric. They cannot + be batched with scan operators and require separate queries. + """ + + def get_grouping_columns(self) -> List[str]: + """ + Return columns to GROUP BY. + + Returns: + List of column names for the GROUP BY clause + """ + ... + + def build_query(self, table: str) -> str: + """ + Build complete CTE-based query. + + Args: + table: Name of the table to query + + Returns: + Complete SQL query string with CTEs as needed + """ + ... + + def extract_result(self, df: "pd.DataFrame") -> "MetricResult": + """ + Extract metric from query result DataFrame. + + Args: + df: DataFrame containing query results + + Returns: + MetricResult with extracted value + """ + ... + + +__all__ = [ + "ScanOperatorProtocol", + "GroupingOperatorProtocol", +] diff --git a/pydeequ/engines/operators/scan_operators.py b/pydeequ/engines/operators/scan_operators.py new file mode 100644 index 0000000..1812ec9 --- /dev/null +++ b/pydeequ/engines/operators/scan_operators.py @@ -0,0 +1,502 @@ +# -*- coding: utf-8 -*- +""" +Scan operator implementations. + +Scan operators compute metrics via SQL aggregations that can be combined +into a single SELECT statement, enabling efficient batch execution. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from pydeequ.engines import MetricResult +from pydeequ.engines.operators.base import ScanOperator +from pydeequ.engines.operators.mixins import ( + ColumnAliasMixin, + SafeExtractMixin, + WhereClauseMixin, +) + +if TYPE_CHECKING: + import pandas as pd + + +class SizeOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """ + Computes the number of rows in a table. + + Unlike other scan operators, Size operates on the dataset level + rather than a specific column. + """ + + def __init__(self, where: Optional[str] = None): + self.where = where + self.alias = "size_value" + + @property + def metric_name(self) -> str: + return "Size" + + @property + def instance(self) -> str: + return "*" + + @property + def entity(self) -> str: + return "Dataset" + + def get_aggregations(self) -> List[str]: + if self.where: + sql = f"SUM(CASE WHEN {self.where} THEN 1 ELSE 0 END) AS {self.alias}" + else: + sql = f"COUNT(*) AS {self.alias}" + return [sql] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class CompletenessOperator(ScanOperator): + """Computes the fraction of non-null values in a column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.count_alias = self.make_alias("count", column) + self.null_alias = self.make_alias("null_count", column) + + @property + def metric_name(self) -> str: + return "Completeness" + + def get_aggregations(self) -> List[str]: + count_sql = self.wrap_count_with_where("1") + null_sql = self.wrap_count_with_where(f"{self.column} IS NULL") + return [ + f"{count_sql} AS {self.count_alias}", + f"{null_sql} AS {self.null_alias}", + ] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + total = self.safe_float(df, self.count_alias) or 0 + nulls = self.safe_float(df, self.null_alias) or 0 + value = (total - nulls) / total if total > 0 else 1.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MeanOperator(ScanOperator): + """Computes the average of a numeric column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("mean", column) + + @property + def metric_name(self) -> str: + return "Mean" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("AVG", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class SumOperator(ScanOperator): + """Computes the sum of a numeric column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("sum", column) + + @property + def metric_name(self) -> str: + return "Sum" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("SUM", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MinimumOperator(ScanOperator): + """Computes the minimum value of a numeric column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("min", column) + + @property + def metric_name(self) -> str: + return "Minimum" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("MIN", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MaximumOperator(ScanOperator): + """Computes the maximum value of a numeric column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("max", column) + + @property + def metric_name(self) -> str: + return "Maximum" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("MAX", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class StandardDeviationOperator(ScanOperator): + """Computes the standard deviation of a numeric column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("stddev", column) + + @property + def metric_name(self) -> str: + return "StandardDeviation" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("STDDEV_SAMP", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MaxLengthOperator(ScanOperator): + """Computes the maximum string length in a column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("max_length", column) + + @property + def metric_name(self) -> str: + return "MaxLength" + + def get_aggregations(self) -> List[str]: + if self.where: + sql = f"MAX(CASE WHEN {self.where} THEN LENGTH({self.column}) ELSE NULL END)" + else: + sql = f"MAX(LENGTH({self.column}))" + return [f"{sql} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class MinLengthOperator(ScanOperator): + """Computes the minimum string length in a column.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("min_length", column) + + @property + def metric_name(self) -> str: + return "MinLength" + + def get_aggregations(self) -> List[str]: + if self.where: + sql = f"MIN(CASE WHEN {self.where} THEN LENGTH({self.column}) ELSE NULL END)" + else: + sql = f"MIN(LENGTH({self.column}))" + return [f"{sql} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class PatternMatchOperator(ScanOperator): + """Computes the fraction of values matching a regex pattern.""" + + def __init__(self, column: str, pattern: str, where: Optional[str] = None): + super().__init__(column, where) + self.pattern = pattern.replace("'", "''") # Escape single quotes + self.count_alias = self.make_alias("count", column) + self.match_alias = self.make_alias("pattern_match", column) + + @property + def metric_name(self) -> str: + return "PatternMatch" + + def get_aggregations(self) -> List[str]: + count_sql = self.wrap_count_with_where("1") + match_cond = f"REGEXP_MATCHES({self.column}, '{self.pattern}')" + match_sql = self.wrap_count_with_where(match_cond) + return [ + f"{count_sql} AS {self.count_alias}", + f"{match_sql} AS {self.match_alias}", + ] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + total = self.safe_float(df, self.count_alias) or 0 + matches = self.safe_float(df, self.match_alias) or 0 + value = matches / total if total > 0 else 1.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class ComplianceOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """ + Computes the fraction of rows satisfying a SQL condition. + + Unlike other scan operators, Compliance operates on a predicate + rather than a specific column. + """ + + def __init__(self, instance: str, predicate: str, where: Optional[str] = None): + self.instance_name = instance + self.predicate = predicate + self.where = where + self.count_alias = "compliance_count" + self.match_alias = self.make_alias("compliance_match", instance) + + @property + def metric_name(self) -> str: + return "Compliance" + + @property + def instance(self) -> str: + return self.instance_name + + @property + def entity(self) -> str: + return "Dataset" + + def get_aggregations(self) -> List[str]: + count_sql = self.wrap_count_with_where("1") + match_sql = self.wrap_count_with_where(f"({self.predicate})") + return [ + f"{count_sql} AS {self.count_alias}", + f"{match_sql} AS {self.match_alias}", + ] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + total = self.safe_float(df, self.count_alias) or 0 + matches = self.safe_float(df, self.match_alias) or 0 + value = matches / total if total > 0 else 1.0 + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class CorrelationOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """Computes Pearson correlation between two columns.""" + + def __init__(self, column1: str, column2: str, where: Optional[str] = None): + self.column1 = column1 + self.column2 = column2 + self.where = where + self.alias = self.make_alias("corr", column1, column2) + + @property + def metric_name(self) -> str: + return "Correlation" + + @property + def instance(self) -> str: + return f"{self.column1},{self.column2}" + + @property + def entity(self) -> str: + return "Multicolumn" + + def get_aggregations(self) -> List[str]: + # Note: CORR doesn't support CASE WHEN wrapping in most DBs + # For WHERE clause, the engine should apply it to the whole query + sql = f"CORR({self.column1}, {self.column2})" + return [f"{sql} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class CountDistinctOperator(WhereClauseMixin, SafeExtractMixin, ColumnAliasMixin): + """Computes the count of distinct values in column(s).""" + + def __init__(self, columns: List[str], where: Optional[str] = None): + self.columns = columns + self.where = where + self.alias = self.make_alias("count_distinct", *columns) + + @property + def metric_name(self) -> str: + return "CountDistinct" + + @property + def instance(self) -> str: + return ",".join(self.columns) + + @property + def entity(self) -> str: + return "Multicolumn" if len(self.columns) > 1 else "Column" + + def get_aggregations(self) -> List[str]: + cols_str = ", ".join(self.columns) + sql = f"COUNT(DISTINCT ({cols_str}))" + return [f"{sql} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class ApproxCountDistinctOperator(ScanOperator): + """Computes approximate count distinct using HyperLogLog.""" + + def __init__(self, column: str, where: Optional[str] = None): + super().__init__(column, where) + self.alias = self.make_alias("approx_count_distinct", column) + + @property + def metric_name(self) -> str: + return "ApproxCountDistinct" + + def get_aggregations(self) -> List[str]: + agg = self.wrap_agg_with_where("APPROX_COUNT_DISTINCT", self.column) + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +class ApproxQuantileOperator(ScanOperator): + """Computes approximate quantile using QUANTILE_CONT.""" + + def __init__(self, column: str, quantile: float = 0.5, where: Optional[str] = None): + super().__init__(column, where) + self.quantile = quantile + self.alias = self.make_alias("approx_quantile", column) + + @property + def metric_name(self) -> str: + return "ApproxQuantile" + + def get_aggregations(self) -> List[str]: + if self.where: + agg = f"QUANTILE_CONT(CASE WHEN {self.where} THEN {self.column} ELSE NULL END, {self.quantile})" + else: + agg = f"QUANTILE_CONT({self.column}, {self.quantile})" + return [f"{agg} AS {self.alias}"] + + def extract_result(self, df: "pd.DataFrame") -> MetricResult: + value = self.safe_float(df, self.alias) + return MetricResult( + name=self.metric_name, + instance=self.instance, + entity=self.entity, + value=value, + ) + + +__all__ = [ + "SizeOperator", + "CompletenessOperator", + "MeanOperator", + "SumOperator", + "MinimumOperator", + "MaximumOperator", + "StandardDeviationOperator", + "MaxLengthOperator", + "MinLengthOperator", + "PatternMatchOperator", + "ComplianceOperator", + "CorrelationOperator", + "CountDistinctOperator", + "ApproxCountDistinctOperator", + "ApproxQuantileOperator", +] diff --git a/pydeequ/engines/spark.py b/pydeequ/engines/spark.py new file mode 100644 index 0000000..2072bac --- /dev/null +++ b/pydeequ/engines/spark.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +""" +Spark execution engine for PyDeequ. + +This module provides a Spark-based execution engine that wraps the existing +v2 Spark Connect API, providing a unified engine interface. + +Example usage: + from pyspark.sql import SparkSession + from pydeequ.engines.spark import SparkEngine + from pydeequ.v2.analyzers import Size, Completeness + + spark = SparkSession.builder.remote("sc://localhost:15002").getOrCreate() + df = spark.createDataFrame([(1, 2), (3, 4)], ["a", "b"]) + + engine = SparkEngine(spark, dataframe=df) + metrics = engine.compute_metrics([Size(), Completeness("a")]) +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence + +import pandas as pd + +from pydeequ.engines import ( + BaseEngine, + ColumnProfile, + ConstraintResult, + ConstraintSuggestion, + ConstraintStatus, + CheckStatus, + MetricResult, +) + +if TYPE_CHECKING: + from pyspark.sql import DataFrame, SparkSession + from pydeequ.v2.analyzers import _ConnectAnalyzer + from pydeequ.v2.checks import Check + + +class SparkEngine(BaseEngine): + """ + Spark-based execution engine. + + This engine wraps the existing v2 Spark Connect API to provide + a unified engine interface. It delegates execution to the + Deequ plugin running on the Spark cluster. + + Attributes: + spark: SparkSession + table: Optional table name + dataframe: Optional DataFrame to analyze + """ + + def __init__( + self, + spark: "SparkSession", + table: Optional[str] = None, + dataframe: Optional["DataFrame"] = None, + ): + """ + Create a new SparkEngine. + + Args: + spark: SparkSession (Spark Connect) + table: Optional table name to analyze + dataframe: Optional DataFrame to analyze (preferred over table) + """ + self.spark = spark + self.table = table + self._dataframe = dataframe + + def _get_dataframe(self) -> "DataFrame": + """Get the DataFrame to analyze.""" + if self._dataframe is not None: + return self._dataframe + if self.table: + return self.spark.table(self.table) + raise ValueError("Either dataframe or table must be provided") + + def get_schema(self) -> Dict[str, str]: + """Get the schema of the data source.""" + df = self._get_dataframe() + return {field.name: str(field.dataType) for field in df.schema.fields} + + def compute_metrics( + self, analyzers: Sequence["_ConnectAnalyzer"] + ) -> List[MetricResult]: + """ + Compute metrics using the Spark Connect Deequ plugin. + + Args: + analyzers: Sequence of analyzers to compute metrics for + + Returns: + List of MetricResult objects + """ + from pydeequ.v2.verification import AnalysisRunner + + df = self._get_dataframe() + + # Build and run the analysis + runner = AnalysisRunner(self.spark).onData(df) + for analyzer in analyzers: + runner = runner.addAnalyzer(analyzer) + + result_df = runner.run() + + # Convert Spark DataFrame result to MetricResult objects + results: List[MetricResult] = [] + for row in result_df.collect(): + results.append(MetricResult( + name=row["name"], + instance=row["instance"], + entity=row["entity"], + value=float(row["value"]) if row["value"] is not None else None, + )) + + return results + + def run_checks(self, checks: Sequence["Check"]) -> List[ConstraintResult]: + """ + Run verification checks using the Spark Connect Deequ plugin. + + Args: + checks: Sequence of Check objects to evaluate + + Returns: + List of ConstraintResult objects + """ + from pydeequ.v2.verification import VerificationSuite + + df = self._get_dataframe() + + # Build and run the verification + suite = VerificationSuite(self.spark).onData(df) + for check in checks: + suite = suite.addCheck(check) + + result_df = suite.run() + + # Convert Spark DataFrame result to ConstraintResult objects + results: List[ConstraintResult] = [] + for row in result_df.collect(): + results.append(ConstraintResult( + check_description=row["check"], + check_level=row["check_level"], + check_status=row["check_status"], + constraint=row["constraint"], + constraint_status=row["constraint_status"], + constraint_message=row["constraint_message"], + )) + + return results + + def profile_columns( + self, + columns: Optional[Sequence[str]] = None, + low_cardinality_threshold: int = 0, + ) -> List[ColumnProfile]: + """ + Profile columns using the Spark Connect Deequ plugin. + + Args: + columns: Optional list of columns to profile + low_cardinality_threshold: Threshold for histogram computation + + Returns: + List of ColumnProfile objects + """ + from pydeequ.v2.profiles import ColumnProfilerRunner + + df = self._get_dataframe() + + # Build and run the profiler + runner = ColumnProfilerRunner(self.spark).onData(df) + + if columns: + runner = runner.restrictToColumns(columns) + + if low_cardinality_threshold > 0: + runner = runner.withLowCardinalityHistogramThreshold(low_cardinality_threshold) + + result_df = runner.run() + + # Convert Spark DataFrame result to ColumnProfile objects + profiles: List[ColumnProfile] = [] + for row in result_df.collect(): + profiles.append(ColumnProfile( + column=row["column"], + completeness=float(row["completeness"]) if row["completeness"] is not None else 0.0, + approx_distinct_values=int(row["approx_distinct_values"]) if row["approx_distinct_values"] is not None else 0, + data_type=row["data_type"] if row["data_type"] else "Unknown", + is_data_type_inferred=bool(row["is_data_type_inferred"]) if "is_data_type_inferred" in row else True, + type_counts=row["type_counts"] if "type_counts" in row else None, + histogram=row["histogram"] if "histogram" in row else None, + mean=float(row["mean"]) if "mean" in row and row["mean"] is not None else None, + minimum=float(row["minimum"]) if "minimum" in row and row["minimum"] is not None else None, + maximum=float(row["maximum"]) if "maximum" in row and row["maximum"] is not None else None, + sum=float(row["sum"]) if "sum" in row and row["sum"] is not None else None, + std_dev=float(row["std_dev"]) if "std_dev" in row and row["std_dev"] is not None else None, + )) + + return profiles + + def suggest_constraints( + self, + columns: Optional[Sequence[str]] = None, + rules: Optional[Sequence[str]] = None, + ) -> List[ConstraintSuggestion]: + """ + Suggest constraints using the Spark Connect Deequ plugin. + + Args: + columns: Optional list of columns to analyze + rules: Optional list of rule sets to apply + + Returns: + List of ConstraintSuggestion objects + """ + from pydeequ.v2.suggestions import ConstraintSuggestionRunner, Rules + + df = self._get_dataframe() + + # Build and run the suggestion runner + runner = ConstraintSuggestionRunner(self.spark).onData(df) + + if columns: + runner = runner.restrictToColumns(columns) + + # Map rule strings to Rules enum (accept both strings and enum values) + if rules: + rule_map = { + "DEFAULT": Rules.DEFAULT, + "STRING": Rules.STRING, + "NUMERICAL": Rules.NUMERICAL, + "COMMON": Rules.COMMON, + "EXTENDED": Rules.EXTENDED, + } + for rule in rules: + # Accept both Rules enum and string values + if isinstance(rule, Rules): + runner = runner.addConstraintRules(rule) + elif rule in rule_map: + runner = runner.addConstraintRules(rule_map[rule]) + else: + runner = runner.addConstraintRules(Rules.DEFAULT) + + result_df = runner.run() + + # Convert Spark DataFrame result to ConstraintSuggestion objects + suggestions: List[ConstraintSuggestion] = [] + for row in result_df.collect(): + suggestions.append(ConstraintSuggestion( + column_name=row["column_name"], + constraint_name=row["constraint_name"], + current_value=row["current_value"] if "current_value" in row else None, + description=row["description"], + suggesting_rule=row["suggesting_rule"], + code_for_constraint=row["code_for_constraint"], + )) + + return suggestions diff --git a/pydeequ/engines/suggestions/__init__.py b/pydeequ/engines/suggestions/__init__.py new file mode 100644 index 0000000..6303692 --- /dev/null +++ b/pydeequ/engines/suggestions/__init__.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +Constraint suggestion module. + +This module provides a modular, rule-based system for suggesting data quality +constraints based on column profiles. + +Architecture: + rules.py - SuggestionRule base class and 10 rule implementations + registry.py - RuleRegistry for organizing rules by rule set + runner.py - SuggestionRunner for orchestrating rule execution + +Available Rule Sets: + - DEFAULT: Basic rules (completeness, non-negative, categorical) + - NUMERICAL: Rules for numeric columns (min, max, mean) + - STRING: Rules for string columns (min/max length) + - COMMON: General rules (uniqueness) + - EXTENDED: All rules combined + +Example usage: + from pydeequ.engines.suggestions import SuggestionRunner + + # Run default rules + runner = SuggestionRunner(rule_sets=["DEFAULT"]) + suggestions = runner.run(profiles, execute_fn=engine._execute_query, table="my_table") + + # Run multiple rule sets + runner = SuggestionRunner(rule_sets=["DEFAULT", "NUMERICAL", "STRING"]) + suggestions = runner.run(profiles, execute_fn=engine._execute_query, table="my_table") +""" + +from pydeequ.engines.suggestions.registry import RuleRegistry +from pydeequ.engines.suggestions.rules import ( + SuggestionRule, + CompleteIfCompleteRule, + RetainCompletenessRule, + NonNegativeNumbersRule, + CategoricalRangeRule, + HasMinRule, + HasMaxRule, + HasMeanRule, + HasMinLengthRule, + HasMaxLengthRule, + UniqueIfApproximatelyUniqueRule, +) +from pydeequ.engines.suggestions.runner import SuggestionRunner + + +__all__ = [ + # Registry + "RuleRegistry", + # Runner + "SuggestionRunner", + # Base class + "SuggestionRule", + # Rules + "CompleteIfCompleteRule", + "RetainCompletenessRule", + "NonNegativeNumbersRule", + "CategoricalRangeRule", + "HasMinRule", + "HasMaxRule", + "HasMeanRule", + "HasMinLengthRule", + "HasMaxLengthRule", + "UniqueIfApproximatelyUniqueRule", +] diff --git a/pydeequ/engines/suggestions/registry.py b/pydeequ/engines/suggestions/registry.py new file mode 100644 index 0000000..f762845 --- /dev/null +++ b/pydeequ/engines/suggestions/registry.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Suggestion rule registry. + +This module provides a registry for suggestion rules, allowing rules to be +organized by rule sets (DEFAULT, NUMERICAL, STRING, COMMON, EXTENDED). +""" + +from __future__ import annotations + +from typing import List + +from pydeequ.engines.suggestions.rules import ( + SuggestionRule, + CompleteIfCompleteRule, + RetainCompletenessRule, + NonNegativeNumbersRule, + CategoricalRangeRule, + HasMinRule, + HasMaxRule, + HasMeanRule, + HasMinLengthRule, + HasMaxLengthRule, + UniqueIfApproximatelyUniqueRule, +) + + +class RuleRegistry: + """ + Registry of suggestion rules by rule set. + + Provides centralized management of suggestion rules and retrieval + by rule set names. + """ + + _rules: List[SuggestionRule] = [] + + @classmethod + def register(cls, rule: SuggestionRule) -> None: + """ + Register a suggestion rule. + + Args: + rule: SuggestionRule instance to register + """ + cls._rules.append(rule) + + @classmethod + def get_rules_for_sets(cls, rule_sets: List[str]) -> List[SuggestionRule]: + """ + Get all rules that belong to any of the specified rule sets. + + Args: + rule_sets: List of rule set names (e.g., ["DEFAULT", "NUMERICAL"]) + + Returns: + List of rules that belong to any of the specified sets + """ + return [r for r in cls._rules if any(s in r.rule_sets for s in rule_sets)] + + @classmethod + def get_all_rules(cls) -> List[SuggestionRule]: + """ + Get all registered rules. + + Returns: + List of all registered rules + """ + return cls._rules.copy() + + @classmethod + def clear(cls) -> None: + """Clear all registered rules (mainly for testing).""" + cls._rules = [] + + +# Auto-register all default rules +def _register_default_rules() -> None: + """Register all built-in suggestion rules.""" + RuleRegistry.register(CompleteIfCompleteRule()) + RuleRegistry.register(RetainCompletenessRule()) + RuleRegistry.register(NonNegativeNumbersRule()) + RuleRegistry.register(CategoricalRangeRule()) + RuleRegistry.register(HasMinRule()) + RuleRegistry.register(HasMaxRule()) + RuleRegistry.register(HasMeanRule()) + RuleRegistry.register(HasMinLengthRule()) + RuleRegistry.register(HasMaxLengthRule()) + RuleRegistry.register(UniqueIfApproximatelyUniqueRule()) + + +# Register rules on module load +_register_default_rules() + + +__all__ = [ + "RuleRegistry", +] diff --git a/pydeequ/engines/suggestions/rules.py b/pydeequ/engines/suggestions/rules.py new file mode 100644 index 0000000..40a7ebf --- /dev/null +++ b/pydeequ/engines/suggestions/rules.py @@ -0,0 +1,380 @@ +# -*- coding: utf-8 -*- +""" +Suggestion rule implementations. + +This module provides the base class and implementations for constraint +suggestion rules. Each rule analyzes column profiles and generates +appropriate constraint suggestions. +""" + +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, List, Optional, Set + +if TYPE_CHECKING: + from pydeequ.engines import ColumnProfile, ConstraintSuggestion + + +# SQL types that are considered string +STRING_TYPES: Set[str] = {"VARCHAR", "CHAR", "BPCHAR", "TEXT", "STRING"} + + +class SuggestionRule(ABC): + """ + Base class for constraint suggestion rules. + + Each rule examines column profiles and generates appropriate + constraint suggestions based on data characteristics. + """ + + @property + @abstractmethod + def name(self) -> str: + """Rule name for identification.""" + pass + + @property + @abstractmethod + def rule_sets(self) -> List[str]: + """Which rule sets this rule belongs to (DEFAULT, NUMERICAL, etc).""" + pass + + @abstractmethod + def applies_to(self, profile: "ColumnProfile") -> bool: + """Whether this rule applies to the given column profile.""" + pass + + @abstractmethod + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + """Generate a suggestion if applicable, or None.""" + pass + + +class CompleteIfCompleteRule(SuggestionRule): + """Suggests isComplete() constraint for fully complete columns.""" + + @property + def name(self) -> str: + return "CompleteIfComplete" + + @property + def rule_sets(self) -> List[str]: + return ["DEFAULT", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.completeness == 1.0 + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Completeness", + current_value="1.0", + description=f"'{profile.column}' is complete", + suggesting_rule=self.name, + code_for_constraint=f'.isComplete("{profile.column}")', + ) + + +class RetainCompletenessRule(SuggestionRule): + """Suggests hasCompleteness() constraint for highly complete columns.""" + + THRESHOLD = 0.9 # Minimum completeness to suggest retaining + + @property + def name(self) -> str: + return "RetainCompleteness" + + @property + def rule_sets(self) -> List[str]: + return ["DEFAULT", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + # Apply only if not fully complete but >= threshold + return self.THRESHOLD <= profile.completeness < 1.0 + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Completeness", + current_value=f"{profile.completeness:.4f}", + description=f"'{profile.column}' has completeness {profile.completeness:.2%}", + suggesting_rule=self.name, + code_for_constraint=f'.hasCompleteness("{profile.column}", gte({profile.completeness:.2f}))', + ) + + +class NonNegativeNumbersRule(SuggestionRule): + """Suggests isNonNegative() constraint for columns with no negative values.""" + + @property + def name(self) -> str: + return "NonNegativeNumbers" + + @property + def rule_sets(self) -> List[str]: + return ["DEFAULT", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.minimum is not None and profile.minimum >= 0 + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="NonNegative", + current_value=f"{profile.minimum:.2f}", + description=f"'{profile.column}' has no negative values", + suggesting_rule=self.name, + code_for_constraint=f'.isNonNegative("{profile.column}")', + ) + + +class CategoricalRangeRule(SuggestionRule): + """Suggests isContainedIn() constraint for low cardinality categorical columns.""" + + MAX_CATEGORIES = 10 # Maximum distinct values to suggest containment + + @property + def name(self) -> str: + return "CategoricalRangeRule" + + @property + def rule_sets(self) -> List[str]: + return ["DEFAULT", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + if not profile.histogram: + return False + hist = json.loads(profile.histogram) + return len(hist) <= self.MAX_CATEGORIES + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + hist = json.loads(profile.histogram) + values = list(hist.keys()) + values_str = ", ".join([f'"{v}"' for v in values]) + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Compliance", + current_value=f"{len(values)} distinct values", + description=f"'{profile.column}' has categorical values", + suggesting_rule=self.name, + code_for_constraint=f'.isContainedIn("{profile.column}", [{values_str}])', + ) + + +class HasMinRule(SuggestionRule): + """Suggests hasMin() constraint for numeric columns.""" + + @property + def name(self) -> str: + return "HasMin" + + @property + def rule_sets(self) -> List[str]: + return ["NUMERICAL", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.minimum is not None and profile.mean is not None + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Minimum", + current_value=f"{profile.minimum:.2f}", + description=f"'{profile.column}' has minimum {profile.minimum:.2f}", + suggesting_rule=self.name, + code_for_constraint=f'.hasMin("{profile.column}", gte({profile.minimum:.2f}))', + ) + + +class HasMaxRule(SuggestionRule): + """Suggests hasMax() constraint for numeric columns.""" + + @property + def name(self) -> str: + return "HasMax" + + @property + def rule_sets(self) -> List[str]: + return ["NUMERICAL", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.maximum is not None and profile.mean is not None + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Maximum", + current_value=f"{profile.maximum:.2f}", + description=f"'{profile.column}' has maximum {profile.maximum:.2f}", + suggesting_rule=self.name, + code_for_constraint=f'.hasMax("{profile.column}", lte({profile.maximum:.2f}))', + ) + + +class HasMeanRule(SuggestionRule): + """Suggests hasMean() constraint for numeric columns.""" + + @property + def name(self) -> str: + return "HasMean" + + @property + def rule_sets(self) -> List[str]: + return ["NUMERICAL", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.mean is not None + + def generate(self, profile: "ColumnProfile") -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + lower = profile.mean * 0.9 + upper = profile.mean * 1.1 + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Mean", + current_value=f"{profile.mean:.2f}", + description=f"'{profile.column}' has mean {profile.mean:.2f}", + suggesting_rule=self.name, + code_for_constraint=f'.hasMean("{profile.column}", between({lower:.2f}, {upper:.2f}))', + ) + + +class HasMinLengthRule(SuggestionRule): + """Suggests hasMinLength() constraint for string columns.""" + + @property + def name(self) -> str: + return "HasMinLength" + + @property + def rule_sets(self) -> List[str]: + return ["STRING", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.data_type in STRING_TYPES + + def generate( + self, + profile: "ColumnProfile", + min_length: Optional[int] = None, + ) -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + if min_length is None or min_length <= 0: + return None + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="MinLength", + current_value=str(min_length), + description=f"'{profile.column}' has minimum length {min_length}", + suggesting_rule=self.name, + code_for_constraint=f'.hasMinLength("{profile.column}", gte({min_length}))', + ) + + +class HasMaxLengthRule(SuggestionRule): + """Suggests hasMaxLength() constraint for string columns.""" + + @property + def name(self) -> str: + return "HasMaxLength" + + @property + def rule_sets(self) -> List[str]: + return ["STRING", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + return profile.data_type in STRING_TYPES + + def generate( + self, + profile: "ColumnProfile", + max_length: Optional[int] = None, + ) -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + if max_length is None or max_length <= 0: + return None + + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="MaxLength", + current_value=str(max_length), + description=f"'{profile.column}' has maximum length {max_length}", + suggesting_rule=self.name, + code_for_constraint=f'.hasMaxLength("{profile.column}", lte({max_length}))', + ) + + +class UniqueIfApproximatelyUniqueRule(SuggestionRule): + """Suggests isUnique() constraint for approximately unique columns.""" + + UNIQUENESS_THRESHOLD = 0.99 # Minimum distinct ratio to consider unique + + @property + def name(self) -> str: + return "UniqueIfApproximatelyUnique" + + @property + def rule_sets(self) -> List[str]: + return ["COMMON", "EXTENDED"] + + def applies_to(self, profile: "ColumnProfile") -> bool: + # Need total row count to determine uniqueness + return True # Check is done in generate with row_count + + def generate( + self, + profile: "ColumnProfile", + row_count: Optional[int] = None, + ) -> Optional["ConstraintSuggestion"]: + from pydeequ.engines import ConstraintSuggestion + + if row_count is None or row_count <= 0: + return None + + if profile.approx_distinct_values >= row_count * self.UNIQUENESS_THRESHOLD: + return ConstraintSuggestion( + column_name=profile.column, + constraint_name="Uniqueness", + current_value="~1.0", + description=f"'{profile.column}' appears to be unique", + suggesting_rule=self.name, + code_for_constraint=f'.isUnique("{profile.column}")', + ) + return None + + +# Export all rule classes +__all__ = [ + "SuggestionRule", + "CompleteIfCompleteRule", + "RetainCompletenessRule", + "NonNegativeNumbersRule", + "CategoricalRangeRule", + "HasMinRule", + "HasMaxRule", + "HasMeanRule", + "HasMinLengthRule", + "HasMaxLengthRule", + "UniqueIfApproximatelyUniqueRule", + "STRING_TYPES", +] diff --git a/pydeequ/engines/suggestions/runner.py b/pydeequ/engines/suggestions/runner.py new file mode 100644 index 0000000..f9b1479 --- /dev/null +++ b/pydeequ/engines/suggestions/runner.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +""" +Suggestion runner for executing rules against column profiles. + +This module provides the SuggestionRunner class that orchestrates +running suggestion rules against column profiles. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, List, Optional + +from pydeequ.engines.suggestions.registry import RuleRegistry +from pydeequ.engines.suggestions.rules import ( + HasMinLengthRule, + HasMaxLengthRule, + UniqueIfApproximatelyUniqueRule, + STRING_TYPES, +) + +if TYPE_CHECKING: + import pandas as pd + from pydeequ.engines import ColumnProfile, ConstraintSuggestion + + +class SuggestionRunner: + """ + Runs suggestion rules against column profiles. + + The runner retrieves rules from the registry based on the specified + rule sets and executes them against each column profile. + + Attributes: + rule_sets: List of rule set names to apply + """ + + def __init__(self, rule_sets: Optional[List[str]] = None): + """ + Initialize SuggestionRunner. + + Args: + rule_sets: List of rule set names (e.g., ["DEFAULT", "NUMERICAL"]). + If None, defaults to ["DEFAULT"]. + """ + self.rule_sets = rule_sets or ["DEFAULT"] + + def run( + self, + profiles: List["ColumnProfile"], + execute_fn: Optional[Callable[[str], "pd.DataFrame"]] = None, + table: Optional[str] = None, + row_count: Optional[int] = None, + ) -> List["ConstraintSuggestion"]: + """ + Run suggestion rules against column profiles. + + Args: + profiles: List of column profiles to analyze + execute_fn: Optional function to execute SQL queries (for rules + that need additional data like string lengths) + table: Optional table name for queries + row_count: Optional total row count for uniqueness checks + + Returns: + List of constraint suggestions + """ + rules = RuleRegistry.get_rules_for_sets(self.rule_sets) + suggestions: List["ConstraintSuggestion"] = [] + + for profile in profiles: + for rule in rules: + suggestion = self._apply_rule( + rule, profile, execute_fn, table, row_count + ) + if suggestion: + suggestions.append(suggestion) + + return suggestions + + def _apply_rule( + self, + rule, + profile: "ColumnProfile", + execute_fn: Optional[Callable[[str], "pd.DataFrame"]], + table: Optional[str], + row_count: Optional[int], + ) -> Optional["ConstraintSuggestion"]: + """ + Apply a single rule to a profile. + + Some rules require special handling (e.g., string length rules need + to query the database, uniqueness rules need row count). + + Args: + rule: The rule to apply + profile: Column profile to analyze + execute_fn: Optional SQL execution function + table: Optional table name + row_count: Optional row count + + Returns: + Constraint suggestion or None + """ + # Handle HasMinLengthRule - needs string length from query + if isinstance(rule, HasMinLengthRule): + return self._handle_string_length_rule( + rule, profile, execute_fn, table, is_min=True + ) + + # Handle HasMaxLengthRule - needs string length from query + if isinstance(rule, HasMaxLengthRule): + return self._handle_string_length_rule( + rule, profile, execute_fn, table, is_min=False + ) + + # Handle UniqueIfApproximatelyUniqueRule - needs row count + if isinstance(rule, UniqueIfApproximatelyUniqueRule): + if rule.applies_to(profile): + return rule.generate(profile, row_count=row_count) + return None + + # Standard rule handling + if rule.applies_to(profile): + return rule.generate(profile) + return None + + def _handle_string_length_rule( + self, + rule, + profile: "ColumnProfile", + execute_fn: Optional[Callable[[str], "pd.DataFrame"]], + table: Optional[str], + is_min: bool, + ) -> Optional["ConstraintSuggestion"]: + """ + Handle string length rules that need database queries. + + Args: + rule: HasMinLengthRule or HasMaxLengthRule + profile: Column profile + execute_fn: SQL execution function + table: Table name + is_min: True for min length, False for max length + + Returns: + Constraint suggestion or None + """ + import pandas as pd + + if not rule.applies_to(profile): + return None + + if execute_fn is None or table is None: + return None + + col = profile.column + agg_func = "MIN" if is_min else "MAX" + query = f"SELECT {agg_func}(LENGTH({col})) as len FROM {table} WHERE {col} IS NOT NULL" + + try: + result = execute_fn(query) + length = result["len"].iloc[0] + if length is not None and not pd.isna(length): + length = int(length) + if length > 0: + if is_min: + return rule.generate(profile, min_length=length) + else: + return rule.generate(profile, max_length=length) + except Exception: + pass + + return None + + +__all__ = [ + "SuggestionRunner", +] diff --git a/pydeequ/v2/analyzers.py b/pydeequ/v2/analyzers.py index 53a979c..b796f12 100644 --- a/pydeequ/v2/analyzers.py +++ b/pydeequ/v2/analyzers.py @@ -25,7 +25,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import List, Optional, Sequence, Union +from typing import Optional, Sequence, Union from pydeequ.v2.proto import deequ_connect_pb2 as proto diff --git a/pydeequ/v2/checks.py b/pydeequ/v2/checks.py index 2a86ba8..c788f6c 100644 --- a/pydeequ/v2/checks.py +++ b/pydeequ/v2/checks.py @@ -19,7 +19,7 @@ from __future__ import annotations from enum import Enum -from typing import List, Optional, Sequence, Union +from typing import List, Optional, Sequence from pydeequ.v2.predicates import Predicate, is_one from pydeequ.v2.proto import deequ_connect_pb2 as proto diff --git a/pydeequ/v2/predicates.py b/pydeequ/v2/predicates.py index adaf23d..3f2c06c 100644 --- a/pydeequ/v2/predicates.py +++ b/pydeequ/v2/predicates.py @@ -40,6 +40,26 @@ def to_proto(self) -> proto.PredicateMessage: def __repr__(self) -> str: raise NotImplementedError + @abstractmethod + def to_callable(self): + """ + Convert predicate to a callable function. + + Returns: + A callable that takes a value and returns True/False + + Example: + pred = gte(0.95) + func = pred.to_callable() + assert func(0.96) == True + assert func(0.90) == False + """ + raise NotImplementedError + + def __call__(self, value: float) -> bool: + """Allow predicates to be called directly like functions.""" + return self.to_callable()(value) + @dataclass class Comparison(Predicate): @@ -62,6 +82,26 @@ def __repr__(self) -> str: } return f"x {op_map.get(self.operator, '?')} {self.value}" + def to_callable(self): + """Convert to a callable function.""" + op = self.operator + target = self.value + + if op == proto.PredicateMessage.Operator.EQ: + return lambda x: abs(x - target) < 1e-9 if x is not None else False + elif op == proto.PredicateMessage.Operator.NE: + return lambda x: abs(x - target) >= 1e-9 if x is not None else False + elif op == proto.PredicateMessage.Operator.GT: + return lambda x: x > target if x is not None else False + elif op == proto.PredicateMessage.Operator.GE: + return lambda x: x >= target if x is not None else False + elif op == proto.PredicateMessage.Operator.LT: + return lambda x: x < target if x is not None else False + elif op == proto.PredicateMessage.Operator.LE: + return lambda x: x <= target if x is not None else False + else: + return lambda x: False + @dataclass class Between(Predicate): @@ -80,6 +120,12 @@ def to_proto(self) -> proto.PredicateMessage: def __repr__(self) -> str: return f"{self.lower} <= x <= {self.upper}" + def to_callable(self): + """Convert to a callable function.""" + lower = self.lower + upper = self.upper + return lambda x: lower <= x <= upper if x is not None else False + # ============================================================================ # Factory Functions - Convenient way to create predicates diff --git a/pydeequ/v2/profiles.py b/pydeequ/v2/profiles.py index 97f71ef..1e2a373 100644 --- a/pydeequ/v2/profiles.py +++ b/pydeequ/v2/profiles.py @@ -1,12 +1,25 @@ # -*- coding: utf-8 -*- """ -Column Profiler for Deequ Spark Connect. +Column Profiler for PyDeequ v2. This module provides column profiling capabilities that analyze DataFrame columns to compute statistics like completeness, data type distribution, and optional KLL sketch-based quantile estimation. -Example usage: +Example usage with DuckDB: + import duckdb + import pydeequ + from pydeequ.v2.profiles import ColumnProfilerRunner + + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id, 'foo' as name") + engine = pydeequ.connect(con, table="test") + + profiles = (ColumnProfilerRunner() + .on_engine(engine) + .run()) + +Example usage with Spark Connect: from pyspark.sql import SparkSession from pydeequ.v2.profiles import ColumnProfilerRunner, KLLParameters @@ -30,8 +43,9 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Dict, Optional, Sequence +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence +import pandas as pd from google.protobuf import any_pb2 from pydeequ.v2.proto import deequ_connect_pb2 as proto @@ -39,6 +53,7 @@ if TYPE_CHECKING: from pyspark.sql import DataFrame, SparkSession + from pydeequ.engines import BaseEngine @dataclass @@ -74,9 +89,15 @@ class ColumnProfilerRunner: ColumnProfilerRunner analyzes DataFrame columns to compute statistics including completeness, data type, distinct values, and optionally - KLL sketches for numeric columns. + KLL sketches for numeric columns. Supports both engine-based and Spark-based execution. - Example: + Example (Engine-based with DuckDB): + profiles = (ColumnProfilerRunner() + .on_engine(engine) + .restrictToColumns(["col1", "col2"]) + .run()) + + Example (Spark Connect): profiles = (ColumnProfilerRunner(spark) .onData(df) .restrictToColumns(["col1", "col2"]) @@ -84,27 +105,49 @@ class ColumnProfilerRunner: .run()) """ - def __init__(self, spark: "SparkSession"): + def __init__(self, spark: Optional["SparkSession"] = None): """ Create a new ColumnProfilerRunner. Args: - spark: SparkSession (can be either local or Spark Connect) + spark: Optional SparkSession for Spark Connect mode. + Not required for engine-based execution. """ self._spark = spark def onData(self, df: "DataFrame") -> "ColumnProfilerRunBuilder": """ - Specify the DataFrame to profile. + Specify the DataFrame to profile (Spark mode). Args: df: DataFrame to profile Returns: ColumnProfilerRunBuilder for method chaining + + Raises: + ValueError: If SparkSession was not provided in constructor """ + if self._spark is None: + raise ValueError( + "SparkSession required for onData(). " + "Use ColumnProfilerRunner(spark).onData(df) or " + "ColumnProfilerRunner().on_engine(engine) for engine-based execution." + ) return ColumnProfilerRunBuilder(self._spark, df) + def on_engine(self, engine: "BaseEngine") -> "EngineColumnProfilerRunBuilder": + """ + Specify the engine to run profiling on (Engine mode). + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + + Returns: + EngineColumnProfilerRunBuilder for method chaining + """ + return EngineColumnProfilerRunBuilder(engine) + class ColumnProfilerRunBuilder: """ @@ -274,9 +317,80 @@ def _run_via_spark_connect( return dataframe_from_plan(plan, self._spark) +class EngineColumnProfilerRunBuilder: + """ + Builder for configuring and executing engine-based column profiling. + + This class works with DuckDB and other SQL backends via the engine abstraction. + """ + + def __init__(self, engine: "BaseEngine"): + """ + Create a new EngineColumnProfilerRunBuilder. + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + """ + self._engine = engine + self._restrict_to_columns: Optional[Sequence[str]] = None + self._low_cardinality_threshold: int = 0 + + def restrictToColumns(self, columns: Sequence[str]) -> "EngineColumnProfilerRunBuilder": + """ + Restrict profiling to specific columns. + + Args: + columns: List of column names to profile + + Returns: + self for method chaining + """ + self._restrict_to_columns = columns + return self + + def withLowCardinalityHistogramThreshold( + self, threshold: int + ) -> "EngineColumnProfilerRunBuilder": + """ + Set threshold for computing histograms. + + Columns with distinct values <= threshold will have histograms computed. + + Args: + threshold: Maximum distinct values for histogram computation + + Returns: + self for method chaining + """ + self._low_cardinality_threshold = threshold + return self + + def run(self) -> pd.DataFrame: + """ + Execute the profiling and return results as a pandas DataFrame. + + The result DataFrame contains columns: + - column: Column name + - completeness: Non-null ratio (0.0-1.0) + - approx_distinct_values: Approximate cardinality + - data_type: Detected/provided type + - mean, minimum, maximum, sum, std_dev: Numeric stats (null for non-numeric) + - histogram: JSON string of histogram (or null) + + Returns: + pandas DataFrame with profiling results (one row per column) + """ + profiles = self._engine.profile_columns( + columns=self._restrict_to_columns, + low_cardinality_threshold=self._low_cardinality_threshold, + ) + return self._engine.profiles_to_dataframe(profiles) + + # Export all public symbols __all__ = [ "ColumnProfilerRunner", "ColumnProfilerRunBuilder", + "EngineColumnProfilerRunBuilder", "KLLParameters", ] diff --git a/pydeequ/v2/suggestions.py b/pydeequ/v2/suggestions.py index b89b07b..5d6c371 100644 --- a/pydeequ/v2/suggestions.py +++ b/pydeequ/v2/suggestions.py @@ -1,12 +1,26 @@ # -*- coding: utf-8 -*- """ -Constraint Suggestions for Deequ Spark Connect. +Constraint Suggestions for PyDeequ v2. This module provides automatic constraint suggestion capabilities that analyze DataFrame columns and suggest appropriate data quality constraints based on the data characteristics. -Example usage: +Example usage with DuckDB: + import duckdb + import pydeequ + from pydeequ.v2.suggestions import ConstraintSuggestionRunner, Rules + + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id, 'foo' as name") + engine = pydeequ.connect(con, table="test") + + suggestions = (ConstraintSuggestionRunner() + .on_engine(engine) + .addConstraintRules(Rules.DEFAULT) + .run()) + +Example usage with Spark Connect: from pyspark.sql import SparkSession from pydeequ.v2.suggestions import ConstraintSuggestionRunner, Rules @@ -33,6 +47,7 @@ from enum import Enum from typing import TYPE_CHECKING, Dict, List, Optional, Sequence +import pandas as pd from google.protobuf import any_pb2 from pydeequ.v2.profiles import KLLParameters @@ -41,6 +56,7 @@ if TYPE_CHECKING: from pyspark.sql import DataFrame, SparkSession + from pydeequ.engines import BaseEngine class Rules(Enum): @@ -79,35 +95,64 @@ class ConstraintSuggestionRunner: ConstraintSuggestionRunner analyzes DataFrame columns to suggest appropriate data quality constraints based on the data characteristics. + Supports both engine-based and Spark-based execution. - Example: + Example (Engine-based with DuckDB): + suggestions = (ConstraintSuggestionRunner() + .on_engine(engine) + .addConstraintRules(Rules.DEFAULT) + .run()) + + Example (Spark Connect): suggestions = (ConstraintSuggestionRunner(spark) .onData(df) .addConstraintRules(Rules.DEFAULT) .run()) """ - def __init__(self, spark: "SparkSession"): + def __init__(self, spark: Optional["SparkSession"] = None): """ Create a new ConstraintSuggestionRunner. Args: - spark: SparkSession (can be either local or Spark Connect) + spark: Optional SparkSession for Spark Connect mode. + Not required for engine-based execution. """ self._spark = spark def onData(self, df: "DataFrame") -> "ConstraintSuggestionRunBuilder": """ - Specify the DataFrame to analyze. + Specify the DataFrame to analyze (Spark mode). Args: df: DataFrame to analyze for constraint suggestions Returns: ConstraintSuggestionRunBuilder for method chaining + + Raises: + ValueError: If SparkSession was not provided in constructor """ + if self._spark is None: + raise ValueError( + "SparkSession required for onData(). " + "Use ConstraintSuggestionRunner(spark).onData(df) or " + "ConstraintSuggestionRunner().on_engine(engine) for engine-based execution." + ) return ConstraintSuggestionRunBuilder(self._spark, df) + def on_engine(self, engine: "BaseEngine") -> "EngineConstraintSuggestionRunBuilder": + """ + Specify the engine to run suggestion analysis on (Engine mode). + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + + Returns: + EngineConstraintSuggestionRunBuilder for method chaining + """ + return EngineConstraintSuggestionRunBuilder(engine) + class ConstraintSuggestionRunBuilder: """ @@ -332,9 +377,92 @@ def _run_via_spark_connect( return dataframe_from_plan(plan, self._spark) +class EngineConstraintSuggestionRunBuilder: + """ + Builder for configuring and executing engine-based constraint suggestions. + + This class works with DuckDB and other SQL backends via the engine abstraction. + """ + + def __init__(self, engine: "BaseEngine"): + """ + Create a new EngineConstraintSuggestionRunBuilder. + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + """ + self._engine = engine + self._rules: List[Rules] = [] + self._restrict_to_columns: Optional[Sequence[str]] = None + + def addConstraintRules(self, rules: Rules) -> "EngineConstraintSuggestionRunBuilder": + """ + Add a constraint rule set. + + Can be called multiple times to add multiple rule sets. + + Args: + rules: Rules enum value specifying which rules to use + + Returns: + self for method chaining + """ + self._rules.append(rules) + return self + + def restrictToColumns( + self, columns: Sequence[str] + ) -> "EngineConstraintSuggestionRunBuilder": + """ + Restrict suggestions to specific columns. + + Args: + columns: List of column names to analyze + + Returns: + self for method chaining + """ + self._restrict_to_columns = columns + return self + + def run(self) -> pd.DataFrame: + """ + Execute the suggestion analysis and return results as a pandas DataFrame. + + The result DataFrame contains columns: + - column_name: Column the constraint applies to + - constraint_name: Type of constraint (e.g., "Completeness", "IsIn") + - current_value: Current metric value that triggered suggestion + - description: Human-readable description + - suggesting_rule: Rule that generated this suggestion + - code_for_constraint: Python code snippet for the constraint + + Returns: + pandas DataFrame with constraint suggestions + + Raises: + ValueError: If no rules have been added + """ + if not self._rules: + raise ValueError( + "At least one constraint rule set must be added. " + "Use .addConstraintRules(Rules.DEFAULT) to add rules." + ) + + # Convert Rules enum to string list + rule_strs = [r.value for r in self._rules] + + suggestions = self._engine.suggest_constraints( + columns=self._restrict_to_columns, + rules=rule_strs, + ) + return self._engine.suggestions_to_dataframe(suggestions) + + # Export all public symbols __all__ = [ "ConstraintSuggestionRunner", "ConstraintSuggestionRunBuilder", + "EngineConstraintSuggestionRunBuilder", "Rules", ] diff --git a/pydeequ/v2/verification.py b/pydeequ/v2/verification.py index c6d8d2f..10e43d0 100644 --- a/pydeequ/v2/verification.py +++ b/pydeequ/v2/verification.py @@ -1,12 +1,31 @@ # -*- coding: utf-8 -*- """ -VerificationSuite for Deequ Spark Connect. +VerificationSuite for PyDeequ v2. -This module provides the main entry point for running data quality checks -via Spark Connect. It builds protobuf messages and sends them to the -server-side Deequ plugin. +This module provides the main entry point for running data quality checks. +It supports two execution modes: -Example usage: +1. Engine-based (DuckDB, etc.) - uses pydeequ.connect() +2. Spark Connect - uses SparkSession with Deequ plugin + +Example usage with DuckDB: + import duckdb + import pydeequ + from pydeequ.v2.verification import VerificationSuite, AnalysisRunner + from pydeequ.v2.checks import Check, CheckLevel + from pydeequ.v2.predicates import gte, eq + + con = duckdb.connect() + con.execute("CREATE TABLE test AS SELECT 1 as id, 'foo@bar.com' as email") + engine = pydeequ.connect(con, table="test") + + check = (Check(CheckLevel.Error, "Data quality check") + .isComplete("id") + .hasCompleteness("email", gte(0.95))) + + result = VerificationSuite().on_engine(engine).addCheck(check).run() + +Example usage with Spark Connect: from pyspark.sql import SparkSession from pydeequ.v2.verification import VerificationSuite from pydeequ.v2.checks import Check, CheckLevel @@ -28,8 +47,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional +import pandas as pd from google.protobuf import any_pb2 from pydeequ.v2.analyzers import _ConnectAnalyzer @@ -39,18 +59,27 @@ if TYPE_CHECKING: from pyspark.sql import DataFrame, SparkSession + from pydeequ.engines import BaseEngine class VerificationSuite: """ Main entry point for running data quality verification. - VerificationSuite allows you to define checks and analyzers to run - on a DataFrame. When run() is called, the checks and analyzers are - serialized to protobuf and sent to the Spark Connect server where - the Deequ plugin executes them. + VerificationSuite allows you to define checks and analyzers to run. + It supports two execution modes: + + 1. Engine-based: Use on_engine() for DuckDB and other SQL backends + 2. Spark-based: Use onData() for Spark Connect + + Example (Engine-based): + suite = VerificationSuite() + result = (suite + .on_engine(engine) + .addCheck(check) + .run()) - Example: + Example (Spark-based): suite = VerificationSuite(spark) result = (suite .onData(df) @@ -58,27 +87,66 @@ class VerificationSuite: .run()) """ - def __init__(self, spark: "SparkSession"): + def __init__(self, spark: Optional["SparkSession"] = None): """ Create a new VerificationSuite. Args: - spark: SparkSession connected via Spark Connect + spark: Optional SparkSession for Spark Connect mode. + Not required for engine-based execution. """ self._spark = spark def onData(self, df: "DataFrame") -> "VerificationRunBuilder": """ - Specify the DataFrame to run verification on. + Specify the DataFrame to run verification on (Spark mode). Args: df: DataFrame to verify Returns: VerificationRunBuilder for method chaining + + Raises: + ValueError: If SparkSession was not provided in constructor """ + if self._spark is None: + raise ValueError( + "SparkSession required for onData(). " + "Use VerificationSuite(spark).onData(df) or " + "VerificationSuite().on_engine(engine) for engine-based execution." + ) return VerificationRunBuilder(self._spark, df) + def on_engine(self, engine: "BaseEngine") -> "EngineVerificationRunBuilder": + """ + Specify the engine to run verification on (Engine mode). + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + + Returns: + EngineVerificationRunBuilder for method chaining + """ + return EngineVerificationRunBuilder(engine) + + # Alias for consistency with other methods + def on_table(self, table: str) -> "EngineVerificationRunBuilder": + """ + Specify a table name for engine-based verification. + + Note: This method requires an engine to be set first via on_engine(). + For direct table access, use: + pydeequ.connect(con, table="my_table") + + Raises: + ValueError: This method is deprecated + """ + raise ValueError( + "on_table() requires an engine. Use: " + "VerificationSuite().on_engine(pydeequ.connect(con, table='my_table'))" + ) + class VerificationRunBuilder: """ @@ -173,14 +241,89 @@ def run(self) -> "DataFrame": return dataframe_from_plan(plan, self._spark) +class EngineVerificationRunBuilder: + """ + Builder for configuring and executing engine-based verification. + + This class works with DuckDB and other SQL backends via the engine abstraction. + """ + + def __init__(self, engine: "BaseEngine"): + """ + Create a new EngineVerificationRunBuilder. + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + """ + self._engine = engine + self._checks: List[Check] = [] + self._analyzers: List[_ConnectAnalyzer] = [] + + def addCheck(self, check: Check) -> "EngineVerificationRunBuilder": + """ + Add a check to run. + + Args: + check: Check to add + + Returns: + self for method chaining + """ + self._checks.append(check) + return self + + def addAnalyzer(self, analyzer: _ConnectAnalyzer) -> "EngineVerificationRunBuilder": + """ + Add an analyzer to run (in addition to those required by checks). + + Args: + analyzer: Analyzer to add + + Returns: + self for method chaining + """ + self._analyzers.append(analyzer) + return self + + def run(self) -> pd.DataFrame: + """ + Execute the verification and return results as a pandas DataFrame. + + The result DataFrame contains columns: + - check: Check description + - check_level: Error or Warning + - check_status: Success, Warning, or Error + - constraint: Constraint description + - constraint_status: Success or Failure + - constraint_message: Details about failures + + Returns: + pandas DataFrame with verification results + """ + # Run checks via engine + results = self._engine.run_checks(self._checks) + return self._engine.constraints_to_dataframe(results) + + class AnalysisRunner: """ Entry point for running analyzers without checks. Use this when you want to compute metrics without defining - pass/fail constraints. + pass/fail constraints. Supports both engine-based and Spark-based execution. + + Example (Engine-based with DuckDB): + from pydeequ.v2.analyzers import Size, Completeness, Mean + import pydeequ + + engine = pydeequ.connect(con, table="my_table") + result = (AnalysisRunner() + .on_engine(engine) + .addAnalyzer(Size()) + .addAnalyzer(Completeness("email")) + .run()) - Example: + Example (Spark Connect): from pydeequ.v2.analyzers import Size, Completeness, Mean result = (AnalysisRunner(spark) @@ -191,27 +334,61 @@ class AnalysisRunner: .run()) """ - def __init__(self, spark: "SparkSession"): + def __init__(self, spark: Optional["SparkSession"] = None): """ Create a new AnalysisRunner. Args: - spark: SparkSession connected via Spark Connect + spark: Optional SparkSession for Spark Connect mode. + Not required for engine-based execution. """ self._spark = spark def onData(self, df: "DataFrame") -> "AnalysisRunBuilder": """ - Specify the DataFrame to analyze. + Specify the DataFrame to analyze (Spark mode). Args: df: DataFrame to analyze Returns: AnalysisRunBuilder for method chaining + + Raises: + ValueError: If SparkSession was not provided in constructor """ + if self._spark is None: + raise ValueError( + "SparkSession required for onData(). " + "Use AnalysisRunner(spark).onData(df) or " + "AnalysisRunner().on_engine(engine) for engine-based execution." + ) return AnalysisRunBuilder(self._spark, df) + def on_engine(self, engine: "BaseEngine") -> "EngineAnalysisRunBuilder": + """ + Specify the engine to run analysis on (Engine mode). + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + + Returns: + EngineAnalysisRunBuilder for method chaining + """ + return EngineAnalysisRunBuilder(engine) + + def on_table(self, table: str) -> "EngineAnalysisRunBuilder": + """ + Specify a table name for engine-based analysis. + + Raises: + ValueError: This method requires an engine + """ + raise ValueError( + "on_table() requires an engine. Use: " + "AnalysisRunner().on_engine(pydeequ.connect(con, table='my_table'))" + ) + class AnalysisRunBuilder: """Builder for configuring and executing an analysis run.""" @@ -270,10 +447,49 @@ def run(self) -> "DataFrame": return dataframe_from_plan(plan, self._spark) +class EngineAnalysisRunBuilder: + """Builder for configuring and executing engine-based analysis.""" + + def __init__(self, engine: "BaseEngine"): + """ + Create a new EngineAnalysisRunBuilder. + + Args: + engine: BaseEngine instance (e.g., DuckDBEngine) + """ + self._engine = engine + self._analyzers: List[_ConnectAnalyzer] = [] + + def addAnalyzer(self, analyzer: _ConnectAnalyzer) -> "EngineAnalysisRunBuilder": + """ + Add an analyzer to run. + + Args: + analyzer: Analyzer to add + + Returns: + self for method chaining + """ + self._analyzers.append(analyzer) + return self + + def run(self) -> pd.DataFrame: + """ + Execute the analysis and return metrics as pandas DataFrame. + + Returns: + pandas DataFrame with computed metrics + """ + results = self._engine.compute_metrics(self._analyzers) + return self._engine.metrics_to_dataframe(results) + + # Export all public symbols __all__ = [ "VerificationSuite", "VerificationRunBuilder", + "EngineVerificationRunBuilder", "AnalysisRunner", "AnalysisRunBuilder", + "EngineAnalysisRunBuilder", ] diff --git a/pyproject.toml b/pyproject.toml index 8168444..9663d50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ coverage = "^7.4.0" black = "^24.0.0" pre-commit = "^3.6.0" pytest-rerunfailures = "^14.0" +matplotlib = "^3.8.0" [tool.poetry.extras] diff --git a/tests/engines/__init__.py b/tests/engines/__init__.py new file mode 100644 index 0000000..2d41719 --- /dev/null +++ b/tests/engines/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Engine correctness testing module. + +This module contains tests for validating the DuckDB engine implementation +and comparing it against the Spark engine baseline. +""" diff --git a/tests/engines/comparison/__init__.py b/tests/engines/comparison/__init__.py new file mode 100644 index 0000000..6b039c7 --- /dev/null +++ b/tests/engines/comparison/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-engine comparison tests. + +Contains tests that compare DuckDB engine results against Spark engine +to validate correctness and functional parity. +""" diff --git a/tests/engines/comparison/conftest.py b/tests/engines/comparison/conftest.py new file mode 100644 index 0000000..faeb701 --- /dev/null +++ b/tests/engines/comparison/conftest.py @@ -0,0 +1,241 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Dual-engine test fixtures for cross-engine comparison. + +Provides fixtures for creating both Spark and DuckDB engines with +identical data for parity testing. These tests require Spark Connect +to be running and SPARK_REMOTE environment variable to be set. +""" + +import os +from dataclasses import dataclass +from typing import Callable, Generator, Optional +import pytest +import duckdb +import pandas as pd + +from pydeequ.engines import BaseEngine +from pydeequ.engines.duckdb import DuckDBEngine +from tests.engines.fixtures.datasets import DATASET_FACTORIES + + +# Check if Spark Connect is available +def spark_available() -> bool: + """Check if Spark Connect is available via SPARK_REMOTE.""" + return os.environ.get("SPARK_REMOTE") is not None + + +# Skip marker for tests requiring Spark +requires_spark = pytest.mark.skipif( + not spark_available(), + reason="Spark Connect not available (set SPARK_REMOTE)" +) + + +@dataclass +class DualEngines: + """Container for both Spark and DuckDB engines with same data.""" + spark_engine: BaseEngine + duckdb_engine: BaseEngine + dataset_name: str + + +@pytest.fixture(scope="module") +def spark_session(): + """Create a module-scoped Spark Connect session. + + Only creates session if SPARK_REMOTE is set. + """ + if not spark_available(): + pytest.skip("Spark Connect not available") + + from pyspark.sql import SparkSession + spark_remote = os.environ.get("SPARK_REMOTE") + spark = SparkSession.builder.remote(spark_remote).getOrCreate() + yield spark + spark.stop() + + +@pytest.fixture(scope="module") +def duckdb_connection() -> Generator[duckdb.DuckDBPyConnection, None, None]: + """Create a module-scoped DuckDB connection.""" + conn = duckdb.connect(":memory:") + yield conn + conn.close() + + +@pytest.fixture(scope="function") +def dual_engine_factory( + spark_session, + duckdb_connection: duckdb.DuckDBPyConnection +) -> Callable[[str], DualEngines]: + """Factory fixture to create both Spark and DuckDB engines with same data. + + Usage: + def test_comparison(dual_engine_factory): + engines = dual_engine_factory("df_full") + spark_metrics = engines.spark_engine.compute_metrics([Size()]) + duckdb_metrics = engines.duckdb_engine.compute_metrics([Size()]) + assert_metrics_match(spark_metrics, duckdb_metrics) + """ + tables_created = [] + + def factory(dataset_name: str) -> DualEngines: + if dataset_name not in DATASET_FACTORIES: + raise ValueError(f"Unknown dataset: {dataset_name}") + + # Get the pandas DataFrame + pdf = DATASET_FACTORIES[dataset_name]() + table_name = f"test_{dataset_name}" + + # Create DuckDB engine + try: + duckdb_connection.unregister(table_name) + except Exception: + pass + duckdb_connection.register(table_name, pdf) + duckdb_engine = DuckDBEngine(duckdb_connection, table_name) + tables_created.append(table_name) + + # Create Spark engine + from pydeequ.engines.spark import SparkEngine + spark_df = spark_session.createDataFrame(pdf) + spark_engine = SparkEngine(spark_session, dataframe=spark_df) + + return DualEngines( + spark_engine=spark_engine, + duckdb_engine=duckdb_engine, + dataset_name=dataset_name + ) + + yield factory + + # Cleanup DuckDB tables + for table_name in tables_created: + try: + duckdb_connection.unregister(table_name) + except Exception: + pass + + +# Convenience fixtures for common datasets + + +@pytest.fixture(scope="function") +def dual_engines_full(dual_engine_factory) -> DualEngines: + """Dual engines with df_full dataset.""" + return dual_engine_factory("df_full") + + +@pytest.fixture(scope="function") +def dual_engines_missing(dual_engine_factory) -> DualEngines: + """Dual engines with df_missing dataset.""" + return dual_engine_factory("df_missing") + + +@pytest.fixture(scope="function") +def dual_engines_numeric(dual_engine_factory) -> DualEngines: + """Dual engines with df_numeric dataset.""" + return dual_engine_factory("df_numeric") + + +@pytest.fixture(scope="function") +def dual_engines_unique(dual_engine_factory) -> DualEngines: + """Dual engines with df_unique dataset.""" + return dual_engine_factory("df_unique") + + +@pytest.fixture(scope="function") +def dual_engines_distinct(dual_engine_factory) -> DualEngines: + """Dual engines with df_distinct dataset.""" + return dual_engine_factory("df_distinct") + + +@pytest.fixture(scope="function") +def dual_engines_string_lengths(dual_engine_factory) -> DualEngines: + """Dual engines with df_string_lengths dataset.""" + return dual_engine_factory("df_string_lengths") + + +@pytest.fixture(scope="function") +def dual_engines_correlation(dual_engine_factory) -> DualEngines: + """Dual engines with df_correlation dataset.""" + return dual_engine_factory("df_correlation") + + +@pytest.fixture(scope="function") +def dual_engines_entropy(dual_engine_factory) -> DualEngines: + """Dual engines with df_entropy dataset.""" + return dual_engine_factory("df_entropy") + + +@pytest.fixture(scope="function") +def dual_engines_compliance(dual_engine_factory) -> DualEngines: + """Dual engines with df_compliance dataset.""" + return dual_engine_factory("df_compliance") + + +@pytest.fixture(scope="function") +def dual_engines_pattern(dual_engine_factory) -> DualEngines: + """Dual engines with df_pattern dataset.""" + return dual_engine_factory("df_pattern") + + +@pytest.fixture(scope="function") +def dual_engines_quantile(dual_engine_factory) -> DualEngines: + """Dual engines with df_quantile dataset.""" + return dual_engine_factory("df_quantile") + + +@pytest.fixture(scope="function") +def dual_engines_contained_in(dual_engine_factory) -> DualEngines: + """Dual engines with df_contained_in dataset.""" + return dual_engine_factory("df_contained_in") + + +@pytest.fixture(scope="function") +def dual_engines_histogram(dual_engine_factory) -> DualEngines: + """Dual engines with df_histogram dataset.""" + return dual_engine_factory("df_histogram") + + +@pytest.fixture(scope="function") +def dual_engines_mutual_info(dual_engine_factory) -> DualEngines: + """Dual engines with df_mutual_info dataset.""" + return dual_engine_factory("df_mutual_info") + + +@pytest.fixture(scope="function") +def dual_engines_where(dual_engine_factory) -> DualEngines: + """Dual engines with df_where dataset.""" + return dual_engine_factory("df_where") + + +@pytest.fixture(scope="function") +def dual_engines_all_null(dual_engine_factory) -> DualEngines: + """Dual engines with df_all_null dataset.""" + return dual_engine_factory("df_all_null") + + +@pytest.fixture(scope="function") +def dual_engines_single(dual_engine_factory) -> DualEngines: + """Dual engines with df_single dataset.""" + return dual_engine_factory("df_single") + + +@pytest.fixture(scope="function") +def dual_engines_empty(dual_engine_factory) -> DualEngines: + """Dual engines with df_empty dataset.""" + return dual_engine_factory("df_empty") diff --git a/tests/engines/comparison/test_analyzer_parity.py b/tests/engines/comparison/test_analyzer_parity.py new file mode 100644 index 0000000..8db0d2e --- /dev/null +++ b/tests/engines/comparison/test_analyzer_parity.py @@ -0,0 +1,393 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-engine analyzer parity tests. + +Tests that verify DuckDB engine produces the same analyzer results +as the Spark engine baseline. Requires Spark Connect to be running. +""" + +import pytest + +from pydeequ.v2.analyzers import ( + Size, + Completeness, + Mean, + Sum, + Maximum, + Minimum, + StandardDeviation, + Distinctness, + Uniqueness, + UniqueValueRatio, + CountDistinct, + ApproxCountDistinct, + ApproxQuantile, + Correlation, + MutualInformation, + MaxLength, + MinLength, + PatternMatch, + Compliance, + Entropy, + Histogram, + DataType, +) + +from tests.engines.comparison.conftest import requires_spark, DualEngines +from tests.engines.comparison.utils import assert_metrics_match + + +@requires_spark +class TestSizeAnalyzerParity: + """Parity tests for Size analyzer.""" + + def test_size_basic(self, dual_engines_full: DualEngines): + """Size produces same result on both engines.""" + analyzers = [Size()] + spark_metrics = dual_engines_full.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_full.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Size basic") + + def test_size_with_nulls(self, dual_engines_missing: DualEngines): + """Size counts all rows regardless of NULLs on both engines.""" + analyzers = [Size()] + spark_metrics = dual_engines_missing.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_missing.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Size with nulls") + + +@requires_spark +class TestCompletenessAnalyzerParity: + """Parity tests for Completeness analyzer.""" + + def test_completeness_full(self, dual_engines_full: DualEngines): + """Completeness produces same result for complete columns.""" + analyzers = [Completeness("att1"), Completeness("att2")] + spark_metrics = dual_engines_full.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_full.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Completeness full") + + def test_completeness_partial(self, dual_engines_missing: DualEngines): + """Completeness produces same result for partial columns.""" + analyzers = [Completeness("att1"), Completeness("att2")] + spark_metrics = dual_engines_missing.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_missing.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Completeness partial") + + def test_completeness_all_null(self, dual_engines_all_null: DualEngines): + """Completeness produces same result for all-NULL columns.""" + analyzers = [Completeness("value")] + spark_metrics = dual_engines_all_null.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_all_null.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Completeness all null") + + +@requires_spark +class TestStatisticalAnalyzerParity: + """Parity tests for statistical analyzers.""" + + def test_mean(self, dual_engines_numeric: DualEngines): + """Mean produces same result on both engines.""" + analyzers = [Mean("att1"), Mean("att2")] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Mean") + + def test_sum(self, dual_engines_numeric: DualEngines): + """Sum produces same result on both engines.""" + analyzers = [Sum("att1"), Sum("att2")] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Sum") + + def test_minimum(self, dual_engines_numeric: DualEngines): + """Minimum produces same result on both engines.""" + analyzers = [Minimum("att1"), Minimum("att2")] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Minimum") + + def test_maximum(self, dual_engines_numeric: DualEngines): + """Maximum produces same result on both engines.""" + analyzers = [Maximum("att1"), Maximum("att2")] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Maximum") + + def test_standard_deviation(self, dual_engines_numeric: DualEngines): + """StandardDeviation produces same result on both engines.""" + analyzers = [StandardDeviation("att1")] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "StandardDeviation") + + +@requires_spark +class TestUniquenessAnalyzerParity: + """Parity tests for uniqueness-related analyzers.""" + + def test_distinctness(self, dual_engines_distinct: DualEngines): + """Distinctness produces same result on both engines.""" + analyzers = [Distinctness(["att1"]), Distinctness(["att2"])] + spark_metrics = dual_engines_distinct.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_distinct.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Distinctness") + + def test_uniqueness(self, dual_engines_distinct: DualEngines): + """Uniqueness produces same result on both engines.""" + analyzers = [Uniqueness(["att1"]), Uniqueness(["att2"])] + spark_metrics = dual_engines_distinct.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_distinct.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Uniqueness") + + def test_unique_value_ratio(self, dual_engines_distinct: DualEngines): + """UniqueValueRatio produces same result on both engines.""" + analyzers = [UniqueValueRatio(["att1"]), UniqueValueRatio(["att2"])] + spark_metrics = dual_engines_distinct.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_distinct.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "UniqueValueRatio") + + def test_count_distinct(self, dual_engines_distinct: DualEngines): + """CountDistinct produces same result on both engines.""" + analyzers = [CountDistinct(["att1"]), CountDistinct(["att2"])] + spark_metrics = dual_engines_distinct.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_distinct.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "CountDistinct") + + def test_approx_count_distinct(self, dual_engines_distinct: DualEngines): + """ApproxCountDistinct produces approximately same result.""" + analyzers = [ApproxCountDistinct("att1"), ApproxCountDistinct("att2")] + spark_metrics = dual_engines_distinct.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_distinct.duckdb_engine.compute_metrics(analyzers) + # Uses APPROX_TOLERANCE (10%) for approximate algorithms + assert_metrics_match(spark_metrics, duckdb_metrics, "ApproxCountDistinct") + + +@requires_spark +class TestStringAnalyzerParity: + """Parity tests for string analyzers.""" + + def test_min_length(self, dual_engines_string_lengths: DualEngines): + """MinLength produces same result on both engines.""" + analyzers = [MinLength("att1"), MinLength("att2")] + spark_metrics = dual_engines_string_lengths.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_string_lengths.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "MinLength") + + def test_max_length(self, dual_engines_string_lengths: DualEngines): + """MaxLength produces same result on both engines.""" + analyzers = [MaxLength("att1"), MaxLength("att2")] + spark_metrics = dual_engines_string_lengths.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_string_lengths.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "MaxLength") + + def test_pattern_match(self, dual_engines_pattern: DualEngines): + """PatternMatch produces same result on both engines.""" + analyzers = [PatternMatch("email", r".*@.*\..*")] + spark_metrics = dual_engines_pattern.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_pattern.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "PatternMatch") + + +@requires_spark +class TestCorrelationAnalyzerParity: + """Parity tests for Correlation analyzer.""" + + def test_correlation_positive(self, dual_engines_correlation: DualEngines): + """Correlation produces same result for positively correlated columns.""" + analyzers = [Correlation("x", "y")] + spark_metrics = dual_engines_correlation.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_correlation.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Correlation positive") + + def test_correlation_negative(self, dual_engines_correlation: DualEngines): + """Correlation produces same result for negatively correlated columns.""" + analyzers = [Correlation("x", "z")] + spark_metrics = dual_engines_correlation.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_correlation.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Correlation negative") + + +@requires_spark +class TestEntropyAnalyzerParity: + """Parity tests for Entropy analyzer.""" + + def test_entropy_uniform(self, dual_engines_entropy: DualEngines): + """Entropy produces same result for uniform distribution.""" + analyzers = [Entropy("uniform")] + spark_metrics = dual_engines_entropy.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_entropy.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Entropy uniform") + + def test_entropy_constant(self, dual_engines_entropy: DualEngines): + """Entropy produces same result for constant column.""" + analyzers = [Entropy("constant")] + spark_metrics = dual_engines_entropy.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_entropy.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Entropy constant") + + +@requires_spark +class TestMutualInformationAnalyzerParity: + """Parity tests for MutualInformation analyzer.""" + + def test_mutual_information(self, dual_engines_mutual_info: DualEngines): + """MutualInformation produces same result on both engines.""" + analyzers = [MutualInformation(["x", "y_dependent"])] + spark_metrics = dual_engines_mutual_info.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_mutual_info.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "MutualInformation") + + +@requires_spark +class TestComplianceAnalyzerParity: + """Parity tests for Compliance analyzer.""" + + def test_compliance(self, dual_engines_compliance: DualEngines): + """Compliance produces same result on both engines.""" + analyzers = [ + Compliance("positive_check", "positive > 0"), + Compliance("mixed_check", "mixed > 0"), + ] + spark_metrics = dual_engines_compliance.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_compliance.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Compliance") + + +@requires_spark +class TestQuantileAnalyzerParity: + """Parity tests for ApproxQuantile analyzer.""" + + def test_approx_quantile_median(self, dual_engines_quantile: DualEngines): + """ApproxQuantile produces same result for median.""" + analyzers = [ApproxQuantile("value", 0.5)] + spark_metrics = dual_engines_quantile.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_quantile.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "ApproxQuantile median") + + def test_approx_quantile_quartiles(self, dual_engines_quantile: DualEngines): + """ApproxQuantile produces same result for quartiles.""" + analyzers = [ + ApproxQuantile("value", 0.25), + ApproxQuantile("value", 0.75), + ] + spark_metrics = dual_engines_quantile.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_quantile.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "ApproxQuantile quartiles") + + +@requires_spark +class TestHistogramAnalyzerParity: + """Parity tests for Histogram analyzer.""" + + def test_histogram(self, dual_engines_histogram: DualEngines): + """Histogram produces consistent results on both engines.""" + analyzers = [Histogram("category")] + spark_metrics = dual_engines_histogram.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_histogram.duckdb_engine.compute_metrics(analyzers) + # Histogram structure may differ, so just check it exists + assert len(spark_metrics) > 0 + assert len(duckdb_metrics) > 0 + + +@requires_spark +class TestDataTypeAnalyzerParity: + """Parity tests for DataType analyzer.""" + + def test_data_type(self, dual_engines_full: DualEngines): + """DataType produces consistent results on both engines.""" + analyzers = [DataType("att1")] + spark_metrics = dual_engines_full.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_full.duckdb_engine.compute_metrics(analyzers) + # DataType format may differ, so just check it exists + assert len(spark_metrics) > 0 + assert len(duckdb_metrics) > 0 + + +@requires_spark +class TestAnalyzersWithWhereParity: + """Parity tests for analyzers with WHERE clause.""" + + def test_size_with_where(self, dual_engines_where: DualEngines): + """Size with WHERE produces same result on both engines.""" + analyzers = [Size(where="category = 'A'")] + spark_metrics = dual_engines_where.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_where.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Size with WHERE") + + def test_completeness_with_where(self, dual_engines_where: DualEngines): + """Completeness with WHERE produces same result on both engines.""" + analyzers = [Completeness("att1", where="category = 'A'")] + spark_metrics = dual_engines_where.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_where.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Completeness with WHERE") + + def test_mean_with_where(self, dual_engines_where: DualEngines): + """Mean with WHERE produces same result on both engines.""" + analyzers = [Mean("value", where="category = 'A'")] + spark_metrics = dual_engines_where.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_where.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Mean with WHERE") + + +@requires_spark +class TestMultipleAnalyzersParity: + """Parity tests for running multiple analyzers together.""" + + def test_all_basic_analyzers(self, dual_engines_numeric: DualEngines): + """All basic analyzers produce same results on both engines.""" + analyzers = [ + Size(), + Completeness("att1"), + Mean("att1"), + Sum("att1"), + Minimum("att1"), + Maximum("att1"), + StandardDeviation("att1"), + ] + spark_metrics = dual_engines_numeric.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_numeric.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "All basic analyzers") + + def test_mixed_analyzer_types(self, dual_engines_full: DualEngines): + """Mixed analyzer types produce same results on both engines.""" + analyzers = [ + Size(), + Completeness("att1"), + CountDistinct(["att1"]), + MaxLength("att1"), + MinLength("att1"), + ] + spark_metrics = dual_engines_full.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_full.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Mixed analyzer types") + + +@requires_spark +class TestEdgeCasesParity: + """Parity tests for edge cases.""" + + def test_single_row(self, dual_engines_single: DualEngines): + """Analyzers produce same results for single-row dataset.""" + analyzers = [ + Size(), + Completeness("att1"), + Mean("item"), + Maximum("item"), + Minimum("item"), + ] + spark_metrics = dual_engines_single.spark_engine.compute_metrics(analyzers) + duckdb_metrics = dual_engines_single.duckdb_engine.compute_metrics(analyzers) + assert_metrics_match(spark_metrics, duckdb_metrics, "Single row") diff --git a/tests/engines/comparison/test_constraint_parity.py b/tests/engines/comparison/test_constraint_parity.py new file mode 100644 index 0000000..0e32232 --- /dev/null +++ b/tests/engines/comparison/test_constraint_parity.py @@ -0,0 +1,334 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-engine constraint parity tests. + +Tests that verify DuckDB engine produces the same constraint evaluation +results as the Spark engine baseline. Requires Spark Connect to be running. +""" + +import pytest + +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.predicates import eq, gt, gte, lt, lte, between, is_one + +from tests.engines.comparison.conftest import requires_spark, DualEngines +from tests.engines.comparison.utils import assert_constraints_match + + +@requires_spark +class TestSizeConstraintParity: + """Parity tests for size constraints.""" + + def test_has_size_success(self, dual_engines_full: DualEngines): + """hasSize produces same result on both engines when passing.""" + check = Check(CheckLevel.Error, "size check").hasSize(eq(4)) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasSize success") + + def test_has_size_failure(self, dual_engines_full: DualEngines): + """hasSize produces same result on both engines when failing.""" + check = Check(CheckLevel.Error, "size check").hasSize(eq(100)) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasSize failure") + + +@requires_spark +class TestCompletenessConstraintParity: + """Parity tests for completeness constraints.""" + + def test_is_complete_success(self, dual_engines_full: DualEngines): + """isComplete produces same result on both engines when passing.""" + check = Check(CheckLevel.Error, "complete").isComplete("att1") + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isComplete success") + + def test_is_complete_failure(self, dual_engines_missing: DualEngines): + """isComplete produces same result on both engines when failing.""" + check = Check(CheckLevel.Error, "complete").isComplete("att1") + spark_results = dual_engines_missing.spark_engine.run_checks([check]) + duckdb_results = dual_engines_missing.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isComplete failure") + + def test_has_completeness(self, dual_engines_missing: DualEngines): + """hasCompleteness produces same result on both engines.""" + check = Check(CheckLevel.Error, "threshold").hasCompleteness("att1", gte(0.5)) + spark_results = dual_engines_missing.spark_engine.run_checks([check]) + duckdb_results = dual_engines_missing.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasCompleteness") + + def test_are_complete(self, dual_engines_full: DualEngines): + """areComplete produces same result on both engines.""" + check = Check(CheckLevel.Error, "multi").areComplete(["att1", "att2"]) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "areComplete") + + +@requires_spark +class TestUniquenessConstraintParity: + """Parity tests for uniqueness constraints.""" + + def test_is_unique_success(self, dual_engines_unique: DualEngines): + """isUnique produces same result on both engines when passing.""" + check = Check(CheckLevel.Error, "unique").isUnique("unique_col") + spark_results = dual_engines_unique.spark_engine.run_checks([check]) + duckdb_results = dual_engines_unique.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isUnique success") + + def test_is_unique_failure(self, dual_engines_unique: DualEngines): + """isUnique produces same result on both engines when failing.""" + check = Check(CheckLevel.Error, "not unique").isUnique("non_unique") + spark_results = dual_engines_unique.spark_engine.run_checks([check]) + duckdb_results = dual_engines_unique.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isUnique failure") + + def test_has_uniqueness(self, dual_engines_distinct: DualEngines): + """hasUniqueness produces same result on both engines.""" + check = Check(CheckLevel.Error, "uniqueness").hasUniqueness(["att2"], is_one()) + spark_results = dual_engines_distinct.spark_engine.run_checks([check]) + duckdb_results = dual_engines_distinct.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasUniqueness") + + def test_has_distinctness(self, dual_engines_distinct: DualEngines): + """hasDistinctness produces same result on both engines.""" + check = Check(CheckLevel.Error, "distinct").hasDistinctness(["att1"], gte(0.5)) + spark_results = dual_engines_distinct.spark_engine.run_checks([check]) + duckdb_results = dual_engines_distinct.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasDistinctness") + + +@requires_spark +class TestStatisticalConstraintParity: + """Parity tests for statistical constraints.""" + + def test_has_min(self, dual_engines_numeric: DualEngines): + """hasMin produces same result on both engines.""" + check = Check(CheckLevel.Error, "min").hasMin("att1", eq(1)) + spark_results = dual_engines_numeric.spark_engine.run_checks([check]) + duckdb_results = dual_engines_numeric.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasMin") + + def test_has_max(self, dual_engines_numeric: DualEngines): + """hasMax produces same result on both engines.""" + check = Check(CheckLevel.Error, "max").hasMax("att1", eq(6)) + spark_results = dual_engines_numeric.spark_engine.run_checks([check]) + duckdb_results = dual_engines_numeric.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasMax") + + def test_has_mean(self, dual_engines_numeric: DualEngines): + """hasMean produces same result on both engines.""" + check = Check(CheckLevel.Error, "mean").hasMean("att1", eq(3.5)) + spark_results = dual_engines_numeric.spark_engine.run_checks([check]) + duckdb_results = dual_engines_numeric.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasMean") + + def test_has_sum(self, dual_engines_numeric: DualEngines): + """hasSum produces same result on both engines.""" + check = Check(CheckLevel.Error, "sum").hasSum("att1", eq(21)) + spark_results = dual_engines_numeric.spark_engine.run_checks([check]) + duckdb_results = dual_engines_numeric.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasSum") + + def test_has_standard_deviation(self, dual_engines_numeric: DualEngines): + """hasStandardDeviation produces same result on both engines.""" + check = Check(CheckLevel.Error, "stddev").hasStandardDeviation("att1", between(1.5, 2.0)) + spark_results = dual_engines_numeric.spark_engine.run_checks([check]) + duckdb_results = dual_engines_numeric.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasStandardDeviation") + + +@requires_spark +class TestCorrelationConstraintParity: + """Parity tests for correlation constraints.""" + + def test_has_correlation(self, dual_engines_correlation: DualEngines): + """hasCorrelation produces same result on both engines.""" + check = Check(CheckLevel.Error, "corr").hasCorrelation("x", "y", is_one()) + spark_results = dual_engines_correlation.spark_engine.run_checks([check]) + duckdb_results = dual_engines_correlation.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasCorrelation") + + +@requires_spark +class TestEntropyConstraintParity: + """Parity tests for entropy constraints.""" + + def test_has_entropy(self, dual_engines_entropy: DualEngines): + """hasEntropy produces same result on both engines.""" + check = Check(CheckLevel.Error, "entropy").hasEntropy("uniform", eq(2.0)) + spark_results = dual_engines_entropy.spark_engine.run_checks([check]) + duckdb_results = dual_engines_entropy.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasEntropy") + + +@requires_spark +class TestStringConstraintParity: + """Parity tests for string constraints.""" + + def test_has_min_length(self, dual_engines_string_lengths: DualEngines): + """hasMinLength produces same result on both engines.""" + check = Check(CheckLevel.Error, "min len").hasMinLength("att1", eq(0)) + spark_results = dual_engines_string_lengths.spark_engine.run_checks([check]) + duckdb_results = dual_engines_string_lengths.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasMinLength") + + def test_has_max_length(self, dual_engines_string_lengths: DualEngines): + """hasMaxLength produces same result on both engines.""" + check = Check(CheckLevel.Error, "max len").hasMaxLength("att1", lte(5)) + spark_results = dual_engines_string_lengths.spark_engine.run_checks([check]) + duckdb_results = dual_engines_string_lengths.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasMaxLength") + + def test_has_pattern(self, dual_engines_pattern: DualEngines): + """hasPattern produces same result on both engines.""" + check = Check(CheckLevel.Error, "pattern").hasPattern("email", r".*@.*\..*", gte(0.5)) + spark_results = dual_engines_pattern.spark_engine.run_checks([check]) + duckdb_results = dual_engines_pattern.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "hasPattern") + + +@requires_spark +class TestNumericConstraintParity: + """Parity tests for numeric value constraints.""" + + def test_is_positive(self, dual_engines_compliance: DualEngines): + """isPositive produces same result on both engines.""" + check = Check(CheckLevel.Error, "positive").isPositive("positive", is_one()) + spark_results = dual_engines_compliance.spark_engine.run_checks([check]) + duckdb_results = dual_engines_compliance.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isPositive") + + def test_is_non_negative(self, dual_engines_compliance: DualEngines): + """isNonNegative produces same result on both engines.""" + check = Check(CheckLevel.Error, "non-neg").isNonNegative("positive", is_one()) + spark_results = dual_engines_compliance.spark_engine.run_checks([check]) + duckdb_results = dual_engines_compliance.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isNonNegative") + + +@requires_spark +class TestColumnComparisonConstraintParity: + """Parity tests for column comparison constraints.""" + + def test_is_less_than(self, dual_engines_correlation: DualEngines): + """isLessThan produces same result on both engines.""" + check = Check(CheckLevel.Error, "less").isLessThan("x", "y") + spark_results = dual_engines_correlation.spark_engine.run_checks([check]) + duckdb_results = dual_engines_correlation.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isLessThan") + + def test_is_greater_than(self, dual_engines_correlation: DualEngines): + """isGreaterThan produces same result on both engines.""" + check = Check(CheckLevel.Error, "greater").isGreaterThan("y", "x") + spark_results = dual_engines_correlation.spark_engine.run_checks([check]) + duckdb_results = dual_engines_correlation.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isGreaterThan") + + +@requires_spark +class TestContainedInConstraintParity: + """Parity tests for isContainedIn constraint.""" + + def test_is_contained_in_success(self, dual_engines_contained_in: DualEngines): + """isContainedIn produces same result on both engines when passing.""" + check = Check(CheckLevel.Error, "contained").isContainedIn( + "status", ["active", "inactive", "pending"], is_one() + ) + spark_results = dual_engines_contained_in.spark_engine.run_checks([check]) + duckdb_results = dual_engines_contained_in.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isContainedIn success") + + def test_is_contained_in_failure(self, dual_engines_contained_in: DualEngines): + """isContainedIn produces same result on both engines when failing.""" + check = Check(CheckLevel.Error, "not contained").isContainedIn( + "category", ["A", "B", "C"], is_one() + ) + spark_results = dual_engines_contained_in.spark_engine.run_checks([check]) + duckdb_results = dual_engines_contained_in.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "isContainedIn failure") + + +@requires_spark +class TestSatisfiesConstraintParity: + """Parity tests for satisfies constraint.""" + + def test_satisfies(self, dual_engines_compliance: DualEngines): + """satisfies produces same result on both engines.""" + check = Check(CheckLevel.Error, "satisfies").satisfies( + "positive > 0", "positive_check", assertion=is_one() + ) + spark_results = dual_engines_compliance.spark_engine.run_checks([check]) + duckdb_results = dual_engines_compliance.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "satisfies") + + +@requires_spark +class TestMultipleConstraintsParity: + """Parity tests for multiple constraints.""" + + def test_multiple_constraints_all_pass(self, dual_engines_full: DualEngines): + """Multiple passing constraints produce same results.""" + check = (Check(CheckLevel.Error, "multi pass") + .hasSize(eq(4)) + .isComplete("att1") + .isComplete("att2")) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "Multiple pass") + + def test_multiple_constraints_some_fail(self, dual_engines_missing: DualEngines): + """Mixed pass/fail constraints produce same results.""" + check = (Check(CheckLevel.Error, "multi mixed") + .hasSize(eq(12)) # Pass + .isComplete("att1")) # Fail + spark_results = dual_engines_missing.spark_engine.run_checks([check]) + duckdb_results = dual_engines_missing.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "Multiple mixed") + + +@requires_spark +class TestCheckLevelsParity: + """Parity tests for check levels.""" + + def test_error_level(self, dual_engines_full: DualEngines): + """Error level produces same results on both engines.""" + check = Check(CheckLevel.Error, "error").hasSize(eq(100)) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "Error level") + + def test_warning_level(self, dual_engines_full: DualEngines): + """Warning level produces same results on both engines.""" + check = Check(CheckLevel.Warning, "warning").hasSize(eq(100)) + spark_results = dual_engines_full.spark_engine.run_checks([check]) + duckdb_results = dual_engines_full.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "Warning level") + + +@requires_spark +class TestConstraintsWithWhereParity: + """Parity tests for constraints with WHERE clause.""" + + def test_completeness_with_where(self, dual_engines_where: DualEngines): + """Completeness with WHERE produces same result on both engines.""" + check = Check(CheckLevel.Error, "filtered").hasCompleteness( + "att1", is_one() + ).where("category = 'A'") + spark_results = dual_engines_where.spark_engine.run_checks([check]) + duckdb_results = dual_engines_where.duckdb_engine.run_checks([check]) + assert_constraints_match(spark_results, duckdb_results, "Completeness with WHERE") diff --git a/tests/engines/comparison/test_profile_parity.py b/tests/engines/comparison/test_profile_parity.py new file mode 100644 index 0000000..c980d34 --- /dev/null +++ b/tests/engines/comparison/test_profile_parity.py @@ -0,0 +1,142 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-engine column profiling parity tests. + +Tests that verify DuckDB engine produces the same column profiling +results as the Spark engine baseline. Requires Spark Connect to be running. +""" + +import pytest + +from tests.engines.comparison.conftest import requires_spark, DualEngines +from tests.engines.comparison.utils import assert_profiles_match + + +def get_profile_by_column(profiles, column_name: str): + """Find a column profile by column name.""" + for p in profiles: + if p.column == column_name: + return p + return None + + +@requires_spark +class TestBasicProfilingParity: + """Parity tests for basic profiling functionality.""" + + def test_profile_all_columns(self, dual_engines_full: DualEngines): + """Profile all columns produces same results on both engines.""" + spark_profiles = dual_engines_full.spark_engine.profile_columns() + duckdb_profiles = dual_engines_full.duckdb_engine.profile_columns() + assert_profiles_match(spark_profiles, duckdb_profiles, "All columns") + + def test_profile_specific_columns(self, dual_engines_full: DualEngines): + """Profile specific columns produces same results.""" + spark_profiles = dual_engines_full.spark_engine.profile_columns(columns=["att1", "item"]) + duckdb_profiles = dual_engines_full.duckdb_engine.profile_columns(columns=["att1", "item"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Specific columns") + + +@requires_spark +class TestCompletenessProfilingParity: + """Parity tests for completeness in profiles.""" + + def test_completeness_full(self, dual_engines_full: DualEngines): + """Completeness is same for complete columns on both engines.""" + spark_profiles = dual_engines_full.spark_engine.profile_columns(columns=["att1"]) + duckdb_profiles = dual_engines_full.duckdb_engine.profile_columns(columns=["att1"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Completeness full") + + def test_completeness_partial(self, dual_engines_missing: DualEngines): + """Completeness is same for partial columns on both engines.""" + spark_profiles = dual_engines_missing.spark_engine.profile_columns(columns=["att1", "att2"]) + duckdb_profiles = dual_engines_missing.duckdb_engine.profile_columns(columns=["att1", "att2"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Completeness partial") + + def test_completeness_all_null(self, dual_engines_all_null: DualEngines): + """Completeness is same for all-NULL column on both engines.""" + spark_profiles = dual_engines_all_null.spark_engine.profile_columns(columns=["value"]) + duckdb_profiles = dual_engines_all_null.duckdb_engine.profile_columns(columns=["value"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Completeness all null") + + +@requires_spark +class TestDistinctValuesProfilingParity: + """Parity tests for distinct values in profiles.""" + + def test_distinct_values(self, dual_engines_distinct: DualEngines): + """Distinct value counts are same on both engines.""" + spark_profiles = dual_engines_distinct.spark_engine.profile_columns(columns=["att1", "att2"]) + duckdb_profiles = dual_engines_distinct.duckdb_engine.profile_columns(columns=["att1", "att2"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Distinct values") + + +@requires_spark +class TestNumericProfilingParity: + """Parity tests for numeric column profiling.""" + + def test_numeric_statistics(self, dual_engines_numeric: DualEngines): + """Numeric statistics are same on both engines.""" + spark_profiles = dual_engines_numeric.spark_engine.profile_columns(columns=["att1"]) + duckdb_profiles = dual_engines_numeric.duckdb_engine.profile_columns(columns=["att1"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Numeric statistics") + + def test_numeric_with_nulls(self, dual_engines_numeric: DualEngines): + """Numeric statistics handle NULLs same way on both engines.""" + spark_profiles = dual_engines_numeric.spark_engine.profile_columns(columns=["att2"]) + duckdb_profiles = dual_engines_numeric.duckdb_engine.profile_columns(columns=["att2"]) + assert_profiles_match(spark_profiles, duckdb_profiles, "Numeric with nulls") + + +@requires_spark +class TestHistogramProfilingParity: + """Parity tests for histogram profiling.""" + + def test_histogram(self, dual_engines_histogram: DualEngines): + """Histogram profiling produces consistent results.""" + spark_profiles = dual_engines_histogram.spark_engine.profile_columns( + columns=["category"], + low_cardinality_threshold=10 + ) + duckdb_profiles = dual_engines_histogram.duckdb_engine.profile_columns( + columns=["category"], + low_cardinality_threshold=10 + ) + # Check profiles exist and have histogram data + # (exact histogram format may differ) + assert len(spark_profiles) > 0 + assert len(duckdb_profiles) > 0 + + +@requires_spark +class TestEdgeCaseProfilingParity: + """Parity tests for edge cases in profiling.""" + + def test_single_row(self, dual_engines_single: DualEngines): + """Single-row profiling produces same results.""" + spark_profiles = dual_engines_single.spark_engine.profile_columns() + duckdb_profiles = dual_engines_single.duckdb_engine.profile_columns() + assert_profiles_match(spark_profiles, duckdb_profiles, "Single row") + + +@requires_spark +class TestMixedTypeProfilingParity: + """Parity tests for mixed column types.""" + + def test_mixed_types(self, dual_engines_full: DualEngines): + """Mixed column types produce same results on both engines.""" + spark_profiles = dual_engines_full.spark_engine.profile_columns() + duckdb_profiles = dual_engines_full.duckdb_engine.profile_columns() + assert_profiles_match(spark_profiles, duckdb_profiles, "Mixed types") diff --git a/tests/engines/comparison/test_suggestion_parity.py b/tests/engines/comparison/test_suggestion_parity.py new file mode 100644 index 0000000..510e17d --- /dev/null +++ b/tests/engines/comparison/test_suggestion_parity.py @@ -0,0 +1,222 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-engine constraint suggestion parity tests. + +Tests that verify DuckDB engine produces the same constraint suggestions +as the Spark engine baseline. Requires Spark Connect to be running. + +Note: Suggestions may differ between engines due to different profiling +algorithms. These tests focus on structural consistency rather than +exact match. +""" + +import pytest + +from pydeequ.v2.suggestions import Rules + +from tests.engines.comparison.conftest import requires_spark, DualEngines + + +def get_suggestions_for_column(suggestions, column_name: str): + """Get all suggestions for a specific column.""" + return [s for s in suggestions if s.column_name == column_name] + + +def get_suggestions_by_constraint(suggestions, constraint_name: str): + """Get all suggestions matching a constraint type.""" + return [s for s in suggestions if constraint_name in s.constraint_name] + + +@requires_spark +class TestSuggestionStructureParity: + """Parity tests for suggestion structure consistency.""" + + def test_default_rules_structure(self, dual_engines_full: DualEngines): + """DEFAULT rules produce structurally similar suggestions.""" + spark_suggestions = dual_engines_full.spark_engine.suggest_constraints(rules=[Rules.DEFAULT]) + duckdb_suggestions = dual_engines_full.duckdb_engine.suggest_constraints(rules=[Rules.DEFAULT]) + + # Both should return a list of suggestions + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + # Both should have required fields + if spark_suggestions: + s = spark_suggestions[0] + assert hasattr(s, 'column_name') + assert hasattr(s, 'constraint_name') + assert hasattr(s, 'description') + + if duckdb_suggestions: + s = duckdb_suggestions[0] + assert hasattr(s, 'column_name') + assert hasattr(s, 'constraint_name') + assert hasattr(s, 'description') + + def test_numerical_rules_structure(self, dual_engines_numeric: DualEngines): + """NUMERICAL rules produce structurally similar suggestions.""" + spark_suggestions = dual_engines_numeric.spark_engine.suggest_constraints(rules=[Rules.NUMERICAL]) + duckdb_suggestions = dual_engines_numeric.duckdb_engine.suggest_constraints(rules=[Rules.NUMERICAL]) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + def test_string_rules_structure(self, dual_engines_string_lengths: DualEngines): + """STRING rules produce structurally similar suggestions.""" + spark_suggestions = dual_engines_string_lengths.spark_engine.suggest_constraints(rules=[Rules.STRING]) + duckdb_suggestions = dual_engines_string_lengths.duckdb_engine.suggest_constraints(rules=[Rules.STRING]) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + +@requires_spark +class TestSuggestionColumnCoverage: + """Parity tests for column coverage in suggestions.""" + + def test_complete_column_suggestions(self, dual_engines_full: DualEngines): + """Both engines suggest constraints for complete columns.""" + spark_suggestions = dual_engines_full.spark_engine.suggest_constraints( + columns=["att1"], + rules=[Rules.DEFAULT] + ) + duckdb_suggestions = dual_engines_full.duckdb_engine.suggest_constraints( + columns=["att1"], + rules=[Rules.DEFAULT] + ) + + # Both should return results (may differ in content) + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + def test_numeric_column_suggestions(self, dual_engines_numeric: DualEngines): + """Both engines suggest constraints for numeric columns.""" + spark_suggestions = dual_engines_numeric.spark_engine.suggest_constraints( + columns=["att1"], + rules=[Rules.NUMERICAL] + ) + duckdb_suggestions = dual_engines_numeric.duckdb_engine.suggest_constraints( + columns=["att1"], + rules=[Rules.NUMERICAL] + ) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + +@requires_spark +class TestSuggestionConstraintTypes: + """Parity tests for suggested constraint types.""" + + def test_completeness_suggestions(self, dual_engines_full: DualEngines): + """Both engines may suggest completeness constraints.""" + spark_suggestions = dual_engines_full.spark_engine.suggest_constraints(rules=[Rules.DEFAULT]) + duckdb_suggestions = dual_engines_full.duckdb_engine.suggest_constraints(rules=[Rules.DEFAULT]) + + spark_completeness = get_suggestions_by_constraint(spark_suggestions, "Complete") + duckdb_completeness = get_suggestions_by_constraint(duckdb_suggestions, "Complete") + + # Both might suggest completeness (or not - depends on data) + # Just verify structure is consistent + assert isinstance(spark_completeness, list) + assert isinstance(duckdb_completeness, list) + + def test_uniqueness_suggestions(self, dual_engines_unique: DualEngines): + """Both engines may suggest uniqueness constraints.""" + spark_suggestions = dual_engines_unique.spark_engine.suggest_constraints(rules=[Rules.COMMON]) + duckdb_suggestions = dual_engines_unique.duckdb_engine.suggest_constraints(rules=[Rules.COMMON]) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + +@requires_spark +class TestSuggestionRuleSetsParity: + """Parity tests for different rule sets.""" + + def test_extended_rules(self, dual_engines_full: DualEngines): + """EXTENDED rules produce consistent suggestions on both engines.""" + spark_suggestions = dual_engines_full.spark_engine.suggest_constraints(rules=[Rules.EXTENDED]) + duckdb_suggestions = dual_engines_full.duckdb_engine.suggest_constraints(rules=[Rules.EXTENDED]) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + def test_multiple_rule_sets(self, dual_engines_numeric: DualEngines): + """Multiple rule sets produce consistent suggestions.""" + spark_suggestions = dual_engines_numeric.spark_engine.suggest_constraints( + rules=[Rules.DEFAULT, Rules.NUMERICAL] + ) + duckdb_suggestions = dual_engines_numeric.duckdb_engine.suggest_constraints( + rules=[Rules.DEFAULT, Rules.NUMERICAL] + ) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + +@requires_spark +class TestSuggestionEdgeCases: + """Parity tests for edge cases in suggestions.""" + + def test_single_row_suggestions(self, dual_engines_single: DualEngines): + """Single-row dataset produces consistent suggestions.""" + spark_suggestions = dual_engines_single.spark_engine.suggest_constraints(rules=[Rules.DEFAULT]) + duckdb_suggestions = dual_engines_single.duckdb_engine.suggest_constraints(rules=[Rules.DEFAULT]) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + def test_all_null_column_suggestions(self, dual_engines_all_null: DualEngines): + """All-NULL column produces consistent suggestions.""" + spark_suggestions = dual_engines_all_null.spark_engine.suggest_constraints( + columns=["value"], + rules=[Rules.DEFAULT] + ) + duckdb_suggestions = dual_engines_all_null.duckdb_engine.suggest_constraints( + columns=["value"], + rules=[Rules.DEFAULT] + ) + + assert isinstance(spark_suggestions, list) + assert isinstance(duckdb_suggestions, list) + + +@requires_spark +class TestSuggestionColumnRestriction: + """Parity tests for column restriction in suggestions.""" + + def test_restrict_to_columns(self, dual_engines_full: DualEngines): + """Column restriction produces consistent suggestions.""" + spark_suggestions = dual_engines_full.spark_engine.suggest_constraints( + columns=["att1", "att2"], + rules=[Rules.DEFAULT] + ) + duckdb_suggestions = dual_engines_full.duckdb_engine.suggest_constraints( + columns=["att1", "att2"], + rules=[Rules.DEFAULT] + ) + + # Check that suggestions are for the restricted columns + spark_columns = {s.column_name for s in spark_suggestions if s.column_name} + duckdb_columns = {s.column_name for s in duckdb_suggestions if s.column_name} + + # Both should only include requested columns (or None for dataset-level) + for col in spark_columns: + if col: + assert col in ["att1", "att2"] + for col in duckdb_columns: + if col: + assert col in ["att1", "att2"] diff --git a/tests/engines/comparison/utils.py b/tests/engines/comparison/utils.py new file mode 100644 index 0000000..e944580 --- /dev/null +++ b/tests/engines/comparison/utils.py @@ -0,0 +1,420 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Comparison utilities for cross-engine testing. + +Provides utilities for comparing results between DuckDB and Spark engines +with appropriate tolerance levels for different metric types. +""" + +import math +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Tuple + +from pydeequ.engines import MetricResult, ConstraintResult, ColumnProfile + + +# Tolerance levels for comparing floating-point results +FLOAT_EPSILON = 1e-9 # Exact comparisons: Size, Completeness, Uniqueness +FLOAT_TOLERANCE = 1e-6 # Statistical: Mean, StdDev, Correlation +APPROX_TOLERANCE = 0.1 # Approximate algorithms: ApproxCountDistinct (10% relative) + + +# Mapping of analyzer types to their expected tolerance +ANALYZER_TOLERANCES: Dict[str, float] = { + # Exact metrics + "Size": FLOAT_EPSILON, + "Completeness": FLOAT_EPSILON, + "Uniqueness": FLOAT_EPSILON, + "Distinctness": FLOAT_EPSILON, + "UniqueValueRatio": FLOAT_EPSILON, + "CountDistinct": FLOAT_EPSILON, + "MinLength": FLOAT_EPSILON, + "MaxLength": FLOAT_EPSILON, + "PatternMatch": FLOAT_EPSILON, + "Compliance": FLOAT_EPSILON, + + # Statistical metrics + "Mean": FLOAT_TOLERANCE, + "Sum": FLOAT_TOLERANCE, + "Minimum": FLOAT_TOLERANCE, + "Maximum": FLOAT_TOLERANCE, + "StandardDeviation": FLOAT_TOLERANCE, + "Correlation": FLOAT_TOLERANCE, + "Entropy": FLOAT_TOLERANCE, + "MutualInformation": FLOAT_TOLERANCE, + "ApproxQuantile": FLOAT_TOLERANCE, + + # Approximate metrics + "ApproxCountDistinct": APPROX_TOLERANCE, +} + + +def get_tolerance(analyzer_name: str) -> float: + """Get the appropriate tolerance for an analyzer type.""" + return ANALYZER_TOLERANCES.get(analyzer_name, FLOAT_TOLERANCE) + + +def values_equal( + actual: Any, + expected: Any, + tolerance: float = FLOAT_TOLERANCE +) -> bool: + """Check if two values are equal within tolerance. + + Handles None, NaN, strings, and numeric values appropriately. + + Args: + actual: The actual value from DuckDB + expected: The expected value from Spark + tolerance: The tolerance for numeric comparison + + Returns: + True if values are considered equal + """ + # Handle None/null + if actual is None and expected is None: + return True + if actual is None or expected is None: + return False + + # Handle NaN + if isinstance(actual, float) and isinstance(expected, float): + if math.isnan(actual) and math.isnan(expected): + return True + if math.isnan(actual) or math.isnan(expected): + return False + + # Handle strings + if isinstance(actual, str) and isinstance(expected, str): + return actual == expected + + # Handle numeric values + try: + actual_float = float(actual) + expected_float = float(expected) + + if tolerance >= APPROX_TOLERANCE: + # Relative tolerance for approximate algorithms + if expected_float == 0: + return abs(actual_float) < tolerance + return abs(actual_float - expected_float) / abs(expected_float) < tolerance + else: + # Absolute tolerance for exact/statistical metrics + return abs(actual_float - expected_float) < tolerance + except (TypeError, ValueError): + # Fall back to exact equality + return actual == expected + + +@dataclass +class MetricDifference: + """Represents a difference between two metric results.""" + name: str + instance: Optional[str] + spark_value: Any + duckdb_value: Any + tolerance: float + is_match: bool + message: str = "" + + +@dataclass +class ComparisonReport: + """Report comparing results from two engines.""" + total_metrics: int = 0 + matching_metrics: int = 0 + differing_metrics: int = 0 + spark_only_metrics: int = 0 + duckdb_only_metrics: int = 0 + differences: List[MetricDifference] = field(default_factory=list) + + @property + def success(self) -> bool: + """True if all metrics match within tolerance.""" + return self.differing_metrics == 0 and self.spark_only_metrics == 0 and self.duckdb_only_metrics == 0 + + def summary(self) -> str: + """Generate a summary string.""" + lines = [ + f"Comparison Report:", + f" Total metrics: {self.total_metrics}", + f" Matching: {self.matching_metrics}", + f" Differing: {self.differing_metrics}", + f" Spark-only: {self.spark_only_metrics}", + f" DuckDB-only: {self.duckdb_only_metrics}", + ] + if self.differences: + lines.append(" Differences:") + for diff in self.differences: + lines.append(f" - {diff.name}({diff.instance}): Spark={diff.spark_value}, DuckDB={diff.duckdb_value}") + return "\n".join(lines) + + +def index_metrics(metrics: List[MetricResult]) -> Dict[Tuple[str, str], MetricResult]: + """Index metrics by (name, instance) tuple for efficient lookup.""" + return {(m.name, m.instance or ""): m for m in metrics} + + +def compare_metrics( + spark_metrics: List[MetricResult], + duckdb_metrics: List[MetricResult] +) -> ComparisonReport: + """Compare metric results from Spark and DuckDB engines. + + Args: + spark_metrics: Metrics computed by Spark engine + duckdb_metrics: Metrics computed by DuckDB engine + + Returns: + ComparisonReport with detailed comparison results + """ + report = ComparisonReport() + + # Index by (name, instance) + spark_index = index_metrics(spark_metrics) + duckdb_index = index_metrics(duckdb_metrics) + + all_keys = set(spark_index.keys()) | set(duckdb_index.keys()) + report.total_metrics = len(all_keys) + + for key in all_keys: + name, instance = key + tolerance = get_tolerance(name) + + spark_metric = spark_index.get(key) + duckdb_metric = duckdb_index.get(key) + + if spark_metric is None: + report.duckdb_only_metrics += 1 + report.differences.append(MetricDifference( + name=name, + instance=instance, + spark_value=None, + duckdb_value=duckdb_metric.value if duckdb_metric else None, + tolerance=tolerance, + is_match=False, + message="Metric only in DuckDB" + )) + elif duckdb_metric is None: + report.spark_only_metrics += 1 + report.differences.append(MetricDifference( + name=name, + instance=instance, + spark_value=spark_metric.value, + duckdb_value=None, + tolerance=tolerance, + is_match=False, + message="Metric only in Spark" + )) + else: + is_match = values_equal(spark_metric.value, duckdb_metric.value, tolerance) + if is_match: + report.matching_metrics += 1 + else: + report.differing_metrics += 1 + report.differences.append(MetricDifference( + name=name, + instance=instance, + spark_value=spark_metric.value, + duckdb_value=duckdb_metric.value, + tolerance=tolerance, + is_match=False, + message=f"Values differ (tolerance={tolerance})" + )) + + return report + + +def compare_constraint_results( + spark_results: List[ConstraintResult], + duckdb_results: List[ConstraintResult] +) -> ComparisonReport: + """Compare constraint results from Spark and DuckDB engines. + + Comparison is done by position within each check group, since constraint + names may differ between engines (e.g., Spark uses 'SizeConstraint(Size(None))' + while DuckDB uses 'hasSize(assertion)'). + + Args: + spark_results: Constraint results from Spark engine + duckdb_results: Constraint results from DuckDB engine + + Returns: + ComparisonReport with detailed comparison results + """ + report = ComparisonReport() + + # Group results by check_description to maintain ordering within checks + def group_by_check(results: List[ConstraintResult]) -> Dict[str, List[ConstraintResult]]: + groups: Dict[str, List[ConstraintResult]] = {} + for r in results: + key = r.check_description + if key not in groups: + groups[key] = [] + groups[key].append(r) + return groups + + spark_groups = group_by_check(spark_results) + duckdb_groups = group_by_check(duckdb_results) + + all_checks = set(spark_groups.keys()) | set(duckdb_groups.keys()) + + for check_desc in all_checks: + spark_list = spark_groups.get(check_desc, []) + duckdb_list = duckdb_groups.get(check_desc, []) + + # Compare by position within each check + max_len = max(len(spark_list), len(duckdb_list)) + report.total_metrics += max_len + + for i in range(max_len): + spark_result = spark_list[i] if i < len(spark_list) else None + duckdb_result = duckdb_list[i] if i < len(duckdb_list) else None + + if spark_result is None: + report.duckdb_only_metrics += 1 + elif duckdb_result is None: + report.spark_only_metrics += 1 + else: + # Compare constraint status + spark_status = spark_result.constraint_status + duckdb_status = duckdb_result.constraint_status + + if spark_status == duckdb_status: + report.matching_metrics += 1 + else: + report.differing_metrics += 1 + report.differences.append(MetricDifference( + name="ConstraintStatus", + instance=f"{check_desc}[{i}]", + spark_value=str(spark_status), + duckdb_value=str(duckdb_status), + tolerance=0, + is_match=False, + message="Constraint status differs" + )) + + return report + + +def compare_profiles( + spark_profiles: List[ColumnProfile], + duckdb_profiles: List[ColumnProfile] +) -> ComparisonReport: + """Compare column profiles from Spark and DuckDB engines. + + Args: + spark_profiles: Column profiles from Spark engine + duckdb_profiles: Column profiles from DuckDB engine + + Returns: + ComparisonReport with detailed comparison results + """ + report = ComparisonReport() + + # Index by column name + spark_index = {p.column: p for p in spark_profiles} + duckdb_index = {p.column: p for p in duckdb_profiles} + + all_columns = set(spark_index.keys()) | set(duckdb_index.keys()) + + for column in all_columns: + spark_profile = spark_index.get(column) + duckdb_profile = duckdb_index.get(column) + + if spark_profile is None: + report.duckdb_only_metrics += 1 + continue + if duckdb_profile is None: + report.spark_only_metrics += 1 + continue + + # Compare profile attributes + attrs_to_compare = [ + ("completeness", FLOAT_EPSILON), + ("approx_distinct_values", APPROX_TOLERANCE), + ("mean", FLOAT_TOLERANCE), + ("minimum", FLOAT_TOLERANCE), + ("maximum", FLOAT_TOLERANCE), + ("sum", FLOAT_TOLERANCE), + ("std_dev", FLOAT_TOLERANCE), + ] + + for attr, tolerance in attrs_to_compare: + spark_val = getattr(spark_profile, attr, None) + duckdb_val = getattr(duckdb_profile, attr, None) + + report.total_metrics += 1 + + if values_equal(spark_val, duckdb_val, tolerance): + report.matching_metrics += 1 + else: + report.differing_metrics += 1 + report.differences.append(MetricDifference( + name=attr, + instance=column, + spark_value=spark_val, + duckdb_value=duckdb_val, + tolerance=tolerance, + is_match=False, + message=f"Profile attribute {attr} differs" + )) + + return report + + +def assert_metrics_match( + spark_metrics: List[MetricResult], + duckdb_metrics: List[MetricResult], + msg: str = "" +) -> None: + """Assert that metrics from both engines match within tolerance. + + Raises: + AssertionError: If metrics don't match + """ + report = compare_metrics(spark_metrics, duckdb_metrics) + if not report.success: + raise AssertionError(f"{msg}\n{report.summary()}") + + +def assert_constraints_match( + spark_results: List[ConstraintResult], + duckdb_results: List[ConstraintResult], + msg: str = "" +) -> None: + """Assert that constraint results from both engines match. + + Raises: + AssertionError: If results don't match + """ + report = compare_constraint_results(spark_results, duckdb_results) + if not report.success: + raise AssertionError(f"{msg}\n{report.summary()}") + + +def assert_profiles_match( + spark_profiles: List[ColumnProfile], + duckdb_profiles: List[ColumnProfile], + msg: str = "" +) -> None: + """Assert that profiles from both engines match within tolerance. + + Raises: + AssertionError: If profiles don't match + """ + report = compare_profiles(spark_profiles, duckdb_profiles) + if not report.success: + raise AssertionError(f"{msg}\n{report.summary()}") diff --git a/tests/engines/conftest.py b/tests/engines/conftest.py new file mode 100644 index 0000000..5a53f9d --- /dev/null +++ b/tests/engines/conftest.py @@ -0,0 +1,330 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DuckDB engine test fixtures. + +Provides fixtures for creating DuckDB engines with various test datasets. +These fixtures are used by DuckDB-only tests that don't require Spark. +""" + +from typing import Callable, Generator +import pytest +import duckdb +import pandas as pd + +from pydeequ.engines.duckdb import DuckDBEngine +from tests.engines.fixtures.datasets import ( + create_df_full, + create_df_missing, + create_df_numeric, + create_df_unique, + create_df_distinct, + create_df_string_lengths, + create_df_empty, + create_df_single, + create_df_all_null, + create_df_escape, + create_df_correlation, + create_df_entropy, + create_df_where, + create_df_pattern, + create_df_compliance, + create_df_quantile, + create_df_contained_in, + create_df_histogram, + create_df_mutual_info, + create_df_data_type, + DATASET_FACTORIES, +) + + +@pytest.fixture(scope="module") +def duckdb_connection() -> Generator[duckdb.DuckDBPyConnection, None, None]: + """Create a module-scoped DuckDB connection.""" + conn = duckdb.connect(":memory:") + yield conn + conn.close() + + +def _create_engine_from_df( + conn: duckdb.DuckDBPyConnection, + df: pd.DataFrame, + table_name: str +) -> DuckDBEngine: + """Helper to create a DuckDB engine from a pandas DataFrame.""" + # Register the DataFrame as a table + conn.register(table_name, df) + # Create engine pointing to the table + return DuckDBEngine(conn, table_name) + + +@pytest.fixture(scope="function") +def engine_factory(duckdb_connection: duckdb.DuckDBPyConnection) -> Callable[[str], DuckDBEngine]: + """Factory fixture to create DuckDB engines for any dataset. + + Usage: + def test_something(engine_factory): + engine = engine_factory("df_full") + results = engine.compute_metrics([Size()]) + """ + tables_created = [] + + def factory(dataset_name: str) -> DuckDBEngine: + if dataset_name not in DATASET_FACTORIES: + raise ValueError(f"Unknown dataset: {dataset_name}") + + table_name = f"test_{dataset_name}" + df = DATASET_FACTORIES[dataset_name]() + + # Unregister if already exists (for reuse in same test) + try: + duckdb_connection.unregister(table_name) + except Exception: + pass + + duckdb_connection.register(table_name, df) + tables_created.append(table_name) + + return DuckDBEngine(duckdb_connection, table_name) + + yield factory + + # Cleanup: unregister all tables + for table_name in tables_created: + try: + duckdb_connection.unregister(table_name) + except Exception: + pass + + +# Individual dataset fixtures for convenience + + +@pytest.fixture(scope="function") +def engine_full(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_full dataset.""" + table_name = "test_df_full" + df = create_df_full() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_missing(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_missing dataset.""" + table_name = "test_df_missing" + df = create_df_missing() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_numeric(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_numeric dataset.""" + table_name = "test_df_numeric" + df = create_df_numeric() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_unique(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_unique dataset.""" + table_name = "test_df_unique" + df = create_df_unique() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_distinct(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_distinct dataset.""" + table_name = "test_df_distinct" + df = create_df_distinct() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_string_lengths(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_string_lengths dataset.""" + table_name = "test_df_string_lengths" + df = create_df_string_lengths() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_empty(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_empty dataset.""" + table_name = "test_df_empty" + df = create_df_empty() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_single(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_single dataset.""" + table_name = "test_df_single" + df = create_df_single() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_all_null(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_all_null dataset.""" + table_name = "test_df_all_null" + df = create_df_all_null() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_escape(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_escape dataset.""" + table_name = "test_df_escape" + df = create_df_escape() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_correlation(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_correlation dataset.""" + table_name = "test_df_correlation" + df = create_df_correlation() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_entropy(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_entropy dataset.""" + table_name = "test_df_entropy" + df = create_df_entropy() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_where(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_where dataset.""" + table_name = "test_df_where" + df = create_df_where() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_pattern(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_pattern dataset.""" + table_name = "test_df_pattern" + df = create_df_pattern() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_compliance(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_compliance dataset.""" + table_name = "test_df_compliance" + df = create_df_compliance() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_quantile(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_quantile dataset.""" + table_name = "test_df_quantile" + df = create_df_quantile() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_contained_in(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_contained_in dataset.""" + table_name = "test_df_contained_in" + df = create_df_contained_in() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_histogram(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_histogram dataset.""" + table_name = "test_df_histogram" + df = create_df_histogram() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_mutual_info(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_mutual_info dataset.""" + table_name = "test_df_mutual_info" + df = create_df_mutual_info() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +@pytest.fixture(scope="function") +def engine_data_type(duckdb_connection: duckdb.DuckDBPyConnection) -> Generator[DuckDBEngine, None, None]: + """DuckDB engine with df_data_type dataset.""" + table_name = "test_df_data_type" + df = create_df_data_type() + duckdb_connection.register(table_name, df) + yield DuckDBEngine(duckdb_connection, table_name) + duckdb_connection.unregister(table_name) + + +# Helper function for metric lookup +def get_metric_value(metrics, name: str, instance: str = None) -> float: + """Extract a metric value from results by name and optionally instance.""" + for m in metrics: + if m.name == name: + if instance is None or m.instance == instance: + return m.value + return None + + +def get_metric(metrics, name: str, instance: str = None): + """Extract a metric result from results by name and optionally instance.""" + for m in metrics: + if m.name == name: + if instance is None or m.instance == instance: + return m + return None diff --git a/tests/engines/fixtures/__init__.py b/tests/engines/fixtures/__init__.py new file mode 100644 index 0000000..73c4067 --- /dev/null +++ b/tests/engines/fixtures/__init__.py @@ -0,0 +1,36 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test fixtures module. + +Contains test dataset definitions ported from the original Deequ Scala +implementation for comprehensive edge case coverage. +""" + +from .datasets import ( + create_df_full, + create_df_missing, + create_df_numeric, + create_df_unique, + create_df_distinct, + create_df_string_lengths, + create_df_empty, + create_df_single, + create_df_all_null, + create_df_escape, + create_df_correlation, + create_df_entropy, + create_df_where, + EXPECTED_VALUES, +) diff --git a/tests/engines/fixtures/datasets.py b/tests/engines/fixtures/datasets.py new file mode 100644 index 0000000..bb22fdf --- /dev/null +++ b/tests/engines/fixtures/datasets.py @@ -0,0 +1,529 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test dataset definitions ported from Deequ Scala FixtureSupport. + +These datasets provide comprehensive edge case coverage for testing +analyzers, constraints, profiles, and suggestions. +""" + +from typing import Any, Dict, List, Tuple +import math + +import pandas as pd + + +def create_df_full() -> pd.DataFrame: + """Basic complete data with no nulls (4 rows). + + Purpose: Basic complete data testing + Edge cases: No nulls, simple values + """ + return pd.DataFrame({ + "att1": ["a", "b", "c", "a"], + "att2": ["d", "e", "f", "d"], + "item": [1, 2, 3, 4], + "price": [10.0, 20.0, 30.0, 40.0], + }) + + +def create_df_missing() -> pd.DataFrame: + """Dataset with NULL handling patterns (12 rows). + + Purpose: NULL handling tests + Edge cases: att1: 50% complete, att2: 75% complete + """ + return pd.DataFrame({ + "att1": ["a", "b", None, None, "e", "f", None, None, "i", "j", None, None], + "att2": ["d", "e", "f", None, "h", "i", "j", None, "l", "m", "n", None], + "item": list(range(1, 13)), + }) + + +def create_df_numeric() -> pd.DataFrame: + """Dataset for statistical tests (6 rows). + + Purpose: Statistical analyzer tests (Mean, Sum, Min, Max, StdDev) + Edge cases: Mean=3.5, includes NULL column + Values: 1, 2, 3, 4, 5, 6 -> Mean=3.5, Sum=21, Min=1, Max=6 + StdDev (sample) = sqrt(17.5/5) = sqrt(3.5) ≈ 1.8708 + """ + return pd.DataFrame({ + "att1": [1, 2, 3, 4, 5, 6], + "att2": [1.0, 2.0, 3.0, 4.0, 5.0, None], # One NULL + "item": ["a", "b", "c", "d", "e", "f"], + }) + + +def create_df_unique() -> pd.DataFrame: + """Dataset for uniqueness pattern tests (6 rows). + + Purpose: Uniqueness analyzer tests + Edge cases: Various uniqueness scenarios + - unique_col: All unique values (uniqueness=1.0) + - half_null: 50% null (completeness=0.5) + - non_unique: Duplicates present + """ + return pd.DataFrame({ + "unique_col": [1, 2, 3, 4, 5, 6], + "half_null": [1, None, 3, None, 5, None], + "non_unique": [1, 1, 2, 2, 3, 3], + "all_same": [1, 1, 1, 1, 1, 1], + }) + + +def create_df_distinct() -> pd.DataFrame: + """Dataset for distinctness testing with duplicates (6 rows). + + Purpose: Distinctness and uniqueness ratio testing + Edge cases: 3 distinct values in att1 with duplicates + - att1: ["a", "a", "b", "b", "c", "c"] -> 3 distinct, 0 unique + - att2: ["x", "y", "z", "w", "v", "u"] -> 6 distinct, 6 unique + """ + return pd.DataFrame({ + "att1": ["a", "a", "b", "b", "c", "c"], + "att2": ["x", "y", "z", "w", "v", "u"], + "item": [1, 2, 3, 4, 5, 6], + }) + + +def create_df_string_lengths() -> pd.DataFrame: + """Dataset for string length edge cases (5 rows). + + Purpose: MinLength and MaxLength analyzer tests + Edge cases: Empty string (""), varying lengths + Lengths: 0, 1, 2, 3, 4 + """ + return pd.DataFrame({ + "att1": ["", "a", "bb", "ccc", "dddd"], + "att2": ["hello", "world", "test", "data", "value"], + "item": [1, 2, 3, 4, 5], + }) + + +def create_df_empty() -> pd.DataFrame: + """Empty dataset with schema (0 rows). + + Purpose: Edge case testing with zero rows + Edge cases: Size=0, Completeness=1.0 (vacuously true for empty) + """ + return pd.DataFrame({ + "att1": pd.Series([], dtype="object"), + "att2": pd.Series([], dtype="object"), + "item": pd.Series([], dtype="int64"), + }) + + +def create_df_single() -> pd.DataFrame: + """Minimal dataset with single row (1 row). + + Purpose: Minimal dataset edge case testing + Edge cases: StdDev undefined/NaN, Uniqueness=1.0 + """ + return pd.DataFrame({ + "att1": ["a"], + "att2": ["d"], + "item": [1], + "price": [10.0], + }) + + +def create_df_all_null() -> pd.DataFrame: + """Dataset with all-NULL column (3 rows). + + Purpose: 0% completeness edge case testing + Edge cases: Completeness=0, Mean=NULL + """ + return pd.DataFrame({ + "value": [None, None, None], + "item": [1, 2, 3], + }) + + +def create_df_escape() -> pd.DataFrame: + """Dataset with special characters (8 rows). + + Purpose: Special character and regex escaping tests + Edge cases: Quotes, special characters (@#$%^&) + """ + return pd.DataFrame({ + "att1": [ + 'hello "world"', + "it's working", + "test@example.com", + "#hashtag", + "$money$", + "%percent%", + "^caret^", + "&ersand&", + ], + "att2": ["normal", "values", "here", "for", "comparison", "testing", "edge", "cases"], + "item": list(range(1, 9)), + }) + + +def create_df_correlation() -> pd.DataFrame: + """Dataset for correlation testing (5 rows). + + Purpose: Correlation analyzer tests + Edge cases: Perfect +1.0 and -1.0 correlation + - x and y: perfectly positively correlated (1.0) + - x and z: perfectly negatively correlated (-1.0) + """ + return pd.DataFrame({ + "x": [1.0, 2.0, 3.0, 4.0, 5.0], + "y": [2.0, 4.0, 6.0, 8.0, 10.0], # y = 2x, correlation = 1.0 + "z": [5.0, 4.0, 3.0, 2.0, 1.0], # z = 6-x, correlation = -1.0 + "w": [1.0, 1.0, 1.0, 1.0, 1.0], # constant, correlation undefined + }) + + +def create_df_entropy() -> pd.DataFrame: + """Dataset for entropy testing (4 rows). + + Purpose: Entropy analyzer tests + Edge cases: Uniform vs skewed distribution + - uniform: 4 distinct values each appearing once -> entropy = log2(4) = 2.0 + - skewed: 1 value appearing 3 times, 1 appearing once -> entropy < 2.0 + """ + return pd.DataFrame({ + "uniform": ["a", "b", "c", "d"], # Entropy = log2(4) = 2.0 + "skewed": ["a", "a", "a", "b"], # Entropy = -(3/4)log2(3/4) - (1/4)log2(1/4) ≈ 0.811 + "constant": ["x", "x", "x", "x"], # Entropy = 0 (single value) + "item": [1, 2, 3, 4], + }) + + +def create_df_where() -> pd.DataFrame: + """Dataset for WHERE clause filtering tests (4 rows). + + Purpose: WHERE clause filter testing + Edge cases: Mixed completeness by filter + - When filtered by category='A': att1 is complete + - When filtered by category='B': att1 has nulls + """ + return pd.DataFrame({ + "category": ["A", "A", "B", "B"], + "att1": ["x", "y", None, "w"], # A: 2/2 complete, B: 1/2 complete + "att2": [1, None, 3, 4], # A: 1/2 complete, B: 2/2 complete + "value": [10.0, 20.0, 30.0, 40.0], + }) + + +def create_df_pattern() -> pd.DataFrame: + """Dataset for pattern matching tests (6 rows). + + Purpose: PatternMatch analyzer and regex compliance tests + Edge cases: Email patterns, phone patterns, mixed valid/invalid + """ + return pd.DataFrame({ + "email": [ + "test@example.com", + "user@domain.org", + "invalid-email", + "another@test.co.uk", + "bad@", + "good.name@company.com", + ], + "phone": [ + "123-456-7890", + "987-654-3210", + "invalid", + "555-123-4567", + "1234567890", + "800-555-1234", + ], + "code": ["ABC123", "DEF456", "xyz789", "GHI012", "JKL345", "mno678"], + "item": list(range(1, 7)), + }) + + +def create_df_compliance() -> pd.DataFrame: + """Dataset for compliance predicate tests (6 rows). + + Purpose: Compliance and satisfies constraint tests + Edge cases: Positive/negative numbers, boundary conditions + """ + return pd.DataFrame({ + "positive": [1, 2, 3, 4, 5, 6], + "negative": [-1, -2, -3, -4, -5, -6], + "mixed": [-2, -1, 0, 1, 2, 3], + "with_null": [1, 2, None, 4, 5, None], + "item": list(range(1, 7)), + }) + + +def create_df_quantile() -> pd.DataFrame: + """Dataset for quantile testing (10 rows). + + Purpose: ApproxQuantile analyzer tests + Edge cases: Sorted values for predictable quantiles + Values: 1-10, Median (50th percentile) = 5.5 + """ + return pd.DataFrame({ + "value": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], + "item": list(range(1, 11)), + }) + + +def create_df_contained_in() -> pd.DataFrame: + """Dataset for isContainedIn constraint tests (6 rows). + + Purpose: Testing containment in allowed value sets + Edge cases: All valid, some invalid, NULL handling + """ + return pd.DataFrame({ + "status": ["active", "inactive", "pending", "active", "inactive", "active"], + "category": ["A", "B", "C", "A", "B", "D"], # D is not in typical allowed set + "priority": [1, 2, 3, 1, 2, 4], # 4 might be outside allowed range + "item": list(range(1, 7)), + }) + + +def create_df_histogram() -> pd.DataFrame: + """Dataset for histogram testing (10 rows). + + Purpose: Histogram analyzer tests + Edge cases: Low cardinality categorical data + """ + return pd.DataFrame({ + "category": ["A", "A", "A", "B", "B", "C", "C", "C", "C", "D"], + "status": ["active", "active", "inactive", "active", "inactive", + "active", "active", "inactive", "inactive", "active"], + "item": list(range(1, 11)), + }) + + +def create_df_mutual_info() -> pd.DataFrame: + """Dataset for mutual information testing (8 rows). + + Purpose: MutualInformation analyzer tests + Edge cases: Perfectly dependent vs independent columns + """ + return pd.DataFrame({ + "x": ["a", "a", "b", "b", "c", "c", "d", "d"], + "y_dependent": ["a", "a", "b", "b", "c", "c", "d", "d"], # Perfectly dependent on x + "y_independent": ["p", "q", "r", "s", "p", "q", "r", "s"], # Less dependent + "item": list(range(1, 9)), + }) + + +def create_df_data_type() -> pd.DataFrame: + """Dataset for DataType analyzer testing. + + Purpose: Testing data type inference + Edge cases: Mixed numeric strings, pure numeric, non-numeric + """ + return pd.DataFrame({ + "numeric_strings": ["1", "2", "3", "4", "5"], + "mixed": ["1", "2", "three", "4", "five"], + "pure_numeric": [1.0, 2.0, 3.0, 4.0, 5.0], + "strings": ["a", "b", "c", "d", "e"], + "item": list(range(1, 6)), + }) + + +# Expected values registry for DuckDB-only tests +# Key: (dataset_name, analyzer_name, instance) -> expected_value +# instance can be a column name, tuple of columns, or None for dataset-level metrics +EXPECTED_VALUES: Dict[Tuple[str, str, Any], float] = { + # Size analyzer + ("df_full", "Size", None): 4.0, + ("df_missing", "Size", None): 12.0, + ("df_numeric", "Size", None): 6.0, + ("df_empty", "Size", None): 0.0, + ("df_single", "Size", None): 1.0, + + # Completeness analyzer + ("df_full", "Completeness", "att1"): 1.0, + ("df_full", "Completeness", "att2"): 1.0, + ("df_missing", "Completeness", "att1"): 0.5, # 6/12 + ("df_missing", "Completeness", "att2"): 0.75, # 9/12 + ("df_all_null", "Completeness", "value"): 0.0, + ("df_single", "Completeness", "att1"): 1.0, + ("df_unique", "Completeness", "unique_col"): 1.0, + ("df_unique", "Completeness", "half_null"): 0.5, # 3/6 + + # Mean analyzer + ("df_numeric", "Mean", "att1"): 3.5, # (1+2+3+4+5+6)/6 + ("df_numeric", "Mean", "att2"): 3.0, # (1+2+3+4+5)/5, NULL excluded + ("df_single", "Mean", "item"): 1.0, + ("df_single", "Mean", "price"): 10.0, + + # Sum analyzer + ("df_numeric", "Sum", "att1"): 21.0, # 1+2+3+4+5+6 + ("df_numeric", "Sum", "att2"): 15.0, # 1+2+3+4+5, NULL excluded + ("df_single", "Sum", "item"): 1.0, + ("df_single", "Sum", "price"): 10.0, + + # Minimum analyzer + ("df_numeric", "Minimum", "att1"): 1.0, + ("df_numeric", "Minimum", "att2"): 1.0, + ("df_single", "Minimum", "item"): 1.0, + ("df_single", "Minimum", "price"): 10.0, + + # Maximum analyzer + ("df_numeric", "Maximum", "att1"): 6.0, + ("df_numeric", "Maximum", "att2"): 5.0, # 6 is NULL position + ("df_single", "Maximum", "item"): 1.0, + ("df_single", "Maximum", "price"): 10.0, + + # StandardDeviation analyzer (sample stddev) + ("df_numeric", "StandardDeviation", "att1"): 1.8708286933869707, # sqrt(17.5/5) + + # String length analyzers + ("df_string_lengths", "MinLength", "att1"): 0.0, # Empty string + ("df_string_lengths", "MaxLength", "att1"): 4.0, # "dddd" + ("df_string_lengths", "MinLength", "att2"): 4.0, # "test", "data" + ("df_string_lengths", "MaxLength", "att2"): 5.0, # "hello", "world", "value" + + # Distinctness analyzer (distinct values / total rows) + ("df_distinct", "Distinctness", "att1"): 0.5, # 3 distinct / 6 rows + ("df_distinct", "Distinctness", "att2"): 1.0, # 6 distinct / 6 rows + ("df_unique", "Distinctness", "all_same"): 1/6, # 1 distinct / 6 rows + + # Uniqueness analyzer (rows with unique values / total rows) + ("df_distinct", "Uniqueness", "att1"): 0.0, # No unique values (all duplicated) + ("df_distinct", "Uniqueness", "att2"): 1.0, # All values are unique + ("df_unique", "Uniqueness", "unique_col"): 1.0, # All values unique + ("df_unique", "Uniqueness", "non_unique"): 0.0, # All values duplicated + + # UniqueValueRatio analyzer (unique values / distinct values) + ("df_distinct", "UniqueValueRatio", "att1"): 0.0, # 0 unique / 3 distinct + ("df_distinct", "UniqueValueRatio", "att2"): 1.0, # 6 unique / 6 distinct + + # Correlation analyzer + ("df_correlation", "Correlation", ("x", "y")): 1.0, # Perfect positive + ("df_correlation", "Correlation", ("x", "z")): -1.0, # Perfect negative + + # Entropy analyzer + ("df_entropy", "Entropy", "uniform"): 2.0, # log2(4) for 4 uniform values + ("df_entropy", "Entropy", "constant"): 0.0, # Single value = 0 entropy + + # ApproxCountDistinct analyzer + ("df_full", "ApproxCountDistinct", "att1"): 3.0, # "a", "b", "c" (a appears twice) + ("df_full", "ApproxCountDistinct", "item"): 4.0, # 1, 2, 3, 4 + ("df_distinct", "ApproxCountDistinct", "att1"): 3.0, # "a", "b", "c" + ("df_distinct", "ApproxCountDistinct", "att2"): 6.0, # All distinct + + # CountDistinct analyzer + ("df_full", "CountDistinct", "att1"): 3.0, + ("df_distinct", "CountDistinct", "att1"): 3.0, + ("df_distinct", "CountDistinct", "att2"): 6.0, + + # PatternMatch analyzer (fraction of rows matching pattern) + # These will be tested with specific patterns in the tests + + # Compliance analyzer (fraction of rows satisfying predicate) + ("df_compliance", "Compliance", "positive > 0"): 1.0, # All positive + ("df_compliance", "Compliance", "negative < 0"): 1.0, # All negative + ("df_compliance", "Compliance", "mixed > 0"): 0.5, # 3/6 > 0 + + # Quantile analyzer (approximate) + ("df_quantile", "ApproxQuantile", ("value", 0.5)): 5.5, # Median + ("df_quantile", "ApproxQuantile", ("value", 0.25)): 3.0, # 25th percentile (approx) + ("df_quantile", "ApproxQuantile", ("value", 0.75)): 8.0, # 75th percentile (approx) +} + + +# Tolerance levels for comparing floating-point results +FLOAT_EPSILON = 1e-9 # Exact comparisons: Size, Completeness, Uniqueness +FLOAT_TOLERANCE = 1e-6 # Statistical: Mean, StdDev, Correlation +APPROX_TOLERANCE = 0.1 # Approximate algorithms: ApproxCountDistinct (10% relative) + + +# Mapping of analyzer types to their expected tolerance +ANALYZER_TOLERANCES: Dict[str, float] = { + # Exact metrics + "Size": FLOAT_EPSILON, + "Completeness": FLOAT_EPSILON, + "Uniqueness": FLOAT_EPSILON, + "Distinctness": FLOAT_EPSILON, + "UniqueValueRatio": FLOAT_EPSILON, + "CountDistinct": FLOAT_EPSILON, + "MinLength": FLOAT_EPSILON, + "MaxLength": FLOAT_EPSILON, + "PatternMatch": FLOAT_EPSILON, + "Compliance": FLOAT_EPSILON, + + # Statistical metrics + "Mean": FLOAT_TOLERANCE, + "Sum": FLOAT_TOLERANCE, + "Minimum": FLOAT_TOLERANCE, + "Maximum": FLOAT_TOLERANCE, + "StandardDeviation": FLOAT_TOLERANCE, + "Correlation": FLOAT_TOLERANCE, + "Entropy": FLOAT_TOLERANCE, + "MutualInformation": FLOAT_TOLERANCE, + "ApproxQuantile": FLOAT_TOLERANCE, + + # Approximate metrics + "ApproxCountDistinct": APPROX_TOLERANCE, +} + + +def get_tolerance(analyzer_name: str) -> float: + """Get the appropriate tolerance for an analyzer type.""" + return ANALYZER_TOLERANCES.get(analyzer_name, FLOAT_TOLERANCE) + + +def is_close(actual: float, expected: float, tolerance: float) -> bool: + """Check if two values are close within tolerance. + + For APPROX_TOLERANCE, uses relative comparison. + For smaller tolerances, uses absolute comparison. + """ + if expected is None or actual is None: + return expected is None and actual is None + + if tolerance >= APPROX_TOLERANCE: + # Relative tolerance for approximate algorithms + if expected == 0: + return abs(actual) < tolerance + return abs(actual - expected) / abs(expected) < tolerance + else: + # Absolute tolerance for exact/statistical metrics + return abs(actual - expected) < tolerance + + +# Dataset factory registry +DATASET_FACTORIES = { + "df_full": create_df_full, + "df_missing": create_df_missing, + "df_numeric": create_df_numeric, + "df_unique": create_df_unique, + "df_distinct": create_df_distinct, + "df_string_lengths": create_df_string_lengths, + "df_empty": create_df_empty, + "df_single": create_df_single, + "df_all_null": create_df_all_null, + "df_escape": create_df_escape, + "df_correlation": create_df_correlation, + "df_entropy": create_df_entropy, + "df_where": create_df_where, + "df_pattern": create_df_pattern, + "df_compliance": create_df_compliance, + "df_quantile": create_df_quantile, + "df_contained_in": create_df_contained_in, + "df_histogram": create_df_histogram, + "df_mutual_info": create_df_mutual_info, + "df_data_type": create_df_data_type, +} + + +def get_dataset(name: str) -> pd.DataFrame: + """Get a dataset by name.""" + if name not in DATASET_FACTORIES: + raise ValueError(f"Unknown dataset: {name}. Available: {list(DATASET_FACTORIES.keys())}") + return DATASET_FACTORIES[name]() diff --git a/tests/engines/test_constraint_evaluators.py b/tests/engines/test_constraint_evaluators.py new file mode 100644 index 0000000..2b67027 --- /dev/null +++ b/tests/engines/test_constraint_evaluators.py @@ -0,0 +1,473 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for constraint evaluators. + +These tests verify the constraint evaluator abstractions work correctly +in isolation, testing SQL generation and evaluation logic. +""" + +import pandas as pd +import pytest + +from pydeequ.engines.constraints import ( + ConstraintEvaluatorFactory, + BaseEvaluator, + RatioCheckEvaluator, + AnalyzerBasedEvaluator, + # Analyzer-based evaluators + SizeEvaluator, + CompletenessEvaluator, + MeanEvaluator, + MinimumEvaluator, + MaximumEvaluator, + SumEvaluator, + StandardDeviationEvaluator, + UniquenessEvaluator, + DistinctnessEvaluator, + UniqueValueRatioEvaluator, + CorrelationEvaluator, + EntropyEvaluator, + MutualInformationEvaluator, + PatternMatchEvaluator, + MinLengthEvaluator, + MaxLengthEvaluator, + ApproxCountDistinctEvaluator, + ApproxQuantileEvaluator, + ComplianceEvaluator, + # Ratio-check evaluators + IsPositiveEvaluator, + IsNonNegativeEvaluator, + IsContainedInEvaluator, + ContainsEmailEvaluator, + ContainsURLEvaluator, + ContainsCreditCardEvaluator, + ContainsSSNEvaluator, + # Comparison evaluators + ColumnComparisonEvaluator, + # Multi-column evaluators + MultiColumnCompletenessEvaluator, +) + + +class MockConstraintProto: + """Mock constraint protobuf for testing.""" + + def __init__( + self, + type: str = "test", + column: str = "", + columns: list = None, + where: str = "", + pattern: str = "", + column_condition: str = "", + constraint_name: str = "", + allowed_values: list = None, + quantile: float = 0.5, + assertion=None, + ): + self.type = type + self.column = column + self.columns = columns or [] + self.where = where + self.pattern = pattern + self.column_condition = column_condition + self.constraint_name = constraint_name + self.allowed_values = allowed_values or [] + self.quantile = quantile + self._assertion = assertion + + def HasField(self, field_name): + if field_name == "assertion": + return self._assertion is not None + return False + + @property + def assertion(self): + return self._assertion + + +class TestConstraintEvaluatorFactory: + """Tests for ConstraintEvaluatorFactory.""" + + def test_create_size_evaluator(self): + """Test creating SizeEvaluator.""" + proto = MockConstraintProto(type="hasSize") + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, SizeEvaluator) + + def test_create_completeness_evaluator(self): + """Test creating CompletenessEvaluator for isComplete.""" + proto = MockConstraintProto(type="isComplete", column="col1") + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, CompletenessEvaluator) + + def test_create_completeness_evaluator_has(self): + """Test creating CompletenessEvaluator for hasCompleteness.""" + proto = MockConstraintProto(type="hasCompleteness", column="col1") + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, CompletenessEvaluator) + + def test_create_is_positive_evaluator(self): + """Test creating IsPositiveEvaluator.""" + proto = MockConstraintProto(type="isPositive", column="col1") + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, IsPositiveEvaluator) + + def test_create_is_contained_in_evaluator(self): + """Test creating IsContainedInEvaluator.""" + proto = MockConstraintProto( + type="isContainedIn", + column="col1", + allowed_values=["a", "b", "c"] + ) + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, IsContainedInEvaluator) + + def test_create_column_comparison_evaluator(self): + """Test creating ColumnComparisonEvaluator for isLessThan.""" + proto = MockConstraintProto(type="isLessThan", columns=["col1", "col2"]) + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, ColumnComparisonEvaluator) + + def test_create_multi_column_completeness_evaluator(self): + """Test creating MultiColumnCompletenessEvaluator.""" + proto = MockConstraintProto(type="areComplete", columns=["col1", "col2"]) + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is not None + assert isinstance(evaluator, MultiColumnCompletenessEvaluator) + + def test_create_unknown_type_returns_none(self): + """Test that unknown constraint types return None.""" + proto = MockConstraintProto(type="unknownType") + evaluator = ConstraintEvaluatorFactory.create(proto) + assert evaluator is None + + def test_is_supported(self): + """Test is_supported method.""" + assert ConstraintEvaluatorFactory.is_supported("hasSize") + assert ConstraintEvaluatorFactory.is_supported("isComplete") + assert ConstraintEvaluatorFactory.is_supported("isPositive") + assert not ConstraintEvaluatorFactory.is_supported("unknownType") + + def test_supported_types(self): + """Test supported_types method returns all registered types.""" + types = ConstraintEvaluatorFactory.supported_types() + assert "hasSize" in types + assert "isComplete" in types + assert "hasCompleteness" in types + assert "isPositive" in types + assert "isNonNegative" in types + assert "isContainedIn" in types + assert "isLessThan" in types + assert "areComplete" in types + + +class TestRatioCheckEvaluators: + """Tests for ratio-check evaluator condition generation.""" + + def test_is_positive_condition(self): + """Test IsPositiveEvaluator generates correct condition.""" + proto = MockConstraintProto(type="isPositive", column="price") + evaluator = IsPositiveEvaluator(proto) + assert evaluator.get_condition() == "price > 0" + + def test_is_non_negative_condition(self): + """Test IsNonNegativeEvaluator generates correct condition.""" + proto = MockConstraintProto(type="isNonNegative", column="count") + evaluator = IsNonNegativeEvaluator(proto) + assert evaluator.get_condition() == "count >= 0" + + def test_is_contained_in_condition(self): + """Test IsContainedInEvaluator generates correct condition.""" + proto = MockConstraintProto( + type="isContainedIn", + column="status", + allowed_values=["active", "pending"] + ) + evaluator = IsContainedInEvaluator(proto) + condition = evaluator.get_condition() + assert "status IN" in condition + assert "'active'" in condition + assert "'pending'" in condition + + def test_is_contained_in_escapes_quotes(self): + """Test IsContainedInEvaluator properly escapes single quotes.""" + proto = MockConstraintProto( + type="isContainedIn", + column="name", + allowed_values=["O'Brien", "D'Angelo"] + ) + evaluator = IsContainedInEvaluator(proto) + condition = evaluator.get_condition() + assert "O''Brien" in condition + assert "D''Angelo" in condition + + def test_contains_email_pattern(self): + """Test ContainsEmailEvaluator uses email regex pattern.""" + proto = MockConstraintProto(type="containsEmail", column="email") + evaluator = ContainsEmailEvaluator(proto) + condition = evaluator.get_condition() + assert "REGEXP_MATCHES" in condition + assert "email" in condition + + def test_contains_url_pattern(self): + """Test ContainsURLEvaluator uses URL regex pattern.""" + proto = MockConstraintProto(type="containsURL", column="website") + evaluator = ContainsURLEvaluator(proto) + condition = evaluator.get_condition() + assert "REGEXP_MATCHES" in condition + assert "website" in condition + + def test_column_comparison_less_than(self): + """Test ColumnComparisonEvaluator for isLessThan.""" + proto = MockConstraintProto(type="isLessThan", columns=["col_a", "col_b"]) + evaluator = ColumnComparisonEvaluator(proto) + assert evaluator.get_condition() == "col_a < col_b" + + def test_column_comparison_greater_than(self): + """Test ColumnComparisonEvaluator for isGreaterThan.""" + proto = MockConstraintProto(type="isGreaterThan", columns=["col_a", "col_b"]) + evaluator = ColumnComparisonEvaluator(proto) + assert evaluator.get_condition() == "col_a > col_b" + + def test_column_comparison_less_than_or_equal(self): + """Test ColumnComparisonEvaluator for isLessThanOrEqualTo.""" + proto = MockConstraintProto(type="isLessThanOrEqualTo", columns=["col_a", "col_b"]) + evaluator = ColumnComparisonEvaluator(proto) + assert evaluator.get_condition() == "col_a <= col_b" + + def test_column_comparison_greater_than_or_equal(self): + """Test ColumnComparisonEvaluator for isGreaterThanOrEqualTo.""" + proto = MockConstraintProto(type="isGreaterThanOrEqualTo", columns=["col_a", "col_b"]) + evaluator = ColumnComparisonEvaluator(proto) + assert evaluator.get_condition() == "col_a >= col_b" + + +class TestEvaluatorToString: + """Tests for evaluator to_string methods.""" + + def test_size_evaluator_to_string(self): + """Test SizeEvaluator to_string.""" + proto = MockConstraintProto(type="hasSize") + evaluator = SizeEvaluator(proto) + assert "hasSize" in evaluator.to_string() + + def test_completeness_evaluator_to_string_is_complete(self): + """Test CompletenessEvaluator to_string for isComplete.""" + proto = MockConstraintProto(type="isComplete", column="col1") + evaluator = CompletenessEvaluator(proto) + result = evaluator.to_string() + assert "Complete" in result + assert "col1" in result + + def test_is_positive_evaluator_to_string(self): + """Test IsPositiveEvaluator to_string.""" + proto = MockConstraintProto(type="isPositive", column="price") + evaluator = IsPositiveEvaluator(proto) + result = evaluator.to_string() + assert "isPositive" in result + assert "price" in result + + def test_is_contained_in_evaluator_to_string(self): + """Test IsContainedInEvaluator to_string.""" + proto = MockConstraintProto( + type="isContainedIn", + column="status", + allowed_values=["a", "b"] + ) + evaluator = IsContainedInEvaluator(proto) + result = evaluator.to_string() + assert "isContainedIn" in result + assert "status" in result + + def test_column_comparison_to_string(self): + """Test ColumnComparisonEvaluator to_string.""" + proto = MockConstraintProto(type="isLessThan", columns=["a", "b"]) + evaluator = ColumnComparisonEvaluator(proto) + result = evaluator.to_string() + assert "isLessThan" in result + assert "a" in result + assert "b" in result + + def test_multi_column_completeness_to_string(self): + """Test MultiColumnCompletenessEvaluator to_string.""" + proto = MockConstraintProto(type="areComplete", columns=["col1", "col2"]) + evaluator = MultiColumnCompletenessEvaluator(proto) + result = evaluator.to_string() + assert "Complete" in result + assert "col1" in result + assert "col2" in result + + +class TestEvaluatorEvaluation: + """Tests for evaluator evaluation logic.""" + + def test_evaluate_none_value_returns_false(self): + """Test that evaluating None value returns False.""" + proto = MockConstraintProto(type="hasSize") + evaluator = SizeEvaluator(proto) + assert evaluator.evaluate(None) is False + + def test_evaluate_1_0_without_assertion_returns_true(self): + """Test that evaluating 1.0 without assertion returns True.""" + proto = MockConstraintProto(type="isComplete", column="col1") + evaluator = CompletenessEvaluator(proto) + assert evaluator.evaluate(1.0) is True + + def test_evaluate_less_than_1_without_assertion_returns_false(self): + """Test that evaluating < 1.0 without assertion returns False.""" + proto = MockConstraintProto(type="isComplete", column="col1") + evaluator = CompletenessEvaluator(proto) + assert evaluator.evaluate(0.5) is False + + +class TestAnalyzerBasedEvaluators: + """Tests for analyzer-based evaluator operator generation.""" + + def test_completeness_evaluator_get_operator(self): + """Test CompletenessEvaluator creates correct operator.""" + from pydeequ.engines.operators import CompletenessOperator + + proto = MockConstraintProto(type="isComplete", column="col1") + evaluator = CompletenessEvaluator(proto) + operator = evaluator.get_operator() + assert isinstance(operator, CompletenessOperator) + assert operator.column == "col1" + + def test_mean_evaluator_get_operator(self): + """Test MeanEvaluator creates correct operator.""" + from pydeequ.engines.operators import MeanOperator + + proto = MockConstraintProto(type="hasMean", column="value") + evaluator = MeanEvaluator(proto) + operator = evaluator.get_operator() + assert isinstance(operator, MeanOperator) + assert operator.column == "value" + + def test_uniqueness_evaluator_get_operator(self): + """Test UniquenessEvaluator creates correct operator.""" + from pydeequ.engines.operators import UniquenessOperator + + proto = MockConstraintProto(type="isUnique", column="id") + evaluator = UniquenessEvaluator(proto) + operator = evaluator.get_operator() + assert isinstance(operator, UniquenessOperator) + + def test_pattern_match_evaluator_get_operator(self): + """Test PatternMatchEvaluator creates correct operator.""" + from pydeequ.engines.operators import PatternMatchOperator + + proto = MockConstraintProto(type="hasPattern", column="email", pattern="^.*@.*$") + evaluator = PatternMatchEvaluator(proto) + operator = evaluator.get_operator() + assert isinstance(operator, PatternMatchOperator) + assert operator.column == "email" + + def test_approx_quantile_evaluator_get_operator(self): + """Test ApproxQuantileEvaluator creates correct operator.""" + from pydeequ.engines.operators import ApproxQuantileOperator + + proto = MockConstraintProto(type="hasApproxQuantile", column="value", quantile=0.75) + evaluator = ApproxQuantileEvaluator(proto) + operator = evaluator.get_operator() + assert isinstance(operator, ApproxQuantileOperator) + assert operator.quantile == 0.75 + + +class TestWhereClauseHandling: + """Tests for WHERE clause handling in evaluators.""" + + def test_ratio_evaluator_with_where_clause(self): + """Test ratio evaluator includes WHERE in query.""" + proto = MockConstraintProto(type="isPositive", column="price", where="status='active'") + evaluator = IsPositiveEvaluator(proto) + assert evaluator.where == "status='active'" + + def test_analyzer_evaluator_with_where_clause(self): + """Test analyzer evaluator passes WHERE to operator.""" + proto = MockConstraintProto(type="hasMean", column="value", where="status='active'") + evaluator = MeanEvaluator(proto) + operator = evaluator.get_operator() + assert operator.where == "status='active'" + + +class TestSpecialConstraintTypes: + """Tests for special constraint types with extra parameters.""" + + def test_compliance_evaluator(self): + """Test ComplianceEvaluator with column_condition.""" + proto = MockConstraintProto( + type="satisfies", + column_condition="price > 0 AND quantity > 0", + constraint_name="valid_order" + ) + evaluator = ComplianceEvaluator(proto) + assert evaluator.predicate == "price > 0 AND quantity > 0" + assert evaluator.name == "valid_order" + result = evaluator.to_string() + assert "satisfies" in result + + def test_correlation_evaluator_requires_two_columns(self): + """Test CorrelationEvaluator handles missing columns.""" + proto = MockConstraintProto(type="hasCorrelation", columns=["col1"]) + evaluator = CorrelationEvaluator(proto) + # Should return None for operator when not enough columns + result = evaluator.compute_value("test_table", lambda q: pd.DataFrame()) + assert result is None + + def test_mutual_information_evaluator_requires_two_columns(self): + """Test MutualInformationEvaluator handles missing columns.""" + proto = MockConstraintProto(type="hasMutualInformation", columns=["col1"]) + evaluator = MutualInformationEvaluator(proto) + result = evaluator.compute_value("test_table", lambda q: pd.DataFrame()) + assert result is None + + +class TestAllConstraintTypesSupported: + """Verify all constraint types have evaluators.""" + + @pytest.mark.parametrize("constraint_type", [ + "hasSize", + "isComplete", + "hasCompleteness", + "hasMean", + "hasMin", + "hasMax", + "hasSum", + "hasStandardDeviation", + "isUnique", + "hasUniqueness", + "hasDistinctness", + "hasUniqueValueRatio", + "hasCorrelation", + "hasEntropy", + "hasMutualInformation", + "hasPattern", + "hasMinLength", + "hasMaxLength", + "hasApproxCountDistinct", + "hasApproxQuantile", + "satisfies", + "isPositive", + "isNonNegative", + "isContainedIn", + "containsEmail", + "containsURL", + "containsCreditCardNumber", + "containsSocialSecurityNumber", + "isLessThan", + "isLessThanOrEqualTo", + "isGreaterThan", + "isGreaterThanOrEqualTo", + "areComplete", + "haveCompleteness", + ]) + def test_constraint_type_has_evaluator(self, constraint_type): + """Verify each constraint type maps to an evaluator.""" + assert ConstraintEvaluatorFactory.is_supported(constraint_type) diff --git a/tests/engines/test_duckdb_analyzers.py b/tests/engines/test_duckdb_analyzers.py new file mode 100644 index 0000000..b5d9186 --- /dev/null +++ b/tests/engines/test_duckdb_analyzers.py @@ -0,0 +1,650 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DuckDB-only analyzer tests. + +Tests all 22 analyzers against known expected values from the test datasets. +These tests do not require Spark and can run quickly in CI. +""" + +import math +import pytest + +from pydeequ.v2.analyzers import ( + Size, + Completeness, + Mean, + Sum, + Maximum, + Minimum, + StandardDeviation, + Distinctness, + Uniqueness, + UniqueValueRatio, + CountDistinct, + ApproxCountDistinct, + ApproxQuantile, + Correlation, + MutualInformation, + MaxLength, + MinLength, + PatternMatch, + Compliance, + Entropy, + Histogram, + DataType, +) + +from tests.engines.conftest import get_metric_value, get_metric +from tests.engines.fixtures.datasets import ( + EXPECTED_VALUES, + FLOAT_EPSILON, + FLOAT_TOLERANCE, + APPROX_TOLERANCE, + is_close, + get_tolerance, +) + + +class TestSizeAnalyzer: + """Tests for the Size analyzer.""" + + def test_size_basic(self, engine_full): + """Size returns correct row count for basic dataset.""" + metrics = engine_full.compute_metrics([Size()]) + value = get_metric_value(metrics, "Size") + assert value == 4.0 + + def test_size_empty(self, engine_empty): + """Size returns 0 for empty dataset.""" + metrics = engine_empty.compute_metrics([Size()]) + value = get_metric_value(metrics, "Size") + assert value == 0.0 + + def test_size_single(self, engine_single): + """Size returns 1 for single-row dataset.""" + metrics = engine_single.compute_metrics([Size()]) + value = get_metric_value(metrics, "Size") + assert value == 1.0 + + def test_size_missing(self, engine_missing): + """Size counts all rows regardless of NULLs.""" + metrics = engine_missing.compute_metrics([Size()]) + value = get_metric_value(metrics, "Size") + assert value == 12.0 + + def test_size_with_where(self, engine_where): + """Size respects WHERE clause.""" + metrics = engine_where.compute_metrics([Size(where="category = 'A'")]) + value = get_metric_value(metrics, "Size") + assert value == 2.0 + + +class TestCompletenessAnalyzer: + """Tests for the Completeness analyzer.""" + + def test_completeness_full(self, engine_full): + """Completeness is 1.0 for columns with no NULLs.""" + metrics = engine_full.compute_metrics([Completeness("att1")]) + value = get_metric_value(metrics, "Completeness", "att1") + assert is_close(value, 1.0, FLOAT_EPSILON) + + def test_completeness_partial(self, engine_missing): + """Completeness reflects NULL ratio correctly.""" + metrics = engine_missing.compute_metrics([ + Completeness("att1"), + Completeness("att2"), + ]) + att1_value = get_metric_value(metrics, "Completeness", "att1") + att2_value = get_metric_value(metrics, "Completeness", "att2") + assert is_close(att1_value, 0.5, FLOAT_EPSILON) # 6/12 + assert is_close(att2_value, 0.75, FLOAT_EPSILON) # 9/12 + + def test_completeness_all_null(self, engine_all_null): + """Completeness is 0.0 for all-NULL column.""" + metrics = engine_all_null.compute_metrics([Completeness("value")]) + value = get_metric_value(metrics, "Completeness", "value") + assert is_close(value, 0.0, FLOAT_EPSILON) + + def test_completeness_empty(self, engine_empty): + """Completeness is 1.0 for empty dataset (vacuously true).""" + metrics = engine_empty.compute_metrics([Completeness("att1")]) + value = get_metric_value(metrics, "Completeness", "att1") + # Empty dataset: 0/0 should be treated as 1.0 (all rows are complete) + assert value is None or is_close(value, 1.0, FLOAT_EPSILON) or math.isnan(value) + + def test_completeness_with_where(self, engine_where): + """Completeness respects WHERE clause.""" + # category='A': att1 has values "x", "y" (2/2 complete) + metrics = engine_where.compute_metrics([ + Completeness("att1", where="category = 'A'") + ]) + value = get_metric_value(metrics, "Completeness", "att1") + assert is_close(value, 1.0, FLOAT_EPSILON) + + +class TestMeanAnalyzer: + """Tests for the Mean analyzer.""" + + def test_mean_basic(self, engine_numeric): + """Mean calculates correctly for numeric column.""" + metrics = engine_numeric.compute_metrics([Mean("att1")]) + value = get_metric_value(metrics, "Mean", "att1") + assert is_close(value, 3.5, FLOAT_TOLERANCE) + + def test_mean_with_nulls(self, engine_numeric): + """Mean excludes NULL values in calculation.""" + metrics = engine_numeric.compute_metrics([Mean("att2")]) + value = get_metric_value(metrics, "Mean", "att2") + assert is_close(value, 3.0, FLOAT_TOLERANCE) # (1+2+3+4+5)/5 + + def test_mean_single(self, engine_single): + """Mean works for single row.""" + metrics = engine_single.compute_metrics([Mean("price")]) + value = get_metric_value(metrics, "Mean", "price") + assert is_close(value, 10.0, FLOAT_TOLERANCE) + + def test_mean_with_where(self, engine_where): + """Mean respects WHERE clause.""" + metrics = engine_where.compute_metrics([Mean("value", where="category = 'A'")]) + value = get_metric_value(metrics, "Mean", "value") + assert is_close(value, 15.0, FLOAT_TOLERANCE) # (10+20)/2 + + +class TestSumAnalyzer: + """Tests for the Sum analyzer.""" + + def test_sum_basic(self, engine_numeric): + """Sum calculates correctly for numeric column.""" + metrics = engine_numeric.compute_metrics([Sum("att1")]) + value = get_metric_value(metrics, "Sum", "att1") + assert is_close(value, 21.0, FLOAT_TOLERANCE) + + def test_sum_with_nulls(self, engine_numeric): + """Sum excludes NULL values.""" + metrics = engine_numeric.compute_metrics([Sum("att2")]) + value = get_metric_value(metrics, "Sum", "att2") + assert is_close(value, 15.0, FLOAT_TOLERANCE) + + def test_sum_single(self, engine_single): + """Sum works for single row.""" + metrics = engine_single.compute_metrics([Sum("price")]) + value = get_metric_value(metrics, "Sum", "price") + assert is_close(value, 10.0, FLOAT_TOLERANCE) + + +class TestMinimumAnalyzer: + """Tests for the Minimum analyzer.""" + + def test_minimum_basic(self, engine_numeric): + """Minimum finds smallest value.""" + metrics = engine_numeric.compute_metrics([Minimum("att1")]) + value = get_metric_value(metrics, "Minimum", "att1") + assert is_close(value, 1.0, FLOAT_TOLERANCE) + + def test_minimum_with_nulls(self, engine_numeric): + """Minimum ignores NULL values.""" + metrics = engine_numeric.compute_metrics([Minimum("att2")]) + value = get_metric_value(metrics, "Minimum", "att2") + assert is_close(value, 1.0, FLOAT_TOLERANCE) + + def test_minimum_single(self, engine_single): + """Minimum works for single row.""" + metrics = engine_single.compute_metrics([Minimum("price")]) + value = get_metric_value(metrics, "Minimum", "price") + assert is_close(value, 10.0, FLOAT_TOLERANCE) + + +class TestMaximumAnalyzer: + """Tests for the Maximum analyzer.""" + + def test_maximum_basic(self, engine_numeric): + """Maximum finds largest value.""" + metrics = engine_numeric.compute_metrics([Maximum("att1")]) + value = get_metric_value(metrics, "Maximum", "att1") + assert is_close(value, 6.0, FLOAT_TOLERANCE) + + def test_maximum_with_nulls(self, engine_numeric): + """Maximum ignores NULL values.""" + metrics = engine_numeric.compute_metrics([Maximum("att2")]) + value = get_metric_value(metrics, "Maximum", "att2") + assert is_close(value, 5.0, FLOAT_TOLERANCE) + + def test_maximum_single(self, engine_single): + """Maximum works for single row.""" + metrics = engine_single.compute_metrics([Maximum("price")]) + value = get_metric_value(metrics, "Maximum", "price") + assert is_close(value, 10.0, FLOAT_TOLERANCE) + + +class TestStandardDeviationAnalyzer: + """Tests for the StandardDeviation analyzer.""" + + def test_stddev_basic(self, engine_numeric): + """StandardDeviation calculates sample stddev correctly.""" + metrics = engine_numeric.compute_metrics([StandardDeviation("att1")]) + value = get_metric_value(metrics, "StandardDeviation", "att1") + # Sample stddev of [1,2,3,4,5,6] = sqrt(17.5/5) ≈ 1.8708 + assert is_close(value, 1.8708286933869707, FLOAT_TOLERANCE) + + def test_stddev_single_row(self, engine_single): + """StandardDeviation for single row is NaN or 0.""" + metrics = engine_single.compute_metrics([StandardDeviation("price")]) + value = get_metric_value(metrics, "StandardDeviation", "price") + # Single value: stddev is undefined (NaN) or 0 + assert value is None or math.isnan(value) or value == 0.0 + + +class TestDistinctnessAnalyzer: + """Tests for the Distinctness analyzer.""" + + def test_distinctness_basic(self, engine_distinct): + """Distinctness = distinct values / total rows.""" + metrics = engine_distinct.compute_metrics([Distinctness(["att1"])]) + value = get_metric_value(metrics, "Distinctness", "att1") + # 3 distinct values / 6 rows = 0.5 + assert is_close(value, 0.5, FLOAT_EPSILON) + + def test_distinctness_all_unique(self, engine_distinct): + """Distinctness is 1.0 when all values are distinct.""" + metrics = engine_distinct.compute_metrics([Distinctness(["att2"])]) + value = get_metric_value(metrics, "Distinctness", "att2") + assert is_close(value, 1.0, FLOAT_EPSILON) + + def test_distinctness_all_same(self, engine_unique): + """Distinctness is 1/n when all values are the same.""" + metrics = engine_unique.compute_metrics([Distinctness(["all_same"])]) + value = get_metric_value(metrics, "Distinctness", "all_same") + # 1 distinct / 6 rows ≈ 0.167 + assert is_close(value, 1/6, FLOAT_EPSILON) + + +class TestUniquenessAnalyzer: + """Tests for the Uniqueness analyzer.""" + + def test_uniqueness_all_unique(self, engine_unique): + """Uniqueness is 1.0 when all values appear exactly once.""" + metrics = engine_unique.compute_metrics([Uniqueness(["unique_col"])]) + value = get_metric_value(metrics, "Uniqueness", "unique_col") + assert is_close(value, 1.0, FLOAT_EPSILON) + + def test_uniqueness_all_duplicated(self, engine_distinct): + """Uniqueness is 0.0 when all values are duplicated.""" + metrics = engine_distinct.compute_metrics([Uniqueness(["att1"])]) + value = get_metric_value(metrics, "Uniqueness", "att1") + # All values in att1 appear twice, so 0 unique + assert is_close(value, 0.0, FLOAT_EPSILON) + + def test_uniqueness_mixed(self, engine_unique): + """Uniqueness handles mixed case correctly.""" + metrics = engine_unique.compute_metrics([Uniqueness(["non_unique"])]) + value = get_metric_value(metrics, "Uniqueness", "non_unique") + # [1,1,2,2,3,3] - all duplicated, uniqueness = 0 + assert is_close(value, 0.0, FLOAT_EPSILON) + + +class TestUniqueValueRatioAnalyzer: + """Tests for the UniqueValueRatio analyzer.""" + + def test_unique_value_ratio_all_unique(self, engine_distinct): + """UniqueValueRatio is 1.0 when unique count = distinct count.""" + metrics = engine_distinct.compute_metrics([UniqueValueRatio(["att2"])]) + value = get_metric_value(metrics, "UniqueValueRatio", "att2") + # 6 unique / 6 distinct = 1.0 + assert is_close(value, 1.0, FLOAT_EPSILON) + + def test_unique_value_ratio_no_unique(self, engine_distinct): + """UniqueValueRatio is 0.0 when no values are unique.""" + metrics = engine_distinct.compute_metrics([UniqueValueRatio(["att1"])]) + value = get_metric_value(metrics, "UniqueValueRatio", "att1") + # 0 unique / 3 distinct = 0.0 + assert is_close(value, 0.0, FLOAT_EPSILON) + + +class TestCountDistinctAnalyzer: + """Tests for the CountDistinct analyzer.""" + + def test_count_distinct_basic(self, engine_full): + """CountDistinct counts unique values correctly.""" + metrics = engine_full.compute_metrics([CountDistinct(["att1"])]) + value = get_metric_value(metrics, "CountDistinct", "att1") + # "a", "b", "c" (a appears twice) = 3 distinct + assert is_close(value, 3.0, FLOAT_EPSILON) + + def test_count_distinct_all_unique(self, engine_distinct): + """CountDistinct equals row count when all values are distinct.""" + metrics = engine_distinct.compute_metrics([CountDistinct(["att2"])]) + value = get_metric_value(metrics, "CountDistinct", "att2") + assert is_close(value, 6.0, FLOAT_EPSILON) + + def test_count_distinct_with_duplicates(self, engine_distinct): + """CountDistinct counts only distinct values.""" + metrics = engine_distinct.compute_metrics([CountDistinct(["att1"])]) + value = get_metric_value(metrics, "CountDistinct", "att1") + assert is_close(value, 3.0, FLOAT_EPSILON) + + +class TestApproxCountDistinctAnalyzer: + """Tests for the ApproxCountDistinct analyzer.""" + + def test_approx_count_distinct_basic(self, engine_full): + """ApproxCountDistinct approximates distinct count.""" + metrics = engine_full.compute_metrics([ApproxCountDistinct("att1")]) + value = get_metric_value(metrics, "ApproxCountDistinct", "att1") + # Should be approximately 3 + assert is_close(value, 3.0, APPROX_TOLERANCE) + + def test_approx_count_distinct_all_unique(self, engine_distinct): + """ApproxCountDistinct handles all-unique column.""" + metrics = engine_distinct.compute_metrics([ApproxCountDistinct("att2")]) + value = get_metric_value(metrics, "ApproxCountDistinct", "att2") + # HyperLogLog can have higher variance on small datasets (up to 20% error) + assert is_close(value, 6.0, 0.2) + + +class TestApproxQuantileAnalyzer: + """Tests for the ApproxQuantile analyzer.""" + + def test_approx_quantile_median(self, engine_quantile): + """ApproxQuantile calculates median correctly.""" + metrics = engine_quantile.compute_metrics([ApproxQuantile("value", 0.5)]) + value = get_metric_value(metrics, "ApproxQuantile", "value") + # Median of [1,2,3,4,5,6,7,8,9,10] = 5.5 + assert is_close(value, 5.5, FLOAT_TOLERANCE) + + def test_approx_quantile_quartiles(self, engine_quantile): + """ApproxQuantile calculates quartiles.""" + metrics = engine_quantile.compute_metrics([ + ApproxQuantile("value", 0.25), + ApproxQuantile("value", 0.75), + ]) + # For small datasets, quantile calculation may vary slightly + q25 = get_metric_value(metrics, "ApproxQuantile", "value") + # Note: DuckDB uses QUANTILE_CONT which interpolates + assert q25 is not None + + +class TestCorrelationAnalyzer: + """Tests for the Correlation analyzer.""" + + def test_correlation_positive(self, engine_correlation): + """Correlation is 1.0 for perfectly positively correlated columns.""" + metrics = engine_correlation.compute_metrics([Correlation("x", "y")]) + value = get_metric_value(metrics, "Correlation", "x,y") + assert is_close(value, 1.0, FLOAT_TOLERANCE) + + def test_correlation_negative(self, engine_correlation): + """Correlation is -1.0 for perfectly negatively correlated columns.""" + metrics = engine_correlation.compute_metrics([Correlation("x", "z")]) + value = get_metric_value(metrics, "Correlation", "x,z") + assert is_close(value, -1.0, FLOAT_TOLERANCE) + + +class TestMutualInformationAnalyzer: + """Tests for the MutualInformation analyzer.""" + + def test_mutual_information_dependent(self, engine_mutual_info): + """MutualInformation is high for perfectly dependent columns.""" + metrics = engine_mutual_info.compute_metrics([ + MutualInformation(["x", "y_dependent"]) + ]) + value = get_metric_value(metrics, "MutualInformation", "x,y_dependent") + # Perfect dependency should have high MI (equal to entropy of x) + assert value is not None and value > 0 + + +class TestMaxLengthAnalyzer: + """Tests for the MaxLength analyzer.""" + + def test_maxlength_basic(self, engine_string_lengths): + """MaxLength finds longest string.""" + metrics = engine_string_lengths.compute_metrics([MaxLength("att1")]) + value = get_metric_value(metrics, "MaxLength", "att1") + assert is_close(value, 4.0, FLOAT_EPSILON) # "dddd" + + def test_maxlength_uniform(self, engine_string_lengths): + """MaxLength works with varying lengths.""" + metrics = engine_string_lengths.compute_metrics([MaxLength("att2")]) + value = get_metric_value(metrics, "MaxLength", "att2") + assert is_close(value, 5.0, FLOAT_EPSILON) # "hello", "world", "value" + + +class TestMinLengthAnalyzer: + """Tests for the MinLength analyzer.""" + + def test_minlength_empty_string(self, engine_string_lengths): + """MinLength handles empty string (length 0).""" + metrics = engine_string_lengths.compute_metrics([MinLength("att1")]) + value = get_metric_value(metrics, "MinLength", "att1") + assert is_close(value, 0.0, FLOAT_EPSILON) # "" + + def test_minlength_basic(self, engine_string_lengths): + """MinLength finds shortest string.""" + metrics = engine_string_lengths.compute_metrics([MinLength("att2")]) + value = get_metric_value(metrics, "MinLength", "att2") + assert is_close(value, 4.0, FLOAT_EPSILON) # "test", "data" + + +class TestPatternMatchAnalyzer: + """Tests for the PatternMatch analyzer.""" + + def test_pattern_match_email(self, engine_pattern): + """PatternMatch detects email pattern.""" + # Simple email regex + metrics = engine_pattern.compute_metrics([ + PatternMatch("email", r".*@.*\..*") + ]) + value = get_metric_value(metrics, "PatternMatch", "email") + # 4 valid emails out of 6 + assert is_close(value, 4/6, FLOAT_TOLERANCE) + + def test_pattern_match_all_match(self, engine_full): + """PatternMatch returns 1.0 when all rows match.""" + metrics = engine_full.compute_metrics([ + PatternMatch("att1", r"^[a-c]$") + ]) + value = get_metric_value(metrics, "PatternMatch", "att1") + # "a", "b", "c", "a" all match + assert is_close(value, 1.0, FLOAT_TOLERANCE) + + +class TestComplianceAnalyzer: + """Tests for the Compliance analyzer.""" + + def test_compliance_all_positive(self, engine_compliance): + """Compliance is 1.0 when all rows satisfy predicate.""" + metrics = engine_compliance.compute_metrics([ + Compliance("positive_check", "positive > 0") + ]) + value = get_metric_value(metrics, "Compliance", "positive_check") + assert is_close(value, 1.0, FLOAT_TOLERANCE) + + def test_compliance_partial(self, engine_compliance): + """Compliance reflects fraction satisfying predicate.""" + metrics = engine_compliance.compute_metrics([ + Compliance("mixed_check", "mixed > 0") + ]) + value = get_metric_value(metrics, "Compliance", "mixed_check") + # [-2,-1,0,1,2,3] -> 3 values > 0 + assert is_close(value, 0.5, FLOAT_TOLERANCE) + + def test_compliance_none(self, engine_compliance): + """Compliance is 0.0 when no rows satisfy predicate.""" + metrics = engine_compliance.compute_metrics([ + Compliance("negative_positive", "negative > 0") + ]) + value = get_metric_value(metrics, "Compliance", "negative_positive") + assert is_close(value, 0.0, FLOAT_TOLERANCE) + + +class TestEntropyAnalyzer: + """Tests for the Entropy analyzer.""" + + def test_entropy_uniform(self, engine_entropy): + """Entropy is log2(n) for uniform distribution.""" + metrics = engine_entropy.compute_metrics([Entropy("uniform")]) + value = get_metric_value(metrics, "Entropy", "uniform") + # 4 equally distributed values: entropy = log2(4) = 2.0 + assert is_close(value, 2.0, FLOAT_TOLERANCE) + + def test_entropy_constant(self, engine_entropy): + """Entropy is 0 for constant column.""" + metrics = engine_entropy.compute_metrics([Entropy("constant")]) + value = get_metric_value(metrics, "Entropy", "constant") + assert is_close(value, 0.0, FLOAT_TOLERANCE) + + def test_entropy_skewed(self, engine_entropy): + """Entropy is between 0 and max for skewed distribution.""" + metrics = engine_entropy.compute_metrics([Entropy("skewed")]) + value = get_metric_value(metrics, "Entropy", "skewed") + # Skewed distribution: 0 < entropy < log2(4) + assert value > 0.0 and value < 2.0 + + +class TestHistogramAnalyzer: + """Tests for the Histogram analyzer.""" + + def test_histogram_basic(self, engine_histogram): + """Histogram returns value distribution.""" + metrics = engine_histogram.compute_metrics([Histogram("category")]) + result = get_metric(metrics, "Histogram", "category") + assert result is not None + # Histogram value should be non-null (JSON or dict) + + +class TestDataTypeAnalyzer: + """Tests for the DataType analyzer.""" + + def test_datatype_numeric(self, engine_data_type): + """DataType identifies numeric columns.""" + metrics = engine_data_type.compute_metrics([DataType("pure_numeric")]) + result = get_metric(metrics, "DataType", "pure_numeric") + assert result is not None + + def test_datatype_string(self, engine_data_type): + """DataType identifies string columns.""" + metrics = engine_data_type.compute_metrics([DataType("strings")]) + result = get_metric(metrics, "DataType", "strings") + assert result is not None + + +class TestMultipleAnalyzers: + """Tests for running multiple analyzers together.""" + + def test_multiple_basic_analyzers(self, engine_numeric): + """Multiple analyzers can be computed in one call.""" + metrics = engine_numeric.compute_metrics([ + Size(), + Mean("att1"), + Sum("att1"), + Minimum("att1"), + Maximum("att1"), + ]) + + assert len(metrics) >= 5 + assert get_metric_value(metrics, "Size") == 6.0 + assert is_close(get_metric_value(metrics, "Mean", "att1"), 3.5, FLOAT_TOLERANCE) + assert is_close(get_metric_value(metrics, "Sum", "att1"), 21.0, FLOAT_TOLERANCE) + assert is_close(get_metric_value(metrics, "Minimum", "att1"), 1.0, FLOAT_TOLERANCE) + assert is_close(get_metric_value(metrics, "Maximum", "att1"), 6.0, FLOAT_TOLERANCE) + + def test_multiple_columns_same_analyzer(self, engine_full): + """Same analyzer type on multiple columns.""" + metrics = engine_full.compute_metrics([ + Completeness("att1"), + Completeness("att2"), + Completeness("item"), + ]) + + assert len(metrics) >= 3 + assert is_close(get_metric_value(metrics, "Completeness", "att1"), 1.0, FLOAT_EPSILON) + assert is_close(get_metric_value(metrics, "Completeness", "att2"), 1.0, FLOAT_EPSILON) + assert is_close(get_metric_value(metrics, "Completeness", "item"), 1.0, FLOAT_EPSILON) + + def test_mixed_analyzer_types(self, engine_full): + """Mix of different analyzer categories.""" + metrics = engine_full.compute_metrics([ + Size(), + Completeness("att1"), + CountDistinct(["att1"]), + MaxLength("att1"), + ]) + + assert get_metric_value(metrics, "Size") == 4.0 + assert is_close(get_metric_value(metrics, "Completeness", "att1"), 1.0, FLOAT_EPSILON) + assert get_metric_value(metrics, "CountDistinct", "att1") == 3.0 + assert get_metric_value(metrics, "MaxLength", "att1") == 1.0 # "a", "b", "c" + + +class TestAnalyzersWithWhere: + """Tests for analyzers with WHERE clause filtering.""" + + def test_size_where_a(self, engine_where): + """Size with WHERE filters correctly.""" + metrics = engine_where.compute_metrics([ + Size(where="category = 'A'"), + Size(where="category = 'B'"), + ]) + assert get_metric_value(metrics, "Size") == 2.0 # Both return 2 + + def test_completeness_where(self, engine_where): + """Completeness varies by WHERE filter.""" + metrics = engine_where.compute_metrics([ + Completeness("att1", where="category = 'A'"), + Completeness("att1", where="category = 'B'"), + ]) + # Category A: att1 = ["x", "y"] -> 2/2 complete + # Category B: att1 = [None, "w"] -> 1/2 complete + a_completeness = get_metric_value(metrics, "Completeness", "att1") + assert a_completeness is not None + + def test_mean_where(self, engine_where): + """Mean varies by WHERE filter.""" + metrics = engine_where.compute_metrics([ + Mean("value", where="category = 'A'"), + ]) + # Category A: value = [10, 20] -> mean = 15 + value = get_metric_value(metrics, "Mean", "value") + assert is_close(value, 15.0, FLOAT_TOLERANCE) + + +class TestEdgeCases: + """Tests for edge cases and boundary conditions.""" + + def test_empty_dataset_all_analyzers(self, engine_empty): + """Empty dataset handles gracefully.""" + metrics = engine_empty.compute_metrics([ + Size(), + Completeness("att1"), + ]) + assert get_metric_value(metrics, "Size") == 0.0 + + def test_all_null_column_stats(self, engine_all_null): + """All-NULL column returns appropriate values.""" + metrics = engine_all_null.compute_metrics([ + Completeness("value"), + Size(), + ]) + assert is_close(get_metric_value(metrics, "Completeness", "value"), 0.0, FLOAT_EPSILON) + assert get_metric_value(metrics, "Size") == 3.0 + + def test_special_characters(self, engine_escape): + """Special characters in data are handled.""" + metrics = engine_escape.compute_metrics([ + Size(), + Completeness("att1"), + MaxLength("att1"), + ]) + assert get_metric_value(metrics, "Size") == 8.0 + assert is_close(get_metric_value(metrics, "Completeness", "att1"), 1.0, FLOAT_EPSILON) diff --git a/tests/engines/test_duckdb_constraints.py b/tests/engines/test_duckdb_constraints.py new file mode 100644 index 0000000..07ffe31 --- /dev/null +++ b/tests/engines/test_duckdb_constraints.py @@ -0,0 +1,640 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DuckDB-only constraint tests. + +Tests all 32 constraint types against known expected values from the test datasets. +These tests do not require Spark and can run quickly in CI. +""" + +import pytest + +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.predicates import eq, gt, gte, lt, lte, between, is_one +from pydeequ.v2.verification import VerificationSuite +from pydeequ.engines import ConstraintStatus, CheckStatus + + +def get_constraint_result(results, constraint_substring: str): + """Find a constraint result by substring match on constraint name.""" + for r in results: + if constraint_substring in r.constraint: + return r + return None + + +def get_check_result(results, check_description: str): + """Find results for a specific check by description.""" + return [r for r in results if r.check_description == check_description] + + +class TestSizeConstraint: + """Tests for hasSize constraint.""" + + def test_has_size_success(self, engine_full): + """hasSize succeeds when size equals expected.""" + check = Check(CheckLevel.Error, "size check").hasSize(eq(4)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_size_failure(self, engine_full): + """hasSize fails when size doesn't match.""" + check = Check(CheckLevel.Error, "size check").hasSize(eq(10)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_size_range(self, engine_full): + """hasSize with between predicate.""" + check = Check(CheckLevel.Error, "size range").hasSize(between(3, 5)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_size_empty(self, engine_empty): + """hasSize correctly reports 0 for empty dataset.""" + check = Check(CheckLevel.Error, "empty size").hasSize(eq(0)) + results = engine_empty.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestCompletenessConstraints: + """Tests for completeness-related constraints.""" + + def test_is_complete_success(self, engine_full): + """isComplete succeeds for non-NULL column.""" + check = Check(CheckLevel.Error, "complete").isComplete("att1") + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_complete_failure(self, engine_missing): + """isComplete fails for column with NULLs.""" + check = Check(CheckLevel.Error, "complete").isComplete("att1") + results = engine_missing.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_completeness_success(self, engine_missing): + """hasCompleteness succeeds when threshold is met.""" + # att1 is 50% complete, check for >= 50% + check = Check(CheckLevel.Error, "partial complete").hasCompleteness("att1", gte(0.5)) + results = engine_missing.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_completeness_failure(self, engine_missing): + """hasCompleteness fails when threshold not met.""" + # att1 is 50% complete, check for >= 90% + check = Check(CheckLevel.Error, "high threshold").hasCompleteness("att1", gte(0.9)) + results = engine_missing.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_are_complete_success(self, engine_full): + """areComplete succeeds when all columns are complete.""" + check = Check(CheckLevel.Error, "multi complete").areComplete(["att1", "att2"]) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_are_complete_failure(self, engine_missing): + """areComplete fails when any column has NULLs.""" + check = Check(CheckLevel.Error, "multi complete").areComplete(["att1", "att2"]) + results = engine_missing.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_have_completeness_success(self, engine_missing): + """haveCompleteness succeeds for combined column threshold.""" + check = Check(CheckLevel.Error, "combined").haveCompleteness(["att1", "att2"], gte(0.5)) + results = engine_missing.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestUniquenessConstraints: + """Tests for uniqueness-related constraints.""" + + def test_is_unique_success(self, engine_unique): + """isUnique succeeds when all values are unique.""" + check = Check(CheckLevel.Error, "unique").isUnique("unique_col") + results = engine_unique.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_unique_failure(self, engine_unique): + """isUnique fails when there are duplicates.""" + check = Check(CheckLevel.Error, "not unique").isUnique("non_unique") + results = engine_unique.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_uniqueness_success(self, engine_unique): + """hasUniqueness succeeds when threshold met.""" + check = Check(CheckLevel.Error, "uniqueness").hasUniqueness(["unique_col"], is_one()) + results = engine_unique.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_uniqueness_failure(self, engine_distinct): + """hasUniqueness fails when uniqueness is below threshold.""" + # att1 has all duplicates, uniqueness = 0 + check = Check(CheckLevel.Error, "low uniqueness").hasUniqueness(["att1"], gte(0.5)) + results = engine_distinct.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_distinctness_success(self, engine_distinct): + """hasDistinctness succeeds when threshold met.""" + # att2 has 6 distinct / 6 rows = 1.0 + check = Check(CheckLevel.Error, "distinct").hasDistinctness(["att2"], is_one()) + results = engine_distinct.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_distinctness_partial(self, engine_distinct): + """hasDistinctness with partial distinctness.""" + # att1 has 3 distinct / 6 rows = 0.5 + check = Check(CheckLevel.Error, "partial distinct").hasDistinctness(["att1"], gte(0.5)) + results = engine_distinct.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_unique_value_ratio_success(self, engine_distinct): + """hasUniqueValueRatio succeeds for all-unique column.""" + check = Check(CheckLevel.Error, "uvr").hasUniqueValueRatio(["att2"], is_one()) + results = engine_distinct.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_unique_value_ratio_zero(self, engine_distinct): + """hasUniqueValueRatio for all-duplicated column.""" + # att1: 0 unique / 3 distinct = 0 + check = Check(CheckLevel.Error, "uvr zero").hasUniqueValueRatio(["att1"], eq(0)) + results = engine_distinct.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestStatisticalConstraints: + """Tests for statistical constraints.""" + + def test_has_min_success(self, engine_numeric): + """hasMin succeeds when minimum matches.""" + check = Check(CheckLevel.Error, "min").hasMin("att1", eq(1)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_min_failure(self, engine_numeric): + """hasMin fails when minimum doesn't match.""" + check = Check(CheckLevel.Error, "min fail").hasMin("att1", eq(5)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_max_success(self, engine_numeric): + """hasMax succeeds when maximum matches.""" + check = Check(CheckLevel.Error, "max").hasMax("att1", eq(6)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_max_failure(self, engine_numeric): + """hasMax fails when maximum doesn't match.""" + check = Check(CheckLevel.Error, "max fail").hasMax("att1", eq(100)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_mean_success(self, engine_numeric): + """hasMean succeeds when mean matches.""" + check = Check(CheckLevel.Error, "mean").hasMean("att1", eq(3.5)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_mean_range(self, engine_numeric): + """hasMean with range predicate.""" + check = Check(CheckLevel.Error, "mean range").hasMean("att1", between(3.0, 4.0)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_sum_success(self, engine_numeric): + """hasSum succeeds when sum matches.""" + check = Check(CheckLevel.Error, "sum").hasSum("att1", eq(21)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_standard_deviation_success(self, engine_numeric): + """hasStandardDeviation with range check.""" + check = Check(CheckLevel.Error, "stddev").hasStandardDeviation("att1", between(1.5, 2.0)) + results = engine_numeric.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_approx_count_distinct_success(self, engine_full): + """hasApproxCountDistinct succeeds when count is approximately correct.""" + check = Check(CheckLevel.Error, "approx distinct").hasApproxCountDistinct("att1", between(2, 4)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestQuantileConstraints: + """Tests for quantile constraints.""" + + def test_has_approx_quantile_median(self, engine_quantile): + """hasApproxQuantile for median.""" + check = Check(CheckLevel.Error, "median").hasApproxQuantile("value", 0.5, between(5.0, 6.0)) + results = engine_quantile.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestCorrelationConstraints: + """Tests for correlation constraints.""" + + def test_has_correlation_positive(self, engine_correlation): + """hasCorrelation for perfectly correlated columns.""" + check = Check(CheckLevel.Error, "positive corr").hasCorrelation("x", "y", is_one()) + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_correlation_negative(self, engine_correlation): + """hasCorrelation for negative correlation.""" + check = Check(CheckLevel.Error, "negative corr").hasCorrelation("x", "z", eq(-1)) + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestEntropyConstraints: + """Tests for entropy constraints.""" + + def test_has_entropy_uniform(self, engine_entropy): + """hasEntropy for uniform distribution.""" + check = Check(CheckLevel.Error, "entropy").hasEntropy("uniform", eq(2.0)) + results = engine_entropy.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_entropy_constant(self, engine_entropy): + """hasEntropy for constant column (entropy=0).""" + check = Check(CheckLevel.Error, "zero entropy").hasEntropy("constant", eq(0)) + results = engine_entropy.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestMutualInformationConstraints: + """Tests for mutual information constraints.""" + + def test_has_mutual_information(self, engine_mutual_info): + """hasMutualInformation for dependent columns.""" + check = Check(CheckLevel.Error, "mi").hasMutualInformation("x", "y_dependent", gt(0)) + results = engine_mutual_info.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestStringLengthConstraints: + """Tests for string length constraints.""" + + def test_has_min_length_success(self, engine_string_lengths): + """hasMinLength for empty string (0).""" + check = Check(CheckLevel.Error, "min length").hasMinLength("att1", eq(0)) + results = engine_string_lengths.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_min_length_failure(self, engine_string_lengths): + """hasMinLength fails when min length is higher.""" + check = Check(CheckLevel.Error, "min length fail").hasMinLength("att1", gte(2)) + results = engine_string_lengths.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_has_max_length_success(self, engine_string_lengths): + """hasMaxLength succeeds when max is correct.""" + check = Check(CheckLevel.Error, "max length").hasMaxLength("att1", eq(4)) + results = engine_string_lengths.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_max_length_bound(self, engine_string_lengths): + """hasMaxLength with upper bound.""" + check = Check(CheckLevel.Error, "max bound").hasMaxLength("att1", lte(5)) + results = engine_string_lengths.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestPatternConstraints: + """Tests for pattern matching constraints.""" + + def test_has_pattern_success(self, engine_full): + """hasPattern succeeds when pattern matches all rows.""" + check = Check(CheckLevel.Error, "pattern").hasPattern("att1", r"^[a-c]$", is_one()) + results = engine_full.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_pattern_partial(self, engine_pattern): + """hasPattern with partial match threshold.""" + # Email pattern matches 4/6 rows + check = Check(CheckLevel.Error, "email pattern").hasPattern("email", r".*@.*\..*", gte(0.5)) + results = engine_pattern.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_has_pattern_failure(self, engine_pattern): + """hasPattern fails when match rate is below threshold.""" + check = Check(CheckLevel.Error, "strict pattern").hasPattern("email", r".*@.*\..*", is_one()) + results = engine_pattern.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + +class TestEmailUrlConstraints: + """Tests for email and URL pattern constraints.""" + + def test_contains_email_success(self, engine_pattern): + """containsEmail with threshold.""" + check = Check(CheckLevel.Error, "email").containsEmail("email", gte(0.5)) + results = engine_pattern.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_contains_url_failure(self, engine_pattern): + """containsURL fails for non-URL column.""" + check = Check(CheckLevel.Error, "url").containsURL("email", gte(0.5)) + results = engine_pattern.run_checks([check]) + result = results[0] + # No URLs in email column + assert result.constraint_status == ConstraintStatus.Failure + + +class TestNumericConstraints: + """Tests for numeric value constraints.""" + + def test_is_positive_success(self, engine_compliance): + """isPositive succeeds for all-positive column.""" + check = Check(CheckLevel.Error, "positive").isPositive("positive", is_one()) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_positive_failure(self, engine_compliance): + """isPositive fails for negative column.""" + check = Check(CheckLevel.Error, "not positive").isPositive("negative", gte(0.5)) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Failure + + def test_is_non_negative_success(self, engine_compliance): + """isNonNegative for positive column.""" + check = Check(CheckLevel.Error, "non-neg").isNonNegative("positive", is_one()) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_non_negative_partial(self, engine_compliance): + """isNonNegative with partial compliance.""" + # mixed: [-2,-1,0,1,2,3] -> 4/6 non-negative + check = Check(CheckLevel.Error, "partial non-neg").isNonNegative("mixed", gte(0.5)) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestColumnComparisonConstraints: + """Tests for column comparison constraints.""" + + def test_is_less_than(self, engine_correlation): + """isLessThan for ordered columns.""" + # x = [1,2,3,4,5], y = [2,4,6,8,10], so x < y always + check = Check(CheckLevel.Error, "less than").isLessThan("x", "y") + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_less_than_or_equal_to(self, engine_correlation): + """isLessThanOrEqualTo for ordered columns.""" + check = Check(CheckLevel.Error, "lte").isLessThanOrEqualTo("x", "y") + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_greater_than(self, engine_correlation): + """isGreaterThan for reverse-ordered columns.""" + # y > x always + check = Check(CheckLevel.Error, "greater than").isGreaterThan("y", "x") + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_greater_than_or_equal_to(self, engine_correlation): + """isGreaterThanOrEqualTo for ordered columns.""" + check = Check(CheckLevel.Error, "gte").isGreaterThanOrEqualTo("y", "x") + results = engine_correlation.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestContainedInConstraint: + """Tests for isContainedIn constraint.""" + + def test_is_contained_in_success(self, engine_contained_in): + """isContainedIn succeeds when all values are in allowed set.""" + check = Check(CheckLevel.Error, "contained").isContainedIn( + "status", ["active", "inactive", "pending"], is_one() + ) + results = engine_contained_in.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_is_contained_in_failure(self, engine_contained_in): + """isContainedIn fails when some values are not in set.""" + check = Check(CheckLevel.Error, "not contained").isContainedIn( + "category", ["A", "B", "C"], is_one() + ) + results = engine_contained_in.run_checks([check]) + result = results[0] + # "D" is not in the allowed set + assert result.constraint_status == ConstraintStatus.Failure + + def test_is_contained_in_partial(self, engine_contained_in): + """isContainedIn with threshold for partial match.""" + check = Check(CheckLevel.Error, "partial contained").isContainedIn( + "category", ["A", "B", "C"], gte(0.8) + ) + results = engine_contained_in.run_checks([check]) + result = results[0] + # 5/6 = 0.833 in allowed set + assert result.constraint_status == ConstraintStatus.Success + + +class TestSatisfiesConstraint: + """Tests for satisfies constraint.""" + + def test_satisfies_simple(self, engine_compliance): + """satisfies with simple predicate.""" + check = Check(CheckLevel.Error, "satisfies").satisfies("positive > 0", "positive_check", is_one()) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_satisfies_complex(self, engine_compliance): + """satisfies with complex predicate.""" + check = Check(CheckLevel.Error, "complex").satisfies( + "mixed >= -2 AND mixed <= 3", "range_check", is_one() + ) + results = engine_compliance.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_satisfies_partial(self, engine_compliance): + """satisfies with partial compliance.""" + check = Check(CheckLevel.Error, "partial satisfies").satisfies("mixed > 0", "partial_check", gte(0.4)) + results = engine_compliance.run_checks([check]) + result = results[0] + # 3/6 = 0.5 > 0.4 + assert result.constraint_status == ConstraintStatus.Success + + +class TestCheckLevels: + """Tests for check levels (Error vs Warning).""" + + def test_error_level_failure(self, engine_full): + """Error level check results in Error status on failure.""" + check = Check(CheckLevel.Error, "error check").hasSize(eq(100)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.check_level == "Error" + assert result.check_status == CheckStatus.Error + + def test_warning_level_failure(self, engine_full): + """Warning level check results in Warning status on failure.""" + check = Check(CheckLevel.Warning, "warning check").hasSize(eq(100)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.check_level == "Warning" + assert result.check_status == CheckStatus.Warning + + def test_error_level_success(self, engine_full): + """Error level check results in Success status on pass.""" + check = Check(CheckLevel.Error, "pass check").hasSize(eq(4)) + results = engine_full.run_checks([check]) + result = results[0] + assert result.check_status == CheckStatus.Success + + +class TestMultipleConstraints: + """Tests for multiple constraints in one check.""" + + def test_all_pass(self, engine_full): + """All constraints pass results in Success.""" + check = (Check(CheckLevel.Error, "all pass") + .hasSize(eq(4)) + .isComplete("att1") + .isComplete("att2")) + results = engine_full.run_checks([check]) + assert all(r.constraint_status == ConstraintStatus.Success for r in results) + assert results[0].check_status == CheckStatus.Success + + def test_some_fail(self, engine_missing): + """Some constraints fail results in overall failure.""" + check = (Check(CheckLevel.Error, "some fail") + .hasSize(eq(12)) # Pass + .isComplete("att1") # Fail + .hasCompleteness("att2", gte(0.5))) # Pass + results = engine_missing.run_checks([check]) + # Check that at least one constraint failed + failed = [r for r in results if r.constraint_status == ConstraintStatus.Failure] + assert len(failed) >= 1 + # Overall check should fail + assert results[0].check_status == CheckStatus.Error + + def test_multiple_checks(self, engine_numeric): + """Multiple checks can be run together.""" + check1 = Check(CheckLevel.Error, "size check").hasSize(eq(6)) + check2 = Check(CheckLevel.Error, "mean check").hasMean("att1", eq(3.5)) + check3 = Check(CheckLevel.Warning, "sum check").hasSum("att1", eq(21)) + + results = engine_numeric.run_checks([check1, check2, check3]) + # All should pass + assert len(results) == 3 + assert all(r.constraint_status == ConstraintStatus.Success for r in results) + + +class TestConstraintsWithWhere: + """Tests for constraints with WHERE clause filtering.""" + + @pytest.mark.skip(reason="WHERE clause support not yet implemented in Check API") + def test_completeness_where(self, engine_where): + """Completeness constraint with WHERE filter.""" + check = Check(CheckLevel.Error, "filtered completeness").hasCompleteness( + "att1", is_one(), where="category = 'A'" + ) + results = engine_where.run_checks([check]) + result = results[0] + # Category A: att1 is complete + assert result.constraint_status == ConstraintStatus.Success + + @pytest.mark.skip(reason="WHERE clause support not yet implemented in Check API") + def test_size_where(self, engine_where): + """Size constraint with WHERE filter.""" + check = Check(CheckLevel.Error, "filtered size").hasSize( + eq(2), where="category = 'A'" + ) + results = engine_where.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + +class TestEdgeCases: + """Tests for edge cases and boundary conditions.""" + + def test_empty_dataset(self, engine_empty): + """Constraints on empty dataset.""" + check = (Check(CheckLevel.Error, "empty check") + .hasSize(eq(0))) + results = engine_empty.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success + + def test_single_row(self, engine_single): + """Constraints on single-row dataset.""" + check = (Check(CheckLevel.Error, "single row") + .hasSize(eq(1)) + .isComplete("att1") + .hasMin("item", eq(1)) + .hasMax("item", eq(1))) + results = engine_single.run_checks([check]) + assert all(r.constraint_status == ConstraintStatus.Success for r in results) + + def test_all_null_column(self, engine_all_null): + """Constraints on all-NULL column.""" + check = (Check(CheckLevel.Error, "all null") + .hasCompleteness("value", eq(0))) + results = engine_all_null.run_checks([check]) + result = results[0] + assert result.constraint_status == ConstraintStatus.Success diff --git a/tests/engines/test_duckdb_profiles.py b/tests/engines/test_duckdb_profiles.py new file mode 100644 index 0000000..01c701a --- /dev/null +++ b/tests/engines/test_duckdb_profiles.py @@ -0,0 +1,266 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DuckDB-only profiling tests. + +Tests the column profiling functionality of the DuckDB engine. +""" + +import math +import pytest + +from tests.engines.fixtures.datasets import ( + FLOAT_EPSILON, + FLOAT_TOLERANCE, + is_close, +) + + +def get_profile_by_column(profiles, column_name: str): + """Find a column profile by column name.""" + for p in profiles: + if p.column == column_name: + return p + return None + + +class TestBasicProfiling: + """Tests for basic profiling functionality.""" + + def test_profile_all_columns(self, engine_full): + """Profile returns data for all columns.""" + profiles = engine_full.profile_columns() + assert len(profiles) >= 4 # att1, att2, item, price + + def test_profile_specific_columns(self, engine_full): + """Profile can be restricted to specific columns.""" + profiles = engine_full.profile_columns(columns=["att1", "item"]) + column_names = [p.column for p in profiles] + assert "att1" in column_names + assert "item" in column_names + + def test_profile_column_name(self, engine_full): + """Profile contains correct column names.""" + profiles = engine_full.profile_columns() + column_names = [p.column for p in profiles] + assert "att1" in column_names + assert "att2" in column_names + + +class TestCompletenessProfile: + """Tests for completeness in profiles.""" + + def test_completeness_full(self, engine_full): + """Completeness is 1.0 for complete columns.""" + profiles = engine_full.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert is_close(profile.completeness, 1.0, FLOAT_EPSILON) + + def test_completeness_partial(self, engine_missing): + """Completeness reflects NULL ratio.""" + profiles = engine_missing.profile_columns(columns=["att1", "att2"]) + att1_profile = get_profile_by_column(profiles, "att1") + att2_profile = get_profile_by_column(profiles, "att2") + assert is_close(att1_profile.completeness, 0.5, FLOAT_EPSILON) # 6/12 + assert is_close(att2_profile.completeness, 0.75, FLOAT_EPSILON) # 9/12 + + def test_completeness_all_null(self, engine_all_null): + """Completeness is 0 for all-NULL column.""" + profiles = engine_all_null.profile_columns(columns=["value"]) + profile = get_profile_by_column(profiles, "value") + assert is_close(profile.completeness, 0.0, FLOAT_EPSILON) + + +class TestDistinctValuesProfile: + """Tests for approximate distinct values in profiles.""" + + def test_distinct_values_unique(self, engine_unique): + """Distinct count for unique column.""" + profiles = engine_unique.profile_columns(columns=["unique_col"]) + profile = get_profile_by_column(profiles, "unique_col") + assert profile.approx_distinct_values == 6 + + def test_distinct_values_duplicates(self, engine_distinct): + """Distinct count handles duplicates correctly.""" + profiles = engine_distinct.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + # att1: ["a", "a", "b", "b", "c", "c"] -> 3 distinct + assert profile.approx_distinct_values == 3 + + +class TestDataTypeProfile: + """Tests for data type detection in profiles.""" + + def test_data_type_string(self, engine_full): + """Data type detection for string column.""" + profiles = engine_full.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.data_type is not None + # Should be some form of string type + assert "str" in profile.data_type.lower() or "char" in profile.data_type.lower() or "text" in profile.data_type.lower() or "object" in profile.data_type.lower() + + def test_data_type_numeric(self, engine_numeric): + """Data type detection for numeric column.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.data_type is not None + + +class TestNumericProfileStatistics: + """Tests for numeric statistics in profiles.""" + + def test_mean_numeric(self, engine_numeric): + """Mean is calculated for numeric columns.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.mean is not None + assert is_close(profile.mean, 3.5, FLOAT_TOLERANCE) + + def test_min_numeric(self, engine_numeric): + """Minimum is calculated for numeric columns.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.minimum is not None + assert is_close(profile.minimum, 1.0, FLOAT_TOLERANCE) + + def test_max_numeric(self, engine_numeric): + """Maximum is calculated for numeric columns.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.maximum is not None + assert is_close(profile.maximum, 6.0, FLOAT_TOLERANCE) + + def test_sum_numeric(self, engine_numeric): + """Sum is calculated for numeric columns.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + assert profile.sum is not None + assert is_close(profile.sum, 21.0, FLOAT_TOLERANCE) + + def test_stddev_numeric(self, engine_numeric): + """Standard deviation is calculated for numeric columns.""" + profiles = engine_numeric.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + if profile.std_dev is not None: + assert is_close(profile.std_dev, 1.8708286933869707, FLOAT_TOLERANCE) + + def test_numeric_with_nulls(self, engine_numeric): + """Numeric statistics handle NULLs correctly.""" + profiles = engine_numeric.profile_columns(columns=["att2"]) + profile = get_profile_by_column(profiles, "att2") + # att2 has values [1,2,3,4,5,NULL] + if profile.mean is not None: + assert is_close(profile.mean, 3.0, FLOAT_TOLERANCE) # (1+2+3+4+5)/5 + + +class TestStringProfileStatistics: + """Tests for string column profiles.""" + + def test_string_column_no_numeric_stats(self, engine_full): + """String columns don't have numeric statistics.""" + profiles = engine_full.profile_columns(columns=["att1"]) + profile = get_profile_by_column(profiles, "att1") + # String column shouldn't have meaningful numeric stats + # (or they might be None) + # Just verify we get a profile back + assert profile is not None + assert profile.completeness is not None + + +class TestHistogramProfile: + """Tests for histogram in profiles.""" + + def test_histogram_low_cardinality(self, engine_histogram): + """Histogram is generated for low cardinality columns.""" + profiles = engine_histogram.profile_columns( + columns=["category"], + low_cardinality_threshold=10 + ) + profile = get_profile_by_column(profiles, "category") + # Should have histogram for 4-value column with threshold 10 + if profile.histogram is not None: + assert len(profile.histogram) > 0 + + def test_histogram_high_cardinality(self, engine_unique): + """Histogram might not be generated for high cardinality columns.""" + profiles = engine_unique.profile_columns( + columns=["unique_col"], + low_cardinality_threshold=3 + ) + profile = get_profile_by_column(profiles, "unique_col") + # With 6 distinct and threshold 3, might skip histogram + assert profile is not None + + +class TestQuantileProfile: + """Tests for quantile/percentile information in profiles.""" + + def test_percentiles_numeric(self, engine_quantile): + """Percentiles are calculated for numeric columns.""" + profiles = engine_quantile.profile_columns(columns=["value"]) + profile = get_profile_by_column(profiles, "value") + # Check for percentile attributes if present + if hasattr(profile, 'approx_percentiles') and profile.approx_percentiles: + # Should have some percentile data + assert len(profile.approx_percentiles) >= 0 + + +class TestEdgeCases: + """Tests for edge cases in profiling.""" + + def test_empty_dataset(self, engine_empty): + """Profiling empty dataset.""" + profiles = engine_empty.profile_columns() + # Should return profiles (possibly with default/None values) + assert isinstance(profiles, list) + + def test_single_row(self, engine_single): + """Profiling single-row dataset.""" + profiles = engine_single.profile_columns(columns=["att1", "item"]) + att1_profile = get_profile_by_column(profiles, "att1") + item_profile = get_profile_by_column(profiles, "item") + + assert att1_profile.completeness == 1.0 + assert att1_profile.approx_distinct_values == 1 + + if item_profile.mean is not None: + assert item_profile.mean == 1.0 + if item_profile.minimum is not None: + assert item_profile.minimum == 1.0 + if item_profile.maximum is not None: + assert item_profile.maximum == 1.0 + + def test_all_null_column(self, engine_all_null): + """Profiling all-NULL column.""" + profiles = engine_all_null.profile_columns(columns=["value"]) + profile = get_profile_by_column(profiles, "value") + assert profile.completeness == 0.0 + # Statistics should be None or NaN for all-NULL column + if profile.mean is not None and not math.isnan(profile.mean): + # Some implementations might return 0 or None + pass + + +class TestProfileDataFrame: + """Tests for profile to DataFrame conversion.""" + + def test_profiles_to_dataframe(self, engine_full): + """Profiles can be converted to DataFrame.""" + profiles = engine_full.profile_columns() + df = engine_full.profiles_to_dataframe(profiles) + + assert df is not None + assert len(df) > 0 + assert "column" in df.columns + assert "completeness" in df.columns diff --git a/tests/engines/test_duckdb_suggestions.py b/tests/engines/test_duckdb_suggestions.py new file mode 100644 index 0000000..3da9a51 --- /dev/null +++ b/tests/engines/test_duckdb_suggestions.py @@ -0,0 +1,287 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DuckDB-only constraint suggestion tests. + +Tests the constraint suggestion functionality of the DuckDB engine. +""" + +import pytest + +from pydeequ.v2.suggestions import Rules + + +def get_suggestions_for_column(suggestions, column_name: str): + """Get all suggestions for a specific column.""" + return [s for s in suggestions if s.column_name == column_name] + + +def get_suggestions_by_constraint(suggestions, constraint_name: str): + """Get all suggestions for a specific constraint type.""" + return [s for s in suggestions if constraint_name in s.constraint_name] + + +class TestBasicSuggestions: + """Tests for basic suggestion functionality.""" + + def test_default_rules_generate_suggestions(self, engine_full): + """DEFAULT rules generate suggestions for complete columns.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + # Should generate some suggestions for complete data + assert isinstance(suggestions, list) + + def test_suggestions_have_required_fields(self, engine_full): + """Suggestions contain all required fields.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + if suggestions: + suggestion = suggestions[0] + assert hasattr(suggestion, 'column_name') + assert hasattr(suggestion, 'constraint_name') + assert hasattr(suggestion, 'description') + assert hasattr(suggestion, 'suggesting_rule') + + def test_restrict_to_columns(self, engine_full): + """Suggestions can be restricted to specific columns.""" + suggestions = engine_full.suggest_constraints( + columns=["att1"], + rules=[Rules.DEFAULT] + ) + # All suggestions should be for att1 (or dataset-level) + column_suggestions = [s for s in suggestions if s.column_name] + for s in column_suggestions: + assert s.column_name == "att1" or s.column_name is None + + +class TestCompletenessRuleSuggestions: + """Tests for completeness-related suggestions.""" + + def test_complete_column_suggestions(self, engine_full): + """Complete columns get completeness suggestions.""" + suggestions = engine_full.suggest_constraints( + columns=["att1"], + rules=[Rules.DEFAULT] + ) + # Should suggest isComplete or hasCompleteness for complete column + completeness_suggestions = get_suggestions_by_constraint(suggestions, "Complete") + # May or may not generate based on implementation + assert isinstance(suggestions, list) + + def test_incomplete_column_suggestions(self, engine_missing): + """Incomplete columns may get retain completeness suggestions.""" + suggestions = engine_missing.suggest_constraints( + columns=["att1"], + rules=[Rules.DEFAULT] + ) + # att1 is 50% complete - might suggest retaining that level + assert isinstance(suggestions, list) + + +class TestUniquenessRuleSuggestions: + """Tests for uniqueness-related suggestions.""" + + def test_unique_column_suggestions(self, engine_unique): + """Unique columns get uniqueness suggestions with COMMON rules.""" + suggestions = engine_unique.suggest_constraints( + columns=["unique_col"], + rules=[Rules.COMMON] + ) + # Should suggest isUnique or hasUniqueness for unique column + uniqueness_suggestions = get_suggestions_by_constraint(suggestions, "Unique") + # Implementation dependent + assert isinstance(suggestions, list) + + +class TestNumericalRuleSuggestions: + """Tests for numerical constraint suggestions.""" + + def test_numeric_column_suggestions(self, engine_numeric): + """Numeric columns get statistical suggestions with NUMERICAL rules.""" + suggestions = engine_numeric.suggest_constraints( + columns=["att1"], + rules=[Rules.NUMERICAL] + ) + # Should suggest hasMin, hasMax, hasMean for numeric column + assert isinstance(suggestions, list) + + def test_min_max_suggestions(self, engine_numeric): + """Numeric columns may get min/max suggestions.""" + suggestions = engine_numeric.suggest_constraints( + columns=["att1"], + rules=[Rules.NUMERICAL] + ) + min_suggestions = get_suggestions_by_constraint(suggestions, "Min") + max_suggestions = get_suggestions_by_constraint(suggestions, "Max") + # May have min/max suggestions + assert isinstance(suggestions, list) + + +class TestStringRuleSuggestions: + """Tests for string-related suggestions.""" + + def test_string_column_suggestions(self, engine_string_lengths): + """String columns get length suggestions with STRING rules.""" + suggestions = engine_string_lengths.suggest_constraints( + columns=["att1"], + rules=[Rules.STRING] + ) + # Should suggest hasMinLength, hasMaxLength for string column + assert isinstance(suggestions, list) + + +class TestCategoricalRuleSuggestions: + """Tests for categorical constraint suggestions.""" + + def test_categorical_column_suggestions(self, engine_contained_in): + """Low-cardinality columns may get containment suggestions.""" + suggestions = engine_contained_in.suggest_constraints( + columns=["status"], + rules=[Rules.DEFAULT] + ) + # May suggest isContainedIn for categorical column + assert isinstance(suggestions, list) + + +class TestMultipleRules: + """Tests for combining multiple rule sets.""" + + def test_extended_rules(self, engine_numeric): + """EXTENDED rules combine all rule sets.""" + suggestions = engine_numeric.suggest_constraints( + rules=[Rules.EXTENDED] + ) + # Should get suggestions from all rule categories + assert isinstance(suggestions, list) + + def test_multiple_rule_sets(self, engine_numeric): + """Multiple rule sets can be combined.""" + suggestions = engine_numeric.suggest_constraints( + rules=[Rules.DEFAULT, Rules.NUMERICAL] + ) + assert isinstance(suggestions, list) + + +class TestSuggestionContent: + """Tests for suggestion content quality.""" + + def test_suggestion_has_description(self, engine_full): + """Suggestions include human-readable descriptions.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + if suggestions: + for s in suggestions: + assert s.description is not None + assert len(s.description) > 0 + + def test_suggestion_has_rule_name(self, engine_full): + """Suggestions identify the suggesting rule.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + if suggestions: + for s in suggestions: + assert s.suggesting_rule is not None + + def test_suggestion_has_current_value(self, engine_full): + """Suggestions include current metric value.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + if suggestions: + for s in suggestions: + # current_value may be present + assert hasattr(s, 'current_value') + + def test_suggestion_has_code_snippet(self, engine_full): + """Suggestions may include code for constraint.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + if suggestions: + for s in suggestions: + # code_for_constraint may be present + assert hasattr(s, 'code_for_constraint') + + +class TestEdgeCases: + """Tests for edge cases in suggestions.""" + + def test_empty_dataset_suggestions(self, engine_empty): + """Suggestions on empty dataset.""" + suggestions = engine_empty.suggest_constraints(rules=[Rules.DEFAULT]) + # Should handle gracefully + assert isinstance(suggestions, list) + + def test_single_row_suggestions(self, engine_single): + """Suggestions on single-row dataset.""" + suggestions = engine_single.suggest_constraints(rules=[Rules.DEFAULT]) + assert isinstance(suggestions, list) + + def test_all_null_column_suggestions(self, engine_all_null): + """Suggestions on all-NULL column.""" + suggestions = engine_all_null.suggest_constraints( + columns=["value"], + rules=[Rules.DEFAULT] + ) + # Should handle all-NULL gracefully + assert isinstance(suggestions, list) + + +class TestSuggestionDataFrame: + """Tests for suggestion to DataFrame conversion.""" + + def test_suggestions_to_dataframe(self, engine_full): + """Suggestions can be converted to DataFrame.""" + suggestions = engine_full.suggest_constraints(rules=[Rules.DEFAULT]) + df = engine_full.suggestions_to_dataframe(suggestions) + + assert df is not None + if len(suggestions) > 0: + assert len(df) > 0 + assert "column_name" in df.columns + assert "constraint_name" in df.columns + + +class TestDatasetSpecificSuggestions: + """Tests for suggestions on specific dataset types.""" + + def test_numeric_dataset(self, engine_numeric): + """Numeric dataset gets appropriate suggestions.""" + suggestions = engine_numeric.suggest_constraints( + rules=[Rules.DEFAULT, Rules.NUMERICAL] + ) + # Should have suggestions for numeric columns + numeric_suggestions = get_suggestions_for_column(suggestions, "att1") + assert isinstance(suggestions, list) + + def test_string_dataset(self, engine_string_lengths): + """String dataset gets appropriate suggestions.""" + suggestions = engine_string_lengths.suggest_constraints( + rules=[Rules.DEFAULT, Rules.STRING] + ) + string_suggestions = get_suggestions_for_column(suggestions, "att1") + assert isinstance(suggestions, list) + + def test_mixed_type_dataset(self, engine_full): + """Mixed-type dataset handles all columns.""" + suggestions = engine_full.suggest_constraints( + rules=[Rules.EXTENDED] + ) + # Should have suggestions for different column types + assert isinstance(suggestions, list) + + +class TestNonNegativeRuleSuggestions: + """Tests for non-negative number suggestions.""" + + def test_positive_column_suggestions(self, engine_compliance): + """All-positive columns may get non-negative suggestions.""" + suggestions = engine_compliance.suggest_constraints( + columns=["positive"], + rules=[Rules.DEFAULT] + ) + # May suggest isNonNegative for positive column + assert isinstance(suggestions, list) diff --git a/tests/engines/test_operators.py b/tests/engines/test_operators.py new file mode 100644 index 0000000..a02d9fc --- /dev/null +++ b/tests/engines/test_operators.py @@ -0,0 +1,484 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for SQL operators. + +These tests verify the operator abstractions work correctly in isolation, +testing SQL generation and result extraction separately from actual +database execution. +""" + +import pandas as pd +import pytest + +from pydeequ.engines import MetricResult +from pydeequ.engines.operators import ( + # Scan operators + SizeOperator, + CompletenessOperator, + MeanOperator, + SumOperator, + MinimumOperator, + MaximumOperator, + StandardDeviationOperator, + MaxLengthOperator, + MinLengthOperator, + PatternMatchOperator, + ComplianceOperator, + CorrelationOperator, + CountDistinctOperator, + ApproxCountDistinctOperator, + # Grouping operators + DistinctnessOperator, + UniquenessOperator, + UniqueValueRatioOperator, + EntropyOperator, + MutualInformationOperator, + # Factory + OperatorFactory, + # Mixins + WhereClauseMixin, + SafeExtractMixin, + ColumnAliasMixin, +) + + +class TestWhereClauseMixin: + """Tests for WhereClauseMixin.""" + + def test_wrap_agg_with_where_no_condition(self): + """Test wrapping aggregation without WHERE clause.""" + class TestClass(WhereClauseMixin): + where = None + + obj = TestClass() + result = obj.wrap_agg_with_where("AVG", "price") + assert result == "AVG(price)" + + def test_wrap_agg_with_where_with_condition(self): + """Test wrapping aggregation with WHERE clause.""" + class TestClass(WhereClauseMixin): + where = "status = 'active'" + + obj = TestClass() + result = obj.wrap_agg_with_where("AVG", "price") + assert result == "AVG(CASE WHEN status = 'active' THEN price ELSE NULL END)" + + def test_wrap_count_with_where_no_condition(self): + """Test wrapping COUNT without WHERE clause.""" + class TestClass(WhereClauseMixin): + where = None + + obj = TestClass() + result = obj.wrap_count_with_where() + assert result == "COUNT(*)" + + def test_wrap_count_with_where_with_condition(self): + """Test wrapping COUNT with WHERE clause.""" + class TestClass(WhereClauseMixin): + where = "status = 'active'" + + obj = TestClass() + result = obj.wrap_count_with_where() + assert result == "SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END)" + + def test_wrap_count_with_where_custom_condition(self): + """Test wrapping COUNT with custom condition and WHERE clause.""" + class TestClass(WhereClauseMixin): + where = "status = 'active'" + + obj = TestClass() + result = obj.wrap_count_with_where("price > 0") + assert "status = 'active'" in result + assert "price > 0" in result + + +class TestSafeExtractMixin: + """Tests for SafeExtractMixin.""" + + def test_safe_float_valid(self): + """Test extracting valid float value.""" + class TestClass(SafeExtractMixin): + pass + + obj = TestClass() + df = pd.DataFrame({"value": [42.5]}) + result = obj.safe_float(df, "value") + assert result == 42.5 + + def test_safe_float_none(self): + """Test extracting None value.""" + class TestClass(SafeExtractMixin): + pass + + obj = TestClass() + df = pd.DataFrame({"value": [None]}) + result = obj.safe_float(df, "value") + assert result is None + + def test_safe_float_missing_column(self): + """Test extracting from missing column.""" + class TestClass(SafeExtractMixin): + pass + + obj = TestClass() + df = pd.DataFrame({"other": [42.5]}) + result = obj.safe_float(df, "value") + assert result is None + + def test_safe_int(self): + """Test extracting integer value.""" + class TestClass(SafeExtractMixin): + pass + + obj = TestClass() + df = pd.DataFrame({"value": [42.7]}) + result = obj.safe_int(df, "value") + assert result == 42 + + +class TestColumnAliasMixin: + """Tests for ColumnAliasMixin.""" + + def test_make_alias_single_part(self): + """Test alias with single part.""" + class TestClass(ColumnAliasMixin): + pass + + obj = TestClass() + result = obj.make_alias("mean", "price") + assert result == "mean_price" + + def test_make_alias_multiple_parts(self): + """Test alias with multiple parts.""" + class TestClass(ColumnAliasMixin): + pass + + obj = TestClass() + result = obj.make_alias("corr", "price", "quantity") + assert result == "corr_price_quantity" + + def test_make_alias_sanitization(self): + """Test alias sanitizes special characters.""" + class TestClass(ColumnAliasMixin): + pass + + obj = TestClass() + result = obj.make_alias("mean", "table.column") + assert result == "mean_table_column" + + +class TestSizeOperator: + """Tests for SizeOperator.""" + + def test_get_aggregations_no_where(self): + """Test SQL generation without WHERE clause.""" + op = SizeOperator() + aggs = op.get_aggregations() + assert len(aggs) == 1 + assert "COUNT(*)" in aggs[0] + assert "size_value" in aggs[0] + + def test_get_aggregations_with_where(self): + """Test SQL generation with WHERE clause.""" + op = SizeOperator(where="status = 'active'") + aggs = op.get_aggregations() + assert len(aggs) == 1 + assert "SUM(CASE WHEN" in aggs[0] + assert "status = 'active'" in aggs[0] + + def test_extract_result(self): + """Test result extraction.""" + op = SizeOperator() + df = pd.DataFrame({"size_value": [100]}) + result = op.extract_result(df) + assert result.name == "Size" + assert result.instance == "*" + assert result.entity == "Dataset" + assert result.value == 100.0 + + +class TestCompletenessOperator: + """Tests for CompletenessOperator.""" + + def test_get_aggregations(self): + """Test SQL generation.""" + op = CompletenessOperator("email") + aggs = op.get_aggregations() + assert len(aggs) == 2 + assert any("count_email" in agg for agg in aggs) + assert any("null_count_email" in agg for agg in aggs) + + def test_extract_result_complete(self): + """Test result extraction with complete data.""" + op = CompletenessOperator("email") + df = pd.DataFrame({ + "count_email": [100], + "null_count_email": [0], + }) + result = op.extract_result(df) + assert result.value == 1.0 + + def test_extract_result_partial(self): + """Test result extraction with partial data.""" + op = CompletenessOperator("email") + df = pd.DataFrame({ + "count_email": [100], + "null_count_email": [20], + }) + result = op.extract_result(df) + assert result.value == 0.8 + + +class TestMeanOperator: + """Tests for MeanOperator.""" + + def test_get_aggregations(self): + """Test SQL generation.""" + op = MeanOperator("price") + aggs = op.get_aggregations() + assert len(aggs) == 1 + assert "AVG(price)" in aggs[0] + assert "mean_price" in aggs[0] + + def test_extract_result(self): + """Test result extraction.""" + op = MeanOperator("price") + df = pd.DataFrame({"mean_price": [42.5]}) + result = op.extract_result(df) + assert result.name == "Mean" + assert result.instance == "price" + assert result.value == 42.5 + + +class TestPatternMatchOperator: + """Tests for PatternMatchOperator.""" + + def test_get_aggregations(self): + """Test SQL generation.""" + op = PatternMatchOperator("email", r"^.+@.+\..+$") + aggs = op.get_aggregations() + assert len(aggs) == 2 + assert any("count_email" in agg for agg in aggs) + assert any("pattern_match_email" in agg for agg in aggs) + assert any("REGEXP_MATCHES" in agg for agg in aggs) + + def test_extract_result(self): + """Test result extraction.""" + op = PatternMatchOperator("email", r"^.+@.+\..+$") + df = pd.DataFrame({ + "count_email": [100], + "pattern_match_email": [95], + }) + result = op.extract_result(df) + assert result.name == "PatternMatch" + assert result.value == 0.95 + + +class TestDistinctnessOperator: + """Tests for DistinctnessOperator.""" + + def test_get_grouping_columns(self): + """Test grouping columns.""" + op = DistinctnessOperator(["category"]) + assert op.get_grouping_columns() == ["category"] + + def test_build_query(self): + """Test query building.""" + op = DistinctnessOperator(["category"]) + query = op.build_query("products") + assert "SELECT category" in query + assert "GROUP BY category" in query + assert "distinct_count" in query + assert "total_count" in query + + def test_extract_result(self): + """Test result extraction.""" + op = DistinctnessOperator(["category"]) + df = pd.DataFrame({ + "distinct_count": [10], + "total_count": [100], + }) + result = op.extract_result(df) + assert result.name == "Distinctness" + assert result.value == 0.1 + + +class TestUniquenessOperator: + """Tests for UniquenessOperator.""" + + def test_build_query(self): + """Test query building.""" + op = UniquenessOperator(["id"]) + query = op.build_query("users") + assert "GROUP BY id" in query + assert "HAVING" not in query # HAVING is used in the inner query + assert "unique_count" in query + assert "total_count" in query + + def test_extract_result(self): + """Test result extraction.""" + op = UniquenessOperator(["id"]) + df = pd.DataFrame({ + "unique_count": [90], + "total_count": [100], + }) + result = op.extract_result(df) + assert result.name == "Uniqueness" + assert result.value == 0.9 + + +class TestEntropyOperator: + """Tests for EntropyOperator.""" + + def test_build_query(self): + """Test query building.""" + op = EntropyOperator("category") + query = op.build_query("products") + assert "GROUP BY category" in query + assert "LOG2" in query + assert "entropy" in query + + def test_extract_result(self): + """Test result extraction.""" + op = EntropyOperator("category") + df = pd.DataFrame({"entropy": [2.5]}) + result = op.extract_result(df) + assert result.name == "Entropy" + assert result.value == 2.5 + + +class TestOperatorFactory: + """Tests for OperatorFactory.""" + + def test_is_scan_operator(self): + """Test scan operator detection.""" + from pydeequ.v2.analyzers import Mean, Sum, Completeness + + assert OperatorFactory.is_scan_operator(Mean("price")) + assert OperatorFactory.is_scan_operator(Sum("amount")) + assert OperatorFactory.is_scan_operator(Completeness("email")) + + def test_is_grouping_operator(self): + """Test grouping operator detection.""" + from pydeequ.v2.analyzers import Distinctness, Uniqueness, Entropy + + assert OperatorFactory.is_grouping_operator(Distinctness("category")) + assert OperatorFactory.is_grouping_operator(Uniqueness("id")) + assert OperatorFactory.is_grouping_operator(Entropy("status")) + + def test_create_scan_operator(self): + """Test creating scan operator from analyzer.""" + from pydeequ.v2.analyzers import Mean + + analyzer = Mean("price", where="status = 'active'") + operator = OperatorFactory.create(analyzer) + + assert operator is not None + assert isinstance(operator, MeanOperator) + assert operator.column == "price" + assert operator.where == "status = 'active'" + + def test_create_grouping_operator(self): + """Test creating grouping operator from analyzer.""" + from pydeequ.v2.analyzers import Distinctness + + analyzer = Distinctness(["category", "brand"]) + operator = OperatorFactory.create(analyzer) + + assert operator is not None + assert isinstance(operator, DistinctnessOperator) + assert operator.columns == ["category", "brand"] + + def test_is_supported(self): + """Test analyzer support checking.""" + from pydeequ.v2.analyzers import Mean, Histogram, ApproxQuantile, DataType + + assert OperatorFactory.is_supported(Mean("price")) + # Histogram and ApproxQuantile are now supported as operators + assert OperatorFactory.is_supported(Histogram("category")) + assert OperatorFactory.is_supported(ApproxQuantile("price", 0.5)) + # DataType is now supported via the metadata registry + assert OperatorFactory.is_supported(DataType("category")) + assert OperatorFactory.is_metadata_operator(DataType("category")) + + +class TestOperatorIntegration: + """Integration tests for operators with actual DuckDB.""" + + @pytest.fixture + def duckdb_conn(self): + """Create a DuckDB connection with test data.""" + import duckdb + + conn = duckdb.connect(":memory:") + conn.execute(""" + CREATE TABLE test_data AS SELECT * FROM ( + VALUES + (1, 'Alice', 100.0, 'A', 'active'), + (2, 'Bob', 200.0, 'B', 'active'), + (3, 'Carol', 150.0, 'A', 'inactive'), + (4, 'Dave', NULL, 'C', 'active'), + (5, 'Eve', 300.0, 'A', 'active') + ) AS t(id, name, amount, category, status) + """) + yield conn + conn.close() + + def test_scan_operators_batch_execution(self, duckdb_conn): + """Test batch execution of multiple scan operators.""" + operators = [ + SizeOperator(), + MeanOperator("amount"), + MaximumOperator("amount"), + MinimumOperator("amount"), + ] + + # Collect all aggregations + aggregations = [] + for op in operators: + aggregations.extend(op.get_aggregations()) + + # Execute single query + query = f"SELECT {', '.join(aggregations)} FROM test_data" + result = duckdb_conn.execute(query).fetchdf() + + # Extract results + results = [op.extract_result(result) for op in operators] + + assert results[0].value == 5.0 # Size + assert results[1].value == 187.5 # Mean (750/4 non-null) + assert results[2].value == 300.0 # Maximum + assert results[3].value == 100.0 # Minimum + + def test_grouping_operator_execution(self, duckdb_conn): + """Test execution of grouping operator.""" + op = DistinctnessOperator(["category"]) + query = op.build_query("test_data") + result = duckdb_conn.execute(query).fetchdf() + metric = op.extract_result(result) + + # 3 distinct categories / 5 rows = 0.6 + assert metric.name == "Distinctness" + assert metric.value == 0.6 + + def test_completeness_operator(self, duckdb_conn): + """Test completeness operator with NULL values.""" + op = CompletenessOperator("amount") + aggs = op.get_aggregations() + query = f"SELECT {', '.join(aggs)} FROM test_data" + result = duckdb_conn.execute(query).fetchdf() + metric = op.extract_result(result) + + # 4 non-null out of 5 + assert metric.value == 0.8 + + def test_operator_with_where_clause(self, duckdb_conn): + """Test operator with WHERE clause filtering.""" + op = MeanOperator("amount", where="status = 'active'") + aggs = op.get_aggregations() + query = f"SELECT {', '.join(aggs)} FROM test_data" + result = duckdb_conn.execute(query).fetchdf() + metric = op.extract_result(result) + + # Active rows: 100, 200, NULL, 300 -> mean of non-null = 200 + assert metric.value == 200.0 diff --git a/tests/engines/test_suggestion_rules.py b/tests/engines/test_suggestion_rules.py new file mode 100644 index 0000000..248182f --- /dev/null +++ b/tests/engines/test_suggestion_rules.py @@ -0,0 +1,462 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for suggestion rules. + +Tests the individual suggestion rules in isolation using mock column profiles. +""" + +import json +import pytest + +from pydeequ.engines import ColumnProfile +from pydeequ.engines.suggestions import ( + RuleRegistry, + SuggestionRunner, + CompleteIfCompleteRule, + RetainCompletenessRule, + NonNegativeNumbersRule, + CategoricalRangeRule, + HasMinRule, + HasMaxRule, + HasMeanRule, + HasMinLengthRule, + HasMaxLengthRule, + UniqueIfApproximatelyUniqueRule, +) + + +def make_profile( + column: str = "test_col", + completeness: float = 1.0, + approx_distinct_values: int = 10, + data_type: str = "INTEGER", + minimum: float = None, + maximum: float = None, + mean: float = None, + histogram: str = None, +) -> ColumnProfile: + """Create a test column profile with specified attributes.""" + return ColumnProfile( + column=column, + completeness=completeness, + approx_distinct_values=approx_distinct_values, + data_type=data_type, + minimum=minimum, + maximum=maximum, + mean=mean, + histogram=histogram, + ) + + +class TestCompleteIfCompleteRule: + """Tests for CompleteIfComplete rule.""" + + def test_applies_when_fully_complete(self): + """Rule applies when completeness is 1.0.""" + rule = CompleteIfCompleteRule() + profile = make_profile(completeness=1.0) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_not_complete(self): + """Rule does not apply when completeness < 1.0.""" + rule = CompleteIfCompleteRule() + profile = make_profile(completeness=0.95) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates isComplete suggestion.""" + rule = CompleteIfCompleteRule() + profile = make_profile(column="my_column", completeness=1.0) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "my_column" + assert suggestion.constraint_name == "Completeness" + assert suggestion.suggesting_rule == "CompleteIfComplete" + assert ".isComplete" in suggestion.code_for_constraint + + def test_rule_sets(self): + """Rule belongs to DEFAULT and EXTENDED sets.""" + rule = CompleteIfCompleteRule() + assert "DEFAULT" in rule.rule_sets + assert "EXTENDED" in rule.rule_sets + + +class TestRetainCompletenessRule: + """Tests for RetainCompleteness rule.""" + + def test_applies_when_high_completeness(self): + """Rule applies when completeness >= 0.9 and < 1.0.""" + rule = RetainCompletenessRule() + profile = make_profile(completeness=0.95) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_fully_complete(self): + """Rule does not apply when completeness is 1.0.""" + rule = RetainCompletenessRule() + profile = make_profile(completeness=1.0) + assert rule.applies_to(profile) is False + + def test_does_not_apply_when_low_completeness(self): + """Rule does not apply when completeness < threshold.""" + rule = RetainCompletenessRule() + profile = make_profile(completeness=0.85) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates hasCompleteness suggestion.""" + rule = RetainCompletenessRule() + profile = make_profile(column="my_column", completeness=0.95) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "my_column" + assert suggestion.suggesting_rule == "RetainCompleteness" + assert ".hasCompleteness" in suggestion.code_for_constraint + + +class TestNonNegativeNumbersRule: + """Tests for NonNegativeNumbers rule.""" + + def test_applies_when_minimum_non_negative(self): + """Rule applies when minimum >= 0.""" + rule = NonNegativeNumbersRule() + profile = make_profile(minimum=0.0) + assert rule.applies_to(profile) is True + + def test_applies_when_minimum_positive(self): + """Rule applies when minimum > 0.""" + rule = NonNegativeNumbersRule() + profile = make_profile(minimum=5.0) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_minimum_negative(self): + """Rule does not apply when minimum < 0.""" + rule = NonNegativeNumbersRule() + profile = make_profile(minimum=-1.0) + assert rule.applies_to(profile) is False + + def test_does_not_apply_when_no_minimum(self): + """Rule does not apply when minimum is None.""" + rule = NonNegativeNumbersRule() + profile = make_profile(minimum=None) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates isNonNegative suggestion.""" + rule = NonNegativeNumbersRule() + profile = make_profile(column="amount", minimum=0.0) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "amount" + assert suggestion.suggesting_rule == "NonNegativeNumbers" + assert ".isNonNegative" in suggestion.code_for_constraint + + +class TestCategoricalRangeRule: + """Tests for CategoricalRange rule.""" + + def test_applies_when_low_cardinality_histogram(self): + """Rule applies when histogram has <= 10 values.""" + rule = CategoricalRangeRule() + histogram = json.dumps({"A": 10, "B": 20, "C": 30}) + profile = make_profile(histogram=histogram) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_no_histogram(self): + """Rule does not apply when no histogram.""" + rule = CategoricalRangeRule() + profile = make_profile(histogram=None) + assert rule.applies_to(profile) is False + + def test_does_not_apply_when_high_cardinality(self): + """Rule does not apply when histogram has > 10 values.""" + rule = CategoricalRangeRule() + histogram = json.dumps({f"val_{i}": i for i in range(20)}) + profile = make_profile(histogram=histogram) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates isContainedIn suggestion.""" + rule = CategoricalRangeRule() + histogram = json.dumps({"A": 10, "B": 20}) + profile = make_profile(column="status", histogram=histogram) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "status" + assert suggestion.suggesting_rule == "CategoricalRangeRule" + assert ".isContainedIn" in suggestion.code_for_constraint + + +class TestHasMinRule: + """Tests for HasMin rule.""" + + def test_applies_when_numeric_with_stats(self): + """Rule applies when minimum and mean are present.""" + rule = HasMinRule() + profile = make_profile(minimum=0.0, mean=5.0) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_no_minimum(self): + """Rule does not apply when minimum is None.""" + rule = HasMinRule() + profile = make_profile(minimum=None, mean=5.0) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates hasMin suggestion.""" + rule = HasMinRule() + profile = make_profile(column="value", minimum=1.0, mean=5.0) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "value" + assert suggestion.suggesting_rule == "HasMin" + assert ".hasMin" in suggestion.code_for_constraint + + def test_rule_sets(self): + """Rule belongs to NUMERICAL and EXTENDED sets.""" + rule = HasMinRule() + assert "NUMERICAL" in rule.rule_sets + assert "EXTENDED" in rule.rule_sets + + +class TestHasMaxRule: + """Tests for HasMax rule.""" + + def test_applies_when_numeric_with_stats(self): + """Rule applies when maximum and mean are present.""" + rule = HasMaxRule() + profile = make_profile(maximum=10.0, mean=5.0) + assert rule.applies_to(profile) is True + + def test_generates_correct_suggestion(self): + """Rule generates hasMax suggestion.""" + rule = HasMaxRule() + profile = make_profile(column="value", maximum=10.0, mean=5.0) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "value" + assert suggestion.suggesting_rule == "HasMax" + assert ".hasMax" in suggestion.code_for_constraint + + +class TestHasMeanRule: + """Tests for HasMean rule.""" + + def test_applies_when_mean_present(self): + """Rule applies when mean is present.""" + rule = HasMeanRule() + profile = make_profile(mean=5.0) + assert rule.applies_to(profile) is True + + def test_does_not_apply_when_no_mean(self): + """Rule does not apply when mean is None.""" + rule = HasMeanRule() + profile = make_profile(mean=None) + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates hasMean suggestion with range.""" + rule = HasMeanRule() + profile = make_profile(column="value", mean=100.0) + suggestion = rule.generate(profile) + + assert suggestion.column_name == "value" + assert suggestion.suggesting_rule == "HasMean" + assert ".hasMean" in suggestion.code_for_constraint + assert "between" in suggestion.code_for_constraint + + +class TestHasMinLengthRule: + """Tests for HasMinLength rule.""" + + def test_applies_to_string_columns(self): + """Rule applies to string data types.""" + rule = HasMinLengthRule() + profile = make_profile(data_type="VARCHAR") + assert rule.applies_to(profile) is True + + def test_does_not_apply_to_numeric_columns(self): + """Rule does not apply to numeric data types.""" + rule = HasMinLengthRule() + profile = make_profile(data_type="INTEGER") + assert rule.applies_to(profile) is False + + def test_generates_correct_suggestion(self): + """Rule generates hasMinLength suggestion.""" + rule = HasMinLengthRule() + profile = make_profile(column="name", data_type="VARCHAR") + suggestion = rule.generate(profile, min_length=3) + + assert suggestion.column_name == "name" + assert suggestion.suggesting_rule == "HasMinLength" + assert ".hasMinLength" in suggestion.code_for_constraint + + def test_returns_none_when_no_length(self): + """Rule returns None when no min_length provided.""" + rule = HasMinLengthRule() + profile = make_profile(data_type="VARCHAR") + suggestion = rule.generate(profile, min_length=None) + assert suggestion is None + + def test_rule_sets(self): + """Rule belongs to STRING and EXTENDED sets.""" + rule = HasMinLengthRule() + assert "STRING" in rule.rule_sets + assert "EXTENDED" in rule.rule_sets + + +class TestHasMaxLengthRule: + """Tests for HasMaxLength rule.""" + + def test_applies_to_string_columns(self): + """Rule applies to string data types.""" + rule = HasMaxLengthRule() + profile = make_profile(data_type="TEXT") + assert rule.applies_to(profile) is True + + def test_generates_correct_suggestion(self): + """Rule generates hasMaxLength suggestion.""" + rule = HasMaxLengthRule() + profile = make_profile(column="name", data_type="VARCHAR") + suggestion = rule.generate(profile, max_length=50) + + assert suggestion.column_name == "name" + assert suggestion.suggesting_rule == "HasMaxLength" + assert ".hasMaxLength" in suggestion.code_for_constraint + + +class TestUniqueIfApproximatelyUniqueRule: + """Tests for UniqueIfApproximatelyUnique rule.""" + + def test_generates_suggestion_when_unique(self): + """Rule generates isUnique when distinct values >= 99% of rows.""" + rule = UniqueIfApproximatelyUniqueRule() + profile = make_profile(column="id", approx_distinct_values=100) + suggestion = rule.generate(profile, row_count=100) + + assert suggestion is not None + assert suggestion.column_name == "id" + assert suggestion.suggesting_rule == "UniqueIfApproximatelyUnique" + assert ".isUnique" in suggestion.code_for_constraint + + def test_does_not_generate_when_not_unique(self): + """Rule returns None when distinct values < 99% of rows.""" + rule = UniqueIfApproximatelyUniqueRule() + profile = make_profile(approx_distinct_values=50) + suggestion = rule.generate(profile, row_count=100) + assert suggestion is None + + def test_returns_none_when_no_row_count(self): + """Rule returns None when row_count is not provided.""" + rule = UniqueIfApproximatelyUniqueRule() + profile = make_profile(approx_distinct_values=100) + suggestion = rule.generate(profile, row_count=None) + assert suggestion is None + + def test_rule_sets(self): + """Rule belongs to COMMON and EXTENDED sets.""" + rule = UniqueIfApproximatelyUniqueRule() + assert "COMMON" in rule.rule_sets + assert "EXTENDED" in rule.rule_sets + + +class TestRuleRegistry: + """Tests for RuleRegistry.""" + + def test_registry_has_default_rules(self): + """Registry has rules registered by default.""" + rules = RuleRegistry.get_all_rules() + assert len(rules) > 0 + + def test_get_rules_for_sets_default(self): + """Can retrieve DEFAULT rules.""" + rules = RuleRegistry.get_rules_for_sets(["DEFAULT"]) + rule_names = [r.name for r in rules] + assert "CompleteIfComplete" in rule_names + assert "NonNegativeNumbers" in rule_names + + def test_get_rules_for_sets_numerical(self): + """Can retrieve NUMERICAL rules.""" + rules = RuleRegistry.get_rules_for_sets(["NUMERICAL"]) + rule_names = [r.name for r in rules] + assert "HasMin" in rule_names + assert "HasMax" in rule_names + assert "HasMean" in rule_names + + def test_get_rules_for_sets_string(self): + """Can retrieve STRING rules.""" + rules = RuleRegistry.get_rules_for_sets(["STRING"]) + rule_names = [r.name for r in rules] + assert "HasMinLength" in rule_names + assert "HasMaxLength" in rule_names + + def test_get_rules_for_multiple_sets(self): + """Can retrieve rules from multiple sets.""" + rules = RuleRegistry.get_rules_for_sets(["DEFAULT", "NUMERICAL"]) + rule_names = [r.name for r in rules] + assert "CompleteIfComplete" in rule_names + assert "HasMin" in rule_names + + +class TestSuggestionRunner: + """Tests for SuggestionRunner.""" + + def test_runner_default_rules(self): + """Runner uses DEFAULT rules by default.""" + runner = SuggestionRunner() + assert runner.rule_sets == ["DEFAULT"] + + def test_runner_custom_rules(self): + """Runner can use custom rule sets.""" + runner = SuggestionRunner(rule_sets=["NUMERICAL", "STRING"]) + assert "NUMERICAL" in runner.rule_sets + assert "STRING" in runner.rule_sets + + def test_run_generates_suggestions(self): + """Runner generates suggestions from profiles.""" + runner = SuggestionRunner(rule_sets=["DEFAULT"]) + profiles = [ + make_profile(column="complete_col", completeness=1.0), + make_profile(column="partial_col", completeness=0.95), + ] + suggestions = runner.run(profiles) + + # Should have suggestions for both columns + column_names = [s.column_name for s in suggestions] + assert "complete_col" in column_names + assert "partial_col" in column_names + + def test_run_with_numeric_profiles(self): + """Runner generates numeric suggestions.""" + runner = SuggestionRunner(rule_sets=["NUMERICAL"]) + profiles = [ + make_profile(column="value", minimum=0.0, maximum=100.0, mean=50.0), + ] + suggestions = runner.run(profiles) + + rule_names = [s.suggesting_rule for s in suggestions] + assert "HasMin" in rule_names + assert "HasMax" in rule_names + assert "HasMean" in rule_names + + def test_run_with_row_count_for_uniqueness(self): + """Runner uses row_count for uniqueness checks.""" + runner = SuggestionRunner(rule_sets=["COMMON"]) + profiles = [ + make_profile(column="id", approx_distinct_values=100), + ] + suggestions = runner.run(profiles, row_count=100) + + rule_names = [s.suggesting_rule for s in suggestions] + assert "UniqueIfApproximatelyUnique" in rule_names diff --git a/tutorials/data_quality_example_duckdb.py b/tutorials/data_quality_example_duckdb.py new file mode 100644 index 0000000..29d4aff --- /dev/null +++ b/tutorials/data_quality_example_duckdb.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +""" +Testing Data Quality at Scale with PyDeequ + DuckDB + +This example demonstrates using PyDeequ with DuckDB as the execution backend, +enabling data quality checks without a Spark cluster. + +It covers: +- Data analysis (AnalysisRunner) +- Constraint verification (VerificationSuite) +- Column profiling (ColumnProfilerRunner) +- Constraint suggestions (ConstraintSuggestionRunner) + +Prerequisites: +1. Install dependencies: + pip install duckdb pandas + +2. Run this script: + python data_quality_example_duckdb.py +""" + +import duckdb +import pydeequ +from pydeequ.v2.analyzers import ( + Size, + Completeness, + Distinctness, + Mean, + Minimum, + Maximum, + StandardDeviation, + Correlation, + Uniqueness, +) +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.verification import AnalysisRunner, VerificationSuite +from pydeequ.v2.predicates import eq, gte, lte, between +from pydeequ.v2.profiles import ColumnProfilerRunner +from pydeequ.v2.suggestions import ConstraintSuggestionRunner, Rules + + +def create_sample_data(con: duckdb.DuckDBPyConnection): + """Create a sample product reviews dataset for demonstration.""" + con.execute(""" + CREATE TABLE reviews AS SELECT * FROM (VALUES + ('R001', 'C100', 'P001', 'US', 5, 10, 12, 2023, 'Great Product', 'Y'), + ('R002', 'C101', 'P002', 'US', 4, 8, 10, 2023, 'Good Value', 'Y'), + ('R003', 'C102', 'P001', 'UK', 5, 15, 18, 2022, 'Great Product', 'N'), + ('R004', 'C103', 'P003', 'DE', 3, 5, 8, 2022, 'Decent Item', 'Y'), + ('R005', 'C104', 'P002', 'FR', 4, 12, 15, 2021, 'Good Value', 'N'), + ('R006', 'C105', 'P004', 'JP', 5, 20, 22, 2023, 'Excellent!', 'Y'), + ('R007', 'C106', 'P001', 'US', 2, 3, 10, 2020, 'Great Product', 'N'), + ('R008', 'C107', 'P005', 'UK', 1, 25, 30, 2021, 'Disappointing', 'Y'), + ('R009', 'C108', 'P002', NULL, 4, 7, 9, 2023, 'Good Value', 'Y'), + ('R001', 'C109', 'P003', 'US', 3, 4, 6, 2022, 'Decent Item', 'N') + ) AS t(review_id, customer_id, product_id, marketplace, star_rating, + helpful_votes, total_votes, review_year, product_title, insight) + """) + + +def run_data_analysis(engine): + """ + Run data analysis using AnalysisRunner. + + This demonstrates computing various metrics on the dataset: + - Size: Total row count + - Completeness: Ratio of non-null values + - Distinctness: Ratio of distinct values + - Mean, Min, Max: Statistical measures + - Correlation: Relationship between columns + """ + print("\n" + "=" * 60) + print("DATA ANALYSIS") + print("=" * 60) + + result = (AnalysisRunner() + .on_engine(engine) + .addAnalyzer(Size()) + .addAnalyzer(Completeness("review_id")) + .addAnalyzer(Completeness("marketplace")) + .addAnalyzer(Distinctness(["review_id"])) + .addAnalyzer(Mean("star_rating")) + .addAnalyzer(Minimum("star_rating")) + .addAnalyzer(Maximum("star_rating")) + .addAnalyzer(StandardDeviation("star_rating")) + .addAnalyzer(Correlation("total_votes", "helpful_votes")) + .run()) + + print("\nAnalysis Results:") + print(result.to_string(index=False)) + + # Extract key insights + metrics = {(r["name"], r["instance"]): r["value"] for _, r in result.iterrows()} + + print("\nKey Insights:") + print(f" - Dataset contains {int(metrics.get(('Size', '*'), 0))} reviews") + print(f" - review_id completeness: {metrics.get(('Completeness', 'review_id'), 0):.1%}") + print(f" - marketplace completeness: {metrics.get(('Completeness', 'marketplace'), 0):.1%}") + print(f" - review_id distinctness: {metrics.get(('Distinctness', 'review_id'), 0):.1%}") + print(f" - Average star rating: {metrics.get(('Mean', 'star_rating'), 0):.2f}") + print(f" - Star rating range: {metrics.get(('Minimum', 'star_rating'), 0):.0f} - {metrics.get(('Maximum', 'star_rating'), 0):.0f}") + + return result + + +def run_constraint_verification(engine): + """ + Run constraint verification using VerificationSuite. + + This demonstrates defining and verifying data quality rules: + - Size checks + - Completeness checks + - Uniqueness checks + - Range checks (min/max) + - Categorical value checks + """ + print("\n" + "=" * 60) + print("CONSTRAINT VERIFICATION") + print("=" * 60) + + # Define checks using the V2 predicate API + check = (Check(CheckLevel.Warning, "Product Reviews Quality Check") + # Size check: at least 5 reviews + .hasSize(gte(5)) + # Completeness checks + .isComplete("review_id") + .isComplete("customer_id") + .hasCompleteness("marketplace", gte(0.8)) # Allow some missing + # Uniqueness check + .isUnique("review_id") + # Star rating range check + .hasMin("star_rating", eq(1.0)) + .hasMax("star_rating", eq(5.0)) + .hasMean("star_rating", between(1.0, 5.0)) + # Year range check + .hasMin("review_year", gte(2015)) + .hasMax("review_year", lte(2025)) + # Categorical check + .isContainedIn("marketplace", ["US", "UK", "DE", "JP", "FR"]) + .isContainedIn("insight", ["Y", "N"]) + ) + + result = (VerificationSuite() + .on_engine(engine) + .addCheck(check) + .run()) + + print("\nVerification Results:") + print(result.to_string(index=False)) + + # Summarize results + passed = (result["constraint_status"] == "Success").sum() + failed = (result["constraint_status"] == "Failure").sum() + + print(f"\nSummary: {passed} passed, {failed} failed out of {len(result)} constraints") + + if failed > 0: + print("\nFailed Constraints:") + for _, row in result[result["constraint_status"] == "Failure"].iterrows(): + print(f" - {row['constraint']}") + if row["constraint_message"]: + print(f" Message: {row['constraint_message']}") + + return result + + +def run_column_profiling(engine): + """ + Run column profiling using ColumnProfilerRunner. + + This automatically computes statistics for each column: + - Completeness + - Approximate distinct values + - Data type detection + - Numeric statistics (mean, min, max, etc.) + """ + print("\n" + "=" * 60) + print("COLUMN PROFILING") + print("=" * 60) + + result = (ColumnProfilerRunner() + .on_engine(engine) + .withLowCardinalityHistogramThreshold(10) # Generate histograms for low-cardinality columns + .run()) + + print("\nColumn Profiles:") + # Show selected columns for readability + cols_to_show = ["column", "completeness", "approx_distinct_values", "data_type", "mean", "minimum", "maximum"] + available_cols = [c for c in cols_to_show if c in result.columns] + print(result[available_cols].to_string(index=False)) + + return result + + +def run_constraint_suggestions(engine): + """ + Run automated constraint suggestion using ConstraintSuggestionRunner. + + This analyzes the data and suggests appropriate constraints: + - Completeness constraints for complete columns + - Uniqueness constraints for unique columns + - Categorical range constraints for low-cardinality columns + - Non-negative constraints for numeric columns + """ + print("\n" + "=" * 60) + print("CONSTRAINT SUGGESTIONS") + print("=" * 60) + + result = (ConstraintSuggestionRunner() + .on_engine(engine) + .addConstraintRules(Rules.DEFAULT) + .run()) + + print("\nSuggested Constraints:") + cols_to_show = ["column_name", "constraint_name", "description", "code_for_constraint"] + available_cols = [c for c in cols_to_show if c in result.columns] + print(result[available_cols].to_string(index=False)) + + print(f"\nTotal suggestions: {len(result)}") + + return result + + +def main(): + print("PyDeequ Data Quality Example with DuckDB") + print("No Spark cluster required!") + + # Create in-memory DuckDB connection + con = duckdb.connect() + + # Create sample data + print("\nCreating sample product reviews dataset...") + create_sample_data(con) + + # Create engine using pydeequ.connect() + engine = pydeequ.connect(con, table="reviews") + + print("\nDataset Schema:") + schema = engine.get_schema() + for col, dtype in schema.items(): + print(f" {col}: {dtype}") + + print("\nSample Data:") + print(con.execute("SELECT * FROM reviews LIMIT 5").fetchdf().to_string(index=False)) + + # Run all examples + run_data_analysis(engine) + run_constraint_verification(engine) + run_column_profiling(engine) + run_constraint_suggestions(engine) + + print("\n" + "=" * 60) + print("EXAMPLE COMPLETE") + print("=" * 60) + + +if __name__ == "__main__": + main() From cbf2f3f156879344d707d4ec1242d93a7acd7c83 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Mon, 19 Jan 2026 23:25:12 -0500 Subject: [PATCH 2/7] fix parity tests and update project dependencies --- Engines.md | 413 ++---- poetry.lock | 1232 ++++++++++++++++- pydeequ/engines/__init__.py | 49 +- .../engines/operators/grouping_operators.py | 8 +- .../engines/operators/profiling_operators.py | 4 +- pydeequ/engines/operators/scan_operators.py | 2 +- pyproject.toml | 114 +- tests/conftest.py | 37 +- tests/engines/comparison/conftest.py | 27 +- tests/engines/comparison/utils.py | 22 +- tests/engines/fixtures/datasets.py | 16 +- tests/engines/test_duckdb_analyzers.py | 16 +- tests/engines/test_duckdb_constraints.py | 3 +- tests/engines/test_duckdb_profiles.py | 3 +- tests/engines/test_operators.py | 2 +- tests/v2/conftest.py | 10 +- tests/v2/test_e2e_spark_connect.py | 10 +- 17 files changed, 1571 insertions(+), 397 deletions(-) diff --git a/Engines.md b/Engines.md index 4a08feb..14d79a7 100644 --- a/Engines.md +++ b/Engines.md @@ -2,343 +2,230 @@ ## Executive Summary -This report documents 18 remaining parity test failures between the Spark and DuckDB engines in python-deequ. The failures are grouped by root cause to facilitate systematic resolution. +This report documents the parity testing results between the Spark and DuckDB engines in python-deequ. After implementing all fixes, **84 tests pass** and **5 tests fail** due to inherent algorithmic differences. -### Summary by Category +### Current Test Results -| Category | Test Failures | Root Cause | -|----------|---------------|------------| -| Standard Deviation | 2 tests | Population vs Sample formula | -| Approximate Algorithms | 3 tests | Different HyperLogLog/quantile implementations | -| Information Theory | 3 tests | Floating-point precision in log calculations | -| Output Format | 2 tests | JSON string vs structured Map | -| Profile Statistics | 8 tests | Combination of above issues | +| Status | Count | Percentage | +|--------|-------|------------| +| **Passed** | 84 | 94.4% | +| **Failed** | 5 | 5.6% | -### Impact Assessment +### Summary of Remaining Failures -- **Critical**: Standard deviation and output format differences affect basic analyzer functionality -- **Moderate**: Approximate algorithm differences are inherent to probabilistic data structures -- **Low**: Information theory precision differences are minor numerical variations - -### Recommended Priority Order - -1. Output format differences (quick fix, high impact) -2. Standard deviation formula alignment (configuration option) -3. Tolerance adjustments for approximate metrics -4. Information theory numerical stability (long-term) - ---- - -## Detailed Analysis by Category - -### 2.1 Standard Deviation Differences - -**Affected Tests:** -- `test_standard_deviation` -- `test_numeric_statistics` (profile) -- `test_all_basic_analyzers` - -**Root Cause:** - -DuckDB uses `STDDEV_SAMP()` which implements the sample standard deviation formula with N-1 denominator (Bessel's correction). Spark may use the population formula (N denominator) or a different algorithm. - -**Evidence:** - -``` -Spark: std_dev(price) = 11.18 (population formula) -DuckDB: std_dev(price) = 12.91 (sample formula) -``` - -The relationship between these values follows the expected formula: -- Sample std = Population std × √(N/(N-1)) -- For small datasets, this difference is significant - -**Options to Bridge:** - -| Option | Description | Pros | Cons | -|--------|-------------|------|------| -| 1. Config option | Add setting to switch between `STDDEV_SAMP`/`STDDEV_POP` | Flexible, user choice | More complexity | -| 2. Match Spark | Determine which formula Spark uses and replicate | True parity | May not match DuckDB conventions | -| 3. Increase tolerance | Relax test assertions for statistical metrics | Quick fix | Doesn't address root cause | - -**Recommended Fix:** Option 1 - Add configuration option with Spark's formula as default for parity mode. +| Category | Test Failures | Root Cause | Fixable? | +|----------|---------------|------------|----------| +| Approximate Quantile | 2 tests | Different quantile algorithms | Inherent difference | +| Approximate Count Distinct | 1 test | HyperLogLog implementation variance | Inherent variance | +| Profile Distinct Values | 2 tests | HyperLogLog variance | Inherent variance | --- -### 2.2 Approximate Count Distinct - -**Affected Tests:** -- `test_approx_count_distinct` -- `test_distinct_values` (profile) +## Fixes Applied -**Root Cause:** +### 1. STDDEV_SAMP → STDDEV_POP -Different HyperLogLog implementations with different precision parameters: -- Spark uses HyperLogLog++ with configurable precision (default p=14) -- DuckDB uses its own HyperLogLog implementation +Changed DuckDB to use population standard deviation to match Spark. -**Evidence:** +**Files Modified:** +- `pydeequ/engines/operators/scan_operators.py` +- `pydeequ/engines/operators/profiling_operators.py` -Approximate distinct counts can vary by several percent between engines due to: -- Different hash functions -- Different precision parameters -- Different merging algorithms +**Tests Fixed:** ~8 tests -**Options to Bridge:** +### 2. Entropy: LOG2 → LN -| Option | Description | Pros | Cons | -|--------|-------------|------|------| -| 1. Exact count for small data | Use `COUNT(DISTINCT)` when dataset is small | Accurate for tests | Performance impact at scale | -| 2. Increase tolerance | Allow >10% variance for approximate metrics | Realistic expectation | Less precise tests | -| 3. Document as expected | Mark as known variance in documentation | Honest about limitations | Doesn't "fix" tests | +Changed DuckDB entropy calculation from log base 2 to natural log to match Spark. -**Recommended Fix:** Option 1 for test datasets, Option 2 for production with appropriate tolerance. +**File Modified:** `pydeequ/engines/operators/grouping_operators.py` ---- +```python +# Before (bits) +-SUM((cnt * 1.0 / total_cnt) * LOG2(cnt * 1.0 / total_cnt)) AS entropy -### 2.3 Entropy & Mutual Information +# After (nats) - matches Spark +-SUM((cnt * 1.0 / total_cnt) * LN(cnt * 1.0 / total_cnt)) AS entropy +``` -**Affected Tests:** +**Tests Fixed:** 3 tests - `test_entropy_uniform` - `test_mutual_information` - `test_has_entropy` (constraint) -**Root Cause:** +### 3. Spark Connect Server Fixture -Floating-point precision differences in logarithmic calculations: -- Log base conversions (ln vs log2) -- Numerical stability in probability calculations -- Handling of edge cases (p=0, p=1) +Added automatic Spark Connect server startup for parity tests. -**Evidence:** +**File Modified:** `tests/engines/comparison/conftest.py` -``` -Spark entropy: 2.3219280948873626 -DuckDB entropy: 2.321928094887362 (may differ in last digits) -``` - -Differences in the 15th-16th decimal places are common due to: -- Different order of floating-point operations -- Different handling of -0 × log(0) = 0 convention - -**Options to Bridge:** - -| Option | Description | Pros | Cons | -|--------|-------------|------|------| -| 1. Round before compare | Round to fewer decimal places | Simple fix | Loses precision info | -| 2. Log-sum-exp trick | Use numerically stable algorithms | Mathematically correct | Implementation effort | -| 3. Increase tolerance | Use relative tolerance ~1e-10 | Acknowledges FP limits | May mask real bugs | - -**Recommended Fix:** Option 3 - Use relative tolerance appropriate for floating-point comparison. - ---- - -### 2.4 Quantile Calculations +```python +@pytest.fixture(scope="session") +def spark_connect_server(): + """Automatically starts Spark Connect server if not running.""" + from benchmark.spark_server import SparkConnectServer + from benchmark.config import SparkServerConfig -**Affected Tests:** -- `test_approx_quantile_median` -- `test_approx_quantile_quartiles` + config = SparkServerConfig() + server = SparkConnectServer(config) -**Root Cause:** + if not server.is_running(): + server.start() -Different interpolation methods for percentile calculation: -- Spark uses T-Digest algorithm for approximate quantiles -- DuckDB uses `QUANTILE_CONT` (continuous) or `QUANTILE_DISC` (discrete) + if not os.environ.get("SPARK_REMOTE"): + os.environ["SPARK_REMOTE"] = f"sc://localhost:{config.port}" -**Evidence:** - -For a dataset [1, 2, 3, 4, 5]: -``` -Median with interpolation: 3.0 -Median discrete: 3 -Q1 with interpolation: 1.75 or 2.0 (depends on method) + yield server ``` -There are 9 different interpolation methods defined in statistics literature. +### 4. Flatten Metrics in DeequRelationPlugin (Histogram/DataType Fix) -**Options to Bridge:** +Fixed the Scala Deequ Connect plugin to properly handle complex metrics like Histogram and DataType by flattening them before output. -| Option | Description | Pros | Cons | -|--------|-------------|------|------| -| 1. Use PERCENTILE_DISC | Exact percentiles, no interpolation | Deterministic | Different semantics | -| 2. Increase tolerance | Allow small variance in quantiles | Pragmatic | Imprecise tests | -| 3. Document methods | Specify interpolation method used | Clear expectations | Doesn't achieve parity | +**File Modified:** `deequ/src/main/scala/com/amazon/deequ/connect/DeequRelationPlugin.scala` -**Recommended Fix:** Option 2 with documentation of method differences (Option 3). +**Root Cause:** The plugin was only collecting `DoubleMetric` instances directly, but Histogram and DataType return complex metric types (`HistogramMetric`, etc.) that need to be flattened first. ---- +**Before:** +```scala +val metrics = context.metricMap.toSeq.collect { + case (analyzer, metric: DoubleMetric) => ... +} +``` -### 2.5 Output Format Differences +**After:** +```scala +val metrics = context.metricMap.toSeq.flatMap { case (analyzer, metric) => + metric.flatten().map { doubleMetric => + val value: Double = doubleMetric.value.getOrElse(Double.NaN) + ( + analyzer.toString, + doubleMetric.entity.toString, + doubleMetric.instance, + doubleMetric.name, + value + ) + } +} +``` -**Affected Tests:** +**Tests Fixed:** 2 tests - `test_histogram` - `test_data_type` -**Root Cause:** - -Different output formats for complex types: -- DuckDB returns JSON strings for maps/structs: `'{"bin1": 10, "bin2": 20}'` -- Spark returns native structured Maps: `Map(bin1 -> 10, bin2 -> 20)` - -**Evidence:** - -```python -# DuckDB histogram output -'{"[0,10)": 5, "[10,20)": 3, "[20,30)": 2}' +--- -# Spark histogram output -{'[0,10)': 5, '[10,20)': 3, '[20,30)': 2} -``` +## Detailed Analysis of Remaining Failures -**Options to Bridge:** +### 1. Approximate Quantile (2 tests) -| Option | Description | Pros | Cons | -|--------|-------------|------|------| -| 1. Parse JSON in comparison | Convert JSON strings to dicts before compare | Quick fix | Only fixes tests | -| 2. Standardize output | Return same format from both engines | Consistent API | Breaking change | -| 3. Format-agnostic comparison | Utility that handles both formats | Flexible | More code | +**Root Cause: Different Algorithms** -**Recommended Fix:** Option 1 (immediate) + Option 2 (future standardization). +| Engine | Algorithm | +|--------|-----------| +| Spark | T-Digest (approximate) | +| DuckDB | QUANTILE_CONT (exact interpolation) | ---- +The algorithms produce different results, especially for small datasets. -### 2.6 Profile Edge Cases +**Resolution:** Accept as inherent difference or implement T-Digest in DuckDB. -**Affected Tests:** -- `test_profile_all_columns` -- `test_profile_specific_columns` -- `test_completeness_partial` -- `test_numeric_with_nulls` -- `test_single_row` -- `test_mixed_types` +### 2. Approximate Count Distinct (3 tests) -**Root Causes:** +**Root Cause: HyperLogLog Variance** -These tests fail due to combinations of the above issues: +Both engines use HyperLogLog but with different implementations: +- Different hash functions +- Different precision parameters -| Test | Primary Issues | -|------|----------------| -| `test_profile_all_columns` | std_dev + distinct count | -| `test_profile_specific_columns` | std_dev formula | -| `test_completeness_partial` | NULL counting edge case | -| `test_numeric_with_nulls` | std_dev with NULLs | -| `test_single_row` | std_dev undefined (0/0) | -| `test_mixed_types` | Type inference + formatting | +**Evidence:** +``` +Spark approx_distinct: 9 +DuckDB approx_distinct: 10 (or 6 vs 5) +``` -**Single Row Edge Case:** +~10% variance is expected for probabilistic data structures. -Standard deviation of a single value is mathematically undefined (sample) or 0 (population): -- `STDDEV_SAMP([42])` → NULL or NaN -- `STDDEV_POP([42])` → 0 +**Resolution:** Accept as inherent variance. The 10% tolerance handles most cases but edge cases with small cardinalities still fail. -**Options to Bridge:** +--- -Fixing the underlying issues (std_dev, distinct count, output format) will resolve most profile test failures. +## Test Results Summary + +### Passing Tests (84) + +All core analyzers and constraints: +- Size, Completeness, Mean, Sum, Min, Max +- StandardDeviation (after STDDEV_POP fix) +- Distinctness, Uniqueness, UniqueValueRatio, CountDistinct +- Correlation, PatternMatch, Compliance +- MinLength, MaxLength +- Entropy, MutualInformation (after LN fix) +- **Histogram** (after flatten fix) +- **DataType** (after flatten fix) +- All constraint tests (32 tests) +- All suggestion tests (13 tests) +- Most profile tests + +### Failing Tests (5) + +| Test | Category | Status | +|------|----------|--------| +| `test_approx_count_distinct` | Analyzer | Inherent HLL variance | +| `test_approx_quantile_median` | Analyzer | Algorithm difference | +| `test_approx_quantile_quartiles` | Analyzer | Algorithm difference | +| `test_completeness_partial` | Profile | Inherent HLL variance | +| `test_distinct_values` | Profile | Inherent HLL variance | --- -## Recommended Fixes - -| Priority | Fix | Impact | Effort | Tests Fixed | -|----------|-----|--------|--------|-------------| -| **High** | Parse JSON in comparison utility | 2 tests | Low | histogram, data_type | -| **High** | Add STDDEV_POP configuration option | 4+ tests | Low | std_dev, profile tests | -| **Medium** | Increase tolerances for approximate metrics | 5+ tests | Low | approx_count, quantiles, entropy | -| **Medium** | Use exact COUNT DISTINCT for small datasets | 2 tests | Medium | approx_count_distinct, distinct_values | -| **Low** | Implement numerical stability improvements | 3 tests | High | entropy, mutual_info | +## Files Modified -### Implementation Roadmap +### Python (python-deequ) -**Phase 1: Quick Wins (1-2 days)** -1. Add JSON parsing to test comparison utilities -2. Add configuration option for standard deviation formula -3. Update test tolerances for approximate metrics +| File | Changes | +|------|---------| +| `pydeequ/engines/operators/scan_operators.py` | STDDEV_SAMP → STDDEV_POP | +| `pydeequ/engines/operators/profiling_operators.py` | STDDEV_SAMP → STDDEV_POP | +| `pydeequ/engines/operators/grouping_operators.py` | LOG2 → LN for entropy | +| `tests/engines/comparison/conftest.py` | Added `spark_connect_server` fixture | +| `tests/engines/comparison/utils.py` | Tolerance adjustments, JSON parsing | -**Phase 2: Standardization (1 week)** -1. Implement exact distinct count fallback for small datasets -2. Standardize output formats across engines -3. Document expected variances in analyzer docstrings +### Scala (deequ) -**Phase 3: Long-term (future)** -1. Implement numerically stable entropy calculations -2. Add configurable interpolation methods for quantiles -3. Create comprehensive engine compatibility matrix +| File | Changes | +|------|---------| +| `deequ/src/main/scala/com/amazon/deequ/connect/DeequRelationPlugin.scala` | Flatten metrics in `analyzerContextToDataFrame` | --- -## Acceptance Criteria - -Tests can be categorized into three resolution states: - -### 1. Fixed -Implementation changed to match Spark behavior: -- Standard deviation formula alignment -- Output format standardization -- Exact counts for small datasets +## Recommendations -### 2. Relaxed -Tolerance increased with documented reasoning: -- Approximate count distinct (inherent HLL variance) -- Quantile calculations (interpolation method differences) -- Entropy calculations (floating-point precision limits) +### Mark as xfail (5 tests) -### 3. Skipped -Marked as known difference with `pytest.mark.xfail`: -- Engine-specific features not portable -- Fundamental algorithmic differences +These tests should be marked with `@pytest.mark.xfail` with documented reasons: ---- - -## Appendix: Test Failure Details - -### Full List of Failing Tests - -1. `test_standard_deviation` - Sample vs population formula -2. `test_approx_count_distinct` - HyperLogLog implementation -3. `test_approx_quantile_median` - Interpolation method -4. `test_approx_quantile_quartiles` - Interpolation method -5. `test_entropy_uniform` - Floating-point precision -6. `test_mutual_information` - Floating-point precision -7. `test_has_entropy` - Floating-point precision -8. `test_histogram` - JSON vs Map output -9. `test_data_type` - JSON vs Map output -10. `test_profile_all_columns` - Combined issues -11. `test_profile_specific_columns` - Combined issues -12. `test_completeness_partial` - NULL handling -13. `test_numeric_with_nulls` - std_dev with NULLs -14. `test_single_row` - Edge case handling -15. `test_mixed_types` - Type inference -16. `test_numeric_statistics` - std_dev formula -17. `test_all_basic_analyzers` - std_dev formula -18. `test_distinct_values` - Approximate count - -### Reference: Standard Deviation Formulas - -**Population Standard Deviation:** -``` -σ = √(Σ(xi - μ)² / N) -``` +```python +@pytest.mark.xfail(reason="HyperLogLog implementation variance") +def test_approx_count_distinct(self, ...): + ... -**Sample Standard Deviation:** -``` -s = √(Σ(xi - x̄)² / (N-1)) +@pytest.mark.xfail(reason="T-Digest vs QUANTILE_CONT algorithm difference") +def test_approx_quantile_median(self, ...): + ... ``` -**Relationship:** -``` -s = σ × √(N / (N-1)) -``` +### Future Improvements -For N=5: s ≈ 1.118 × σ +1. **Exact Count for Small Data**: Use `COUNT(DISTINCT)` instead of HyperLogLog when dataset size < threshold +2. **Quantile Algorithm Alignment**: Consider implementing T-Digest in DuckDB for exact parity --- ## Conclusion -The 18 test failures between Spark and DuckDB engines stem from five root causes, all of which have viable resolution paths. The recommended approach prioritizes: +The parity testing initiative achieved **94.4% test pass rate** (84/89 tests). The remaining 5 failures represent inherent algorithmic differences: -1. Quick fixes that achieve parity (output format, std_dev config) -2. Appropriate tolerance adjustments for probabilistic algorithms -3. Documentation of inherent differences +1. **Probabilistic algorithm variance** (3 tests) - Inherent to HyperLogLog +2. **Algorithm differences** (2 tests) - T-Digest vs QUANTILE_CONT -With the high-priority fixes implemented, the test suite should achieve >90% parity. The remaining differences reflect fundamental algorithmic choices that should be documented rather than hidden. +All major analyzers (Size, Completeness, Mean, StandardDeviation, Entropy, Correlation, Histogram, DataType, etc.) now have full parity between engines. diff --git a/poetry.lock b/poetry.lock index 5b439ef..a9272e1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "black" @@ -6,6 +6,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -52,6 +53,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -63,6 +65,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -77,17 +80,277 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "contourpy" +version = "1.3.2" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, + {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2"}, + {file = "contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0"}, + {file = "contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd"}, + {file = "contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f"}, + {file = "contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e"}, + {file = "contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912"}, + {file = "contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef"}, + {file = "contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f"}, + {file = "contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd"}, + {file = "contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1"}, + {file = "contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5"}, + {file = "contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.15.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "contourpy" +version = "1.3.3" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.11" +groups = ["main", "dev"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, +] + +[package.dependencies] +numpy = ">=1.25" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" version = "7.10.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, @@ -199,7 +462,23 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "distlib" @@ -207,17 +486,74 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] +[[package]] +name = "duckdb" +version = "1.4.3" +description = "DuckDB in-process database" +optional = false +python-versions = ">=3.9.0" +groups = ["main", "dev"] +files = [ + {file = "duckdb-1.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa7f1191c59e34b688fcd4e588c1b903a4e4e1f4804945902cf0b20e08a9001"}, + {file = "duckdb-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fef6a053a1c485292000bf0c338bba60f89d334f6a06fc76ba4085a5a322b76"}, + {file = "duckdb-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:702dabbc22b27dc5b73e7599c60deef3d8c59968527c36b391773efddd8f4cf1"}, + {file = "duckdb-1.4.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854b79375fa618f6ffa8d84fb45cbc9db887f6c4834076ea10d20bc106f1fd90"}, + {file = "duckdb-1.4.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bb8bd5a3dd205983726185b280a211eacc9f5bc0c4d4505bec8c87ac33a8ccb"}, + {file = "duckdb-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:d0ff08388ef8b1d1a4c95c321d6c5fa11201b241036b1ee740f9d841df3d6ba2"}, + {file = "duckdb-1.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:366bf607088053dce845c9d24c202c04d78022436cc5d8e4c9f0492de04afbe7"}, + {file = "duckdb-1.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d080e8d1bf2d226423ec781f539c8f6b6ef3fd42a9a58a7160de0a00877a21f"}, + {file = "duckdb-1.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9dc049ba7e906cb49ca2b6d4fbf7b6615ec3883193e8abb93f0bef2652e42dda"}, + {file = "duckdb-1.4.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b30245375ea94ab528c87c61fc3ab3e36331180b16af92ee3a37b810a745d24"}, + {file = "duckdb-1.4.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7c864df027da1ee95f0c32def67e15d02cd4a906c9c1cbae82c09c5112f526b"}, + {file = "duckdb-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:813f189039b46877b5517f1909c7b94a8fe01b4bde2640ab217537ea0fe9b59b"}, + {file = "duckdb-1.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc63ffdd03835f660155b37a1b6db2005bcd46e5ad398b8cac141eb305d2a3d"}, + {file = "duckdb-1.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6302452e57aef29aae3977063810ed7b2927967b97912947b9cca45c1c21955f"}, + {file = "duckdb-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:deab351ac43b6282a3270e3d40e3d57b3b50f472d9fd8c30975d88a31be41231"}, + {file = "duckdb-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5634e40e1e2d972e4f75bced1fbdd9e9e90faa26445c1052b27de97ee546944a"}, + {file = "duckdb-1.4.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:274d4a31aba63115f23e7e7b401e3e3a937f3626dc9dea820a9c7d3073f450d2"}, + {file = "duckdb-1.4.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f868a7e6d9b37274a1aa34849ea92aa964e9bd59a5237d6c17e8540533a1e4f"}, + {file = "duckdb-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef7ef15347ce97201b1b5182a5697682679b04c3374d5a01ac10ba31cf791b95"}, + {file = "duckdb-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:1b9b445970fd18274d5ac07a0b24c032e228f967332fb5ebab3d7db27738c0e4"}, + {file = "duckdb-1.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16952ac05bd7e7b39946695452bf450db1ebbe387e1e7178e10f593f2ea7b9a8"}, + {file = "duckdb-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de984cd24a6cbefdd6d4a349f7b9a46e583ca3e58ce10d8def0b20a6e5fcbe78"}, + {file = "duckdb-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e5457dda91b67258aae30fb1a0df84183a9f6cd27abac1d5536c0d876c6dfa1"}, + {file = "duckdb-1.4.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:006aca6a6d6736c441b02ff5c7600b099bb8b7f4de094b8b062137efddce42df"}, + {file = "duckdb-1.4.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2813f4635f4d6681cc3304020374c46aca82758c6740d7edbc237fe3aae2744"}, + {file = "duckdb-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:6db124f53a3edcb32b0a896ad3519e37477f7e67bf4811cb41ab60c1ef74e4c8"}, + {file = "duckdb-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:a8b0a8764e1b5dd043d168c8f749314f7a1252b5a260fa415adaa26fa3b958fd"}, + {file = "duckdb-1.4.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:316711a9e852bcfe1ed6241a5f654983f67e909e290495f3562cccdf43be8180"}, + {file = "duckdb-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9e625b2b4d52bafa1fd0ebdb0990c3961dac8bb00e30d327185de95b68202131"}, + {file = "duckdb-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:130c6760f6c573f9c9fe9aba56adba0fab48811a4871b7b8fd667318b4a3e8da"}, + {file = "duckdb-1.4.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c88effaa557a11267706b01419c542fe42f893dee66e5a6daa5974ea2d4a46"}, + {file = "duckdb-1.4.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b35491db98ccd11d151165497c084a9d29d3dc42fc80abea2715a6c861ca43d"}, + {file = "duckdb-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:23b12854032c1a58d0452e2b212afa908d4ce64171862f3792ba9a596ba7c765"}, + {file = "duckdb-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:90f241f25cffe7241bf9f376754a5845c74775e00e1c5731119dc88cd71e0cb2"}, + {file = "duckdb-1.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aa26a7406205bc1426cee28bdfdf084f669a5686977dafa4c3ec65873989593c"}, + {file = "duckdb-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:caa2164c91f7e91befb1ffb081b3cd97a137117533aef7abe1538b03ad72e3a9"}, + {file = "duckdb-1.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d53b217698a76c4957e2c807dd9295d409146f9d3d7932f372883201ba9d25a"}, + {file = "duckdb-1.4.3-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8afba22c370f06b7314aa46bfed052509269e482bcfb3f7b1ea0fa17ae49ce42"}, + {file = "duckdb-1.4.3-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b195270ff1a661f22cbd547a215baff265b7d4469a76a215c8992b5994107c3"}, + {file = "duckdb-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:23a3a077821bed1768a84ac9cbf6b6487ead33e28e62cb118bda5fb8f9e53dea"}, + {file = "duckdb-1.4.3.tar.gz", hash = "sha256:fea43e03604c713e25a25211ada87d30cd2a044d8f27afab5deba26ac49e5268"}, +] + +[package.extras] +all = ["adbc-driver-manager", "fsspec", "ipython", "numpy", "pandas", "pyarrow"] + [[package]] name = "exceptiongroup" version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -235,6 +571,8 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -246,17 +584,176 @@ version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] +[[package]] +name = "fonttools" +version = "4.60.2" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "fonttools-4.60.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e36fadcf7e8ca6e34d490eef86ed638d6fd9c55d2f514b05687622cfc4a7050"}, + {file = "fonttools-4.60.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e500fc9c04bee749ceabfc20cb4903f6981c2139050d85720ea7ada61b75d5c"}, + {file = "fonttools-4.60.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22efea5e784e1d1cd8d7b856c198e360a979383ebc6dea4604743b56da1cbc34"}, + {file = "fonttools-4.60.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:677aa92d84d335e4d301d8ba04afca6f575316bc647b6782cb0921943fcb6343"}, + {file = "fonttools-4.60.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edd49d3defbf35476e78b61ff737ff5efea811acff68d44233a95a5a48252334"}, + {file = "fonttools-4.60.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:126839492b69cecc5baf2bddcde60caab2ffafd867bbae2a88463fce6078ca3a"}, + {file = "fonttools-4.60.2-cp310-cp310-win32.whl", hash = "sha256:ffcab6f5537136046ca902ed2491ab081ba271b07591b916289b7c27ff845f96"}, + {file = "fonttools-4.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:9c68b287c7ffcd29dd83b5f961004b2a54a862a88825d52ea219c6220309ba45"}, + {file = "fonttools-4.60.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2aed0a7931401b3875265717a24c726f87ecfedbb7b3426c2ca4d2812e281ae"}, + {file = "fonttools-4.60.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea6868e9d2b816c9076cfea77754686f3c19149873bdbc5acde437631c15df1"}, + {file = "fonttools-4.60.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa27f34950aa1fe0f0b1abe25eed04770a3b3b34ad94e5ace82cc341589678a"}, + {file = "fonttools-4.60.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13a53d479d187b09bfaa4a35ffcbc334fc494ff355f0a587386099cb66674f1e"}, + {file = "fonttools-4.60.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fac5e921d3bd0ca3bb8517dced2784f0742bc8ca28579a68b139f04ea323a779"}, + {file = "fonttools-4.60.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:648f4f9186fd7f1f3cd57dbf00d67a583720d5011feca67a5e88b3a491952cfb"}, + {file = "fonttools-4.60.2-cp311-cp311-win32.whl", hash = "sha256:3274e15fad871bead5453d5ce02658f6d0c7bc7e7021e2a5b8b04e2f9e40da1a"}, + {file = "fonttools-4.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:91d058d5a483a1525b367803abb69de0923fbd45e1f82ebd000f5c8aa65bc78e"}, + {file = "fonttools-4.60.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e0164b7609d2b5c5dd4e044b8085b7bd7ca7363ef8c269a4ab5b5d4885a426b2"}, + {file = "fonttools-4.60.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dd3d9574fc595c1e97faccae0f264dc88784ddf7fbf54c939528378bacc0033"}, + {file = "fonttools-4.60.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98d0719f1b11c2817307d2da2e94296a3b2a3503f8d6252a101dca3ee663b917"}, + {file = "fonttools-4.60.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d3ea26957dd07209f207b4fff64c702efe5496de153a54d3b91007ec28904dd"}, + {file = "fonttools-4.60.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ee301273b0850f3a515299f212898f37421f42ff9adfc341702582ca5073c13"}, + {file = "fonttools-4.60.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6eb4694cc3b9c03b7c01d65a9cf35b577f21aa6abdbeeb08d3114b842a58153"}, + {file = "fonttools-4.60.2-cp312-cp312-win32.whl", hash = "sha256:57f07b616c69c244cc1a5a51072eeef07dddda5ebef9ca5c6e9cf6d59ae65b70"}, + {file = "fonttools-4.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:310035802392f1fe5a7cf43d76f6ff4a24c919e4c72c0352e7b8176e2584b8a0"}, + {file = "fonttools-4.60.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bb5fd231e56ccd7403212636dcccffc96c5ae0d6f9e4721fa0a32cb2e3ca432"}, + {file = "fonttools-4.60.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:536b5fab7b6fec78ccf59b5c59489189d9d0a8b0d3a77ed1858be59afb096696"}, + {file = "fonttools-4.60.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b9288fc38252ac86a9570f19313ecbc9ff678982e0f27c757a85f1f284d3400"}, + {file = "fonttools-4.60.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93fcb420791d839ef592eada2b69997c445d0ce9c969b5190f2e16828ec10607"}, + {file = "fonttools-4.60.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7916a381b094db4052ac284255186aebf74c5440248b78860cb41e300036f598"}, + {file = "fonttools-4.60.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58c8c393d5e16b15662cfc2d988491940458aa87894c662154f50c7b49440bef"}, + {file = "fonttools-4.60.2-cp313-cp313-win32.whl", hash = "sha256:19c6e0afd8b02008caa0aa08ab896dfce5d0bcb510c49b2c499541d5cb95a963"}, + {file = "fonttools-4.60.2-cp313-cp313-win_amd64.whl", hash = "sha256:6a500dc59e11b2338c2dba1f8cf11a4ae8be35ec24af8b2628b8759a61457b76"}, + {file = "fonttools-4.60.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9387c532acbe323bbf2a920f132bce3c408a609d5f9dcfc6532fbc7e37f8ccbb"}, + {file = "fonttools-4.60.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6f1c824185b5b8fb681297f315f26ae55abb0d560c2579242feea8236b1cfef"}, + {file = "fonttools-4.60.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:55a3129d1e4030b1a30260f1b32fe76781b585fb2111d04a988e141c09eb6403"}, + {file = "fonttools-4.60.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b196e63753abc33b3b97a6fd6de4b7c4fef5552c0a5ba5e562be214d1e9668e0"}, + {file = "fonttools-4.60.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de76c8d740fb55745f3b154f0470c56db92ae3be27af8ad6c2e88f1458260c9a"}, + {file = "fonttools-4.60.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ba6303225c95998c9fda2d410aa792c3d2c1390a09df58d194b03e17583fa25"}, + {file = "fonttools-4.60.2-cp314-cp314-win32.whl", hash = "sha256:0a89728ce10d7c816fedaa5380c06d2793e7a8a634d7ce16810e536c22047384"}, + {file = "fonttools-4.60.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa8446e6ab8bd778b82cb1077058a2addba86f30de27ab9cc18ed32b34bc8667"}, + {file = "fonttools-4.60.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4063bc81ac5a4137642865cb63dd270e37b3cd1f55a07c0d6e41d072699ccca2"}, + {file = "fonttools-4.60.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebfdb66fa69732ed604ab8e2a0431e6deff35e933a11d73418cbc7823d03b8e1"}, + {file = "fonttools-4.60.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50b10b3b1a72d1d54c61b0e59239e1a94c0958f4a06a1febf97ce75388dd91a4"}, + {file = "fonttools-4.60.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:beae16891a13b4a2ddec9b39b4de76092a3025e4d1c82362e3042b62295d5e4d"}, + {file = "fonttools-4.60.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:522f017fdb3766fd5d2d321774ef351cc6ce88ad4e6ac9efe643e4a2b9d528db"}, + {file = "fonttools-4.60.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82cceceaf9c09a965a75b84a4b240dd3768e596ffb65ef53852681606fe7c9ba"}, + {file = "fonttools-4.60.2-cp314-cp314t-win32.whl", hash = "sha256:bbfbc918a75437fe7e6d64d1b1e1f713237df1cf00f3a36dedae910b2ba01cee"}, + {file = "fonttools-4.60.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0e5cd9b0830f6550d58c84f3ab151a9892b50c4f9d538c5603c0ce6fff2eb3f1"}, + {file = "fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008"}, + {file = "fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc"}, + {file = "fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311"}, + {file = "fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b"}, + {file = "fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e"}, + {file = "fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5"}, + {file = "fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37"}, + {file = "fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501"}, + {file = "fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a"}, + {file = "fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + +[[package]] +name = "fonttools" +version = "4.61.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24"}, + {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958"}, + {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da"}, + {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6"}, + {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1"}, + {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881"}, + {file = "fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47"}, + {file = "fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6"}, + {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09"}, + {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37"}, + {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb"}, + {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9"}, + {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87"}, + {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56"}, + {file = "fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a"}, + {file = "fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7"}, + {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e"}, + {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2"}, + {file = "fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796"}, + {file = "fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d"}, + {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8"}, + {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0"}, + {file = "fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261"}, + {file = "fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9"}, + {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c"}, + {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e"}, + {file = "fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5"}, + {file = "fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd"}, + {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3"}, + {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d"}, + {file = "fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c"}, + {file = "fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b"}, + {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd"}, + {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e"}, + {file = "fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c"}, + {file = "fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75"}, + {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063"}, + {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2"}, + {file = "fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c"}, + {file = "fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c"}, + {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa"}, + {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91"}, + {file = "fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19"}, + {file = "fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba"}, + {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7"}, + {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118"}, + {file = "fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5"}, + {file = "fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b"}, + {file = "fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371"}, + {file = "fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + [[package]] name = "googleapis-common-protos" version = "1.72.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, @@ -274,6 +771,7 @@ version = "1.76.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, @@ -350,6 +848,7 @@ version = "1.76.0" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18"}, {file = "grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd"}, @@ -366,6 +865,7 @@ version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, @@ -374,23 +874,433 @@ files = [ [package.extras] license = ["ukkonen"] +[[package]] +name = "importlib-resources" +version = "6.5.2" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, + {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] +[[package]] +name = "kiwisolver" +version = "1.4.7" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, +] + +[[package]] +name = "matplotlib" +version = "3.9.4" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, + {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, + {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26"}, + {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50"}, + {file = "matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5"}, + {file = "matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d"}, + {file = "matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c"}, + {file = "matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099"}, + {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249"}, + {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423"}, + {file = "matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e"}, + {file = "matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3"}, + {file = "matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70"}, + {file = "matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483"}, + {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f"}, + {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00"}, + {file = "matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0"}, + {file = "matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b"}, + {file = "matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6"}, + {file = "matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45"}, + {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858"}, + {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64"}, + {file = "matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df"}, + {file = "matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799"}, + {file = "matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb"}, + {file = "matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a"}, + {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c"}, + {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764"}, + {file = "matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041"}, + {file = "matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965"}, + {file = "matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c"}, + {file = "matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7"}, + {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e"}, + {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c"}, + {file = "matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb"}, + {file = "matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865"}, + {file = "matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "matplotlib" +version = "3.10.8" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7"}, + {file = "matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656"}, + {file = "matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df"}, + {file = "matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17"}, + {file = "matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933"}, + {file = "matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a"}, + {file = "matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160"}, + {file = "matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78"}, + {file = "matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4"}, + {file = "matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2"}, + {file = "matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6"}, + {file = "matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9"}, + {file = "matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2"}, + {file = "matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a"}, + {file = "matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58"}, + {file = "matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04"}, + {file = "matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f"}, + {file = "matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466"}, + {file = "matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf"}, + {file = "matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b"}, + {file = "matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6"}, + {file = "matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1"}, + {file = "matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486"}, + {file = "matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce"}, + {file = "matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6"}, + {file = "matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149"}, + {file = "matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645"}, + {file = "matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077"}, + {file = "matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22"}, + {file = "matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39"}, + {file = "matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565"}, + {file = "matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a"}, + {file = "matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958"}, + {file = "matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5"}, + {file = "matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f"}, + {file = "matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b"}, + {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d"}, + {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008"}, + {file = "matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c"}, + {file = "matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11"}, + {file = "matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8"}, + {file = "matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50"}, + {file = "matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908"}, + {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a"}, + {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1"}, + {file = "matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c"}, + {file = "matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b"}, + {file = "matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2"}, + {file = "matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=3" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mypy-extensions" version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -402,6 +1312,7 @@ version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, @@ -413,6 +1324,7 @@ version = "2.0.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, @@ -467,6 +1379,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -478,6 +1391,7 @@ version = "2.3.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, @@ -577,6 +1491,7 @@ version = "1.0.3" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c"}, {file = "pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d"}, @@ -588,12 +1503,249 @@ optional = ["typing-extensions (>=4)"] re2 = ["google-re2 (>=1.1)"] tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] +[[package]] +name = "pillow" +version = "11.3.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions ; python_version < \"3.10\""] +xmp = ["defusedxml"] + +[[package]] +name = "pillow" +version = "12.1.0" +description = "Python Imaging Library (fork)" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd"}, + {file = "pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0"}, + {file = "pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8"}, + {file = "pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1"}, + {file = "pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda"}, + {file = "pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7"}, + {file = "pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a"}, + {file = "pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef"}, + {file = "pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09"}, + {file = "pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91"}, + {file = "pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea"}, + {file = "pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3"}, + {file = "pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0"}, + {file = "pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451"}, + {file = "pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e"}, + {file = "pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84"}, + {file = "pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0"}, + {file = "pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b"}, + {file = "pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18"}, + {file = "pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64"}, + {file = "pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75"}, + {file = "pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304"}, + {file = "pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b"}, + {file = "pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551"}, + {file = "pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208"}, + {file = "pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5"}, + {file = "pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661"}, + {file = "pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17"}, + {file = "pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670"}, + {file = "pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616"}, + {file = "pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7"}, + {file = "pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d"}, + {file = "pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c"}, + {file = "pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1"}, + {file = "pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179"}, + {file = "pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0"}, + {file = "pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587"}, + {file = "pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac"}, + {file = "pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b"}, + {file = "pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea"}, + {file = "pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c"}, + {file = "pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc"}, + {file = "pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644"}, + {file = "pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c"}, + {file = "pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171"}, + {file = "pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a"}, + {file = "pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45"}, + {file = "pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d"}, + {file = "pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0"}, + {file = "pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554"}, + {file = "pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e"}, + {file = "pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82"}, + {file = "pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4"}, + {file = "pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0"}, + {file = "pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b"}, + {file = "pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65"}, + {file = "pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0"}, + {file = "pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8"}, + {file = "pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91"}, + {file = "pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796"}, + {file = "pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd"}, + {file = "pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13"}, + {file = "pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e"}, + {file = "pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643"}, + {file = "pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5"}, + {file = "pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de"}, + {file = "pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9"}, + {file = "pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a"}, + {file = "pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a"}, + {file = "pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030"}, + {file = "pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94"}, + {file = "pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4"}, + {file = "pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2"}, + {file = "pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61"}, + {file = "pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51"}, + {file = "pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc"}, + {file = "pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14"}, + {file = "pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8"}, + {file = "pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924"}, + {file = "pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef"}, + {file = "pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988"}, + {file = "pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6"}, + {file = "pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a"}, + {file = "pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19"}, + {file = "pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -610,6 +1762,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -625,6 +1778,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -643,6 +1797,7 @@ version = "6.33.4" description = "" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d"}, {file = "protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc"}, @@ -662,6 +1817,7 @@ version = "0.10.9.7" description = "Enables Python programs to dynamically access arbitrary Java objects" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "py4j-0.10.9.7-py2.py3-none-any.whl", hash = "sha256:85defdfd2b2376eb3abf5ca6474b51ab7e0de341c75a02f46dc9b5976f5a5c1b"}, {file = "py4j-0.10.9.7.tar.gz", hash = "sha256:0b6e5315bb3ada5cf62ac651d107bb2ebc02def3dee9d9548e3baac644ea8dbb"}, @@ -673,6 +1829,7 @@ version = "21.0.0" description = "Python library for Apache Arrow" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26"}, {file = "pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79"}, @@ -728,6 +1885,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -736,12 +1894,28 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyparsing" +version = "3.3.1" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82"}, + {file = "pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pyspark" version = "3.5.0" description = "Apache Spark Python API" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pyspark-3.5.0.tar.gz", hash = "sha256:d41a9b76bd2aca370a6100d075c029e22ba44c5940927877e9435a3a9c566558"}, ] @@ -768,6 +1942,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -791,6 +1966,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -809,6 +1985,7 @@ version = "14.0" description = "pytest plugin to re-run tests to eliminate flaky failures" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pytest-rerunfailures-14.0.tar.gz", hash = "sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92"}, {file = "pytest_rerunfailures-14.0-py3-none-any.whl", hash = "sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32"}, @@ -824,6 +2001,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -838,6 +2016,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -849,6 +2028,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -931,19 +2111,20 @@ version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -951,6 +2132,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -962,6 +2144,8 @@ version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, @@ -1018,6 +2202,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -1029,6 +2214,7 @@ version = "2025.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "dev"] files = [ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, @@ -1040,6 +2226,7 @@ version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, @@ -1056,9 +2243,36 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\"" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[extras] +all = ["duckdb", "pyspark"] +dev = ["black", "coverage", "duckdb", "matplotlib", "pre-commit", "pyspark", "pytest", "pytest-cov", "pytest-rerunfailures"] +duckdb = ["duckdb"] +spark = ["pyspark"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "18db29f1829ab8baebdd68c486c74b5e7e4304a6d344a26773685b07b85fe7c3" +content-hash = "7097bf5f307c1956c17cb9b53c57ed2adfa5a18836e3ab01a9beec01589fb20b" diff --git a/pydeequ/engines/__init__.py b/pydeequ/engines/__init__.py index b24a697..a165078 100644 --- a/pydeequ/engines/__init__.py +++ b/pydeequ/engines/__init__.py @@ -347,29 +347,42 @@ def connect( con.execute("CREATE TABLE reviews AS SELECT * FROM 'reviews.csv'") engine = pydeequ.connect(con, table="reviews") """ + connection_type = type(connection).__name__ + connection_module = type(connection).__module__ + # Try DuckDB - try: - import duckdb - if isinstance(connection, duckdb.DuckDBPyConnection): - if table is None: - raise ValueError("table parameter is required for DuckDB connections") - from pydeequ.engines.duckdb import DuckDBEngine - return DuckDBEngine(connection, table) - except ImportError: - pass + if "duckdb" in connection_module.lower(): + try: + import duckdb + if isinstance(connection, duckdb.DuckDBPyConnection): + if table is None: + raise ValueError("table parameter is required for DuckDB connections") + from pydeequ.engines.duckdb import DuckDBEngine + return DuckDBEngine(connection, table) + except ImportError: + raise ImportError( + "DuckDB backend requires the 'duckdb' package. " + "Install it with: pip install pydeequ[duckdb]" + ) from None # Try Spark - try: - from pyspark.sql import SparkSession - if isinstance(connection, SparkSession): - from pydeequ.engines.spark import SparkEngine - return SparkEngine(connection, table=table, dataframe=dataframe) - except ImportError: - pass + if "pyspark" in connection_module.lower() or "spark" in connection_type.lower(): + try: + from pyspark.sql import SparkSession + if isinstance(connection, SparkSession): + from pydeequ.engines.spark import SparkEngine + return SparkEngine(connection, table=table, dataframe=dataframe) + except ImportError: + raise ImportError( + "Spark backend requires the 'pyspark' package. " + "Install it with: pip install pydeequ[spark]" + ) from None raise ValueError( - f"Unsupported connection type: {type(connection).__name__}. " - "Supported types: duckdb.DuckDBPyConnection, pyspark.sql.SparkSession" + f"Unsupported connection type: {connection_type}. " + "Supported types:\n" + " - duckdb.DuckDBPyConnection (pip install pydeequ[duckdb])\n" + " - pyspark.sql.SparkSession (pip install pydeequ[spark])" ) diff --git a/pydeequ/engines/operators/grouping_operators.py b/pydeequ/engines/operators/grouping_operators.py index d4c58f6..702cfac 100644 --- a/pydeequ/engines/operators/grouping_operators.py +++ b/pydeequ/engines/operators/grouping_operators.py @@ -160,10 +160,10 @@ def extract_result(self, df: "pd.DataFrame") -> MetricResult: class EntropyOperator(GroupingOperator): """ - Computes entropy = -SUM(p * log2(p)). + Computes entropy = -SUM(p * ln(p)). Entropy measures the information content of a column's - value distribution. + value distribution. Uses natural log (nats) for Spark parity. """ def __init__(self, column: str, where: Optional[str] = None): @@ -199,7 +199,7 @@ def build_query(self, table: str) -> str: SELECT SUM(cnt) AS total_cnt FROM freq ) SELECT - -SUM((cnt * 1.0 / total_cnt) * LOG2(cnt * 1.0 / total_cnt)) AS entropy + -SUM((cnt * 1.0 / total_cnt) * LN(cnt * 1.0 / total_cnt)) AS entropy FROM freq, total WHERE cnt > 0 """ @@ -252,7 +252,7 @@ def build_query(self, table: str) -> str: ) SELECT SUM( (j.cnt * 1.0 / t.n) * - LOG2((j.cnt * 1.0 / t.n) / ((m1.cnt1 * 1.0 / t.n) * (m2.cnt2 * 1.0 / t.n))) + LN((j.cnt * 1.0 / t.n) / ((m1.cnt1 * 1.0 / t.n) * (m2.cnt2 * 1.0 / t.n))) ) AS mi FROM joint j, total t, marginal1 m1, marginal2 m2 WHERE j.{col1} = m1.{col1} AND j.{col2} = m2.{col2} AND j.cnt > 0 diff --git a/pydeequ/engines/operators/profiling_operators.py b/pydeequ/engines/operators/profiling_operators.py index a69f722..93cbfbb 100644 --- a/pydeequ/engines/operators/profiling_operators.py +++ b/pydeequ/engines/operators/profiling_operators.py @@ -101,7 +101,7 @@ def build_base_query(self, table: str) -> str: MAX({col}) as max_val, AVG({col}) as mean_val, SUM({col}) as sum_val, - STDDEV_SAMP({col}) as stddev_val + STDDEV_POP({col}) as stddev_val FROM {table} """ else: @@ -349,7 +349,7 @@ def build_numeric_stats_query(self, table: str) -> str: f"MAX({col}) as max_{col}", f"AVG({col}) as mean_{col}", f"SUM({col}) as sum_{col}", - f"STDDEV_SAMP({col}) as stddev_{col}", + f"STDDEV_POP({col}) as stddev_{col}", ]) return f"SELECT {', '.join(aggregations)} FROM {table}" diff --git a/pydeequ/engines/operators/scan_operators.py b/pydeequ/engines/operators/scan_operators.py index 1812ec9..b9a7f22 100644 --- a/pydeequ/engines/operators/scan_operators.py +++ b/pydeequ/engines/operators/scan_operators.py @@ -207,7 +207,7 @@ def metric_name(self) -> str: return "StandardDeviation" def get_aggregations(self) -> List[str]: - agg = self.wrap_agg_with_where("STDDEV_SAMP", self.column) + agg = self.wrap_agg_with_where("STDDEV_POP", self.column) return [f"{agg} AS {self.alias}"] def extract_result(self, df: "pd.DataFrame") -> MetricResult: diff --git a/pyproject.toml b/pyproject.toml index 9663d50..26b5237 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,18 @@ -[tool.poetry] +[project] name = "pydeequ" version = "2.0.0b1" description = "PyDeequ - Unit Tests for Data" -authors = ["Chenyang Liu ", "Rahul Sharma "] -maintainers = ["Chenyang Liu ","Rahul Sharma "] -license = "Apache-2.0" readme = "README.md" -homepage = "https://pydeequ.readthedocs.io" -repository = "https://github.com/awslabs/python-deequ" -documentation = "https://pydeequ.readthedocs.io" +license = {text = "Apache-2.0"} +requires-python = ">=3.9,<4" +authors = [ + {name = "Chenyang Liu", email = "peterl@amazon.com"}, + {name = "Rahul Sharma", email = "rdsharma@amazon.com"}, +] +maintainers = [ + {name = "Chenyang Liu", email = "peterl@amazon.com"}, + {name = "Rahul Sharma", email = "rdsharma@amazon.com"}, +] keywords = [ "deequ", "pydeequ", @@ -23,17 +27,64 @@ keywords = [ classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License" + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Database", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", +] + +# Core dependencies - minimal set required for the base package +dependencies = [ + "numpy>=1.23.0", + "pandas>=1.5.0", + "protobuf>=4.21.0", + "setuptools>=69.0.0", # Required for Python 3.12+ (distutils removed) +] + +[project.optional-dependencies] +# DuckDB backend - lightweight, no JVM required +duckdb = ["duckdb>=0.9.0"] + +# Spark backend - requires Spark Connect server +spark = ["pyspark[connect]>=3.5.0"] + +# All backends +all = [ + "duckdb>=0.9.0", + "pyspark[connect]>=3.5.0", ] +# Development dependencies +dev = [ + "pytest>=8.0.0", + "pytest-cov>=4.1.0", + "coverage>=7.4.0", + "black>=24.0.0", + "pre-commit>=3.6.0", + "pytest-rerunfailures>=14.0", + "matplotlib>=3.8.0", + "duckdb>=0.9.0", + "pyspark[connect]>=3.5.0", +] -[tool.poetry.dependencies] -python = ">=3.9,<4" -numpy = ">=1.23.0" -pandas = ">=1.5.0" -protobuf = ">=4.21.0" -setuptools = ">=69.0.0" # Required for Python 3.12+ (distutils removed) -pyspark = {version = "3.5.0", extras = ["connect"]} +[project.urls] +Homepage = "https://pydeequ.readthedocs.io" +Repository = "https://github.com/awslabs/python-deequ" +Documentation = "https://pydeequ.readthedocs.io" +Issues = "https://github.com/awslabs/python-deequ/issues" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +# Poetry-specific settings (for poetry install compatibility) +packages = [{include = "pydeequ"}] [tool.poetry.group.dev.dependencies] pytest = "^8.0.0" @@ -43,12 +94,8 @@ black = "^24.0.0" pre-commit = "^3.6.0" pytest-rerunfailures = "^14.0" matplotlib = "^3.8.0" - -[tool.poetry.extras] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +duckdb = ">=0.9.0" +pyspark = {version = ">=3.5.0", extras = ["connect"]} [tool.black] # https://github.com/psf/black @@ -58,7 +105,7 @@ include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true -target_version = ['py38'] +target_version = ['py39'] include = '\.pyi?$' exclude = ''' /( @@ -90,27 +137,10 @@ indent = ' ' multi_line_output = 3 include_trailing_comma = true skip_glob = ['__init__.py'] -#force_grid_wrap = 0 atomic = true -#lines_after_imports = 2 -#lines_between_types = 1 -#src_paths=isort,test - -# [mypy] -# python_version = 3.8 -#warn_return_any = True -#warn_unused_configs = True - -#[mypy-pyspark.*] -#ignore_missing_imports = True -# pytest -n 2 --reruns 3 --reruns-delay 5 --dist loadscope --tx 2*popen//python=python -[pytest] -testpaths = "tests" -norecursedirs = ".git .* *.egg* old docs dist build" +[tool.pytest.ini_options] +testpaths = ["tests"] +norecursedirs = [".git", ".*", "*.egg*", "old", "docs", "dist", "build"] cache_dir = "./.pytest_cache" python_files = "*test_*.py" -looponfailroots = "pydeequ tests" -# addopts = "-n3 --reruns 3 --reruns-delay 5 --dist loadscope" -# rsyncdirs = . mypkg helperpkg -# rsyncignore = .hg diff --git a/tests/conftest.py b/tests/conftest.py index 543a27e..75dd490 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,8 +2,7 @@ """ Pytest configuration for PyDeequ tests using Spark Connect. -All tests use the Spark Connect server which must be running before tests. -Start it with: scripts/start-spark-connect.sh +The Spark Connect server is automatically started by the spark_connect_server fixture. """ import os @@ -15,6 +14,35 @@ os.environ.setdefault("SPARK_VERSION", "3.5") +@pytest.fixture(scope="session") +def spark_connect_server(): + """Session-scoped fixture to start Spark Connect server. + + Automatically starts the Spark Connect server if not already running, + using the benchmark configuration. The server is NOT stopped after + tests complete (to allow reuse across test runs). + """ + from benchmark.spark_server import SparkConnectServer + from benchmark.config import SparkServerConfig + + config = SparkServerConfig() + server = SparkConnectServer(config) + + if not server.is_running(): + print("\nStarting Spark Connect server for tests...") + server.start() + print("Spark Connect server started.") + else: + print("\nSpark Connect server already running.") + + # Set SPARK_REMOTE if not already set + if not os.environ.get("SPARK_REMOTE"): + os.environ["SPARK_REMOTE"] = f"sc://localhost:{config.port}" + + yield server + # Note: We don't stop the server here to allow reuse across test runs + + def create_spark_connect_session() -> SparkSession: """ Create a Spark Connect session for testing. @@ -29,11 +57,12 @@ def create_spark_connect_session() -> SparkSession: @pytest.fixture(scope="module") -def spark() -> SparkSession: +def spark(spark_connect_server) -> SparkSession: """ Pytest fixture providing a Spark Connect session. The session is shared within each test module for efficiency. + Depends on spark_connect_server to ensure server is running. Yields: SparkSession for testing @@ -75,6 +104,6 @@ def config(self, key, value): return self def getOrCreate(self): - return get_spark_connect_session() + return create_spark_connect_session() return SparkConnectBuilder() diff --git a/tests/engines/comparison/conftest.py b/tests/engines/comparison/conftest.py index faeb701..b8fc389 100644 --- a/tests/engines/comparison/conftest.py +++ b/tests/engines/comparison/conftest.py @@ -15,8 +15,8 @@ """Dual-engine test fixtures for cross-engine comparison. Provides fixtures for creating both Spark and DuckDB engines with -identical data for parity testing. These tests require Spark Connect -to be running and SPARK_REMOTE environment variable to be set. +identical data for parity testing. The Spark Connect server is +automatically started if not already running. """ import os @@ -31,17 +31,9 @@ from tests.engines.fixtures.datasets import DATASET_FACTORIES -# Check if Spark Connect is available -def spark_available() -> bool: - """Check if Spark Connect is available via SPARK_REMOTE.""" - return os.environ.get("SPARK_REMOTE") is not None - - -# Skip marker for tests requiring Spark -requires_spark = pytest.mark.skipif( - not spark_available(), - reason="Spark Connect not available (set SPARK_REMOTE)" -) +# Marker for tests requiring Spark - uses the spark_connect_server fixture +# from the top-level conftest.py which automatically starts the server +requires_spark = pytest.mark.usefixtures("spark_connect_server") @dataclass @@ -53,16 +45,13 @@ class DualEngines: @pytest.fixture(scope="module") -def spark_session(): +def spark_session(spark_connect_server): """Create a module-scoped Spark Connect session. - Only creates session if SPARK_REMOTE is set. + Depends on spark_connect_server fixture to ensure server is running. """ - if not spark_available(): - pytest.skip("Spark Connect not available") - from pyspark.sql import SparkSession - spark_remote = os.environ.get("SPARK_REMOTE") + spark_remote = os.environ.get("SPARK_REMOTE", "sc://localhost:15002") spark = SparkSession.builder.remote(spark_remote).getOrCreate() yield spark spark.stop() diff --git a/tests/engines/comparison/utils.py b/tests/engines/comparison/utils.py index e944580..78be3df 100644 --- a/tests/engines/comparison/utils.py +++ b/tests/engines/comparison/utils.py @@ -18,6 +18,7 @@ with appropriate tolerance levels for different metric types. """ +import json import math from dataclasses import dataclass, field from typing import Any, Dict, List, Optional, Tuple @@ -29,6 +30,7 @@ FLOAT_EPSILON = 1e-9 # Exact comparisons: Size, Completeness, Uniqueness FLOAT_TOLERANCE = 1e-6 # Statistical: Mean, StdDev, Correlation APPROX_TOLERANCE = 0.1 # Approximate algorithms: ApproxCountDistinct (10% relative) +ENTROPY_TOLERANCE = 1e-4 # Information theory metrics: Entropy, MutualInformation # Mapping of analyzer types to their expected tolerance @@ -52,9 +54,9 @@ "Maximum": FLOAT_TOLERANCE, "StandardDeviation": FLOAT_TOLERANCE, "Correlation": FLOAT_TOLERANCE, - "Entropy": FLOAT_TOLERANCE, - "MutualInformation": FLOAT_TOLERANCE, - "ApproxQuantile": FLOAT_TOLERANCE, + "Entropy": ENTROPY_TOLERANCE, + "MutualInformation": ENTROPY_TOLERANCE, + "ApproxQuantile": APPROX_TOLERANCE, # Approximate metrics "ApproxCountDistinct": APPROX_TOLERANCE, @@ -96,6 +98,18 @@ def values_equal( if math.isnan(actual) or math.isnan(expected): return False + # Handle JSON strings vs dicts (for Histogram, DataType) + if isinstance(actual, str) and not isinstance(expected, str): + try: + actual = json.loads(actual) + except (json.JSONDecodeError, TypeError): + pass + if isinstance(expected, str) and not isinstance(actual, str): + try: + expected = json.loads(expected) + except (json.JSONDecodeError, TypeError): + pass + # Handle strings if isinstance(actual, str) and isinstance(expected, str): return actual == expected @@ -349,7 +363,7 @@ def compare_profiles( ("minimum", FLOAT_TOLERANCE), ("maximum", FLOAT_TOLERANCE), ("sum", FLOAT_TOLERANCE), - ("std_dev", FLOAT_TOLERANCE), + ("std_dev", APPROX_TOLERANCE), # Use relative tolerance for sample vs pop ] for attr, tolerance in attrs_to_compare: diff --git a/tests/engines/fixtures/datasets.py b/tests/engines/fixtures/datasets.py index bb22fdf..af002e8 100644 --- a/tests/engines/fixtures/datasets.py +++ b/tests/engines/fixtures/datasets.py @@ -57,7 +57,7 @@ def create_df_numeric() -> pd.DataFrame: Purpose: Statistical analyzer tests (Mean, Sum, Min, Max, StdDev) Edge cases: Mean=3.5, includes NULL column Values: 1, 2, 3, 4, 5, 6 -> Mean=3.5, Sum=21, Min=1, Max=6 - StdDev (sample) = sqrt(17.5/5) = sqrt(3.5) ≈ 1.8708 + StdDev (population) = sqrt(17.5/6) ≈ 1.7078 """ return pd.DataFrame({ "att1": [1, 2, 3, 4, 5, 6], @@ -194,12 +194,12 @@ def create_df_entropy() -> pd.DataFrame: Purpose: Entropy analyzer tests Edge cases: Uniform vs skewed distribution - - uniform: 4 distinct values each appearing once -> entropy = log2(4) = 2.0 - - skewed: 1 value appearing 3 times, 1 appearing once -> entropy < 2.0 + - uniform: 4 distinct values each appearing once -> entropy = ln(4) ≈ 1.386 + - skewed: 1 value appearing 3 times, 1 appearing once -> entropy < 1.386 """ return pd.DataFrame({ - "uniform": ["a", "b", "c", "d"], # Entropy = log2(4) = 2.0 - "skewed": ["a", "a", "a", "b"], # Entropy = -(3/4)log2(3/4) - (1/4)log2(1/4) ≈ 0.811 + "uniform": ["a", "b", "c", "d"], # Entropy = ln(4) ≈ 1.386 + "skewed": ["a", "a", "a", "b"], # Entropy = -(3/4)ln(3/4) - (1/4)ln(1/4) ≈ 0.562 "constant": ["x", "x", "x", "x"], # Entropy = 0 (single value) "item": [1, 2, 3, 4], }) @@ -379,8 +379,8 @@ def create_df_data_type() -> pd.DataFrame: ("df_single", "Maximum", "item"): 1.0, ("df_single", "Maximum", "price"): 10.0, - # StandardDeviation analyzer (sample stddev) - ("df_numeric", "StandardDeviation", "att1"): 1.8708286933869707, # sqrt(17.5/5) + # StandardDeviation analyzer (population stddev) + ("df_numeric", "StandardDeviation", "att1"): 1.7078251276599330, # sqrt(17.5/6) # String length analyzers ("df_string_lengths", "MinLength", "att1"): 0.0, # Empty string @@ -408,7 +408,7 @@ def create_df_data_type() -> pd.DataFrame: ("df_correlation", "Correlation", ("x", "z")): -1.0, # Perfect negative # Entropy analyzer - ("df_entropy", "Entropy", "uniform"): 2.0, # log2(4) for 4 uniform values + ("df_entropy", "Entropy", "uniform"): 1.3862943611198906, # ln(4) for 4 uniform values ("df_entropy", "Entropy", "constant"): 0.0, # Single value = 0 entropy # ApproxCountDistinct analyzer diff --git a/tests/engines/test_duckdb_analyzers.py b/tests/engines/test_duckdb_analyzers.py index b5d9186..189c80f 100644 --- a/tests/engines/test_duckdb_analyzers.py +++ b/tests/engines/test_duckdb_analyzers.py @@ -232,11 +232,11 @@ class TestStandardDeviationAnalyzer: """Tests for the StandardDeviation analyzer.""" def test_stddev_basic(self, engine_numeric): - """StandardDeviation calculates sample stddev correctly.""" + """StandardDeviation calculates population stddev correctly.""" metrics = engine_numeric.compute_metrics([StandardDeviation("att1")]) value = get_metric_value(metrics, "StandardDeviation", "att1") - # Sample stddev of [1,2,3,4,5,6] = sqrt(17.5/5) ≈ 1.8708 - assert is_close(value, 1.8708286933869707, FLOAT_TOLERANCE) + # Population stddev of [1,2,3,4,5,6] = sqrt(17.5/6) ≈ 1.7078 (matches Spark) + assert is_close(value, 1.7078251276599330, FLOAT_TOLERANCE) def test_stddev_single_row(self, engine_single): """StandardDeviation for single row is NaN or 0.""" @@ -492,11 +492,11 @@ class TestEntropyAnalyzer: """Tests for the Entropy analyzer.""" def test_entropy_uniform(self, engine_entropy): - """Entropy is log2(n) for uniform distribution.""" + """Entropy is ln(n) for uniform distribution.""" metrics = engine_entropy.compute_metrics([Entropy("uniform")]) value = get_metric_value(metrics, "Entropy", "uniform") - # 4 equally distributed values: entropy = log2(4) = 2.0 - assert is_close(value, 2.0, FLOAT_TOLERANCE) + # 4 equally distributed values: entropy = ln(4) ≈ 1.386 (matches Spark) + assert is_close(value, 1.3862943611198906, FLOAT_TOLERANCE) def test_entropy_constant(self, engine_entropy): """Entropy is 0 for constant column.""" @@ -508,8 +508,8 @@ def test_entropy_skewed(self, engine_entropy): """Entropy is between 0 and max for skewed distribution.""" metrics = engine_entropy.compute_metrics([Entropy("skewed")]) value = get_metric_value(metrics, "Entropy", "skewed") - # Skewed distribution: 0 < entropy < log2(4) - assert value > 0.0 and value < 2.0 + # Skewed distribution: 0 < entropy < ln(4) ≈ 1.386 + assert value > 0.0 and value < 1.3862943611198906 class TestHistogramAnalyzer: diff --git a/tests/engines/test_duckdb_constraints.py b/tests/engines/test_duckdb_constraints.py index 07ffe31..a1124ed 100644 --- a/tests/engines/test_duckdb_constraints.py +++ b/tests/engines/test_duckdb_constraints.py @@ -291,7 +291,8 @@ class TestEntropyConstraints: def test_has_entropy_uniform(self, engine_entropy): """hasEntropy for uniform distribution.""" - check = Check(CheckLevel.Error, "entropy").hasEntropy("uniform", eq(2.0)) + # ln(4) ≈ 1.386 (matches Spark's natural log convention) + check = Check(CheckLevel.Error, "entropy").hasEntropy("uniform", between(1.38, 1.39)) results = engine_entropy.run_checks([check]) result = results[0] assert result.constraint_status == ConstraintStatus.Success diff --git a/tests/engines/test_duckdb_profiles.py b/tests/engines/test_duckdb_profiles.py index 01c701a..f105fa1 100644 --- a/tests/engines/test_duckdb_profiles.py +++ b/tests/engines/test_duckdb_profiles.py @@ -153,7 +153,8 @@ def test_stddev_numeric(self, engine_numeric): profiles = engine_numeric.profile_columns(columns=["att1"]) profile = get_profile_by_column(profiles, "att1") if profile.std_dev is not None: - assert is_close(profile.std_dev, 1.8708286933869707, FLOAT_TOLERANCE) + # Population stddev (matches Spark) + assert is_close(profile.std_dev, 1.7078251276599330, FLOAT_TOLERANCE) def test_numeric_with_nulls(self, engine_numeric): """Numeric statistics handle NULLs correctly.""" diff --git a/tests/engines/test_operators.py b/tests/engines/test_operators.py index a02d9fc..62ccd24 100644 --- a/tests/engines/test_operators.py +++ b/tests/engines/test_operators.py @@ -335,7 +335,7 @@ def test_build_query(self): op = EntropyOperator("category") query = op.build_query("products") assert "GROUP BY category" in query - assert "LOG2" in query + assert "LN" in query assert "entropy" in query def test_extract_result(self): diff --git a/tests/v2/conftest.py b/tests/v2/conftest.py index 0474335..611d170 100644 --- a/tests/v2/conftest.py +++ b/tests/v2/conftest.py @@ -20,11 +20,13 @@ import pytest from pyspark.sql import Row, SparkSession -@pytest.fixture(scope="session") -def spark(): +@pytest.fixture(scope="module") +def spark(spark_connect_server): """ - Session-scoped Spark Connect session. - Shared across all tests for efficiency. + Module-scoped Spark Connect session. + + Depends on spark_connect_server fixture from tests/conftest.py + to ensure the server is running before creating the session. """ remote_url = os.environ.get("SPARK_REMOTE", "sc://localhost:15002") session = SparkSession.builder.remote(remote_url).getOrCreate() diff --git a/tests/v2/test_e2e_spark_connect.py b/tests/v2/test_e2e_spark_connect.py index 58c18fd..1dc019d 100644 --- a/tests/v2/test_e2e_spark_connect.py +++ b/tests/v2/test_e2e_spark_connect.py @@ -42,14 +42,8 @@ # Import the new Spark Connect API from pydeequ.v2.verification import AnalysisRunner, VerificationSuite -# Skip all tests if SPARK_REMOTE is not set -pytestmark = pytest.mark.skipif( - "SPARK_REMOTE" not in os.environ, - reason="SPARK_REMOTE environment variable not set. Start Spark Connect server first.", -) - - -# Note: spark fixture is defined in conftest.py (session-scoped) +# Note: spark fixture is defined in conftest.py and depends on spark_connect_server +# which automatically starts the server if needed @pytest.fixture(scope="module") From b8a3244f9830e0bbe97f73efae5ed2666d500181 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Mon, 19 Jan 2026 23:28:43 -0500 Subject: [PATCH 3/7] fix readme --- README.md | 186 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 153 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2d19db5..f351ca9 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ PyDeequ is a Python API for [Deequ](https://github.com/awslabs/deequ), a library ## What's New in PyDeequ 2.0 -PyDeequ 2.0 introduces a new architecture using **Spark Connect**, bringing significant improvements: +PyDeequ 2.0 introduces a new multi-engine architecture with **DuckDB** and **Spark Connect** backends: | Feature | PyDeequ 1.x | PyDeequ 2.0 | |---------|-------------|-------------| -| Communication | Py4J (JVM bridge) | Spark Connect (gRPC) | +| Backends | Spark only (Py4J) | DuckDB, Spark Connect | +| JVM Required | Yes | No (DuckDB) / Yes (Spark) | | Assertions | Python lambdas | Serializable predicates | -| Spark Session | Local only | Local or Remote | -| Architecture | Tight JVM coupling | Clean client-server | +| Remote Execution | No | Yes (Spark Connect) | **Key Benefits:** -- **No Py4J dependency** - Uses Spark Connect protocol for communication +- **DuckDB backend** - Lightweight, no JVM required, perfect for local development and CI/CD +- **Spark Connect backend** - Production-scale processing with remote cluster support - **Serializable predicates** - Replace Python lambdas with predicate objects (`eq`, `gte`, `between`, etc.) -- **Remote execution** - Connect to remote Spark clusters via Spark Connect -- **Cleaner API** - Simplified imports and more Pythonic interface +- **Unified API** - Same code works with both backends ### Architecture @@ -46,33 +46,136 @@ flowchart LR ### Feature Support Matrix -| Feature | PyDeequ 1.x | PyDeequ 2.0 | -|---------|:-----------:|:-----------:| -| **Constraint Verification** | | | -| VerificationSuite | Yes | Yes | -| Check constraints | Yes | Yes | -| Custom SQL expressions | Yes | Yes | -| **Metrics & Analysis** | | | -| AnalysisRunner | Yes | Yes | -| All standard analyzers | Yes | Yes | -| **Column Profiling** | | | -| ColumnProfilerRunner | Yes | Yes | -| Numeric statistics | Yes | Yes | -| KLL sketch profiling | Yes | Yes | -| Low-cardinality histograms | Yes | Yes | -| **Constraint Suggestions** | | | -| ConstraintSuggestionRunner | Yes | Yes | -| Rule sets (DEFAULT, EXTENDED, etc.) | Yes | Yes | -| Train/test split evaluation | Yes | Yes | -| **Metrics Repository** | | | -| FileSystemMetricsRepository | Yes | Planned | -| **Execution Mode** | | | -| Local Spark | Yes | No | -| Spark Connect (remote) | No | Yes | +| Feature | PyDeequ 1.x | PyDeequ 2.0 (DuckDB) | PyDeequ 2.0 (Spark) | +|---------|:-----------:|:--------------------:|:-------------------:| +| **Constraint Verification** | | | | +| VerificationSuite | Yes | Yes | Yes | +| Check constraints | Yes | Yes | Yes | +| Custom SQL expressions | Yes | Yes | Yes | +| **Metrics & Analysis** | | | | +| AnalysisRunner | Yes | Yes | Yes | +| All standard analyzers | Yes | Yes | Yes | +| **Column Profiling** | | | | +| ColumnProfilerRunner | Yes | Yes | Yes | +| Numeric statistics | Yes | Yes | Yes | +| KLL sketch profiling | Yes | No | Yes | +| Low-cardinality histograms | Yes | Yes | Yes | +| **Constraint Suggestions** | | | | +| ConstraintSuggestionRunner | Yes | Yes | Yes | +| Rule sets (DEFAULT, EXTENDED, etc.) | Yes | Yes | Yes | +| Train/test split evaluation | Yes | No | Yes | +| **Metrics Repository** | | | | +| FileSystemMetricsRepository | Yes | Planned | Planned | +| **Execution Environment** | | | | +| JVM Required | Yes | No | Yes | +| Local execution | Yes | Yes | Yes | +| Remote execution | No | No | Yes | --- -## PyDeequ 2.0 Beta - Quick Start +## Installation + +PyDeequ 2.0 supports multiple backends. Install only what you need: + +**From PyPI (when published):** +```bash +# DuckDB backend (lightweight, no JVM required) +pip install pydeequ[duckdb] + +# Spark Connect backend (for production-scale processing) +pip install pydeequ[spark] + +# Both backends +pip install pydeequ[all] + +# Development (includes all backends + test tools) +pip install pydeequ[dev] +``` + +**From GitHub Release (beta):** +```bash +# Install beta wheel + DuckDB +pip install https://github.com/awslabs/python-deequ/releases/download/v2.0.0b1/pydeequ-2.0.0b1-py3-none-any.whl +pip install duckdb + +# For Spark backend, also install: +pip install pyspark[connect]==3.5.0 +``` + +--- + +## Quick Start with DuckDB (Recommended for Getting Started) + +The DuckDB backend is the easiest way to get started - no JVM or Spark server required. + +### Requirements +- Python 3.9+ + +### Installation + +```bash +pip install pydeequ[duckdb] +``` + +### Run Your First Check + +```python +import duckdb +import pydeequ +from pydeequ.v2.analyzers import Size, Completeness, Mean +from pydeequ.v2.checks import Check, CheckLevel +from pydeequ.v2.predicates import eq, gte + +# Create a DuckDB connection and load data +con = duckdb.connect() +con.execute(""" + CREATE TABLE users AS SELECT * FROM (VALUES + (1, 'Alice', 25), + (2, 'Bob', 30), + (3, 'Charlie', NULL) + ) AS t(id, name, age) +""") + +# Create an engine from the connection +engine = pydeequ.connect(con, table="users") + +# Run analyzers +metrics = engine.compute_metrics([ + Size(), + Completeness("id"), + Completeness("age"), + Mean("age"), +]) +print("Metrics:") +for m in metrics: + print(f" {m.name}({m.instance}): {m.value}") + +# Run constraint checks +check = (Check(CheckLevel.Error, "Data quality checks") + .hasSize(eq(3)) + .isComplete("id") + .isComplete("name") + .hasCompleteness("age", gte(0.5))) + +results = engine.run_checks([check]) +print("\nConstraint Results:") +for r in results: + print(f" {r.constraint}: {r.constraint_status}") + +# Profile columns +profiles = engine.profile_columns() +print("\nColumn Profiles:") +for p in profiles: + print(f" {p.column}: completeness={p.completeness}, distinct={p.approx_distinct_values}") + +con.close() +``` + +--- + +## Quick Start with Spark Connect (Production Scale) + +For production workloads and large-scale data processing, use the Spark Connect backend. ### Requirements @@ -142,6 +245,11 @@ pip install pyspark[connect]==3.5.0 pip install setuptools ``` +Or using the extras syntax (once published to PyPI): +```bash +pip install pydeequ[spark] +``` + ### Step 5: Run Your First Check ```python @@ -444,7 +552,8 @@ The legacy PyDeequ API uses Py4J for JVM communication. It is still available fo ### Installation ```bash -pip install pydeequ +# Install with Spark backend (required for 1.x API) +pip install pydeequ[spark] ``` **Note:** Set the `SPARK_VERSION` environment variable to match your Spark version. @@ -638,7 +747,14 @@ sdk install spark 3.5.0 ### Poetry ```bash -poetry install +# Install all dependencies (including dev tools and both backends) +poetry install --with dev --all-extras + +# Or install specific extras +poetry install --extras duckdb # DuckDB only +poetry install --extras spark # Spark only +poetry install --extras all # Both backends + poetry update poetry show -o ``` @@ -646,7 +762,11 @@ poetry show -o ### Running Tests Locally ```bash +# Run all tests (requires Spark Connect server for comparison tests) poetry run pytest + +# Run DuckDB-only tests (no Spark required) +poetry run pytest tests/engines/test_duckdb*.py tests/engines/test_operators.py ``` ### Running Tests (Docker) From ef51c1c0a43742b0b545c9ab62d788800576ec91 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Tue, 20 Jan 2026 09:27:12 -0500 Subject: [PATCH 4/7] refactor test helper and benchmark results --- BENCHMARK.md | 34 +++--- imgs/benchmark_chart.png | Bin 180620 -> 185152 bytes tests/conftest.py | 8 +- tests/helpers/__init__.py | 19 ++++ tests/helpers/spark_server.py | 193 ++++++++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 22 deletions(-) create mode 100644 tests/helpers/__init__.py create mode 100644 tests/helpers/spark_server.py diff --git a/BENCHMARK.md b/BENCHMARK.md index 856bb20..b85c43e 100644 --- a/BENCHMARK.md +++ b/BENCHMARK.md @@ -58,36 +58,36 @@ Benchmark run on Apple M3 Max (14 cores), macOS Darwin 25.2.0. | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.080 | 1.159 | **14.6x** | -| 1M | 0.114 | 1.824 | **16.0x** | -| 5M | 0.243 | 2.491 | **10.3x** | -| 10M | 0.354 | 3.276 | **9.2x** | -| 50M | 1.153 | 10.959 | **9.5x** | -| 130M | 2.792 | 27.385 | **9.8x** | +| 100K | 0.052 | 0.667 | **12.8x** | +| 1M | 0.090 | 1.718 | **19.1x** | +| 5M | 0.221 | 2.591 | **11.7x** | +| 10M | 0.335 | 3.504 | **10.5x** | +| 50M | 1.177 | 12.808 | **10.9x** | +| 130M | 2.897 | 29.570 | **10.2x** | ### Experiment 2: Varying Columns | Cols | Checks | DuckDB (s) | Spark (s) | Speedup | |------|--------|------------|-----------|---------| -| 10 | 16 | 0.108 | 1.572 | **14.5x** | -| 20 | 46 | 0.280 | 2.049 | **7.3x** | -| 40 | 106 | 0.824 | 2.760 | **3.3x** | -| 80 | 226 | 2.320 | 4.425 | **1.9x** | +| 10 | 16 | 0.118 | 1.656 | **14.1x** | +| 20 | 46 | 0.286 | 2.129 | **7.5x** | +| 40 | 106 | 0.713 | 2.869 | **4.0x** | +| 80 | 226 | 2.214 | 4.434 | **2.0x** | ### Experiment 3: Column Profiling | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.097 | 0.651 | **6.7x** | -| 1M | 0.372 | 0.778 | **2.1x** | -| 5M | 1.446 | 1.898 | **1.3x** | -| 10M | 2.614 | 3.450 | **1.3x** | +| 100K | 0.086 | 0.599 | **7.0x** | +| 1M | 0.388 | 0.814 | **2.1x** | +| 5M | 1.470 | 2.399 | **1.6x** | +| 10M | 2.659 | 4.109 | **1.5x** | ### Key Takeaways -1. **DuckDB is 9-16x faster** for row-scaling validation workloads -2. **Speedup decreases with complexity** - more columns/checks narrow the gap -3. **Profiling converges** - at 10M rows, both engines perform similarly +1. **DuckDB is 10-19x faster** for row-scaling validation workloads +2. **Speedup decreases with complexity** - more columns/checks narrow the gap (14x → 2x) +3. **Profiling converges** - at 10M rows, DuckDB is still 1.5x faster 4. **No JVM overhead** - DuckDB runs natively in Python, no startup cost ## Quick Start diff --git a/imgs/benchmark_chart.png b/imgs/benchmark_chart.png index 9283677829ea8448927c85417cecaa06b5e40182..f22a870e1f85d7938a98136ff447449986984343 100644 GIT binary patch literal 185152 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 T#-&j7yQ(T{D&?HHR literal 180620 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ĸ{3*UneCuvwa8u!=AViJ* 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 ZP_8Qp=_go5eIjqXUqfeilDduW{{oEWcWVFu diff --git a/tests/conftest.py b/tests/conftest.py index 75dd490..077afec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,12 +18,10 @@ def spark_connect_server(): """Session-scoped fixture to start Spark Connect server. - Automatically starts the Spark Connect server if not already running, - using the benchmark configuration. The server is NOT stopped after - tests complete (to allow reuse across test runs). + Automatically starts the Spark Connect server if not already running. + The server is NOT stopped after tests complete (to allow reuse across test runs). """ - from benchmark.spark_server import SparkConnectServer - from benchmark.config import SparkServerConfig + from tests.helpers.spark_server import SparkConnectServer, SparkServerConfig config = SparkServerConfig() server = SparkConnectServer(config) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 0000000..9c7f9e4 --- /dev/null +++ b/tests/helpers/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test helpers for PyDeequ tests.""" + +from tests.helpers.spark_server import SparkConnectServer, SparkServerConfig + +__all__ = ["SparkConnectServer", "SparkServerConfig"] diff --git a/tests/helpers/spark_server.py b/tests/helpers/spark_server.py new file mode 100644 index 0000000..2679433 --- /dev/null +++ b/tests/helpers/spark_server.py @@ -0,0 +1,193 @@ +# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Spark Connect server management for tests. + +This module provides utilities to start and manage a Spark Connect server +for running integration tests that require Spark. +""" + +import os +import socket +import subprocess +import time +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class SparkServerConfig: + """Configuration for Spark Connect server.""" + + java_home: str = field( + default_factory=lambda: os.environ.get( + "JAVA_HOME", + "/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home", + ) + ) + spark_home: str = field( + default_factory=lambda: os.environ.get( + "SPARK_HOME", "/Volumes/workplace/deequ_rewrite/spark-3.5.0-bin-hadoop3" + ) + ) + port: int = 15002 + startup_timeout: int = 60 + poll_interval: float = 1.0 + driver_memory: str = "4g" + executor_memory: str = "4g" + deequ_jar: str = field( + default_factory=lambda: os.environ.get( + "DEEQU_JAR", + "/Volumes/workplace/deequ_rewrite/deequ/target/deequ_2.12-2.1.0b-spark-3.5.jar" + ) + ) + + +class SparkConnectServer: + """Manages Spark Connect server lifecycle for tests.""" + + def __init__(self, config: Optional[SparkServerConfig] = None): + """ + Initialize Spark Connect server manager. + + Args: + config: Server configuration (uses defaults if not provided) + """ + self.config = config or SparkServerConfig() + self._process: Optional[subprocess.Popen] = None + self._started_by_us = False + + def is_running(self) -> bool: + """Check if Spark Connect server is running by attempting to connect.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex(("localhost", self.config.port)) + sock.close() + return result == 0 + except (socket.error, OSError): + return False + + def start(self) -> float: + """ + Start Spark Connect server if not already running. + + Returns: + Time taken to start the server (0 if already running) + + Raises: + RuntimeError: If server fails to start within timeout + """ + if self.is_running(): + print(f"Spark Connect server already running on port {self.config.port}") + return 0.0 + + start_time = time.time() + + # Build the startup command + start_script = os.path.join(self.config.spark_home, "sbin", "start-connect-server.sh") + + if not os.path.exists(start_script): + raise RuntimeError(f"Spark Connect start script not found: {start_script}") + + cmd = [ + start_script, + "--conf", f"spark.driver.memory={self.config.driver_memory}", + "--conf", f"spark.executor.memory={self.config.executor_memory}", + "--packages", "org.apache.spark:spark-connect_2.12:3.5.0", + "--jars", self.config.deequ_jar, + "--conf", "spark.connect.extensions.relation.classes=com.amazon.deequ.connect.DeequRelationPlugin", + ] + + # Set up environment + env = os.environ.copy() + env["JAVA_HOME"] = self.config.java_home + env["SPARK_HOME"] = self.config.spark_home + + print(f"Starting Spark Connect server on port {self.config.port}...") + print(f" JAVA_HOME: {self.config.java_home}") + print(f" SPARK_HOME: {self.config.spark_home}") + + # Start the server + self._process = subprocess.Popen( + cmd, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self._started_by_us = True + + # Wait for server to be ready + deadline = time.time() + self.config.startup_timeout + while time.time() < deadline: + if self.is_running(): + elapsed = time.time() - start_time + print(f"Spark Connect server started in {elapsed:.1f}s") + return elapsed + time.sleep(self.config.poll_interval) + + # Timeout - try to get error output + if self._process: + self._process.terminate() + _, stderr = self._process.communicate(timeout=5) + error_msg = stderr.decode() if stderr else "Unknown error" + self._process = None + self._started_by_us = False + raise RuntimeError( + f"Spark Connect server failed to start within {self.config.startup_timeout}s: {error_msg[:500]}" + ) + + raise RuntimeError( + f"Spark Connect server failed to start within {self.config.startup_timeout}s" + ) + + def stop(self) -> None: + """Stop Spark Connect server if we started it.""" + if not self._started_by_us: + print("Spark Connect server was not started by us, skipping stop") + return + + stop_script = os.path.join(self.config.spark_home, "sbin", "stop-connect-server.sh") + + if os.path.exists(stop_script): + print("Stopping Spark Connect server...") + env = os.environ.copy() + env["JAVA_HOME"] = self.config.java_home + env["SPARK_HOME"] = self.config.spark_home + + try: + subprocess.run( + [stop_script], + env=env, + timeout=30, + capture_output=True, + ) + print("Spark Connect server stopped") + except subprocess.TimeoutExpired: + print("Warning: stop script timed out") + except Exception as e: + print(f"Warning: Error stopping server: {e}") + else: + # Fall back to killing the process directly + if self._process: + print("Terminating Spark Connect server process...") + self._process.terminate() + try: + self._process.wait(timeout=10) + except subprocess.TimeoutExpired: + self._process.kill() + print("Spark Connect server process terminated") + + self._started_by_us = False + self._process = None From 5c16f2291dc1dac4a0f4717f30f3c820f04304f2 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Tue, 20 Jan 2026 10:07:42 -0500 Subject: [PATCH 5/7] Update readme --- README.md | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f351ca9..85c1714 100644 --- a/README.md +++ b/README.md @@ -24,25 +24,40 @@ PyDeequ 2.0 introduces a new multi-engine architecture with **DuckDB** and **Spa ### Architecture ```mermaid -flowchart LR +flowchart TB subgraph CLIENT["Python Client"] - A["Python Code"] --> B["Protobuf
Serialization"] + A["pydeequ.connect()"] --> B["Engine Auto-Detection"] end - B -- gRPC --> C["Spark Connect (gRPC)"] - subgraph SERVER["Spark Connect Server"] - D["DeequRelationPlugin"] --> E["Deequ Core"] --> F["Spark DataFrame API"] --> G["(Data)"] + + B --> C{Connection Type} + + C -->|DuckDB| D["DuckDBEngine"] + C -->|SparkSession| E["SparkEngine"] + + subgraph DUCKDB["DuckDB Backend (Local)"] + D --> F["SQL Operators"] --> G["DuckDB"] --> H["Local Files
Parquet/CSV"] end - G --> H["Results"] -- gRPC --> I["Python DataFrame"] - %% Styling for compactness and distinction - classDef code fill:#C8F2FB,stroke:#35a7c2,color:#13505B,font-weight:bold; - class A code; + + subgraph SPARK["Spark Connect Backend (Distributed)"] + E --> I["Protobuf"] -- gRPC --> J["Spark Connect Server"] + J --> K["DeequRelationPlugin"] --> L["Deequ Core"] --> M["Data Lake"] + end + + H --> N["Results"] + M --> N + N --> O["MetricResult / ConstraintResult / ColumnProfile"] + + classDef duckdb fill:#FFF4CC,stroke:#E6B800,color:#806600; + classDef spark fill:#CCE5FF,stroke:#0066CC,color:#003366; + class D,F,G,H duckdb; + class E,I,J,K,L,M spark; ``` **How it works:** -1. **Client Side**: PyDeequ 2.0 builds checks and analyzers as Protobuf messages -2. **Transport**: Messages are sent via gRPC to the Spark Connect server -3. **Server Side**: The `DeequRelationPlugin` deserializes messages and executes Deequ operations -4. **Results**: Verification results are returned as a Spark DataFrame +- **Auto-detection**: `pydeequ.connect()` inspects the connection type and creates the appropriate engine +- **DuckDB path**: Direct SQL execution in-process, no JVM required +- **Spark path**: Protobuf serialization over gRPC to Spark Connect server with Deequ plugin +- **Unified results**: Both engines return the same `MetricResult`, `ConstraintResult`, and `ColumnProfile` types ### Feature Support Matrix From fb7c23ad89272f69619fe735b643cfbc3552b994 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Wed, 21 Jan 2026 11:33:27 -0500 Subject: [PATCH 6/7] Optimize performance --- BENCHMARK.md | 198 +++++++++++- imgs/benchmark_chart.png | Bin 185152 -> 186257 bytes pydeequ/engines/__init__.py | 8 + pydeequ/engines/constraints/__init__.py | 7 + .../engines/constraints/batch_evaluator.py | 298 ++++++++++++++++++ pydeequ/engines/duckdb.py | 236 ++++++++++---- pydeequ/engines/duckdb_config.py | 140 ++++++++ pydeequ/engines/operators/__init__.py | 7 + pydeequ/engines/operators/grouping_batcher.py | 220 +++++++++++++ .../engines/operators/profiling_operators.py | 36 +++ 10 files changed, 1068 insertions(+), 82 deletions(-) create mode 100644 pydeequ/engines/constraints/batch_evaluator.py create mode 100644 pydeequ/engines/duckdb_config.py create mode 100644 pydeequ/engines/operators/grouping_batcher.py diff --git a/BENCHMARK.md b/BENCHMARK.md index b85c43e..ca9b309 100644 --- a/BENCHMARK.md +++ b/BENCHMARK.md @@ -58,38 +58,202 @@ Benchmark run on Apple M3 Max (14 cores), macOS Darwin 25.2.0. | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.052 | 0.667 | **12.8x** | -| 1M | 0.090 | 1.718 | **19.1x** | -| 5M | 0.221 | 2.591 | **11.7x** | -| 10M | 0.335 | 3.504 | **10.5x** | -| 50M | 1.177 | 12.808 | **10.9x** | -| 130M | 2.897 | 29.570 | **10.2x** | +| 100K | 0.022 | 1.171 | **54.4x** | +| 1M | 0.064 | 1.829 | **28.6x** | +| 5M | 0.170 | 2.474 | **14.6x** | +| 10M | 0.267 | 3.033 | **11.3x** | +| 50M | 1.132 | 10.593 | **9.4x** | +| 130M | 2.712 | 27.074 | **10.0x** | ### Experiment 2: Varying Columns | Cols | Checks | DuckDB (s) | Spark (s) | Speedup | |------|--------|------------|-----------|---------| -| 10 | 16 | 0.118 | 1.656 | **14.1x** | -| 20 | 46 | 0.286 | 2.129 | **7.5x** | -| 40 | 106 | 0.713 | 2.869 | **4.0x** | -| 80 | 226 | 2.214 | 4.434 | **2.0x** | +| 10 | 16 | 0.090 | 1.556 | **17.2x** | +| 20 | 46 | 0.111 | 2.169 | **19.5x** | +| 40 | 106 | 0.143 | 2.878 | **20.2x** | +| 80 | 226 | 0.253 | 4.474 | **17.7x** | ### Experiment 3: Column Profiling | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.086 | 0.599 | **7.0x** | -| 1M | 0.388 | 0.814 | **2.1x** | -| 5M | 1.470 | 2.399 | **1.6x** | -| 10M | 2.659 | 4.109 | **1.5x** | +| 100K | 0.044 | 0.638 | **14.5x** | +| 1M | 0.297 | 0.701 | **2.4x** | +| 5M | 1.521 | 1.886 | **1.2x** | +| 10M | 2.902 | 3.406 | **1.2x** | ### Key Takeaways -1. **DuckDB is 10-19x faster** for row-scaling validation workloads -2. **Speedup decreases with complexity** - more columns/checks narrow the gap (14x → 2x) -3. **Profiling converges** - at 10M rows, DuckDB is still 1.5x faster +1. **DuckDB is 10-54x faster** for row-scaling validation workloads +2. **Consistent speedup across complexity** - 17-20x speedup regardless of column count +3. **Profiling converges** - at 10M rows, DuckDB is still 1.2x faster 4. **No JVM overhead** - DuckDB runs natively in Python, no startup cost +## Performance Optimizations + +The DuckDB engine includes several optimizations to maintain performance as check complexity increases: + +### Optimization 1: Grouping Operator Batching + +Grouping operators (Distinctness, Uniqueness, UniqueValueRatio) that share the same columns and WHERE clause are fused into single queries. + +**Before**: N queries for N grouping operators on same columns +```sql +-- Query 1: Distinctness +WITH freq AS (SELECT cols, COUNT(*) AS cnt FROM t GROUP BY cols) +SELECT COUNT(*) AS distinct_count, SUM(cnt) AS total_count FROM freq + +-- Query 2: Uniqueness +WITH freq AS (SELECT cols, COUNT(*) AS cnt FROM t GROUP BY cols) +SELECT SUM(CASE WHEN cnt = 1 THEN 1 ELSE 0 END) AS unique_count, SUM(cnt) AS total_count FROM freq +``` + +**After**: 1 query computing all metrics +```sql +WITH freq AS (SELECT cols, COUNT(*) AS cnt FROM t GROUP BY cols) +SELECT + COUNT(*) AS distinct_count, + SUM(cnt) AS total_count, + SUM(CASE WHEN cnt = 1 THEN 1 ELSE 0 END) AS unique_count +FROM freq +``` + +**Impact**: 20-40% improvement for checks with multiple grouping operators + +### Optimization 2: Multi-Column Profiling + +Profile statistics for all columns are batched into 2-3 queries instead of 2-3 queries per column. + +**Before**: 20-30 queries for 10 columns +```sql +-- Per-column queries for completeness, numeric stats, percentiles +SELECT COUNT(*), SUM(CASE WHEN col1 IS NULL...) FROM t +SELECT MIN(col1), MAX(col1), AVG(col1)... FROM t +SELECT QUANTILE_CONT(col1, 0.25)... FROM t +-- Repeated for each column +``` + +**After**: 3 queries total +```sql +-- Query 1: All completeness stats +SELECT COUNT(*), SUM(CASE WHEN col1 IS NULL...), SUM(CASE WHEN col2 IS NULL...)... FROM t + +-- Query 2: All numeric stats +SELECT MIN(col1), MAX(col1), MIN(col2), MAX(col2)... FROM t + +-- Query 3: All percentiles +SELECT QUANTILE_CONT(col1, 0.25), QUANTILE_CONT(col2, 0.25)... FROM t +``` + +**Impact**: 40-60% improvement for column profiling + +### Optimization 3: DuckDB Configuration + +Configurable engine settings optimize DuckDB for analytical workloads: + +```python +from pydeequ.engines.duckdb_config import DuckDBEngineConfig + +config = DuckDBEngineConfig( + threads=8, # Control parallelism + memory_limit="8GB", # Memory management + preserve_insertion_order=False, # Better parallel execution + parquet_metadata_cache=True, # Faster Parquet reads +) + +engine = DuckDBEngine(con, table="test", config=config) +``` + +**Impact**: 5-15% improvement for large parallel scans + +### Optimization 4: Constraint Batching + +Scan-based constraints (Size, Completeness, Mean, etc.) and ratio-check constraints (isPositive, isContainedIn, etc.) are batched into minimal queries. + +**Before**: 1 query per constraint +```sql +SELECT COUNT(*) FROM t -- Size +SELECT COUNT(*), SUM(CASE WHEN col IS NULL...) FROM t -- Completeness +SELECT AVG(col) FROM t -- Mean +``` + +**After**: 1 query for all scan-based constraints +```sql +SELECT + COUNT(*) AS size, + SUM(CASE WHEN col IS NULL THEN 1 ELSE 0 END) AS null_count, + AVG(col) AS mean +FROM t +``` + +**Impact**: 20-40% improvement for checks with many constraints + +### Optimization 5: Query Profiling Infrastructure + +Built-in profiling helps identify bottlenecks and verify optimizations: + +```python +engine = DuckDBEngine(con, table="test", enable_profiling=True) +engine.run_checks([check]) + +# Get query statistics +stats = engine.get_query_stats() +print(f"Query count: {engine.get_query_count()}") +print(stats) + +# Get query plan for analysis +plan = engine.explain_query("SELECT COUNT(*) FROM test") +``` + +### Measured Performance Improvements + +Benchmark comparison: Baseline (2026-01-20) vs After Optimization (2026-01-21) + +#### Experiment 2: Varying Columns (KEY METRIC - Speedup Degradation Fix) + +| Cols | Checks | Before DuckDB | After DuckDB | Spark | Before Speedup | After Speedup | +|------|--------|---------------|--------------|-------|----------------|---------------| +| 10 | 16 | 0.118s | 0.090s | 1.556s | 14.1x | **17.2x** | +| 20 | 46 | 0.286s | 0.111s | 2.169s | 7.5x | **19.5x** | +| 40 | 106 | 0.713s | 0.143s | 2.878s | 4.0x | **20.2x** | +| 80 | 226 | 2.214s | 0.253s | 4.474s | 2.0x | **17.7x** | + +**Key Achievement**: The speedup degradation problem is **SOLVED**. +- **Before**: Speedup degraded from 14x (10 cols) down to 2x (80 cols) +- **After**: Speedup is consistent **~17-20x** across ALL column counts + +#### DuckDB-Only Performance Gains + +| Cols | Before | After | Improvement | +|------|--------|-------|-------------| +| 10 | 0.118s | 0.090s | 24% faster | +| 20 | 0.286s | 0.111s | 61% faster | +| 40 | 0.713s | 0.143s | 80% faster | +| 80 | 2.214s | 0.253s | **89% faster (~9x)** | + +#### Experiment 1: Varying Rows (16 checks) + +| Rows | Before | After | Improvement | +|------|--------|-------|-------------| +| 100K | 0.052s | 0.022s | 58% faster | +| 1M | 0.090s | 0.064s | 29% faster | +| 5M | 0.221s | 0.170s | 23% faster | +| 10M | 0.335s | 0.267s | 20% faster | +| 50M | 1.177s | 1.132s | 4% faster | +| 130M | 2.897s | 2.712s | 6% faster | + +#### Experiment 3: Column Profiling (10 columns) + +| Rows | Before | After | Change | +|------|--------|-------|--------| +| 100K | 0.086s | 0.044s | 49% faster | +| 1M | 0.388s | 0.297s | 23% faster | +| 5M | 1.470s | 1.521s | ~same | +| 10M | 2.659s | 2.902s | 9% slower | + +Note: Profiling shows slight regression at very high row counts due to batched query overhead, which is a trade-off for the significant gains in column scaling. + ## Quick Start ### Run DuckDB Only (No Spark Required) diff --git a/imgs/benchmark_chart.png b/imgs/benchmark_chart.png index f22a870e1f85d7938a98136ff447449986984343..e1551e3ad4a7ff6d5a627fd2141b9886119856fc 100644 GIT binary patch literal 186257 zcmeFZXH=8hyDy4pED>xp6%nx@1QDc4Z&C#53B4*c5I_*51r!AYq$tu$=#bD$f`EWv zL7McIh=@wBQUWBDJHvAQ_u1p#5BJL%V~=qTV=PIs0(p7ooX`BVXNKu$sWL*(L+R+~ z81Jgx(W9d~rcFl&**$s~yrMUB_YwFe`AFI5k-n?lqsP|nwse}-kKCMHA2~bTzvN}> z?&0X_B7F0f;LY1NE;&4UT+@iSHn8`np<&M{VCb8@-Pcr)$y8yF$#s9}ui0Nyg@rf1ZOC{m z&NY1Bn9_Gx`d;tH2(IG4zH*Y9x9b1vDeX}V=TU8NP59gf4<~RE=xoH^9($=#D6-S{(pTTCjNiNWxz!m+4esr zrZ!o`@xGYz@Touc!N=Lel*5^CUDJpcIdaiE1FM%C?ntb{I*!)jc`e>~Hz!QAa0XUI zb83{||J1QdU2=uN3#_zcw`}@K%%waw$3w6C8}}L)p__S4stQI0PDuPdrJt?B=+UFh zmQ>j#OJaT`<3|=LttXpIQ;%@^cUK_?V3rBI##h^GlO#QHHB;$+S-bCaG8M_I8W=~) zyhC@!)0&kwK22~X_bQGz5aRhwlbfC$v7fnxLlnl(`1v*NkcB7O;)JiKx&=A*t$!E$h4%gh&Kb<#-;rQOU=cxD#Jp zQB7W%XrJ0j9;( z&7(6rS~i)lN%E2|d2!TfcWa7swNG1ar1Vn+TZ%mfJ-9YE)K%rsb9uqA>N_cL`%mZ& zWirLJ<9BPU=IUf;XNJNvXDgw)-gV&z#41NIwN2_$qy6ScY2z-kQFdodpDK>9w%Xy> zS3A2u#5Rm5>iz>Cv-|lRs$COrIW8hwyPN|79E%5n%9w+o|ceP6{#bvZMNuXi#@*@(HwzRbg!P)V{nJ+tNMMJ z?s5H7b>vxl2q$R+7c+9;TP0)<2;$Cx&24 zmhmHOfimDcknsAN2GQ#o?&E&hoz4vXTz{f}P>ldZ=wpUSwcNV&O z$WrwNcx(NP4~VzAF-i*`&Wku$Nj{oB#d>G0aN;&mrAvDCC1bw>=b#YmW5Lkb1(~T7 zF_cLGIz5{@`mlu~bBX1}1zCUZX=;i~ZR)(RA)&p?z1nSKy~Df&x~*!n>Nat8;~rrhvcVnjzxyFQiGU|y*OQ>7BWxVH7XZ>o&^IJh^cDtfaC zwh<6bwvgUg>suH*j$U*m1%O@7i?f%iyQGh(?442-AyvCpV+_IG(lENTUT;y26T<~( zTT~PL+$4f9=ER}JcdJ^f?+>0F?$SdKPP^V8DlyMq+>oswT2J3_9P73J2M!o3|APx+ zTt%=&$M*EKWki`>9x}}%XIOLYBH2>UXs+2^zddn;b9?y>&YJh^FYT{m4S@QTQKaOM%Ai3e-XDjxg&&I}(*A_)1%W z2kj`q>;(-`4uW^8T3>*i<_MNxX6~U3Jb1VHGlJ|_>@h`hZN!fsgs@e0G+Bkvvp_8& z8&cIL)^pc$>B1CINej=azxx)VPfD4DzEU()@Qni1PJCVr^1-EX$19cr&hq7?_JrL8 z@4{-m@4Aq%UB{Q@%JsP+Qt3=zK1mFVrmU57wR=05S_JN_TQ~7So}~NEVx5Bnx4Vqw zq;sc?t6kC<(Ghhm z=HmjxIyweC&wuw(x8ALO6)rZdChT4Gn=jAA@XDW`S+c$1+r{_x_ns!0Bu{&XECWZS z$-@t<6|SoWmNFB*RXOVS4;5RD6_z$2<(Tf{n+EJ_WG&Z>r~EA~wCO&XdJ)CSA z^iM&Y&q8t13iKuI0>yA`rWKd_qG!Ez9j=;OJB5L%PTX+|GKsB$zS$HBD_RcYOoi%w zIvxBl{-$~IxIIq9lZ2|aJ*u_E?5q1;{y>77b(mTcFD6I;*AWY44(N8$)6qmcTPwAk z$kR%bJhzzbmPE{k6qawoAu4fxSqoB8gkOx}-(t9KPw0zQs_|c7Nz`E}v~q)~A>!cW z+;0USyoqNR95$JG;LDd;7c37RIl*uoeA*8k`EjrVp8${He_%#U%8LMVBvR9|*E2mi zBj{NI+|=Qtg#fADTFQr^OcDYjy85)FIt>bS@hLW^iq7XhF?Z3WH~*7edMN_yo>+1SvOid*9&Yc+&{JG0fh z<>uo`7%mgbS3&v;&;iGkPZa9uuw%L=+?gwl~X2A~Orx%qPzZzSpv;Qx}%oS&L13zj3{CC;SSIISa=vp3fi*yR1kvy zDlwH>=H-j~ewv}I020d`A(~lQ7IRVcK%z~K=g;aJ5iB+l{jyGAgR@>Zsyu!K<%bB- zdq4gwT8>cdBvfzN7(n=`Ulp!G)PmM509a0dcJ`=pqOMa0lE02=5u=d#)B5Y#a(mmt zYxT{H3Fcl&#ogII!f=GQ7$;JpUcBW5w;I=>;G=kcr@xTGibAQZBrs^A%7@k+7_;yVa2YA zW)*e$HhkE$s*7UHyx>QaJ1I3^mKf~^R#6JT(V-275FzxUtyiP>v zu(MHF@QNgMjw*wRmw!$b`a-XO@cMdo1l18Y%Dkeo^OI zPd-Q1;nvfMkCB)m$)$r15gH#m^gmTZV1-dAc;l@v!NOTek zf42caBPH2g-+jRIB$Mu* z)*(K>w>X8b0Iz?*nHB`S;Ix_P&5bDjI8LvFbj*1l<}{So>L9n7Nn&wui!&-|pdhIi zT!PF>wOOLKy=;No;BiYJjs3dKMR9(urm;*zw>vr?##n^p&`;c%Z++%AZ zfuybxg@a+6!|0V(KRVnqvF1=G8<;AzhkvSOscSFUeh0L z07z`){Tsh72h08n%0hqD65lBb`QA~Ch)957q4Wg@s z-vXY28l=6%-q@B*cB(&#Gb~Manrqp6*%{gOvV-Rxu8YCY=dOZ0q;{LtqiNP6puNFr zq6Pr^5d*KNU6+9Z-47w#YZmYy0==@Y&*6XSt2s<}XX+My4h!-?e#o~ zYiEM3)+wn*m}5i?zGSD(kf@Ds;9a26rmYbEg9m^4LQ==32a`X*2BQO_aUw}GK2IGP)} zihhQ{x;C45AM)2^L8SZJy7J2~oT=_iTg1=FKR9Lm-!cw-!hO&F@f5tc#283L%Joyi zl=%r}Zf%H6Xs_?;hmaPI$7Uh&p_YHtGYs*bezzqWUG@d&(tpC2`i>|)CHHP42{BBb=(J9f3nQnVh+-STxiU- zC@Kr1XTiDrih$2tJG<5^36JC1lR5P*VW;dxkS_FD0e`BAzat47m#0kPhkbj z-6laH+H#>A`4@2WiM%RO=Yf(7@UOX_2-Hqr?gK0iFv#u~0VM>BtX3z)qApcb^DLF+ z1{~j3Y87jaRAt3-z;iH;K9_?Tg9y$eXV(9Swx*wxi4GD*7XA*czG^U#HeXc=Z9_s5 zym=0tE;9M8KM?~kw#Zt&$qVfH1l(;$ciFf1#sc@yd0p7`eCpOfqW>!*)^^Z<&uC}n z$Fq}S5-bb1f)3Ofk(3VL)f!oAXg^H@>}zMZ?=y zXjEWUzm)KnRIdO4vqgp>w3qiMDlXlcsv#IOW#<>)B%(*{$@9!$~1L`!g z#KHh~GvsPB;-}tu%7O5nCE}fop4_E268mR0PznP`eSXmam8BO~q68&!1>eBzYH-FT zUI+(xE^nhu(XPR)S0jJEb~{UPz1_0wJ-0^v0(hw=%Qco0U*}~w=cdZzuUzy^Bc1>h zqKo`s|F-|={;M|#o|N6pFvcU}cJy-J04bo&f2H;MWT*I8)qr3YBtHwk=u!NJIctU2 zChn-*_OjCO29(6}n??K6ONVVQ*Q8+0tcNV}c%~>wb)_hs$ zd#C`RoELLW%x&q=y3}&g8%r@0GB7-Tm_pMUi_zEhAcYPU)bC%MISOTd0Mg7v9)`X= zZ0Tw@cl|oJtG#|5Cmm*1bpl<1nrihdDBu&p%2vDwMXAdpzd(Aj@pcq$!~_}P%yQif z40b~bD&EkmLzz@b7omYzj(nH{*2KK15!Hmg{vGDBJ9Ytrh4-ijP7NsPMQ36-0lgp~ z6Ns||I0WC~P}1m=^u|W5bZLna9pU}^nw0rhcyn|(V-Q5OJJ=`k~%EG&abL;8AjJUW3c&gx7 zy=!%8&j#u)BMNk5uID2jR-DlaC-84WAIRd9;e7RX5R0t>U0}-|-Yr3j)3_H36?^R2X4mM!8!Nq_kySxC)e}5XR|l- zieR1%$)Zj~FE>MTPXv#SgzuU|U!EpA5US3_>Nj=W!_W6kIlG|kY=U9xj2x08Yqev5 z1~O#!S`A=-?E2GCi9oREy2*f^&cYn?1+VDbE|Xx!G4EfIZx7mYNO>`!OReIY@AlwC zuqN@uLpUNl=ag$qywk7Gk$Lw&g`W$UtUk^daF|ad7iXdcbl!W_E~Cw`EzLy1rr(28 zPrtdh^G^bKwPK*<+wSi0gXkon`q*xj@8A06f5AT-p8H`0@04&(+PiFPXJd@X!c!fg zkf)8g=#{cJ4D?^`;ol`D9?HnN4ImK{+c=^A5T(;i8T~-}5&C9%;EADy&nK<7M@+9m zNIKYNl^nXbcn$tH_m2^B^}k$KWBIV6B)IuQk>12Ec)}lg?T}-A0ex$4uEqm{)V8o6 zgck;PRpK1_y^4TlMu5E}IKiL|;<8S7p06C14-rN7e>tqhe6Po^XKYv4{;Npq zPk`V=F@d{2!>FdN0xPwvy?)vQNoz(uhYCfp6*@WVrQynq=S9j;GbQg<)K}N~v{ljf zg6)|VC4lVrLH6;$3I5a8%m{A1cGa<9HjBiV9{`ZQEb89%|9sB=dGLXqcr?+)t`hFzCfi{+J-HC;&gFHYJ$b^zd<-DPqBRoxa~i*|y8q2h9alHrB7>IlX@z=&Q8X zCef>NJs%nz4enI{0>lw7b%Kk{bJ z>`*Buc?Zl02ieUZ&&o)EM>rj1A<826L|wnXa}Jh#{dDj93+5vI%z>shZ(q(M2NRmssU5;yHU*Sl`YDo)*OCW4 zmn-G>0>^{d6yrjfdBPA+)iB~Ewf&&XUKl)~xumhsHIi%V11j$alOK*$OZ57{NI-|T zoN$FAYWl*0{_YB{(>Z6Olz2GZZ?R@Fgxy6jT6*OLI!yt}Vk>&#w}aI5pJ#zmSqR_0 zu~UY{J-XL$MnGQStpw1xjI`#H+a-#jvKPdZ?xpL$zj=08Z-5uQ_=%bnGjJ;Hcm?)+ zJUv{t{vRLe%P}H(Us-=0&Yi{r4}+gm8h@G{B?((>(UboIVPn3jXUn6xiPKYddu@0i z(kkV)<{GsY-W6hr^6u)NP?R{Pkdp6zBWS|Aa zTZQ2bBqrA?Mi@mDp`QOLwIt2{J6X~b*%2BeXj^XR`)6eU{jY=n}Pi#-sdy?#7r zU%nWOx|v@k!*c53z(CcW69zB{rI06KoEGn9@=~06c&=ArW@T8vJx1JBn|puqI`3N6 zCEFads>12&hN^Vtc(^SO!cgDAwkZgbbTc9-PR4ibD7v)kME&D+!%Rod~Z46C@s`6@Ow$yT|rl(ZeLb^P+7x% zR=BkkdKXf76T4!*K*T?gZNOn>mSTtpaJ86bLt@v?Mio)J_I>)`E3#{~G{p}`KvV8@ z%NFg3pXKiC>^iiA8f=)Q4<*{$b)}=KukTmek$)NTBj%T3%;~j2MXX%AjniX}_V_B% z@@Tf7_rn6NHZ#m!PCB#uc|(?_H+7s2(gcUJo`e_dV1h37z?wGWbnbMOs`Kq0s^JcQ zcHp39-Vr{bE#_HYu#bFj(MJe0RwTrmvnU549=hLed&5iSvv~aNVQ4Y^q(H2qdI0+^ z2ul|JO5N|*8bWXe4Q9mAKC+v_amX>|#PA&NKR+jdAtQ=ar?vyhU2$wqv`|v-#w!zb z?w}ym>QS#0)U#;^<24K27q`TDb-ck2`feq9hhH@FP*QczUQhNe901VLggEiZ zUd3PAHFFALRU>A4enYDUyFS&wbHBtm`T&NF7# zi$XSPdC{dConSXOx9<9CFv7%B5~)z2ZU*FfFQd6Th^OuI!V0LPOq!tl6gCE|1?aud zVwu|?l`pV=S8o)z<1<7CT=|+&9B+RAnR}Vo>*?N?XO|ex3SLsm;MIGp6wQQfi^L_Y zEs%x`rd}wc;^7n5hG*|GH7SLjh%_lz2GBcXte5W$f)5B78vPhfkB(cDz#49UB;gS; zn92R&3ZhLHk_mJ;*}l6EgU?shyK4ZK%^A;xmUO&_mx@UD}0yH4NhZGWS{JX+^*l=xC>a5Sj9I; zr6dF~1geNlMo32i&!H}z&#)wQW(juo+&25%JlKrIQVYTzWg$=3&z+6j_1cVMGmmKd zmW4CkbwZ@H7Cm3wzn%Yq6put&O9s(E)IK_DP--D#%6RphqFU@?D2JC( z-ho2k)xXy&Ya|n$2(}~IayvGKCmem4r*-DC)L87d?=Bx^Jf8?a!N{NrZayd82Wrlf zk>&O@$QpX^PS%vJ@>xyqLPQ2J9^3oIWYKf++sJO=pk#s?KkT>4m-Q2 z&_jraiSbBM-k=R~G*6vJ^`cLX`Qc&>h^}4|SzzHh;MQ455*~S5KreRI5I_kmxI#x9 z%sbMg#QSTtSRVG@7wX|U*XSGlT_Y;!GDX&ehgTfvd8pzaVa*S5thrBe1`Th3jr47? zRld5Mi9HgPFXob;?u#=|Kvw37p^|!P5y5Hg0*&7eE5jGmircHq08FhWsbrqnN#USK z>uTH?YSw32s={Ev4)<`>vOOQC0>wL=t4jS-HQNs4A8%f79NGuq?j*vzM^!zG7ee$F zB2!OFVaHr2MY28s->iR4VJEVO&~7PG?j*eL;7$hj_#2eZETV{rC<^12s!@pPX z|0Ak{bqEmn=j~fF0IrDuq{xW21opCU;I4mc$-glfO2F4ti9Eer^{v`_t(T1lJ8AwI zEja;zzDomTmao!t&Okrw&N2NOciX-Ug@b(!Xg@$?k`kjcsV~poYNo*iJHT%8jLPp% z0@^OpnIbL@cu(ApY&rBRPZU`4;VX)dV|{TR4^ z$$)pi-?c1#2yiL)>8hLD_Y$oDJhrDnP%vl$9zUKQNj@)%97eg*QahKG!$0nLcggL& z^8v{UrLj^IJnX{~ZTQ}SawB1~s1`k!0@iQM0)bSqp59vQS^s#+f?B)24lrgSFd)G$ z5Aq5m^ySJ463CP!3E74x0VBJ952S87X%V6*8M0Adk+JZ;w7l^^D=lz$%ggf1y> zDa_MIxH$=K;>ppxvaf8G{??6onkjY`{(qF&zlZ(C=BHO!);(T2QD@EtLw6?L`f#`a zTq~RCa&}BNxOu1m>OyrHiN&oqA&4JJH&9|uvPrmT%2foIpyWJR)M;?I)X)+=@bbK9 zTZs10a)+KQoy6;}n63J1m#af*$piA%TCUXqO;k4=)+nU{f+V=5Y5kT)9_Jcr@Fja7 z`Qz~`Qal-Qob~x=$#S})7jXjvrh4L18kp9k_nF!xt@a{;`n_~C*_alvnsSf`+#Ej= z%Rt(n@J}Ae|Fc@@o-k*Wy;jt2`T>uDjnfQwJ76W93&@LU@febueq3;1%GpJG4%b(8YqfCawgBf zCq>+qFc1c63oFh{*rEH9sb-6~|K@U;l_rQfXab_{>}}tXc#A+Ecl*u9>}{D37{^XPvK8gnWqhkp)_Gn}0&eF%UiwbZi_f0b0~4dX7`G0_ zD}`oy!F}$w=Ll*TV2j5=8Mr_Rpv6bPIOB`+a>=eBGE!ANCxO?hy4O==R?~YpfqC|B z709H87DqfgA^FA3XCiL0Tj``I2ziKs01))J4)$`wpyO3y2~-OWM)0A`hHZj5Yk7SZ zq0XdMf*1h(_DO@PGuD7cnYI?Sb$S+~`&{*nsvLAlSrzJWFuRrCtpo_hZ;9cY&_@Hx z3ifgwN!%9LK;uP-1%>v@DnQS@qHnL&de!ZXze+?iwC5nzA7(@LDRYW@T zq#lI7xbBXA;qAuoM0yM4H6tKIQR@eD(@htSa|Bym4$hzK!nhu^0`8Iix30i8-u&Ux zOAEpQ-ne^7LSBvG)vpK5ACKCa3fL*Tlsf6kt0NLG1XsZiXjaLkg3T)vX0uekk-oJx zg;{WVoGAxcNd-#7h!tkcVC6ZJRv9&^vABXg9av8YAUXDPcnw<{`@Woa`KJW+p8j`V zd7avP4OADfL*%HPW-mBu@Dv9!g(2#)QLtNE#UE+rmX4dcNtJ^&8C2=+4Nml$?(mX; zam7`oiOX_sIkw2rTF?He1hBz|pvM5I*Kup(*N^4abnd=8;05Q06;^d%{8#QyQxBfF z{oc%bJUDYHT5^_$DjkLM+(O{)ATaxW8PJL+O%ksU2k2(rWJ`62pW`(sP#LJH;w;cK zGYHZT%k#`(eF62d6)lQon=_gGn#NA9$EirfkZ4|&8MRiId$EaEc?wmEh^9>Wr| ze$PA_5X@#ZMP}#zPyw(B1>p?7pdNqY+@ApO?D)=JQRUZ)0q=gjd7xldv1p5-h&FtE z102kOy&{>NDoxXnC~1vNk!}F`Cz8CWT{8fRtYUV9@CGRFX|MiD6m1? z?;{#?pWG=$%z@pfvAPtYa1nBuhBw#&i&7o9?++c`9KV2<)_*eHKwYeC;?hl2Mub%@ z2=^5l3YbcK=@BYeH_Y*TgFrTVf>KKKvZLq?TwOS+fTT{Y9^?$rkTK+K$=}HYp}@=^ zuV6-wrZuy7a3Z-a`VsQ{A5N=Ycr~+HC-x-kQW$Rh_BHi4U&^fFpq0rbEvxqL~I*(Bl z##OvYsIr`{{G7jIF{j&pR>Pvm2^~Ob&qO*SBd7J3yGxBGtH1R7X<`(h1&*c&KqfwT zIMlGnu95B|{kEQdoUdBYH+}!Z-p~d0cdE1e%`(&X!|p9a0g(mMje;^pa!3t^JP0|# zfdnEJhRZ4^-o9xN36-DG7(l2*Uqn6Qf_m6YMhG;N@#k*0y6{Ki@~7wGO4JpDznN>$pF-J;AW_$!WYf2a~jqa7iD~HOhoTCUFA0{TZwAu zk1uu_Dj`1x$uSnP6f|U7V@^BHF-Vp=FYIRt`Q{I0tI-LM*XOUINmPkXZv|fjnb-#( z>;hh7f`!d3EC8aFMsDh~?I`BQ8xUjCF*HzQ%c4uF^jaX6VU&>vS^bM~X1|*CY2i)F z5p)3L%nrfPHvpnlRb0jCU}-1_q)u&yLX|iKu-wA7k}r?4nu*KuPsMO4&F?Xto0jR9J=abn1p za<>t0-NqTltG#&`a9!qsXq% z$RY5fH_YA&_JylJTWCob(Tx3==?)DzUIdA_GLp;N@el_7dG{2U>lUh(SzZs%V-Iku zbz>mFlPV03TR|IP$^+>DZJzQ<3DSwe1#Q8PX>uvxo%^UiL2|mfi4*Z8SqzojTJl3^ z&L=3SdZzxIqxp1w3A);%-j`d>0kfuz>(5>A|MQ70MSRR(xX;bhMb9(mb0j;dJ62Yl z`CFKwOX=JXa_6G#=?Ho(%}=l^@Mev+a{3H*JYoaq+G7PSbHq$9^&ZRmnh$0gwJ$fy zOJ@D!oX}m=)QT{9jn$0hULR=g#N-&1A>ThakZJWyQ4V@@lWB;S{zNeXEF#8^b8kA0 zE)BnfY-fvvt-UaT;1DFP&L^{rSVDwCjDNTOe+BpuKETLc$AoZFpaPC%o@{|cLca5bE4bHxXR(x zP9qElP;K{aRD9=7l*0(fTAruuf?V&h!tSi_8-`{NgVCMPCM+a_(;fIC8_qG83^rBU zfipd8r4fJQm{b1Qu)$14IbTMt_IUnIAi5)0dO>8-WrGhxl%rVlK=%Bp^Of2mEyoD- z4BxNE1DbxdXeT-73UX-LVeF*Au&#p^wOE@3kMm+?rIEW|0lNr}UJ2hJ51arD~Mkuqq+|M7`8je?|y| zjP7NIUaW|-x)2--Kdr(vUt!f0)Q!&8vF_~mfv@*BQ`=Z2^4V>5D(UP|N4OXHRY6A}a$Qzr(f4T__F51Cg zz6CJNlnWIi*Fnh{-uUU5pJrEa`E;ppxk-GOiL16^Z#Yd)D7ue8VCTH(vKGcwG) zK1lkMmpr51$sxov?Ogqiw!}>M?$!J#E-=T)c2!(4@=*^wxMtByL6jL_W)^GKM)JA} zX|RJl^DndNW%&zhj_Mq5uwpp_2|NKjSC9lxQez1OVZ!sZnuusnc9;b$zJ*}?ajZ-c z#GWnGfY}7MW!dX4kcd7-MuU>{AxJ+9r=I+xarLVdDwjDp|9%Z+w(tYJV3wDTZ1FQA z=J$H1TJv#cvYOjR3Kh2o;Fl56y0;yAbM8(8Pe>S~rWucG)jWCLc;eSFr9%n&b28@< zsw)RIpzmm=pJ{Pzg~our%ey}+t6(>>AO2d7dwT7)$qUAd(zoXcKrAGMo5?mxCRnnl zXMP0ulmxZg4aoe_4HlMTZ=@oSp=OQ*RB^(L&N8660nG#l$^;NDKvQ%WYImnzH=70^6#ro=o_|9emrhOUh-P2XG#CQFposE{ z*i&p&ez{2UCGnHvPbs}GpQ`=0RuboAdW8;|dThmM*?>M3>#-rwyYS%w&leVkS{t!f zoXiY1*irKvNHkl~?3to?{qpfIazN9Zw}Ox_z9qV44ciC%);qo>l4UsdKOW!7}4=pwOjVkmP~d*o0OLNi$; z%y{O8k@=UNhQjU#f@iU_ZeZqWP7ARz|0$aL(ZDuSdCA+Jc9X5pP# zLsP}K8v=&#*CX!~8zcCa0WGhx?BRjqyb?ichs(4W;wrp-KioWV=)#Dh>kB0D4`f63@(@OLyUm>)GD+YAY6=q=FuZ{qq{%m+>tqc8A0E4FXU}xa zF%TYVJo1J~HSAKH*i$J$NEUwna1n4Kf76nnOJ~!2rygmMj|Jo5kvdC0V?bdzKloj= zx*zBpH>Bf?kKfy$LZ{gOLM!QV91}zdfy-kqKCfpN6lZ%G@uF>hOE}gzH`n_P)cF(A zmhGXvW9rYXEQ@Cyq zWUHPeS==zOE$E;Rb04-2wZ-z=cv;vBI-FhboMeIQNqWw{v95s!JAH}aG7jkCN`>`p z1&v?`h6yH_?@ir4nkp0bRVWN2Ct65AO;vH*HqYZP23K{Ou6CSi=F zj~(cKM(6=OCmq{ctDPyX4dPJf3826(;_yl#Oo1$9=dG3tOzQzn@y}o3 z1`fGO%@$+}ZGQ!Q5FIpSbpoJw@XL(R6f;d7lbE~|A18iLe_xI!saWlU#C8k&0f## zN>oF9bI)$Ov_If8FaNdyKE=s$`Y%5%-aNDRr-d^V9XMa!LM*j{B!P^AO~HL|4V)tx zFPiz2-*iRFKZo7i8pqOl!oZDxz7qw5n0t-biWNKrDnbg#q$CIIP?Vih5;~ZZ0ngN( z_CtlTyS(L^f6N?o{IK5rR-|nyaQ8=PWSh`7P-I-G7}sFoGr_|CGo0D19orgvJ>}uA z&$dT9u|MH>fB(0$!`gw>a%F<&OUl?oYQw(04eT>t!V5?~d%Dc*ygH1DQ+AUG;u`;RKE4K5fV8)1*p!HX_vH*LfP>Ay0ur2xA! zWyZY(=qI zDFz77Ru00Y9xsZ}N}Z8ZC8$}XyN?l*HNZjayn8*(24vuqmnTX7h*tcLS|kJy&HW&La6Tt{2e^4 zb5DBEo+#WU%|w=>bOeCV^oL#6G+?0~^9k6$jlC>!|1`TE)v>*R*wYE-){JthAmVL3 zXtIL`XgVV9(eeU7^_I=V(1J2Ra}>`56H2ToE8`74yH@M#YYSaxif!BD?KYRkGf%yu zx2i1$YlZAtfYTAI%Kf2Xz<2Z7!Rp1GF6u(gjEoMnP!-mIO@>Xyw=H#YeUc-;uv z4!~#Yje&q)`nKsC-~9yU#TLe(CsmFgL`sPQA#n{2Iyq60iyM-IMaB=RlhQSs_z?>r zatrzsOHs*bJFDS*B}YlS2M;rp&8b=6fN<^Tq0G^?3r^9t%o%s zp~$LNc06>tYLTU=(5$8cg`YLV>bKw76IQY2gAjw51=pV4Q|L?b@@@~bIC0<}!LjdUjQ%Hp5?Q_F;5|Foc=+HdIt z&SVk~UAC7N@O=|$joHQhIdq7eW3G4;z4tbhwHddj?0TE!pC2>i|=}(vYwkZYj#>qm*fcws`mFGrJuy2<^bcB>>Cx`;4#> zW{I+Ww5_w-_9`W1oNS7B+Xl-R6`_C*nUl%dz{-ADZ}?}AkT7&8E&R1_nC{1!?#X}Ug8UL9(bpK;co%p|l@_*`}==IP7Ag#3XLukQ@&Xt|Xj-&@L1kOh96KIUEp&f#- z2M~n0FmM^4%XsbW?sRVc`K2^=l0&jz#A)!x?2_w9#V48wIzL)B6c7jmwPdiXHC;`? z>gOi|x$gSf4&X%hD!^G2CLI_yK7)>V088?K2S96Xr6mDq882>g&{^5{&5MNcst0N% z>5YJ;?9U~TMSh&;vpP8menR+f|9&JT5wv!TUr~(`0XD7>yO^^e?OYTPp%w(xK^%}D z!d)-m(1pZHOR<#jMye5flo{VFnDC=1l^N&1~JiF0J1pIn*aUY(GtS?z%Q)p z0Blic$jl>>*Sz+TVtVL<-QR2A*3eQ~Nn5K^qz8k*G#s4=Fw2Xx0APLEAt;?R?`U}n z)teJ0GZ^=8U;VMbs7F1emU6<@&4)Yye@p>1Vffdi8VH=+_XUmrdt(*08p7VoKNuQ? zK`ug==AM(nS^1ICe?(6((9nxD3P5Nf1apv5+6fXGqJiR8V!R zwmA3RO5Wy-X`>!kX8?(w2HXfl-bPRjMLp<9daKMdp%OC7?+=pqpj74KS~O_HaN&0Mi9DfI0QHl> z88M^-N!&uT9EMcpI$&-4r*wck*>Fw@t)D3s;61WTAfS;W>&NKm&d|w1~B{=S2+rsS5GH^nNms6(cLmZlTBZX<{1?OU=$Oo(f7OCT2kOhMxqb9G` zL%+_^(FrAsyQbFwnBEC8Z5PgmI+F;UH;gMjhUh-J2ye-)83!+m(3}d!vUk+QIy>OO zOaib>_>}?v{T^cS$jlRPTGB6$-~4IRf!z#=qrtBprJGq3K$_)udtSHl#q;c?rqy4! zfj8=7A3<>D4$H~? zqi!T}V4fgb+`gntPnS~@~V_XSKK9gVpA=SN|(YYQ@pp2km%)TPu)K z&We^ppbR}Y7HcLlo}Cb)g%xG&dWGIb5I_%$j42m_joIPcD=HN~SpPYnc#_#>RqB;P zT|GGJ!$j7UUJ5$gR^MBz-$Kf zy1zG{$%La_dI!oSdkSM$R>1KCBTZo?vd4Olje%(@y;k2wP(gCWFfhB_oruu@IQ5Fb zg+7+U=ZoGJMvK1aMo7LQ7%JrQWzvr5*((*}292reA!E9q4x2YIJ3lxbsi=>`O;!_h zc{HAinlJn&`!VY2`82kjK-^{*w0go316<6(2=&_s>0&IQ&A0Y|1gF%*n|8=xop-3& zxP?8r=oAFYoUS+bGWAEIoR2?{vIxm9< z-ll?sb>{a(StB0(d3DoW*HljP@Z@vYxQcJU$Iw3vbeD9Y&9`l4`?2J2xaoHdpSfBt zc)Uzw(jmD$KCuQYvU0WsZrIzL<4WMX2r`Idv}j3c^}c&9)m4Cl)6QsGIC@EPuXyn| zvM1!-Xeo%kqoyuBvJQ2KQqPp+Fq~k)&#cOT<3A=rF#BX3ztq%BY2BhEHBKGedsEuI z2mQAh@AZw84h8{haMITeR)~^j?)_8h66xj1?;d>^UpxCbpKx*J&OkIgj-Mzy3{=)W z5Y2r1O#kPRd|KGCPbaXb2uU}^W zItR`HNy{OZ8Vp@kyzJJ2xj+kCcC)P0&KRPdM+3<>>((s3XBL4}WinE`n5_~C-2q$9 zsm;Ajqa(h7H76EFVS7)Ti^vV|PtY&i+XA~~`iNFStoa!hIvZVC%dpq<;qp86W7 z2iNC+nx*-Hl!{ZcK^lxgjn0P^)yjWtl&2Zz2;whXeN8+< zff<%K_p+>V`9%CVsCNuVNLSyKEF)$N^MUX~XbxWm4Lie^Lb79r7h%>2yza^WhqLzp z$GU(2#xRS>LF&MZF6@_aVtE##lSmF27FmBq%*? z*}`+GTf4r5Ft_BID@6T{yqhBFXmsbMLqRd{X-jm09V0xV%7;;nSUz%fag87IYWirw zRo~S=jt?^*h1{?7$_3Vdlii0Y`Lm39YjEzqMGR=>EKmtx-?s0$s&GW$R*A5S-LZv! z>yVsIwN^2;wsgD55sJ?jUn96DtW^#z{-Yck2KGhOkM zW9o-6oZ}3Joan0K=d#yg38kz$75>sqOw+HXnZrmIf0t(l_^w7X$j4dsyd~T*bw~3c zC64~x`MNVQUl$45`i~LwkH5$}l-(cnl%NJng7a>J>U@MHw3(#R+lGHhv|}H zgGv%PzKf860heffQ{C#dqet2-9@1*c%>Y{2B$|ZP_z~JhM}j%o`MT#FP-64N*H9i|oLo#E9d&x(&vA*Jgx+bjOyE>&{swfH%mq(v zHXIN6&NcVG^n0nf=QKZLAtpfjFllk0BNt$HxSnL7s`iYr_spg4vX3=OV#K!$OM^ll zfzp~mD4`>bhA0Ph=B$g<)azmzN10;x4QqE9oY5(#*T3SG8_FmeOhcp^S}2sKs|EH{ zgYsiknL<9{&ry9l&tJQ%Ja#yni#l^=r#NJ*n7XjAN@anmo!{H}Ev4V5{;T<)C|WiL z>_$I4THn-K5-h&nXSD;gA;rq?qzG=P0L->YbUkz%TBBX*K-_0L$X z$MKbmBHYjyq<o|R-?p9KrPW@~8YU%e z*FX|J3F1%r41<@8&jm>^(C}PS`pUjf$6h+^So!PKsnN~{AB3m()_Qao4pn(`j>PJ9 ze~>0QUQySbCK_@|b1kKtM6;$=G}8OQ9`MbN6CTa@6G|x0sF31k5jz-iZn|B-qBl?f z_5IRQdb=d0A?t*msB4_SBpyPLA@Id1TUC7E{&vu92`|T-!P8mXWM)1)!0PH1eNz)` z&A*mX`}+Rd+kDn|sa8n~S&oTTKWw~fSo{f$Fhtm;WqZ9Zb*S8E@=Ugz;N zt7+A{?U&**T{TOvEO3H0o2~q?h?}+S12Y}NdRd{TS1}#}QMs8!gCFjQrn-C-b89i> zWN}G(9Vf3F{Xv<3R&UFUh}))o;`K>CA)>N1p;ln~!OFhiyeUI+4#QEg-20u@dq-dF z>7VxP`wWi0wRdB)9u|epaj}sHe%w{%)f98A+ew|9FMGJAavha*5ecIe;*RE;-FY1xY)Co z92BLj@BTS);f%+K+4V$Mdi-OKJDEY`9&W^;ls8!Cd8vD5?-_iZ*Aw#JI!$*DMya0O zeP$c`DtK)|?VTdsZ8i2;X>8kxR-pFu&>YPO3nBEJ#u-wt-XSDR{~2;Bag|RxY_5q| zk^$prWcl96+R<0>mRGRaW1xPp59Ziu&pB>R_hmXD_7Rj2WWmqfi5%cuIi9j`>V z6VJFhl0DD4zWnhn?i1<4ClUI+yAHA1p{skXdiV5McPW?Xbp0+ekW<-3w8ZFE7KYw1(H*0^V42l*lXE@_}Bf^o=GIOl)&`=6JOXb25MMydiEkiFUU!tyzvdkIMJcAzMjpBO3eaZ0+NR8+J0>n{$1n9%>m! zuA2!rCpspM8Pwdyty@vEUB#^#s7^Q8KiB6zWt%`QId&93Z8fgL-%nRVZRy@~qr^6K zNrN-g=PjCNXuq1jpFQ^7fkVE=Yxj07qvyHlEtSt-g(scEY5OjOZ+4YXdGeZWn+iAH z&2-J#=cXR_x$0Y?#HdPy5?bh&uv#- zGhbYULQ#bC+S9Sq_^o7Ks%4)8bRBjQgP!|2-0%2yOFT1>;u)<_;|xdMYcx-tKo=6C z%kBMc<-4`DSc5-?BBMb5K2T0p=qYTb)w^g*r+*#}Nan6rkQf+sYqhm3T6tgb;SSWP z5=F#EwR-UfE=|6w7pt{3D`L7Uz}r=w)RS!BU1M<9sU))A^5tY{QHznuFmt7fne2AX zefftw@A7Ig>n5)qojjJds!4Xq9wx)N;#M|Ao1d`L9{zM23Vja7cVkW8B}s0Uryt>o z2=D2tPb{j$-D2cey7uMt+$<6GXohy0-14lR^sx_2wo7s!aM7WAwDQcyin*0Dy5?K$ zW|;*YL~@z6hVI@g)=-w92;lX-c!lVdw#8wciA2$8g2Qe@6%Vip3~i$MIk+By=iCa* zGaq#4rh#)+bnGSO#WbF>)g|pWVvopU@thBxjeWCLS}Hwj91(Z#(Ana>)-IC%*O;NQ zo=nlK!$Q{VNtxGn{IqM#U))b)+AiyP+fiG_OiLLQ*FtN`cyyfD(!nR^h^N{e)rc1= zZBl-GM7`S^OE$;8^e>UtDGi9Xb@fCWw$k0%_NR>@j2m2+_qX`zCv*%~Kgphhb)XtBgUX>(Ma^jRun8PpoU&wpG=t zP4RMlbc%RiXpUg~%MVrdLp$vSEfjFx^aAVc`6=FZDzzD-jrt*5${TNl$?G=QcpEX# zGk4ChQ;3;?;@UYKx&-Ea$3fhfB>q$Gmyr>K_IA=^g48_~vzlIS&r|Q4FN@}ihD&Sq z2*f!?gg9nhcZ+s&@P)ofr<$txlXi!ttb27vH(d#bSpqWBeL6+@HZ9>VIjz8D!<}NdAN7qLR zIvkc>(8z?m|I= z;I0R9WQC+B9awUPv=yC7BUy`%=Lmjdpf6yuH!k|#Wvml$gw((DeUPS2#magup^Ago zNZapBrk@q(dIhSxVr26EsNQ@-tS@x*X#g~d>;me}c@c7Y|4Ixmk0QG1z@8pnx`-nt zVs%iMplxO}ik}Cu&!4UPj_En%Ib%;jLtqYy3c1L zr-<6`DpLCgow_r0*NW)NAojSxiNy`24Q53%g@o|0eF@C*I(-`WD!vo8yNBX*U*0{$ zSu+$(z+Mq{iOn=GfusX47K`2m;M-Q*GFvb`J+U#;&AEN$f#cc!fOziX&T0zNJQkNO zx)>EQPi2`aV|pYk`WUQg~X&xr<5uIBVQYOQb#eE`)IZHGc1KDaPeV{P845?bz zJ?>vC3<{*|$~;G8=}h$|KI-mLxh z5~Jt+8ULV?hUO@)1rrVWy+7-vFgTcTDJpb}=VBtB?})T!gVCSLcvGsCsSytEU%vkH zCEKbmhN){R0txoY?j?}n`|Xe{)=C$Y%SQO`miT)`Xtnvj9Clvh*%aOwP`%?uJNw}9 z4eK_JoDn<4xzEp)cLG(T(_1<64r@}(nco%*m&fR26vq#H|8DiTMg6 zY2NHYfcK&)%#|w9TmsjO^C`KOmBk(z;!NUL&FylMYDeh4+w+PS18w})!Valp7h(yW z9fpCDF~aS7_k3(6P~RVpt6f*JFyHT81JW3ArFYhto|Qd73c~8IBCJn{3Y0ixPdHd+ zjmdfs%pE2Ph}+4zB1FA3Q>WWN&74}d9w^bUCE%TFpr?rKKF6Xdt~~-3Hj$uiP6#I} z-vn!5lN_au{@)A+8lexAlyuah>;DAuk}JBmbDg%+eVoC$9H!kVyGhD z$s1!_%KSuyW0Frl9Vw`be`xC)B-FMwe<>zKGH`O_h``-D>fWyPTQ# z6Y`6Nt5x&Z&KR_)kNu9Bx4`Iav3PRtb(FO6wd+`pu~xTj-af?a=~Zd_O6b^-IM?4y z!gTDk;#XmvHyMQ5*ehS6Zn_p!=?R?iZTQfJ8#bvIX0^q#`Z=%;>bAIgp3K74*1k|= zzd1oj(^D^SgYsq#)yd!6Q@Mrr=CJ1(ruk%Hq~0=vGlDjbxV?IYRYtyeA)?RaYHvcF zuB!K`i<^5v+>cd$`}~^=!%DJy&P0v>HF}nggOB|geS2-f<`w-y$PRW&L6F;6X*6_v=~esPfk4PCwXSD zw<9>|thTzXcC8_iP;w{ea)DiQeC*igcT!yhmvl@|-(^=JjS+`#n@V%MB*}Z-EGtG_ z`#Xr_<;j`-*hkU^OvUZ07SS#Y=DxYYFAc()>y#Y10nssscmGC*qkeQtbD=p7@D>ndSHe?8b=x8YpD~NwYRSF4h|KWfgUu8otrZ@z6=^5vDMcGT`=7^xM|YrBRN-Po1cu)CQPK zKXENS^fmY*)n!(wmH&F{#XgF6z@qfgP6~ffd-{o+%-n2fb2_mD{a)>TYu%U8*92_m zi7}-9%L64_!jUi#-QOBe876WF2i=!p)%-Rx=HGFx&&gO7^&0S1xlNU>T(2>T*L-WV zG0IiQ?EZkoAeq-+(`4@h-5C`MwK)~53o#(g*iP`mcV}(1|I$%2_61Fir{1?Uvv~&T zL>A8NV2%sqXJ@`05@B$$8^{%yF!gT5N}JE5kA_H}*?uHY<-7>fV|gVnexK4TfSKuY z=jBLhV)8&|g;)vZ1)^uhwg#kk)~GE+C}U!RN{DXQ3{&xbId#95Y>xivu&}_EEucqH zS7oF9H4Q78)G) z&%Hk^99aGST+Zy@pv(AlmH!KhP`EHNs>HGLvD-l^8pFAq87q6w-p$_jnx^mO$U{2X z>%B5liCvFssl)#?ToLHOiFg=K$r3hkJd|5{`}QY)Nb|Ra2(5yGYQOU;;+RKUUC#@B z|NG-l@Vl|59YShMF6flC1<%A_cpp(S6a1vq?()@|%Qi|gOzs}xlpQV`eR^t7XW)pW zk=9V{yZcF%(!t}0sTvZQK2| z0gC|I-*Nfb!aI$8R)XG@cPLh`Bvw%1;mvq8=d!;a3DabVUAw_w$|SeN2h*|!UOMVO zpRAKjK07Dn3KZ4b4Y~q~^SXSav@5wVvhdSu3yI5kF43+u{G?U8Q-P-ksQ*>0Y1qB< z{?iXm$*P)DPv1U7{lud3&ls~T&7J=4mvxSN<5HIz=wF&PkC#4hHn7*pI+NXX{}RI! zyNX;hZ|Bg2T@A96JHJ!_;1k z)U(Tr9MFaoBUOFa8F!uR8Wo5S6t-^cksX>%qPZSA6F)yvWENiapg#MEEWx!mFP~Nq z)OLI37{&Wu^8c+D;TX)8XAfDsVpj$dlHKTlnf#9> z-t5=U+q^v$tL;1}^y2uQh3d2=vNmwc`W2bvc-n@!NiQf68W{mB+iy4w=Kpl@Dz?hZ ziTlY4zf1SD1b{NZD(kKvG=AAE-vZS;B8v4ZA=`UBd8I+%&l>|8TVfvNU3ax??{Y+N zw_Bt18Vnd793*Ty72IN!lOSYuEh_+P{qx`$DIOUD350|t?2J@4`%brqfU#BaS@&;Rg4QX8H6Ws`vN1onUzoBdQnEJ`pN;IVa9NDG= zs||mwReQ;Cnw9zi-Ycc`^lhe8G%9Iwv2V`PU6H=DUMw|sPnNyho^a7#KD%Y;jbiAS zd@m;g?+Oelo~UH1vaEW?zI7O)7eBAz1(Xd9&??hLvhH2^t8NY#pLu&Jowvsg6?khw z-$86&4c`+mt$wcUL!8uU>jevF(nv*A0EzQyFS zeiySBNuqbTadJtgLvxlDim&YBXpO0%+@b5lUS4}>NxZ-pG@3ullIadFRL zKJHMUJhup1g2fCfe%Q3tmHGC$wus^iyIxw63?gk~tn=BKev91V?cmCvYNDjnCz(H` z;K_i>y&H|}cQ&)&2WpnU$`$M0PT=E-+;FCiU_%<>G%oPd{P#YY9p;za^u>!8I|Z1 z88AC*_;?~-jK>>x%q4!%TFflv%B9{BNWdB7qH%`RTL8OxHs=yzS2tPT0qs)L{brhU zVYGJZZ}+@!e)V{J{;(iffv`_$d=@*`_PgvbV@2-B0|^p)f%TxskW$B!%oNVkuB#rJ z-_cCi31ROMSz}xgt|yVux9FQgvkvIAxZlial=Qx3wp&kmU**dE^Ox?8zoA$CT;O8; zmFQV-k4l1ic)pOg&K#HZ&gO4@Vd4^h6KI-B5#Z}nCE9)-EVo~9eGr>u1wKi)YH}xj z@8`_*7D3*pzky{qlzpV__bUN~Pxl)1D%8bUUO5J5ue`-50E^*E+)Af?rF{ZSb=FL) zP34i5l*86t*w7@_jCZr#m1$_(v#prCvtux=?yxw3pX?BvWR?Ewji%!02P6&Gu1>EsQRc!yTjz27G$FKJKP6`h@%S{(f7PMJ`PuE`^ocNo5g*Js zQjd^K0q5p>YviC}x+H^w<7lqZ`rfDnLob;j|MWXQJI}cp{}T7-Uac21xz!c6^VHSS z1O;BsswF5IQQ^C3kBUb;kAYF+Cs*kT#9GM42@+i;F47l&UCCX>D#)IuBa=xVXYI{_X(`5TKy^%Z9?=C}0*Xtk~7#Q?x%H17C@?r(!{45IH2zA2|OsDn{V%8+pEsn zctINj{eR`x{nOLZ=CZ+m=A^^9F?c`@S`QUmEW2zeI6eLmd;{uUOORDN2n+LdA-RIA zz^Ooc7QdY18((+KHMqBaED5^BTilvX4rn2MnbGO=J_1IUz7S58ASfU#Padd;_IwCT$YlKpI0&kymd?fLN`%)6K) zQHn1{axw2bb;tt9Vw8UEAqS?6+XIRQG&SGi>XQWs)_cNBQm!&P7Q5fv84c6TzF3Ne zX7slM(|4WkH_lY2w#eei7TH00dMF=GA6eOo~e*2lSw#wDLGiHpC_F2 zC?xzj{l19xWC(ZUY1yE2EOpBrcZ?%Kc{hlhTLH>w2lNQj&+rF?P>b6lNnO9hrQ z@AX8#i&#A5v$8-&$dnN;?eKFM+%!&B%BpFavY$%^;?B`d5KD6B)aiPr9dtv7BMGCL z^Puf}tYG2LfQE|b-MMc5pHD9@rx2IC7E`MSho0V3GR2|Of!aD&NzYE}=HZktO}p-z zi&eOZ`kk%`3n%zlK_N9DagoZ3VPT*mOe*{qQ2Cl3-WE{3!U*Oc4Lv8Ug)JwD*LO({ zccjUe`rV@*x6m=m%a~}T)6Ny`O3x{uj4soy99E+k4;rqF{vb`Nlaj)W<9X4B)fRa6 z?is19%(ZuuIbn^M*j|Mb<^g>wFJ*Iew)2LWnZ&YoueBe2MKC9zpU89!qiWXYAS^3w zx^3aA;#e17^fh>0ktIkdZFR2`K9M3D)4rnI=_Ze2t&{C{PYM@J)DR!SlQM2}Y0e$J zbLq%v-)9c*J(?9~u;7AhLy$KsxuMJ6bTFACvYt=iY8OZ42rtWKjQPC{7`E&kGk^ZP zD7asqr#5w0CH&#bkPd@3+lr89j#)T`nmEbQjIO{CGQW%4hG`3F=EM|7kbHd(E%gy* zy41B~16%6LCMjXzzQT(x>J5$MyI$SAzZ$9pym|_zBE;t{yo|Iw*Ei?1w%_l&T?)wd zKS=24$M`Q)DT~EhBle>^TSYB?%~H0Jkzi6cS%kP>Nc=QC`&Gxa6HP(V z5SQQE&fgAJe#oqD26eY@puyhz1`c7RNBzy$9IjO8JuT^Oe2(tDi~ZCgrJ#!Z>7OK? z5*5GHBRsmAS`?ouoR4qt+j{Z7Ve?RiFTk`fQadMQj$QYvyb;my&BFX+>gdxP8U>y! zH8BORW!27XW}m>$oOqUdcvRB{(8?!WZ@(7lW{MZ5$C~_N0@|OC_>VWV89(9EJ-O=* zQ;S;_*Po)d2fF3}Sn!xumZndX2;B96q)2~E`x_;H^uSXUGad?H?wteXUfBMcC(z@F ziXD`dbQk(Sa)q=)hh=Ix$;e-tF!NQ`4lokencjgtSKo5u?Dpb@iCX1NY{Kma8J{0Z zZ-szpSxJ>frBgOPj8Ce6D>npHFeIGb(AiyTmFn?qHSx`!#|JVBtL1iQsj zI#_3G7xnAH6%cZ?rczGQ?Y+5}2k#B5st!3NB_K3X6EeTLm|96)Ql>jYhh@+;IA^ zN}?gR1;oFCJK_XUf#R+Vl|-^~F*Pm3F<##XO&}iNol#~Aoc90~s!0&qDcuK(PcHZ= z^3A~~OHb9XBPD^mnZn#O0s3HHmOhIfMT`)<7v~*n_aV|*!*uZ}``L`NXt7iLpPW?M z)Ib)~$_NL>b#GvVbHFt@>wyrx4ZD{JjQw^#;PDENH1LyT622@wpC}Z0@a`2%xa_k7gAd|Pr z{eG#iDnB#_rJC|xZupi+9N*sNm@M#!H3qhxav-Zf%-MSKt zXCu<=qI5AbGbJEEH@>c%8Vs!LUT=%e^p_(+zSkcNq+Bi0n*GloMnw=`6nLl4ED z7kVe@h?k>$ms|5ED@E|eK&rKa~Wa%bY2 z%BI(*R zefj^(>;7MdivG9ho&OkeqNhL?y9?q(Nc0qGBJdzS;1eplr)cD>944S-VFir21;g}a z2U3|k9gVtwL(1L#h1G=sAzBol(7AB&{ey0n?FhksBTeFFof6)Mi259$2=Kej+mAT}r$-Gr3Y!EnD1d4=~hzH7dyNkeVFhTZ){iQav_OajJ-e_5!>1a^N(T;LN^ID`2FW(7f2W8VD zq&jzb&jYk35Uby5@W)#|+eD;AzGBMwGX60!t+k&rsKXQ_IPnMla-U^m1|$mBbfGKc`RE`X7x0u9TRC2ppeO(Tym*V2vw-TIC~a#ZyCo%1W55 zz|;{0*oLNLL-SiSZ!&RnVSbpQYUQE*#+XL1J_A~hZ(n4VCd4yZ|#qoK`_#0 zMXsIT1*V6+*%0>bK#u1$772evFpi)ZegGj%$=r9ja?vsg#`Xtfk}=x zXlvM1Z`I$=vqdDpbtx9fpPoThyZ? z$Bh3}V&oaGBVPq1M-vSObp1$8Y25^%30|~m%)oBf6LVy)-ruFO{;`c^Jv5p?}Uo1r6*(VDX&%xOX ztY{e!YD}b|Jw+=ts0|xT-~PgPWPy4LsJMdFWiXw+@Wv2g?A2Hao1#& zoj7a|)-SofI#A_;tGW)Z& z2&jd5ExMak8N2dFKt|I9vO5E|SvhRQFJ$1=S1tT^1;|W~7ZOmKN4#4nuZBn7c*+ar z@CT`KC5uPiz(|NyPnr`@*x$W-t+|Ods1y-yC#kiAOilYt&r;1=w^oT`DUJ|Hn94l{ zYmd*vsi$SYzDWicNX;51u|{GV(bKIhitJ`*?>%BHHt!4&z$lwNu!Il}MwbAP2%4oj zErSb_c}*Hd6-my5p5aUC8W16+D`uR};dKY`?&6{A!*F!w#m|*3fqZK5k1bP`A=UFn zF;4wvligZ2#o2Gk>g7%Ti3)c@sLYV&3IikeyW>iN&!KA+4I58La!ygrae90cw8dmW z0miC)5T&Ig<1i3)Yv4G#wKF*KV#CI>_TUuT$A%y8U{to3>vOLa$P5NGJ0I?W%HTq; z2%KRwT93Y9FDnIwAjejDU6SP|OwY>$m61J6w*e`OW+%w9bnNMdTc8)cg>q9+qBice zf=yjqy9IivkB4$Np5S_Lg#q<+kdT&^W`2%m9efRHmeH_iTU5V7Hs@_crX8@@ap6GU z1ZX@bi}_l~#whem6YD(9L@vTKTET=h2)a|{=azY?O+4Vb#m=VPitDlYQbLrlw^t`l>1+(?iWQ8_UBCanw`3z|9B`9@ z{n(LIN0{rdR?<+rNg0z1DxE^G)o&F4j=qv;5? z*Xc0G?+Stw$oFx610#iUqmlq;kSU!nLw2eqhhMU`wvzqi&-Mr!$H-^k-e{JxX_vKv zTGh?@DRUAwkTx*vY*2W!2z-7h>N2m6ntFE_m}*~$QHN|t#Ju%)O+69rbV?92w+)Bh z2fTyhQCW`vie+%Z?qgL~3T12`kw|l)OdZSFe$So~So?R4ETc zD{cp9rt#sVZ-QM8WtQE7>Ls)8tSDFLQX%DJtBG+K@9P`j$yi7Bv+Tog<`_UPNq-rG z5_O+X#%vB*=?0aMNAZiV;QZHG4wkj4a&(Ws?r+Qt^g*%;XjTp#s!nw>IF*bCnFQ(4 zw%~ki6=Arb24v-YWQ!Hy{j&sW;w&Tc^t; zH~2(jFpht4Ni*3X`Zd|5rdv#gNBZUus5;EsX4EMPzzl`kTI&)MUkdIpi&hCpYKW5^ z=RT2&J7O-Hs~T!fmZ5-c2%+XGI?^l~%&|voaB>oshln}d&4@(O7^c_8TJlH4z1cTm zo+}Cr8v0PignTB7J%^BB#4AretdoY?;b(47aZ}SEV*L#%BSB+p{=+)-_P(!HQ#jz% zGQtv=h9EDr6imM=0-6XuF%N>zxCSSbFuL{))89W>keG8imIG<>wA*C&vT%o?p`B}3 zOYX-(umWWs9X45Ghaf}dP@91_x7P4S=&|#s!>poGk zoht}xKHsip`mqCfN#Af%6j3?~O@=nGt3Cr)KguEVZw2_mgWSO1fClzra$wz76`qLP z0wm8!U4c`Q%GN*oXL4A5#_g(U>;sJy_a_iDn3X=?Ti6p$19Dh zgM089|48oV3Tynu;Bol`5fJ5HvpQegeLCPJr0m95<>O+0Y`BA#wf8!d`k=dgjv8Wg zftoFxB?BdO3Y8&n*f)51760srm~oR?d$$kA{Mdb`wFKd*82e_Jo-#)2tQ)(5=XP~} zlo?uHL|WXW$sG0XU03F74lV(Yys0WbL8y?em`t_KjX+M7^nOL^@x>l`7)gP@(aUytGc zYzI~SK$9}ansLBqNIWD2r^gDMY0*GpM}}x7Wg{OR9r*?#Lf@bOwE$;hjsah`%CFTB z7f%Ym1@ZR2Jbkh0?V7zmU$fNn>p&T_mD^_crUMViTu7b7e+9EKmc07~JTt0hpO=lk z5er*$LwVJ>Byp(SS5R&r4#h%8Z?s#I*FTfaynFbgE||^d<|lHPLR%fWR*nV<3K7bynb&#Bj&>{=5J>al1LZAK%{j2~M% zeU~61ZGN~7f&3hf6#5H(mdDaKwVbe3a0FxFS?%u)s3s~EXCea;G_T8g@7dWa1-m=2 zsV?KE1zu5f!H5joH_RXv&euy{(X;4$m33ZuoPf0zZ<*%raWM0Q21C$coB{J5@^|8BR{W9I-cuGbLvRg85AVi&l?1f-*Lg<*8|)$UxQpq zcS}JxE`i=eA#b4`C@vQJLjXQ}Y8WoJH+ejuVl@lI1P-7N6lSl$!BcI?whigP*^>F+ z0)QfjsTISXz=Iuo2a4^5|HD?HLxac0&}3WRXVEM=K1aRSH2Md5uIDX5^=rMq3wQ_D zPEP+8k5zxCs_U}Z;S|WhQvoGO1zCtq0W$(!MCKI32s$+!fkE62v5eJGK*jzgP7LS|i8zZ|5s zo|Haz!(Q7~g^(}hHKeO#gz=+19)!-my<$|Z^MYmsA98-M2j4Ssk3D!?(NJ?m*98#1 zJ#>VicjS2wN@zjrMk6nhj5AU#oGULe@K=5?vX+S8zgLp(M5AtE$)o zcnt5KtMg4m{uBi!J&UVa)qsY*0T+P)S<`DCMWEhKNslaR%fVSfOt`1(2mH8*(%Td1M)jh{s12EA!3}LtHCBo8*8UWRi}6bl$U&{Z>%9ekiZ>v@086>>s+fzk zTDW|yKo`;y;AKx~?W#RSYl&uR?ZNflu%BWHl^&vLP~diGKdi-{1OWHct|gdwtbb)x zO!Q~eD#^0K?}af}LA}7MF1l4Y2wskHXuq2Z=gfLJ+=J(=fRm7of^sm(-xcz~5B ze@N;4-#;QlhDep}+Z#zJXFa?ae%)!I&jKgcs|Q-_y6($7U7=<#wFi>k1#{RPiJy`p z`v+0LZxo>ULKMs~3h^cQka%4_=+01Ve0|=uG*GA&1Qu~viJGqjai36{=7_=w9 zp>q=cVc8VPW`n@GBiNRkE5R3pz1|}_mJ9?pk^%??cp?>gXrZBp?89;A(iFPC7igzk zLoveEh3O@-76m&eLnMMMxOrUljoUk8=gesJC!+E!v>kKfTqIa6jNcAL?4#h?fQ_#FFX!hSg$jVG#VOoY(X%7Fy zD`kW;AUKUGAKN(I7{0KJ##0%j1m~GS+C#ki{TV~tn;%{nM#jNar0W3K(Sg}XGf49xiNTvteNLjL^zI_SD=1JJ zACW@_A&i03yNfNbWVzy=?i3=k&o9oqn)8X`cwHi#jv2Rh*AtX9_Eje@-WsoEdQ+g;J`Wxzv{st!+ z*YPu44?Z2=Ca?Xa0`dQw{|S=$nwRNB$otKjS!IUFF(4Z~zj5f6pg+kC*hAqHN9MeY zq6_OPyE9?;SUG^X^{%hVy|n7E2WsIPG=!|LLk?(EHUiDPvlzK&n)5TbWh7MdI8VGQ ziL`lksL~mv_DR#j06Z~vnFnxAoJh`WYw`Vx9CWRhdS}&+ror3!jMt%sh==z#t-^0whHj8SulAvTh-Wd=^4jTS1B}a0>UmXorGU7HqC>Z=h}j*UgZA8|0HD^jLEh zQnBxka}jJdu#~M+*^P`N4&*vpF1mex2jjz%I^AjUUi|=Oecr19<&G1Y2XDUY7Mu_t z39|?eklvxI`TK!fSBbmbr0AHAQ7*P7wlN)L& zj9VZAELK_tT6L44E5ZY{!wq;2Hy}YPLnSLRGMaBeB5yxzVah!|{cHMYCrZR%UcfL@ z4%Aq5`MrkF`!9P1b3j$ly2No1Wj>&1^ltRx&4A=o<_~OTqldT2WRY0v*ISBR*QOw) zDM+Z_eB2|*ia21TPD>wHz*Ci0Vmc9guBg=RWwp1GO{Sog^&pHKMy+~)D>C} z2~gIfMft+bAQ+GZf9wBc3yHa4?t0^w;e@g^3hN1?6BPp(6bXeSH?|MbuSIyA6y_sS zIJsxO%D9`3(BOzpl-TcSQ_9{RG%yOt(ToHWhhAwlSTa12Pr1HEr4XLTUMR;Y&^&0h z`nN=aZ(egbE`M>x5w_l*JD|32A$1YYqF+OH*89hpC18rzz<7HdNQh*q(Xi+E=kq?w zKz+1e1wca4zV%=k$9%Qq?y*v%hYbeO8HyZ3HA^t#dKn0E){($UXoY%)7J9#N?rpNCHf)x(_U@ z%3&!lw0OY(zzHepf%g_V@?584o#!Ja5jKX>irrY~pG2di7b=WpMAR-hS1yC)K za3l0*;ua=s0IZ39h#c0C{Mn6-Z@vgrBV2^rnT zU1ZJ51$inLbPtrX)HyoR6)%JD(+%YVY$68CCf7GqjHC}SWGE!~nvGOf&GZ*%W*e`; zs_n~c(Rss^2?w%?(AGG~3LKHW>|KCYNBR|`SV4cvAh_2c7z)}a*=&EelRG|l8k?mjmlKIjJf8d4LF6V#JGgcGkG@6wN6q70v1(hLZ; z=2RU@u1xr5m$~$jTBOh4oWk%EDxvX|?mlqCq1*h=mq_42FY7=5#-n3tDs;7`+4htmkW+byM2vyAD%p&*;8b(8FZ|9|Nx?yUoh%u! z<^pOi2S|f)n#8%LY7`ir7&!aytK)q+{P(K$kkA&grh@hs*DeAo{X&YlZ=Q zx#6G%f}!8^pHLfvXjI43&|(NK12{JF^x4(~KXBJj(}6l{@|Ch22b@G4H-=r3`T$|g zsRC-tKVNj%gAeS>5s)z9K!)}Tj?$tGP&`=nE`SuvGjbOovs5B2G_?C?hN~)3pgK54 zfF3GVleF#)x)q#Y@We@SRVJ?{cZ%S}pwQl?0c<`;=vlSw+$a6_z1j&-H^Ika6^^d~ zZ?ebBpwT)AC!Fw^kIKsk%f%^ytBo%|fCW~Lj6EebP;c`lL&x-rJ=AN_@PK%4rt-zTW4PmKQ)f2Ve5JB~uAh;c(Bo0|3g7SDR?pRnaQe%h2`JbE|y_1ySflX_xB;GfJZ#)P_3Xm(psy=s|r54@a#)u8Oxm~7-IC` z7(VB;4F|CSLX}aP@c86~2fQ}JmYOA&W(eJaeRFMl8}b;>xc67_ehBgW9SJOCa5o0tT) zXJPgh{LBc-R>Of67WKRjp!UrSLV9&b#}-*%6g~<@kqfrdH!6=`waAlxYgg&eQPd+k z(8&W0J6^shEOh)K#V31EHJNzOZLaj#vg_swqxgrFVkRuhAn=_L^y1=#rU1%YS=~a{ zPIvA=MtHms)#hvgu_~^27XBdLp4JwV?=fcJd2;6pGI=LUL8@P@kZG)dVA8-ZENr^@ z7OX4s4LE>UKugLP(Lm6F01kAGr9H>#l8}NB&_tq8$p7`PpC{fIca^xNA6*>igg7ur z8iyZ=-2BIYtlnX1c*skl9fxJ!o&Z;+sNF#EADIbaPYO7E=u3>gp5xMkHVo*d1NEif zaN8D6Hht#yiyLKu%DDAo^j!atdwikhj&?{Ii+P`O6U1v4i?n<4cm83{6i%IV_}z(I zdB9f49GQk_Jrkfo_I*=Fg#Jul-gZIQGBlX8&ESL1VH1W2^`a^LW`rzaJnKgX8>xqF#ev%6O;YyesQRM?tqYuS1wU#fgfXQ-B_RvO!&RJp67nFI|H( z%*<>Y>OoB-8*fl1o8PF=LsubEy8)?jG@8wZ^q7C%2^$-Rl;t{b%5aMDGQ>J4UE{j~ z*%>!7yGCTOC_dWxAmQo^oyrm`#~o;@OtdkJ79_xhr+cuXOEr{)aD?_L*I>2Rt{Yml zZ{c8i3p;8625|kz4zQ0$ATG%Ca{(4P9Mp}gzBc&D$e!<{aEG`)G!ap48uKm%9{d+! z@G&cs?Ba!w(R_f!8TuG904&k0Y?8*n-hDL#3>zZJ4BQl4iWREF#3sSsxhO(-2`G9f z=4xfHz{my<(hG&+Nw2!<&N%Twe8PM21mH=s-|JvR(*Nt#ZDoq0gGC^JV6Y`<#;FkQ z3sJ|Qc?1gbrG*+e!d)K6o1Og)B&))%Y=j9Zzjyhqtz`{yVXz1QJ#^glH|-%2tK!B&6Lva9=a^!$xY`J=?7Yg^x*JLRNOb^VlPGoT8wB2}ah8GSF>WXaQP_ z&>+1pA6x)Mx!!m@18(`AR+ofk|PPVQ2} zrW zR+RL@>%zkL)NqI`Ch13C4-`GOOMF=O8pxmjeD$TCs0Ky0Ud##LU9jJBTV)3&*I?Ip z;Ar-FCt)M(N;47BNCejpd(bjH@I`_)Y++;Ni3o@Y3=UmJyW*%{dViAQL)mkAN@oe`DVjWG@^0L57z?dm5vP z*9dYt-`OE?YFhe?uJc7Gaw)43UwDs-Gc8Y)@s#w7(jU||cLb09xUCHF)+V(jm+52- z+ZUaj3JJ)N?Y5$PV)Xw$R2!7WcHf*h^6;m2u#bIPAvvi68{LHqyi4LH=AR+^Nf z!@o9Gq#ishR_bIPi~e331%|Pn9}!HwMt`4oBQ)lvOL&6e++dhpDtGeC3B273ydTT1 zqgXw-&1=U&5#9tbGCIKhr~`iTv?eJYMm;)IS8x9CCuxRfsRz;cg1}WJyj$u~$|IR! z5rYpu5}_ouKtB_@TEnQ}d4WkgVN@!!Nf~4&GPeN7ZH6vq(p8d%;0p#FkKK}x#NI)| z2t{P`iV^JS3@hL|8n3WveZzH=$+Xo4$Z&4iwA@#=YQIw%4C1ipLlD_(J30i2-~zrJ zn#uXK0+7LgH<%evtZ}Gth=N9J}8$T^B+scEW66|86xl)v-~b^Idq~!@l1vEef)_5rSL-Vj9a# zHDsJoa#Xxli7$3oL>m9j3V;`Hp>HyP(mgnsc@f#_2DCJ|p>vd>0cT9-_P1EU9(43Z zzJ`g(i4}k{AeUZ>PMRCYETrYT)0Ch%C z!v+4%;L-?pvON6dCpE;3}wk(>a{En*uD#+ z`cNU1L_EKDmdTY5!s+%(j3$sWsXvVFmpm8V6r0ui53hIK>7MZ)%9x&8Y09DnHkCWQ z))3ymbxfgC)5js<|6}jX!)kuJx8WVhH$=)5Av6!7Nz#PSD4H~>P+O^tNuflBGB(eX zG$>Ij5lP6f8>G2RB`T?uqEPBN*N%I5e(zuJ@xFgN$MNjrxbNGY?ETrF&vmVJu5+E| zy7HFe)+qKA^k2OHkM$zSb^x;M1GBJKGM(NPc-A~4T}*e~fzA`$5uw`MD0S{(m@DSqiU~hdA;lx`l(&6Doc$@3vX<__o@dF$ps%6 z_tJ|nD@KfNK9Z5V%Z&KB(CZZThYpy=8NJhbW`vq4oRahJpU_?fr)JLcyrOv~l_pO& zO`#q(9coS_X>pUWlhXGjlTDJ^YSOiPtRK+)Q$d+aTw>H0uG}$0_(n^JdPsm}gL|5P zci3J0j}wF!qKzGUmW8Go?Rnk$%$;ThS7pwoyP|EHF8H0a!^G-w#bLb7WUIHD@2?_E zsJraOxLJcq>0aj-#u;H$?BevQ5Xntx>3tZUy-jQw<2SO89(JHxd*#DouXP9BdA6Ur z)h9(IThoIFzt0|A=(UdQl=9FF3ocbOT6cV^;X$}=RH1v!9#nEujlhaf4Q zmV=`G^>}%{EkP)AT)A!kTetr+HB09JYLD#E_30|DO*i;%kG=h$|DJR)cbZ|Sw1u3s z*37i)@m&ZQo_DwX*ROoMfnOmR1(g|_U-#v|-a7Q(pD|(Wt23!JVVV0|OK42=^lOF~ zGx~1i2m)>U<2C}i|9S;joo#!yBsBmFHj0jos!$&)!|@`rT{}qzNQz<%Vc%FNL&CDZ zFU{3iBu@ZD2%frl=GWDIi&P!Xv(=#{rf)I`ZH5!#Ju`?V)>J^Glpm0>0h{G>fNOe!pA|B{C)H-IKwtbgXN%LQy|t7 zg6x2N$ZN|$b#F(DD5m4=zp({!MjMH8xco!6kG2l-2`zBSk{XEz7F+k*0!|6iyLPdc z>fcZIIgz;+-P`|qeVjN34gdAS|9SMt|BGhX|5voqzpe`P&i}oP|Nrhr?`XrlX}#j< zJzq!9XbNfi_z2g$JoWO3qi&DTBym%Y)n|1Xt5uAWu2h{fnI1ZAzSaZ|?)$nMbd$7u zj6`^=Mk?|j9P2X>x-1-QzhcN+twJ?AHu7y`tU)$Liw{!UlHs#<@qagZ|4;hC+3M!B z=4Lz3zK=TA3#eVeXqn;))HC+tyKQf#-iV}Sk2YmCA`LtUXWu+HX1iwX+9yw*tlQuI zLIw;$7)mey+qZ9ro?*uoy_YF}gIm$fVigZbVPUN>S=*(k+Jd0gkF-lmMY*7TaiPZU zFz609_kCn7LDdin;m4DKUixHSsa&T&m%!L-eREQr%KndjQ|2mYj{sum#H!q4Nt&Y3 zCt;qI0H$OuR9ibrtV!6i|HD(Bm3y0gSh}|m{;5fdGbpUqpP#b`q)zsinRH!6dWi;v za_Hr=5gyrWw+%-qZEt;?5=2zayew!_8qKU?4zm%VRJ^!|*e{`{k*Rezwn|YISv%9s-Cb$531;oI#nRvMW;#}n{Ti-( z&d#{AuFWP|U%q@P;HRIq%>4I*Q8=RXy0-yqXRZ&vkumMrVlBeebB=fI4kHlm9M9l?e#O%E1!BqkX|o1vd7bIZ9t45ut=Lvft% zh=1g>B&_?$E95$Zmmx}`FXGgk>ma0FXO?LrgYg@oats}kQH``OoOl*2TD+wTs|y2s%G6W&c?j9rnkYPVtJvTY|wkN+(b zRJ9H0dNF)vIPrk?vARLR{ub?>ol>a6!vWGtVpbjy77`MwR zK`Gc*n_ny4jE)>1HzCowaJWzJ%&}uwQ9jBQsu0qsViWnJY_K5Kw(d9Ti{NHg z4*&WiWT~O{td`^`Sw@32d%5_0s1g$Cmd`)f8TPR((il=zH3@+78PevPR8>_U4Nm6f z4vb#0rvxG?eY13|p%T1@3`p4f9edKCmOg(X58GD6+uPd+gV+1}eJ4)S?+r8S`-Dx? zG3vQMO-$`jcMs6zFZtK!W=ai|wA1ERo>aPC2TZado!r<88ffw!O~+Z1JaOz}PF3FC zNOQp%h}&zoaWM8ezLLPUR~J}jr|GdF&i#uQ%AQItF2{g=;_ga;l>xuwUS6)doCPVlk8cz zWjSgPe&yQ4b(3*W<5htzn*q?nQQVcFFxOu%$@BT_3x9sGvg=<46E&r1$?h}BGn}{w z4%>O&w&RoBm|76B(f)kjsd)YR4J^2u?O(nu1y-3dEk@!5cSjFxR)>9KN#OUzI7Z{+t-fhj|99D6qwEl0(=mZ+@k4QjG` zB_*R+_aK%NU2=z+fR#}#weT45t*9N;Wj61R8|(V!D$J2mR&LNgdL#~mOror0Wiz*^ z6RvFjBt1Ewaj=zPRSMn**M7cehXRkX~IFgj@)|LoGj4WHd6Ul|-7>e1-$ z2XB`Ipd6&=Z1(lrw-8t(BhQ;Mn*SM#co$huo`|fDzXlW3<{Q&^coaWKrfVJ_SK&2P z=5;t|U5cq_jJ>fJt3UriWj%VXw|9zGQ?&Q%uW=q6C{#!xG^6 zU|e!k)iFA+C`Xi|X!=-O?Nzl@H}s9XVRK&+0-j*RvEq&i)gzH0$0E3Q!w$#H9qKr_ zk|(UHzq-#iDzX6KEc@7~w*?p>iJyZHMt>V3(T+UYv5VKNxaxS8ji#ngzM6a2obcI7 z7n)2P#(Nuf8{660t%)Dk0z&**abx33o6MYzs#*ToUU;a%q_3ZxaNL+?gbEb!9O$SL z32t07(Z0#!1JAXolL1%mA!LxSuSz9W=W255?xw=|PWNwAH(tVwwDY+(Jv%|-4!=P| zoBBLI_3IPTiD+o_|JT#_ryqGEn%e=~>V_Y7Y@1WOKl68;P74C*rJTtrzt!+{L+n{W zEDTYKTlFK!YZc;jFdbpkPlUQb{0sa!334M!*f>~|cMs9cy!DZ(G_9yC;ab=T3lvC_ zD_Zn&9H!eML8)tYZ7n=%K@(N`1+M9t7iDv8JoR?khmRkHiGa+Rs;H^iu`@5MruM}H zbOD}Y>nEYmsKV(?IdouQ70M~kZ=a^ezxV6cEW({Dfz7B)-=e#>_M(l%k#9oSW}oJsQtcGDMEZupOT`J8WM% z%13L_1_kde+ot_cyJct}r#n0ecBY=lR5zCnDF5@Ot0pVltEonGFm3=ubDk+_dkROmZ`)l*d3BS^n zy*Q$?Egw;7z2lK1M@rEgx>Mt44B|~upi;qHcF(&4W-OPX)24W?^>iv{)yTGoKDy(% zZdtDZHcbK>^S%?E9GP>oWy8=@es=eTBA@ISHcQ+OL?i*8p3q#|2tu=dokX`rn(4JL zi*O_+`O7qvH~Zt_&&%E9UIY3)DK9rb1{!#D-guirfZq5k*wwp5S!xFIXQgJ(o~`$S zT0z<_+XYpV<)z%h*6Dxx;3kA_O7T1LsxcR@fAZ|vGwJEK(Dj$zTv-$fyK-Nikc~rG zM5TR9m1gR)ZB6n$O%F!jJVP8a+5N7-NiO|VrHk{1i-Li$FY4fkT1ljdBPqt7n39&g zr%9cEFNx#i4=ATkWeV>e1K3)v$OnPr!lw!<+l>ow+kYvnd-4?x=enMTDsC4f(b=}P zw)_l5q*pwe3Ox-Qy#X&I{*(th(LWB%>-g!Uv!q-Sme0&*zpM!g6^lQ8XpCm17cBi0 zZT>#cE0Hdn00=66s~>KEG2fWkXZmTd>coV}QH=$-Zz~)|zj-!$jKvYZvrI=4GISvt z)pJ3++eP1a_;qXey_yD$SlAgAxw%0Brk*)wy9HI>+`_J zA;a@9o>71;0gW_IO6DT&mAwsI2&EOZ&`rx1Dp$RQ7KE|-C#PSnTX*)8{U+&B?O_tb zX=>^bIFIv* zB3+DMDVbWDUcpIaDK56=+hv6Wv;?L<$I-W8bnb7PzhFyh0;0dRpQ*#X@ zhRKizzJF+}TLHjaAP3M)om@^XE?vmQ4K{@qE{Ij78!L3tH#K+BWnaV16E>dG!FfC$ zM~*}{qJDXunD+dj`zwTQeTfX@kP;t`@rlw-N%K``M#%gP(`DeqbQ9*#)OT#}0nMBQ z_fE@)5B+bvZtbsZ;@=DsOkbw};(;fh6P*|#T-)zUyUK2;XWlSMKFjvQ7tAeP ze5f2>X8WpvJ+^A*{pc$Kx^=3kw&!`K;Jg^SrF8wiVLj zDnQWCCiTvg;QAhc=+_v^Gd&2=q27ds*b5R&$)PE>>|TgH0}kaH{R>Fp*92T8E@wN83RLh|~@h0cYNzekf=N1#p@0#ue0nWfNg zO19YFmUc~lz^6{mwP)~6DoU?nbB^wa*~x}j=fbcZhoA(AI=FP{(iBb;O-;=o-c)+v z23s98XZTD|=s(MKf4#!7k)y;~gh*TTMgCR{SUq~XQ>Y(v6%6tud17j(m_B^?5Q}t2 zo|9-K9;6>2Lm~-Fx({vYvuqZJlTmyeTq}!On!1)wZC9`Ax+LoaucrYb<1$77Qdxk^LNu8~ z!s{SKJY79KJBNz6t(bh`Ixd&DzbH*bF`aeG>bC0_K4xjxqITJ>i+m<&CjJ2rs*r0U zLu~w~03bkl)v9RLskpeflu@ASdCcLprKPhajI;_03iv+8(9&&i>@dIO^Tw-awwQtHlG2<9RP`8x$HD4^ zw5%eDWT*05ZcX4o%6_9fEXCe7(7BGy6IIg=n*L&VeL#d$b4U)4Ox}#=6Bzr(a$B=U z;m@{tI&rG`k#l~yHHTE`cavlwvQA7tB0#8}gjz-Gy=y!H% zc4r1=YIR3I`P#u-HCkcGR8)yF%|h6ggX<^_b&!WeJ|bI*)t>tfMp4d@>O2W39Ahn* zFJdJ6+bdRp)yi@h3xkdp>g6h-pl%#xPjhX7LcGL2o`^!bCaOmteuIAKIc4_WFJHcN z925Z+>T)h+alOTU=(NLeT~RKZw?U68cKrD9mYyD2&{d-2&$v%D;KNl)wY z<;yA7O&HslxV0H7Y|^!O?ig#jji^Dy)=*R&EbK{ruGXucJji%*XD{B= zXMT>f$1beaz%R~iX{ayFzkj0-<4yguI3dLegV*IbmU(@Mbf!H(r*YVcaWC!rlFYhA=>i zyf#S8a4Evxj6Hky>^pSm(z(YR44XCD+J4-B!e^tI%GZQ|O%hoj&#v5%4eOfAYU|rh zxy7Ck;DD2qr?Qw|;ov}*#ZTlGd*m!;fG9pNOptivqHFobMhii%o+^SEmr=CZe6DIM zA_O!a!M4YK*G}=#*m{vhH#6^DyLOFw3kBnNV)p0VY{svlGe7OmQyKh2y14vk*se`Ap!8%(E=fk2qhT7ZG_^#->pa({ zj`lr}Zhi#OW^K!;By*~uikmiOiY&ZA;_YUJbgAksVA{q9#~wiVs0-n#_<{us0`qJU zJ857T7f0@uM37u&^XK~TOqw)F&O!kGr=>uq=iN1A^vDKUifN`I#3OO%R;?~2EQXx6 zFtZ(bPtfXUo)hZZS*?f5*C&g6PGxA;lXeU^Zb;ZXi;cr^;5 zK>oNX2}<%cpiD`#eNf1fqp|4wBRRA>Ab1P|aEkWlf5jyZtw54q_zV53ev@k1`tTv9 zMOzxA6nZG(%7u!meWyqor|8Rh{c_WZluORAyj7C^cwE+M4vDt0M^yaJ?&V1@W{-SS(3E6l>&vZUYI_aF5Ur;CMHB)Q3y6c2zy+A zYJxnPT^dz|fTBtkX$s3!whP$E%F6Ql%&a?eIr6H)Ja;ON9u!wsj4%4C09dXc_fq;0 zO0~ZoF$w-(-x5377-mJOoMFclCs$!rSz~>0{blMCrwV3$&SJQ{L=8?R>nil}dn02j zD@O%T(NZ9a6u1Jx3P1RQ;8T(r{B%%q2x5~i0B^azQLMjvd@Cn?Km_{O6^Z$m{NU5)XN9Xr%{cGr!Z z^sAUJDClw`Xt4YB&T&bP9c_IFq&^iwR~v+38<80e(-x=(Ap5dVaTu7nC=c|cEvbAk zVdu_v4>cw_84cOM3}R9B6j_88_@+KmSz_Z+WKU*9F;KuhJYZI8^?I(w27*2hBTVpc zO)N)x;{v&zFI@6E%We>CL$j(CbXr?sSY_o!eY}&Dh)MMN@!8*2RRT#kK+W-(pgTT!@}%&~YaDh`8{d`^*X)ehnyZH3OK@iHTo?Mvd?&Lr5ASel8lYj=0C~aKgm_&$0qKMqDE%H! z)?a_f$Jz6!jU^8@U;GyoT~v(ygtn zGrgC)&AQ69xnq7wjrd%-7BX=TcA2 zH+~?+!SLBx4xtyP7D$eYquv^qxYW0#8Lj(VpSkA?4J64vGPpaqj+XY(AG^X1Ix)29b*(-G@R;RQuNk>)-eM_Id?=Sdetv{tCN>~iE5p9wA$ON%T+JS2}#;eOfMa^w{>~oC-zvenI zQS>kjzt*NyHcQM;fiY?m8k3yD}|qeD6O}>^D6KqpLh=2wnTVjWrM!2XRMK84W}IO3$ihl) zej-grH#`zG!)A$KC8$&yzn=2zzBlB=EiElUFH?~N7)7&Aq?&-Oa6p%oDf$#)th{VW z_sQPgUS9Obv>#4q2v&m-f4pirTse~D<;#H-NDIV(J@V%M;UD{`sOqRm;zt9!IQ55P z^`}FE^N&J=d7qV%mecrn7jl5~?Ui7fz(0-nnbSUc1&s*^& zvRS-*V?6|b!Lb8Sx&={1-;hs1EbC@-+Om}g+Cs3+&op6#+g&HePYG)q%3K*2N>A zvK+TgSoaW3kE@FiPQZDDzE?=02VXm7_nhs9{f@Osx2>liNFe?WX=ui{V_ zfW(#iLevCBCf3DjyTcY7!~(&8#^4>J1Wk?(e0c5*aF5tB+mdBmrs1Pbf$QBxddDSg287yqsZ0pvmu$)l6eO5OYg{f)UR zy@&6>WK#sNg!l9jp~{7K@Px0g$g*WG7cIR7Wyc?uNpnj6-Mb|iEJm;?8%mn|eIHJr zJbn7?nKNftTA}FC-XM=lfHqNgXcPuN9e^bw05@I_B|JLe(oC+WU^|NI>$gDB;mUMz z>uRXP=V}Y(y=l~G7sh60-|1p6+cmLsYd^la{(9=Zv>ba3s zI$?%YPgl|b0ODd!S|KfFs9rksdfDWUh)zxTDsf?xNz(b-ZR8tM8AG|A{mc&F1BiXB zP|M9I96|ey$@i0l6Yz}(0>+4Z@?-~y%gqv|8011$MBq=73hk(BKydQEckdo1B3Y8{ zAbT@M)}DWTUmjXn#cJJpd-HCNa5DTpVel#~uPEE{O717)P#>Qz}s=_@JOxRlNN@w9fGh+Z6 zcM0Z@Q7SeA?-63xPQ-xFK#cIk7>#2#Ac8~y%~v3GJy^Kq41FU;=~!(qSPh&=j6enF z#&sr;!&B1>TWW<(1-suepVN>knv|!9P|ndv3OSIQIfd98RX2BRpbED33^DkO(my(* z*8nvl7zru+MiyR9nr85%bS3*K1~;b>l?DdlBQkf8y4PWD9v+@1T%00lhziit=))fW zZr$j2bTISBSM^N97IyKECjc+!6&#VKijKhgo$s|~EKmh^uNs9Q+ zUHb9rZ$rKT$HWqylfIS+D9wzqTbH0d+`Cpje><>W_Ks&$$#RPaxh_apXM4KKk6*t^ zfIuXW?@0vqmn)har{dX{1eQUxq$q9Ks{nD+^bkQ+kC*?sM={oTu6T0rL&RMf)C?X> z{Ur(tCSdZ!LB#VK8XErl%_dEq8ivMqVU0KFHn#eL!H^y!T{R<+%u2B!L{T>eVXKw` z%n6H&YvVSS=V9bY#4fg+u`3>Q8t_;E3XKx*jnMgtlz0vQ{@sd7aJwj5<`%v74^uN& z&=gA~hi zxD>^N{u3TdWZk2Ko{&AxWZFME;uWdj6!ft?I{X}cfrgbR#_*TA=sU^+zy|}!M+iwC z>}pv46aFiMW)o!1R+Oy4n7M6!&|KN?KTG-xji_t%(ArPNSHnm?Em%6iROL3Dew&b_cchMSzjHYeE8c{^aJ%sG_q*X zBIbQA(@+_!x{}8aS37=i1I*5b2Q1C}=JwnngIE<)(o&MX0$fFuz-ppPF;T`6)ys5t zY0enZVm}?90U75s_4)O4P)*pEgy?`ze^$aHc}q}2Bet>Y!{MR4$t=nVED^Bzo+`O?os&VhnoRN4>Rw3 zpZN)eg8^4DcmY$43lXLQ?6?WzPuJgP$11eNusPfEy>R}h*oxq5OKl4U;Vj4FE_&*l zisZlZgptDQOI1SYutAr=1ib{t$6&aMZPA+1X3Vs)1Q%xi>E&hFGL_bkAMNGN*_TBu zKl&E`vK118KFo!W7>ZI^x#fm+%K0Y9CR=ddI`iwpTFE1L#38xj5U~+cDq} zM$G94+zvbHWeHUf%l@hb&n-?D>K5(D)mrXT>_3JQVneQZjg#%`eZMw`t%wJG(pv+e z?KZj5Pd?ktQx=xb!sC+wr56rquP)S#_8`zwY?OJ!hlhvtEs{yS;iG;KK~pDQ{XS0O zG^KegCCfURRTxFfb7rNZSYhE|IY2F9l&IyuGp}*-u@$ zujM2r^K*5<~exJ6n@!8$Wib`SxaNfiJRNo}UZ?TyAio+F@aY zvedL8I`}H*1-g;;2bCd8hDvWS>Zfxc_Y4Z|-$#`vfm({M5Hoe$ji@DUwjX-ClzsP5 zxkjoSl-x3%(Q9ov*PSfv2iBJII!BuSesRTu2XiCD8*_H9X!0!l^;wE~pWkTNvFJI!bT?v7LBirM-F$&!;-V4p6a;Y42H)7nV;$N zlcVxCjZZ@ZWn#_7H;o2i@TB`Z)$$@4D&WJ~-&=-+-q(q`SJCBpYb^x8*#k zu9iO<8+NMJXKS(m&n>|WjNFk2#^aWKKe1?J47%{HAG&}l%1PA7ska6#^RhXBzh^=~ zAwziZ^a-IRKJL;JsS5LanO})U7O{9C-pLj zjqO-gE9ZrFG)ao1Nd+K!YZR-1?^^*!@gMQNVKwU>HsGaj)+1Ww_+zy9?sD&B2M(+- zDX26Lr?tQQ8jhG2UDlxTP?^Hdu>!E-`@Bc!_N$>*N(TX%4NoS2QX=I3<#tz?JO7!S zcozr{2;*tj4NuN`+Ee89V zGRvbJc0+|MO2a`+r)7vQdI!Zob@--#NtcC85yO(fT2$dyw5y-tvhFx}1MnikE)$x8 z5(UHIuNsV6MaTZ~d$+uQMLPmd5wto)V#v~qKqp)Ie z=l7*AH*P48Lp;&SckhnkPN>p+gY0{HXG)c3I%tFE)G&J1fgg!;Pi~MH!vT*dTcL~) zM)xO}@D)R>TkPf6X|bvp^g4^JShXoIN5QiF;Ct$u|C0Rn6O-H|(N0+2iG{or4$M$c z3_1YXkB62dCI&zF_D123REEl0ln7qxv)6o zgYKXy{#6hGVntkVflZZD^dPXZ*=$#Y;5KEIMQU9hM8T7|vU)J#p=DHuRjkuEN$J$i zJQxElsf&zJU^mr?KoGh~I<6GI-`JHqyvC`#zKl`aH@zV(MA(`+7BID1;Uj>gd(T&7 z2dal;NkNC4AN76PY6E27P!!zHel3QcPNk&FbT02LnuBnFEC61^SZZaiCJw(9_DZ7W zrE-FdG#M2$QK0E-9KmD=A(SB(+~Z-fAv=Xhrp;SU3FpW+D3_jedXCGwob5#|k#C*# zh7>zHfE$S?h>5XTI!j7ONRx`^1xMP-1jvK3{V<#C;9c_f%7J!+P~G(o!44M`5Wp*e z;sF)>w@8kfJJg%y&uxXJWlVF7BKp~z6eQ%5?q8PQh82f)BkjcO$CKpv)e|l<^f6koe zt=b=f;h86Xd|35k`RWs?>Y&GfSQF{|3(+V!L{`ze*^oL@RZCp*k0&o>KUz*61|K8p zl~LDLXXJ&ornos8W&NevbM%CKwyYbgGE5YVt3kK%dmlsS7EQPcDEhur;aoM=LV4l7 z)f2Z{cQ9!;e-`8;4_<(I9BXP6x;>y4(X>|keLc260*i{ZRY{h+j%$qz1RIdeo znfb*zB^~%9Izh@nd3=%j6J-xwgnBS54)6moE5%Du0G`}a|6Dh=oOk*DO;_$Kyxaxw zTm|*C$0en67ZMVLXk?*&owTbx?3xudD-3CbUVPNss();(E=bqv?=q}dj6|W?(&9Kq zQOTMmYH4d`3FKOJckBc zzYY$r*f(za?b%Xh`lvE?)j@e?Ne&i%`|BKKH2&$gA5|x{D@8@~<}PG(erj5CbTHbv zi-n#NpQwj!$HJo!W+wru^g$aK;a39q7yZ;XCC~KN1lygqj=EN^){dvsKQT2M>3N9*T1ui)Q1HV4V z@+FW$1i+qIZE{6l!yO^8=F`f{?$@L5}isN}coo@i|SiAE{r$#1xkk8H&A@8r28O1blos z4b=>GE>w@nP$|>V`NFiS z7vlzGZ{BjEr&B;K1tMw^-{2?}>Cz!ZbdZ;#)e@w3#61%K@zAU(eYtP4>jN@=-F5Gt z>-3rcg4Xu#D>Iz$)kX?9+9$eZB7cLCeF)HmfOE=NM{GmlKK{Z#6 zzc^Plw84=z^@8vP<#SrbeV`ymB6E-8TaP$S+;KzD;$u~4w6!~37NfTW;%EC4VHtbT zRq5^XmeupUsjyd9-sF>}GhFgE9bdtvD*5JEp0F?|zq369Wfn ziTg}2oN6WhgbaZtiXIm7TO`ZPEC9#*8!^UN3GOAI46EB4(Vi<2ncy<#p|NZS0#C~M zXVq@8Vc+nWx!1~Xgt+yw=i(PSuI*cU|HxS=$1xZ-Nthx{gbxMqM8Sz30&}k_p!M|= zhB7MkWPjfw%(^pM5?)4$-~2OW-fscJ9lUiH)wfMqLK2GTmOE7@o+3%Wl!jf5I-ou( z9jh*{otUG6FL57t8u-gJ31nK^2zq2Fj_)%uTIW7 z1kYmoP!9p%tzhIw7HFj>O-)Ul%kNK;xt4m#cy3pco(cL&+kuSnEVej$Cip!h$z~YN ze~Q*OjykR@=5;1?0iC62+Yy5g9Ic#U{L7*nt*S51$>}(6GU9w7c(S$}B_tcNi0Puq z%u-6WkWsWNVX<_G-9!Uv$=*p>`;-)l9bYSSyKdSP@TSnq3KAE)I;Ul|5ZT(SJVmAw z_A84gYGxu@!lJ!SD8J<}{ORk!Kp5@9^9GhxhQ4WF!OQ17#O`mUj1lOH}4sRB`28yRb9L)MQM%E5V1_}9*MG8gR=&nPe}w75;}QDg%7uJ`Tm$%#|wvGKNz70)c z^g4N6r^Z;KVW`axs#dX9+)Y(e?<1C1iTwph%Utn2q%N^*Z;|%U?R08_NQi1W);*vP z<7)XuG@vl)B><~)$m=cFEk#hO2A~#x#;JoGqeEId7I>1R-?>ZE3U7dXh^mLhYQBK- zUOUzM>{&5iSz%-7xn&DYjv|75Kgb)Bf%V0WqM5D-Q$sS1oG*BbMr+w&S3AegD#wl; z^Eb3ShI27)Rqn3u?*xZ0FvqDjJrqX|K1^yw(U4|c;IX`_Fv?Ze);H`+wPhO_b)4A4 z+k_Y7)&0w`%y19#+=VWG%B(wezv=`U56?3j96|-m6p$P1 z?_G*3g>c82YaNKhh29&%3CcGn)4@x5R;>_UfcoVtjjMRqam2op;E^1UkledfL&ZbP z2QI(w>wOvxjgnh9F!=6kVPpN!tby2f(;JM~8H4>y8aB73;UEhtzyaCUl7a{Ka%U2$ zK*J%-V4*=3Kkude1hz>d`5AZ?U6{*!HZ)A>^Bcbqt1yThSH}Z!_Sv(JvBoA(Ud?}e z*Dz1r@qun!!x#%Fj8$IYB8wr+;9KfBMcMwkv-8`-mjS7mfkJ zQV`>ZVBxQ`6750y!xo;&BAC;v1hqzaH+Yc`-^hu=XyCFMgBI+2xvEt4`;V_Xpu+w> zd9XV24H%4|J2?I@fA*IHJ@3@ecnHO!T9>Z|nRVL$`fFkFkUqd~utRSQx8cvS;fhV0 zCFd)zT)DYpQr7q8G{C?TU~mzr@`o$ei-1Gv3Kr3urQ^75RP#!S%FKOUP=pu6k!alK z9f^eCp?#tD^P0-_F_)Ad_<@=7HNOhGbEgZz^03oN336z00$8xOPTXV$XwUNPYw8lM&5TE8OQ>^AW4h?8at$I zEB{3rLPU566tP%Lv-TZ05R8IXd(DA^2NiQzSENd3=c#8LrF-R5T>kFQ-mL>*5=PG_ z5Oq%E5K$FGWFTRX$3_l4J#dfhu1M*}p?W^Bcv{>zJF$nx)EovU=zr~3W_ohAUUC+VIL7_SXZ^vbKesDr@Ohd}aN;fQ=~g zKayClo8c;jyzqKBLm}cb))3psjV}^$lDa(T1Dr^GH12|1$^f)N#DX$U=cQbzK7I=8 zN(WHP;APPD-hlxn`RCK%ka{7zSZx$BT^l=%uM77T4L#XEcRwqR%K(s5*XG#ogTY(V zaTE=DkTVOtP>GXhj%HXJxTgcI^s*&w?qyZY63O2c5zU-uS=Hw{8S|?uZhOQ@Gm!7x zxfAhSWmBSjDUM@Z1O&73Q#G~JcrSIOvHj6Shf!^^Yt$ew?2W_Bdl%=SF|`#XjVRu5 z340tx)`75zhK<2E^-UK)D=!Nmg(0WjR>=SU3>XN3QbO#T1iSl(xA*aIL-C3>mww1` z<89Vkw;Hsb1%l{%N8i)^1qApQq{A8=8Pp)h2`49~*1h;OH%immG0nFVrlvy?e7*+Cy?fkX7#gNt<=mPEgdy4EnoX zK}J6N%eC=5Us)9uJ^AhQz@jNl`FzN3W%JuUKZ2KRn7R@@6HV$PQUJm)&s)EP( z-wpYLXxk8N3NGhOdMZ5yACIx-mk%|lhS4n|0*hKyX#2j(&^XX`ULTH260b|zVCur# zE>Z*(6+&4Z3VQ|`yXQ;|0UWSe&yHORa;qdgc64K`+-1FAQewQ8`h1C!zJG+73mr#zG#a@RT8o2|Hje z!_Kk$YkcURfA4BEroo-e?KtUK%jQV8;Md3i?F6npw zqi;uR21guif`CU1^^X6*Bj~OXR`dU@yG9t}6BLvt9W(YR5%&KC(XzE$@u!tQqmqN4 ztX;d-)?>8|lO4II@~NVbmXO$#;3Giq1eqi~I4$ssBIp!^;-sMno&9K0g@Vx62BeJ7 zS4GnlRK*neBAkCapB1n1fSw8JgU897jD}kvir&2$_*6;4J0Y}~cVyGx^8h~*&9}5H zM$a^hEt+Qt*--Bw5T6t|WiCxU0gfamG#Q?=F~kuzxr=W8B=|#B8XmZ01G(QwMAb(j z7`^b0CF2+kiZZoZvh8*7L3(TA2^{%&kQCW8!+i791*$*ZylSE^Wp@p?V6Pj9&rtZy zN=eQSDJkh6PNdSb6o@{4V4_5G4HQmb^GyO#$>|_E((@x)jdSHOpxBnrpCwWMx1ON8 zI`vU@vxI@|wd2M>g5gL2mfv&fqj+W69|b{8- z1}?r{|9hdThn+WCCb^h#@YMc`IF=t^OnYQIT=ug}Gi|maiOd*@q(++1zMKq~CR8-! z$jpWWBggSfG+sJiM-;lXtKE9&v+bnVpB=SrZI1T3zkVuipR>l0viPU<%5{C&cZ@7=y-*z)=CZ8yT->vY3PT)(D}`90XpvI$ z|2(_^I>*>Kcm_SCKr_jpw?rMyef_V@>uJCVss-_EkUwuqoMo2XaZ^KfboxsEz2SJg zT?uMKQdd-=7uRkRg*c3k)mHSChUHKM^nO=YS5L`}SKoprHpxb+5>}62Di4X(I6s=z z=sgv92ArdA1wBvy2*g9EAjpD%SMe(B(B4sfbdpgcFGiG5XM+RnN7`#Rth0wF>KAFgh=oA>*tD4fuXeF`6{S_Yc3?$| z{*w-|IoZn82h(Ahso*pxvVlxZ*a1_U1|aUBwmh^!n`u;Z7y7Z9st&~VHRIPJxGD%FYoTpE6n6t57kqYi!pAr9EUL` zj*zOK6)wAww?SGZ4KkOucDuZxwR`-~ zm`Gp?n@}ZD453;O{a-4cy)DJu^1G+=^749*I&^%XcU0cnYvSnWSfCCAGJ+-K6`I>A zACaZ)A9#0#ovM4w-HwRTW|p&YUBNW`X3_FuNsn|CD^(C@cD`V*o0jQ#TK*Dy z(>Gq&wZFU0bsuM=+>z%$^|t(zFpgoq9c?HYIR$n?Y4GuGdJ4*?%_kTp>qi;^wKCr} z(iLc`;=u^r{D+%+`Cm|9yj5NNKecQO%wBL3>ciB%XA>qclvT@XMvz-{(6CQNGTv-6 z4J9+b$FQ5(AV?WHDo@|+BbMEBKu8*OMH=A_aHZIVPv2+C`e@Dhdw*YFK5qQNUv+Ha z8Sjb{Cr;S^5vcI1&Wyz<96hvh?C}IrG7itdnmMovPr;(_X#RP@l}7v9$|Pu9^ud%FC0E50@)-D00O$1W(&%p+?^-p z7j$T}kw|w-lcQn$dyX8vz%Y}GVZ-wuYX>(mi=?19(n>$=KKQGGWge1%8k=`l^L64< zga1=Hvf3lT3<{|vGxqb=hZUrq8d0bw8@JszRX-tK%e#<0>h=;ejc zMD?$nfE6b-n~z~Ph&TOSHMFZ2wqPI6GBoQ%P+}9;OS7sZJ`B3C9w0E4f*AWq2TKi- zybFAqqQh_O*d1RCfg)*y?a$6}KFX+&==L1>(-C2xs3e`Ko8u^`;TT8fRP@s>H>w^QR}EM#{=f$7oXXN&NHydY{E%!SScO7kHSr& z`!*U+Y5#U81TZo=86yi%r2c)pK5J3c1PT;mzsk;|yZq=@ zlo>Iqo>iQi(x{xJ{xDVxY(w9)j~kPv<$nko$N{eFvxSpkKX$+WylfXd-diI<9N48kr-YtVkEQE;pwUY&s-Mos=n}Wj>eO-gZlZ5F` zEf`b-k$oWe61CvUNsN}sk^j0U_#Dj;Ft%dQS+D=`8?&W#VIdbIZ<2!RD-+U%X$<%J zawpLoJZK^;Fw|um>_i_Pg#jo>^Bs$Fm4t@ZKJG9$RfD#4N+91kgd^;^53xb{pYLI( zcht*k$=gAgDfQ-*D}SexCt97NLQAO9s>p=H$!aD;_*JvS1z|O$th5bt`S|#{y+NPd z{PmLF!)}G^T!TaEkhGIVKk`C5^vFat03YZA_-+6GhJMTD9To`$7QuQH@s!0;1i0B< z#wsFBX8V`fsOXKpPG_&RqOjtf(R2nyFrs9ng$Iz2l7@yyZxN?a$4WG@jB%2+;0N|; zhrNBsOl7UK4+II&5UvJ>ht-#r1nP#6fHynB9^s95jRFr8U6e+KOt4S}%fde6yb!vT zXqO5Dw1t8)ddm7r@s(ahqdOY4i=f%nkMzeS(A2lDWI!a>84M9Q-a3&kUBnQsQx#U|iM!`h7l7{T|w1w*D z`}^td?T*kGK}b3SATclewgVR+3Q^0&{Z`O?IG|TFD{c{fjXfa&{Th{h`}b%6tuUVK zj84H9VNb)NF&)wIlV^zY%Cg;^MeYE1wf5li|Td$Z*(WW18ahoM6e(Q zC!^9A<0~eWDw{%ZvUHluE70u4m7khKB2&92KS<4*I2`x1spy7`Z70f#2YVO({&ItV zp@Q@EuqW57^ZTSiU!8M=@{8I`Ai1DkXf%Qv6YZ&}K(qUw5oG-@F=Ot!wWg@Xh_!Sv zwyq!HSiC!y@0T<~mIWOM-X9) zw1t>-!cWJl(P{^-_;)FT#!1?X$W;plXejhjeV(gT_0qiu4_>JJd3u83d=2k6`cIXy z-<@ntP+Owl5T%S!**}ia70$JBrPVBMyL&Ode7~kJglC2QF17xTdSz+hYXv0IK-k|| zXdqzdQMrBL;l}gq<=^t+lE#QQK>aP&MbkVN3u0o35#iJ&Ne@I_Vsv1EKq^kSSwz$& ziUSt9FQhOKUE6gHHKTRLi$T!tZSIdwax>-DqZB3)vRv{*WQkp(&NHOUx{}A=j9@Ih z4Ts}mhx`dHGap-Q@hC=5_ed+7lBDHyh5NrKNVR zLg_-71f3&<3MXoH1R{nWMEEV;Kj;=G(g6$4v4(-BH_(wrJM&gJ*Xh%oqHI<8sWwwy z!jzQ|e2r)w0qB1&-Y)Hgdl??vJ~DAYbVT9){cFS^dtDA3&-I$qXmcNF(ETj4??kQk zf?k(w6*@fEE}Q`~`Pz#Uwg~u*dFx%HP%K6kNz89B$Lu0#cUeL0v>)sY+9b*)XPP#f z%;AZ!1g0vBUICfDQ(o%gb!VT$e)ZwIj5Zu%I5c{3iEGqMJ^AhPp(W4VgJU>qQy&_GMggUXM65cr8xsH( zD!0y-wNbDwyEWqp9!jH1(O&`NmG0K;JAm&Ay-iYC?I-6qD5 zFsbU*C{6q!LQt@wQ?nUaVjCeJRay)e(IQah z{@ELIQcl-eyt1s{i;(f(qIpg8S?2S5<|sG@fQl>C&;EgsR(A3%WDrpY@5&%R_Cd}} zdTE+`m!d@$jSt@!BN#M>%xS`LAb`|Ms*EdNJZ2#C>1eGs$)mUkh?|V`OQRRxS>uX? zZpa(PVt5ZS-4fjTOYRG0JTdvmf@(PSq)7(j&xqfINi)QSAf*l>9|vh!d3)}_wfg+{C$7r!9iXlEd+)myXx@=A7q36gD4%6SZ6O~<_Prxd0Zq|CN0?hb z9U3Hwc8GTm=j_4valm*5(M~96f{R5xcEPC8(%n6O0mf}5-C>6PBI&oz9ql`z1t(I!ox4-lUkAlY`USEE=k8-;hrE+)4%f#Ig^Cnu6HEK;U&J(W8e3qc z$d-JcyKLriN6AD!5#5L})-&^kj>q*m)*p@Je z=+F`TURL;r)(n37`+qC#Tp%dFtJ6J_3)w~~bOj$;zXQIVANx+rAi4=gJ!J#YoI^?T zsne&|d+tL=b1_Qk=127P-OJ$5BYhs(DB_Ry`J)7oItRk5q(NJ0>R0ye*29-*oqEs5%8VZ*!T2hKM zv}kFUkZ33pl}ZaqlT<3{cRbJHa((ab?{VM%-sc~mk1IOI`+UEi<2a7z@g&`R_}xdl z4^-a)M#G2E={4LD5c4eSxPMXu*BLd6`qSDjbd!`7%AVk}2gY5P)!K*dn|KtbehB|Z zb(3o>-bpy2G4KUEMJWtS%rzkOHV5eYlP@BE=aCw?FEO0*K856!UrM&H|76E1AJZwo zO3{VyM3wiXvMm#^X`s5RYMJy2nCgj_O&&fh~eC_+?Tp z(4|SaTw-`&Yb<>c3xiaqSMF@wmns5&ZHlWjU6ZEk8hV5ex!ifAEFHAwV4%+7!-q*| z%b&q@6{CL0^&rGSE&8J>F9dY@wZsr=Yrx zAQZGK-_h|JC8^Akq5~pz25!{l<_o1?WWrBwzOxfGVu>DGq^@Ewp$M;&|Jg-Q7UixerQ zpUbDU#BX+vXBc*UPhL&(`Lbf~rCvHwk2G(vlQvdOhE_S2V>hDDQ>1WyK1Q4AzEaf< z^&t>-GOe0N^+gcoI!<{AgU+97>lC(>|J8Azk^4R)i~ zo8$E|)X{T~D4r~Y%-*n{?1t-TF%SLJzA2Q8?FdR`MC^LNV%Fk) zwhowgK0KEnzMx{HQDH)J2t7xkH;eKH3Hp3{T`;UhMO9w8`mTK@Z{Hr_*MlA7=$$OZ zcauS(F3%biquszaV;VP}60A-r$%;WRXhxn<&`(*2sqBg0w`VS9UGcR%_yVX#d$ttT za?i~;{*h0MgRu}OWj>dMpgT6ts3#uWMy|psLl`^SORI#$HnbG_>=^9tqDjc+ODIJW zs&HL1BI&b?#~_Hn=#c1>uEWLtFO0&ZAz%EF3DQRacM1hZW{s+Z&Y3;fSxPm3eYAzX z<_Kd}n#vT{xeV_guOc^}7!Ey`a5zqaHZ->TIWE0xcRN zhQ57zaa7uYEXu7Rcxgj9umW9Z^1p5YXOANMN*Z@y9+e+bewCD=fuW&dq{?voys?ey zCDbbCInNT>M&5)pB0$5GaJ_yx)hh`v+Z~6?9n__ZU@z(Oxc66a`LlTKM9*S67FD7v zVKR`@|6cs#Bq}l>s9Vzp<-uy4yS0mQ0cTt#GXT^J)HeUsEm+6nP>UftaZQxcPq*5P z5uT3-ounG2<fHRa$+5ljkB}D*nKSkwVQC6mejvBW?@0X8rwUp(~Lf@a>Uk6-qlPE^A@98z^mPiJIf ztd|_0ZR@M6b=+V-&RhU5w+E?%*iu?7_1Cz#?kx+`i0VkP@^cf0h=YG94USk;e@ux% z*b64r0XT79(iLDphqpTr!Lup~B?+0{0*0=s1;3RQjzM9j-n#4+w9!mbdI8?p{Z+)$XYJMIHX81Xpq3LD1NMWw6I~E=MgdZQgFhYVhi6gv zB&+~B$%zh#R3!ZZ7L z$}t#$=c!tRDrG(DF39WF{~W3!!6&LV5y|&Zbe?yVQlb}I%eAo=+aH6rZ_cmga+WQ5 zoh-Pcgb!&J&G63u20)8-FtGGhBOikl4>2NfTN!A149$ z9U=}J!{lYkPXRo8`)g>wgt1)r8YcAvx1pFQ6B3zitw(vv!;4kbuH#*AAdY^#56)bm zN*xlw4Ha@-CjVM@Jx+o0n^IU=xvtz5qV;6?3J6@2<%!P`Bbn`{ z9r#B()WF+OY5Y$qBUE||439f6 zZ3JnE8e`1Y+g;!WG27jJBw+C2;P>`aK-Wr`SPIdB%zoBC_wiiMM!gn2$bA4_IF~Ol z3zf4dB#^Bk&T zWgezb0&~;VGK;b+1K%MuWVZ`a{fKY@KLAF8>HIY@D+Y1XJ*hNp&4v1TJwmPckO3x6 zfr|;EMfOJ1zp+09NTmSzcVp1_%>68k)=@Vy!3@obB3aJ)+(eYmo;O;peZhAbY0v>6 zFS4ls?ki+tn}mp^QQ8G;O2X9<-ekABh+J2g0DHd(_9?5Mtvi@M*++ES_;B)4Gch`{ z^$-|%1PL`C$!(MRu$JP;8Vv|?S6~9Gu?@s2Z8=f1k8^Z#Dnwyo@FfGTd6wl~E%*jc z)FGMgGL`Enf!WzLN-L-nm^4JrGTgvpPWTn*8+M)ffjd|c6G6-LId67X-Bnxux;`>6 zir_+@MKoXMF`R_Lfer}tRJ8ez{LU*FR}{SN0-sopJvW!8#IP`SbOVor!|i~08})}5 zq9JV#D%mqXVg6N6Q3+DfjLNz^m^q#5!>BI}xlYB}l0M!u39H^->x|@W&Y1L8A z_y8w44ZumC8@)TT_Ie)-!5{ev{tpG49WDS<^bch&9` zOzjs$vKe(TfJ-jSTS%A>VM`z@W8qDccdx%Wk)fcmlHc@f)3vMEg)T)Sswx4`4Ar?m z;)2ffSZdp}402GM?FPF1{F^q^fLC8eKEe)&G3U<>C8rDjb^c^nX-l_$9qn>5uc(Aj zoWm;!PIXK-;L9;+7-&J*dc>2~`U~eRH=u3~d`|j~ zyvBXyv zPqgNP35SZ@r0MJ77f191Tfs6JvEP@-SlxX6fU-giy|QX-UA3!A<8kSO-XY0qkYMiq>l`PeJW zSq-$AId=ijXp-tpxPR>(m1=lUT1!Zf#<4uE?dg z{*1s5#AdQBQwO$3>*=GXb6~@Q z$7~Vme?YRSqc7u-iU8qYsQ4W#NDLZ>ATO`;ZP$ev+<#e#g|DdwLHT6^KOgaXvr6>^lbQ z$;pVi0?ksXjy$U!UUes zT{Er7AP&Q{k`llx5hxjEBS=GaDa!E(LS75L)4-V|Z>!G28}-;UK^0~|d!hJP3JC?f zLDZ#b?93Y_V*~>&;)k{-tJBy2n?1npG~tLx>hmS$sNo!!3_>Zs@=+<(3?7SERgn%$ zjr_h~#T&LMK94(!ZwCI33S@0ecq@T_u)v}Ds=r!E{H~QU>Gpt(?a0`*(hC9&p7&*> zAn;f;k!i$6vq|LpHv#sHb{J0cCPWP&GSgQJQw8LdNO;G8ZLKczqj1Lr`}~h<_3ziv zr{LXRcyKTe%Beyq*H)s8lpLoK1AQx{<^ci$`d@p*z*}k?(IIAMh?0f=}KocYTgXLEUCm>5S)ZVzOs4g@Ie{7QXlqv()dA(r9eekJ z;IfU#qUq+h7>QphMGv9BaSf*Kk>RbKA7dUWj5_lgE_e}~ zdOo02zKSNc7~pR>(hgZv`COvV$S`LgO-Bc4L$ZeP@)#ROff$PeZ`OTJOe94GmTr5K zK6PgsT1}_cXEKG4G+tc^pSh5PASb~6$ z67w@SKXpRVcQ(I%l8cq$k*W5$6%V>FuR_9@Vh*UU$+^oD*KXt5I7e6d-v5+F3?@P< zE7}t8122SSTnlI=9C1|SpoSRAcZ)ua+`iq-Tll(c2Dq85+|r*Rd~VNX zgN?^y@0BaTPb8;*jyr=aQYiB9CsV0ydO$=keP-o`Q=c#Y}pC zP5eEY`Ooj+I2V%$5F$S@yEHp}a}#paXULrxrBw3XCV!F<@!G0wwFhh{-YeT&p(A{c zmUo^q4i?Z$UlYJhTjz%4w}eHnZx+{=s z$t#-A&;3M9-NJqmqsgmv@QlY8yFOFljtKT;{9 z1E-1rT;bb9k)>&wBT3o$tjK?)Q77{I)O7!kvEWX5mFBLTGmhS0-dLij%EEd>1*mO| zF_NBS_=v?6V?sJhpcY1bFD{E?Dt)qyn4|;?(aTndbbcAV-QAPR#1Q1y3Nqz8e^&7J zeSoBZ>9>$LZ&ARM*QAla>L{4oztfxoVR|STJE6Sg1MBNUK2@Kc=-r-M>qzc~e1TL z=+?TG{)^|)?+A;$7N&Q9oW?A#$YtdMOMwPlY!;^%pS?2bl*~Anx%1}>fL!%P^yT}` zj1%~~(TC%<393(bgPn);y{cjs0epWR4RXdq3#&Q*+NCefU2Q z1s;_tc7}iclm4KfFaaA9{(4v`oa1whL4E)R{_#lKeGCsGW-J5W>I)3#IU!hBDsj2e zmb$q=55o=Db}m~(nres=iB~M@pmj`S^oas=B~+N#(5BMcHUhjv9T#Hf#jlC3uC9ly zKmByCro%wZ9hZvINZ=-B}&d{jA@T?dp`805foV#4}thcd2t^X92`$v63UyW3*4cj)1@e&J6)f|6CIe9eV;prGyR zpI2|XQ0L$A`l9N5Mg&v8ch3GH>(sf|YcMt{vC)bguaw9tm&j?FT`pe#pNpHY$Uex) z?Ek5!3#&bbGk;A;M~q`tZwjskr3=$OnoMr~8H^TU6_p`9%~coBs-24$M0g8~3d?ip zKVkGJvFzb`%qI^U6uNNP{(Mm#b;_|G!x$Y+J5Z|6^D5EGy9Oe}jy-Rq&$f+cVHunB{qqlav*j1j$k}ZEvjQ z3BHqVe?&j!FnDDpod85nfH$;2Ik22^+W43(cef8DQ3dN~6bg_GzGx5n}DghrA^esZgb=V@^+_=sx5Vuo@kfaz(lQ%=@!f)b}UJK$&TE zB;AC@fUimaPntuxK&pXq3mt149+9sn%E_RYQKK(z7>FBR34!^=b@O>B4t(}}0f_rb zMi><{JaYKa-C!aot-BW$hd-IK;BN^_wt)eV#@v}8!xj0aH;^ea$*9ndeEesL+nOaw zz_&3Sif?j?t22G*g;K7=IQ}op73se)fAawuj-*$I-?mA2aGbjgs00|$4$Nn9n+}6` zRDRn@6}0Gk5UYVgdqEWK4Vw}IYm5u_y7;<30MuW{q5xc#y^-m`G7dWYF09B7Hp`EJ=06>(_#GE~G;)Jr_ZBk-GvHyF%?Y$_Rc2^80 zxyl`!#mUM08|!v{39MVpF6>V7i2)n(+=YdP9y#%HTw3x9=M9U?_^2O_)=d*OK8VlQ zgve3+0bMe!!|^#!XeBb6C|m@Rx$86GwE9DuJ+eGbozU)YqWD2yhdSmH4yU4dQlTya zMZ3f}ZStK)W%ji7C<>2>ATBoCmJC{FUd00r^C}>%LaJ3MFWMNq*tf>KYE3I782sbz zTpb24A!e}qiP!^I@UdiXQUp<}09X_s6l!|N=X1;MqrT0vet_-kSfRKIL0zocX6&xy zK&$9;z^p#kwXvAw1w1oTGTd-s(dL$S`+>T&s|sgoQO z1Iu6ec8s4b9zXQ+wW@5OVKh{d*X`ynd;?C9vOXZUZSUNX&G=#~{%q*x+pXvoBXK%< zq5--K6>{)M&cP+&2}kUo~24CD8|O6XJ!2(yPbpa zIrVItFV_Fix*|d&Kq1mDCtTl4knpGiWD8{6pU&{U?(M{uFkX69q`~5x|A6wl!?ErO zU-t5jSc6ql=Lm2vu6}6SqM{KavJ8TN_DNrWqR|u!$6ZK0_y=HAN@mp7KLvj0>b&A| zGmaZhQ4qJ9lo+BfGZ9)UjZWMTzo%JNlmK_|A6J}w`^}xYGlPAEPWLU6 zIgvgvI};N+uYb%3?lWzXv?h6KsotN5uSw(%?YDMW)>Qi1XU@XYCgCf3d|j>c>-S)( z^o%hX`;0~H_W4KIwJkvUL>g67#~S(z%R7`=r|O49a=VbT8%~L{CM+fhR_&9Y7G*9qtRoR1-~n$iVXueO(mBk9WW$c`5v2KZK#4_-98q zr{PM;+mh_9uhBm(gQvs!6cusB(-tny2Jr*ILe(QmQc=c3kWw8^nuA?qk;?i|zE{s( zTN93e%Xj(<3Zrn0EL;Cz`}bavhUF}@hbvw_KEl|epzvHguXKR=VC3CwR=00`}Y+RkjwcRTklVCl{()y4)aG2Wx zfL3Vh&Fze>k({~*9COG{J7<23#+35Ah23z&Mj=@?oofk9Q~&-Y&X<64#84uG ziSpEt-PbP*fO=y#a|)H-Z1+aMOkx}np^3+i%f^Yxsh#as9Q=FN`2P81r>A z1lhAu;l@J$U*bD(^i>$^G2|7i$s_mOxEMi^f#Ilr`JlwUhCb#-K^ZL)N0j9AsQ|9H z>zzJxLH1jrE~I9vb(^i;bMOHJ0dm-A8fEPm?+# zxj!B+JcjBh@|7?2cN21{mt#KUp0THFdHPx+%U7k~vVyoe|BJN|&1^A%;c0^ciCk#@ z4ynxubpY8jj!nrunXnJ{j}(iT>Nxu_+$nr7qjAP5CTTo70rzr~8rbvDh~VVaP^T1wHE1A$HB%a?BwH)n+@$pZ{ns|1_r=tKT@k8CANrMbb9xK8{1 zLqnHvF)M^_8iG+i!yL{77WyI+Xi3n{3Ve(!DfjnAzATzeE*1!>fxq<*;-*8OC21WG z7I@sj7)`m*!(W53ffex|#|3J4q$8Z(DY5dOXKL)9z>6^PZ0ofAi`(@UZmd@!@8XpzR;Y627 zMQ^k?8-2&%pw5nx9KZjYG5y^C`~kntmQaY0G0dfefsq&bVc=3v$q?Yce0Ypp$=Pr+ zDEjA!?61Vg)1L)5@F$sQt=jgy{LEZmU-i^gN~z0yCmU;NhHqQ?>fVw1RY!Lo+1$56 zH|f~Ov!O5UpBzj&18Z`Q^*GBc?n!S6Subw;DOg;*CG%cc?*P&n>cEYx;f;CGO;6e0 zeTvUgciHp%mFfS*cs-*$Bow)$IHl~YVsui4{391ioB3zCn%)2N4)M?Anh@8*t=WQ$ z{0n5Ft5w4L=Kp>cZ#O|z2wgWm&|3bKWc{e}4<(b)z=?8g1p=H#AKupEqvpSP_%?MU z=ULdcMwWs0`!95-+``o~o%`1mvK$)?SxpVzR}*__5q)FI{;4{?w62;JfW#u4)yhTiQ`4SyfJDjMKu^n~Z*h{5w#Gg9eb z3P+f4>+D{EH!GnxG~}^TyqDD}o%QouaR+*w6yW5+q6{TeOg<$?DG(ZM{0kWhzlIrb z9jJlyZ)x5|9(#utvra5=DRbanL8_?l&xH&3#E?%lsIhpf#>bm=qBi0e?c6KlbsY+l zTw8)|FS}wpTOT0n8jRo>x7>6Hc@wW&x!C#hQbcouq#Q~CJUOFgQOtMDGY1>M>y7=T zM}GD^c^?WcufHEv?5xmcz(2y;Uw{bp)!LTnpzDqYCzA?n$)znmtsP%o65Lo{1CFyP z=vA9Zs?ynCFI({;d|xWMlmhP*5cIlc_a`l3d& z#v8^S`Grm`HxGk9xUB78mQ^QM&t-rl@c@8HMjI^aj7W6FIPmvpWYhFl4ZH)AYW`Z? z=fEdw4g5Y{pgl*aj$aSr{?)A-f*5b1??)!rvF7jFy>O0}UL#8sF6!K78NV8hfgDVj zZ9kE%*jHy)sow`@^@8*+jOUYW&4!`&H=o=Sf`Og!7|XvS!aJSZ z%2Lrgpp^)jvuZa_Q}Y5zvkyf*v?q_quJmEFcxxe4hfE+8y?RlpbX%N}+e$pI+@P=I zUonbj-yW1xn2NW6B}5uN-9A(%`uw>lCUlVYFmmB`(%2oA*F!kc@#tFL!okJ?v$#*= zz<{pB8dsEcrzI_Gt8fL4p+rf-?CJ}AN)s@5Rsy+%*zj2V=jMfJc%E>^huP3fMCQO& z*B^s~S#Sl@f2o0a?(xO^R%Vb2yhSl3*X>)2?u(q5X#}ZQJ3`EZOaJ)QsWE0Xsqvs~ zH~GEi#tV4&{gq3-&!b&7a;UQl$jXmIfz~-2Vqm*kN+ohXjV`x*!yo>AcrX3ytEM0G z2>kV2|6hJkNZ>yTw>7bQVrj9_3%b>zcVo+HMETF=8anh#lyz!|R6}V+g zvvgUFXBNbPxp*74ZR!xTOjQn`RayRuZVYc_w48@XQUQ{%W~xZGA)vg}s1Rp?_9XNI z4}eCz+k~cZy?tj!%RWFKdb|CA`4qs{*9`GA>jexVfxcOQt$BzpAar;VcDMcK(-k%# z9X_nR<6#*1cZ&2dN^tHq(+cZ4FL-ZqZ6t#>7E~jq9~7cp zz%_5Q3_9)!rYk;DhwVu+h>pw}oEQ@*kDYcW__4%;*lhY$pLpn>sp;hwxWW&hV8*$t zsY(N?<@tB5qcD-p1R9SEAEDE-0+3ZLgaL%aiIOPSN+GE{K;-YHulXGQKmzi5Kplx3 zsm67E3Bh~kJz0PX{wTVGFlF%Ky66+2bcX<09js?z%)AhnSVuhi*hAquI+RH)p+AoY zDGqM4!IDN@IyMa7q#f8y{2HW=F=)}mZz^jAR1k7DG@I_(Mu>M&zmjDaMc zxqrJ?co=!ZEpG$*t&25eDu=|NlK*Uu$o|?Q=*%GVN(Hcy(ZJs|@V)9~i!_2oT`z8I z2w8jd-U|42>0$zzyax*Fxfl%YDgU{TrjZ&0p3+tydp5E~s;|9^>Dy&!m$8okb|Xz`yxJW^sgfx+{4L}u^=eusBEBC;7KzO2Wo zV1O{`*`uxP%_SD5Za7>x@k_B56AgH3n>UCDE1Ln3{48lt&H5jgO-5PQ}rN zGUlV6#~vKyy5XXYl0Z8Vwr&C;s)B{!=T2FC*VPX};LH8hBr$k7jDz5B=bd%x3-HP; zM^32-hJgMu)7tdJtW!QB1~N!ydb8u#V<>N_tr;_`{l19Ezke5H^kexIe}Ak0Z$H@C z`F{<0_&B)Vt!Z>F7C_Adw0<;po^mlH3SyPb0mus8n<&3A6nx{(jY<%_l-PG%wb?U~N1D-jc^gDAe2xmS=b9I0M8b&Q#PyvXN#Vy=qp8<_I z1S8a`8--yY%QC!+uRW`VEPK$*Cc_ay2k4ELzg9zq*9@5> z=<}>cuwS{7@H6I!1Oj2-P}jp6SkhVGp0hovjSF}NEdD#(#bF1V1qFF=k^hZDa@Y3Z zZWnyz@N|yW1WOI9ZkD>Qo%k4rJ`2+I(=N>ueT?`p=lepoYtqnOX5DK*Uvy!|v|zF+ zYd^O#Bms5rJ_H~Anf^r+t_89&yy5ft^unB!7rG9KYU3Es4gZa;ywQlsr2G(qg!g0( zSx-Bx*GY_BZkQ`^csM9D z=lY(zyO+j7&$cUA>J?G-%3aeLGhc8@`K><6sTam0hZ&_Ak2KU2#P9vS#klr6K7zGc zyNS{19^{{DWW9I{f%Rb?uzvgl6`xyZRxom>BuWJP@#1w{$O7sv(ah@&4f8^f75sAcz_xI^=*)K z>D#`L?8DG%*3g@k(YIm{eNcnd=~d?;6!X2#Vum~;Ug&1HUwpYC(luxplsM^C63Isa-*t{az@dv6>$NxlHz>)Ev+l{of=$Vki$`+UhiP`|3JZS!Ykm7a@}QBDg2=WM&M z5H2oR9hWzBT3uO!l@P_b_rIN_x68=qI~v!&?1|bMAL&(?9b1r&y2jiezNYgw?}Gl+ z;pS8dCYi&Jr;1D~`K;dHSC30G_qRC(5bDgdWgF{KaDTpVkWyh|%S&=dNGD ze0GLMMR)^gud2{>u0}u-wyi*irtd!lj;;pKSRJowuVhU9ZXby`lOryw{hBs#IMg*Y z=q0ud>2q## z>N~crs*Akl=7y?cN-bArJDi#pG_U8}-3#x>%#?TN`jWFOK#1?9!7FTfatSW_#V@&T z?9E=>byE3@t>$)qRtXI1JYVe6F6vMZL7N5Y@KhwSA?)R`z*p88R-)ksz|el2I?l%f z+pfSPF=*N+Kw-GpWogPG-sN=pFoV%eml zqlf7^J`I7XIl{M5>V6Sce_*zUOWrQBMSYFGaO0}8W~d={rZ6=W9!IVbBEugG{9l;8 z4~QsB8vk|0>RZ@Dw#-ao5B@+m{i>dR)eKDMB1+C%#0-b2AR4jF*6b}HX6!C{$-QJ9pjJXS@`u)8bO{VuA_kbH&Q%hb zL=6}uFh#3;G|``#W5;1CW>s*z2eM<;hh@gcxdH3HApUI*jKoDg1c_h*Sx~zTb*yUx z+c-x^`z7Gv=1%wa#AkZZGMnnb($yWZ!#&}y*Zl^^LT{g?an!qX>pRqNuxif{){~l( zHX7LF2G_z0N*8?0r6scO_d7PS>cT~MQ*VVvqKpZ0+*%CLo$U{maeW7tjw_0X=@r88 zxVY>nN*49~=I2q^1BG>#$R1rt(}nP zm*C!bn#&w=x_cY>flqfLrj|tHfk{u?`0Pk)nCp7sn^%QO%NWN$eQK#Q zE=D{~f#sr^iBQ(kxf(J-Xr>5oRTIx~w&#ZerzPQ-qQx9^x_)I{}-(w6FDxVwjh7MleEntk!=G;O5t+L8I`4+p=* z>hIoz9=-!eOe$8#zX7b#*4c$UZ&R1OJh~BH`R!`FRfv50pRVrC-1W>_U489?qxUij zAOSQ2p!)z**xPsM2=V5c8;nI+y00Bh#K?!|uIqJmhPEhRa|gnW}zFS^fV2Q29%Ot{#i{&C1{34&jLBnmnB8ceT;ATAI3)r`*73DT;_t*j?Cr{?` zoqaFInbL3TN`B1NzS)1`>$X@(ad3;ga8-HU-)`tszX;;S$0!H zYP7)iM|Y#Jx8k__>)t!wK^;)K-??=K;g;10=shub>MnR@Y}wtQ`Sl>HzOx1WNTDs_ zCDbonNh9Rs^4Z>aH`Q=lmLZTL=(AU8!+=gg&#T)vM#1$v?hom2Ju5C!Zi(e;}V%ut*{;}KjZ!pP5jd|lJZZ^^hNLO2 zJzKUTI(mp>Ya}xS7hGA=#izQ;N-^TG-IR#g4Q&3-P3KnX?X7`WOc%s)-Foe}aU<*W z>@kxL5+}4c7`%qZXSr@oPPGQjREd;l$u|``B z$!ZC4sXaA|^#KWwF-`NilX&?55kiq%d4!*t{dEesPOJlun)T6NR|pfs_Baabyd&EW zpS!K@TeS886wO>~{Ll*wVfB_?wG2xXUcOY>XALE0mI%#zx||Cm#F8m5w^+If9F4Wj z1ihXyBKfEZ)sy|^J*XaM2u1CrTzghHP=hI?Mn}Gf?FC0P=Z%T$IrGBQ=erHo>+T)5 z`i5vOj@uBvY72m?odbB6Uploirn7tA)eH4Z){KmyaLL`E%vqv|S{#z23)nXP%2>(; z)eno8C27mPZ`U+Ag)33&I^EaqOKOc}GQfaLF)_TpR5FG8TlP0&ozTmC;?p9Fzau(z zeGqb?95l2yzNKEER)z#mESRvjj2^$u%>@Fxl~#<9gX$e$DLhGel!Wg;G^GTp>S+|` z4Y6)$c&h8}^Q=hgm>?sQb3KzKJZsrXsZSXLj85I*$x(P*r$yL0#%DGlt@DiIED7(E2kbvS4MfWe zXaP8u-!+9&@}7hZbS$2hT0sYK7jgC6o%d_QF?ZECdXn>JbXmG|2b%x%tut z@bVQKqH}U{4EOi`q|vak-^zhzlVQX_&y%%`zyjHaV`ptIhh+A&J7!9ESI;hmPlx^x zdE<0i-$=NvoQYpi7#S;A!nOG&*bm;tjj;mI0;eY1?=Gj`9Vxvw?ilMf^wwp5^)D}4 zz7XQ_TVI1=yk6KsvfrqP*;5SY;#1P@LTE890PPEF_;mu@3CsCju4La=eKP2$`KKDBPeLcikOCIb48)z)8zEN8{58&rlV90jo3P}D;BmvDLmi$joS zv@g2=3`RB=3)IAW#uK1>JnNSh_gC;a)tHXebMZn1kJyP&AF zAGbG?oaNBW7%y0Bo`A|BX*n)ox#({^(nFIiSYUtLEsOW_gIV!@f&;~NJ~;B~F$70u zz)fueEur=~l2&VTp;*EWkp=ybx4vWJY0Ci=nCT@Ch%rQtu7cny4x+lfKn%F7BA%Tp zLMhC1f!Y_`lE%bw^g9CzdjLtTcH`sI)s$sh7ygFPG%GJUeIAs6?A&7c#xc?-ek7pv zf92OhIn}3k6M86~fj@*z_%c=%;|reqGtF13yWkXXUrO3V*;0QCrE>kB0FXL_ts7bO zoq#VNMq#b=VLmPx&+7A@mc){wiiltF%C~yu{86n0v-HFF9Pqo6=N7YyaZL2Q$2-=w zFyyz4BF;ST!&cmxQGKFZWKk$vu(VX~!)r&l>;6++;bH+{qZjdTTzY3lIS2E-zP~c| zUYURKou4gp2J=;S^4|~}^nSstLh)B(YsbFrDYMzRU7vp%yX_dCI(Jv#%O)~@ev(h-|eFxJlqqBvj(EHICzfOqs&58Skn$@ zItgWj&(5Y7s7`uHw=(ASA7IkzJ-HH*Ss_uL=nxMRYLcQns&x6{ z{_&A`($*a>Y|>Oa9^e@~mW*CN@5f*vnkQ~25*6J@GUn;BYJ%X61EL`j>|aqGVLv?0 zO6U6?K}l&2z^o5-r8cpg7Be<|_pSpl)4853%Cp*&iiP4eklLro*!XqGo870&zs2~n zHB;EdqZ7^f&&D4OUMEk^%IM5&YX?r8M)3e$`6K3>LYUmann^{{5klnNJY|B)ImMCa z_Ik8o7Qov9(b3!feqoe?Q#gQRngjwe^Q8A~M3&=T3--~MCszyRgAFggy4lJ*A zv#n{b;P2C&*Pd1NHLzKnYjtQwO1D1$iBIJ%xk|A^yMB~B{UEFIvn5U8`rm?!UsFWY_wE~3dH($2tXJK-J`2NIwLePjdMKsxdS$Mv_`QmsG7*=q9`6`WlMzEM z)JLYPp47mAMG7dztV*{2`Sc7D!;9HkEL*iC7R4@%YUO%nIw-$jd9(D*%Qf=?Rm2Cc z##PMIfnnCST*~6!TLd)k&P+{SWSw@jQaBdw&we5X?{WP&nt!3kt3@AWgkm}sT-(Ni z;S^E&m8@7EHIQf)xzYD6mL7g&Nq+BoPF}iEdH9O@R*1jh*U}6kt(2H?cM4`l z7|$Jj>+l??|X2D7tWzh3fJPyP3~M+o%g5V*tHY(#p6YHJ2>d3UswV!THxwjioUWM-h%C( z;rM$-etsa;UySes!F%bC*$=|`)g^2 z>02*dMKQL~D8zHR&bckVvkSme2Lv@IYq*588z!y;>Tnoie&Q7M~4hiX|2U7yQA;3Gl=2c zTz2~z|7%~x;YFLAD_9TmH&J>sxeA}5;;-|IW6aw-C zU3^buEaClEQ;T3G#W ziXWbywd#_qyXE`r^$G6Zb6Qnf7ONl@jxTOYRCrhBkHN!A%(yn0oH45>eG2uu8!+?q z($>uWZolJd7d{LGu?t-}<(^8RnRINh9@>XNmUci6NdPOVkZ629)XE3o@;}XVlim*| zzRUm3F@B!;+35fy+QF)%0pBHf{ZQG)o@pmFxbMb~=Zyf6_zqlltZuw+tc(42kHd;<5BW;un}dHq}V6-XHr_>sP+^@_=Z zRr(3#zs_J={B>$!BujyGTflJa_$3KDg;m0Asd5t|D$eyV0lTSD$Q3 zo9{vJVP!*@^clj!(MN<%{kcjlO#WI<%Mp$mnZDOzsLd}pj-U^wBO;J%sMsx2{~s*GJeXy3ut28$yLMg6R$lcRM4$VVt0(8o_?iMh^Nb( z?i$SJ5?CHqy2~S6#st)ynuE%m^M^R2cj=svXzU8RAZ$|~#pYe!HG+Hj7_<;J4|^Ub zvhi>pTK-&N-XvV+bgpuNf&r>qW*%1Ti7ioqc5!dMv8Ic@*A!x>A>0?V8q!eR^l>7G zul*0~H)OAsG0bDEn=IhqM@E;tt!51vg8DGhn7n|_Y0Nh zUf|pq!LvQ5Q|Iu_ZQFh1Pozp5iE1||s2$}@MvcVTsBs(}OIQbEDmEW1-IYBY{m36_ z>y-YLD>8#;%a+bO{qT*Py4u@StSSV^!K07!`QqF4$poYoTwc^xI_b?^mFF2#yeq!f zrK2KG=(oPOOfp(T>7+)HRByq8W9fbVpR+U{Hsj<>H~QYJ&DwBa?6#gkxAhY#gX6c% zmo$9F(Xx)o)Squx0AsOeuPKd)%ZlG!J_AlZ;PxPh9!YbE>eFm+#>0 z8xWTTkB{=r)eelB9IW`>wO#F5Hj7wABWLS?W^VXquZdIimgheOliv=~BUwo#M?&SK z#{1mYJnrG;uJaoz4VCp2&o8yhmEAP>9&T>IuDx%xW_cY=k$texxc*v8a>T7*0QEXR zbm|NvS3>>r7G{kOn`(2jn$8Q>cHK;xwJppwYMON;i{`AfGJnO7(Y*h7MrP;nzjCN0 z9P=LRt2VxB;oILG=GpkT8Y(9c%)a&VJZ$c8({*)3 z74=ujUT^(s+2g-nqrulK<7lJ*T(7{VwYR;dl-A%Jx9Nm@z0G@ zHmy^$3Y)l3GgDP3c1!Ef-b-$MD`(!MdlGVV7rk5i({GvG*9*Vgd@3Y}v2&ZnOyh4A zqK;4#JWUV9v0n|9Sm&`LCK5NF#hwT)vO?+Nb7+nnW9+y)=i#`|FBk~JjQv^L9q|(< z_cP{}sp?qixfnF{OQ z!IztTaJpanjX&TaYvJyGX6l)#0gn{Y_}iuri4Vx=T30)KIfZ?Qw%IJ)bMo)jZ#I_O zQvTD@>Eoa;tKteb)P!9uQJR;_e6HJabg23z?cw&e8p0grH{q%Ua^Hvdj^BA3eUR|f zgX#5a{Th2boj7bQS$J9f{4^Q$2$WCUqHHcMdtfToKwD+Cl2mn!v60fC2Ac|cp_DNW zHbVlRs`&ALIkw+<9NDaC+rm8YxUGb%3s2Y+ysw9!{*#MA8*%c>4lhSq*E5XSt z)v>n6nEgh?V4-hk7~t|+}GwOhTTYT zEjn@YoZWUG{j|29*w5SU5Q`4p{)J>EcBDP2%~jhjFtT2rB3*sDLhX6&$sA=hoJK8&-nZ%HeeGl zgL|*{h0gE`0sp-7;xi|&AB}xCRahn~DA&}7Hukl^1GODmlxek*?b7f1jczKwFZ0(5 zTfKBh<&jj8P=w7aViOwrAYaLi%r!4y&yAgi>eP16iu|w7zHu{#@2LZ=2a` z(I3#IXV?cMJu#c>MBIz~lm|WUA;`|NdEC$Mk#2~f&y8Bf6oG2f!C;X?xO;Yff>6N5 zeR%vCxAz@iE_y>OKEXBcRCxzACl_lRI^G$h&a__PP@yKIy78<0t%~XDm&=0%ZHu5J zWfA`-F)x0En^%Rq$fF0$pDy?^FUv}y`hONXKiAhv*m1X5IIDy;eL$%nqFf|2{Og{@U@g zp{S}eFDPEW2P9mrh3{(8V01|{;FE%5%e=?&_TknM7~G`p9}QJZ)R*Q?byJ^X8dCn^5DMc(5=yBc zUF|`_*We?X!C*-t%)u)?6R?P0RL;bqoNmqHAh0Q*88-&li=g+PANUB7OL0G%90ZRb&4qfqT!xlY^ojfCb0&Tz>#b57kS)A@>B_SEqh%h3 zORXE%q!e)v3o&LlmxI@mtI-&NdeH-Qfuhe-P6_Z+pAxD8-WqiM`cxcn1%*NO@2kg) ztGD>~)w8RyNUJ7>xRoJ0e7|PT!7>dJCY>ID1!MYVbkMTO7=(E<@lDUu(-RXQMc?c& z495Kdvf|2Wmo2p#sUS-0?Ot*uTovP7c*lb~!dHuN+ue$WoH}j8bv`D#Vm+j0lA=xx z=UHt`Ds%30;r%ouUVbjnQ<^G?{+yV$O!mT|>hfcLp3~n9`~p2Tk=@Fe z0!x%{;xSY^B=fm7biA~y&K?KaHjd@&I;6r^qSH_LQf(z(Fi`$5E;@(hxi-=6itag( zt)@aVvyKNMLbi2ndy)?hk7pCu^BrUV?_B%qTEfo0af&owpxB=aRx+Sde|(5Fxg)Gw zlL>PP(pR>vGWkF3y=7d~Y4kTdfP(^J0TPO!qA;K!NQtC@bayKb(jnbtfFhy{B1kiI zibx}1fQ$+wAYF<`mz0wCx!B$Ne}A6m)&1sq_3&o*<1V`s*Y8~CeCvohkTUF{%S!26 z0UDK-Ab06EvN@QP3B<@5KqwCqmci@#PI-=TKGmM@C6=7sa2^1H@;NtCisK6FL&-0( z4iFyL%}xizkkXUomh2ykJs{(Kfufy}<0JN^!l@SocWc0q9FrlZXgi`P%c1~~gNiA! ze&@?2b#>NkM_B>iNAtI-utk=vNYR<}mDH7t7R9HN{a5OOX(u~ZbH&7|I4u>>0X+`f zz*tCD4JQFU$-Aa%O{tXkqkkaO;ApynD!K(`f{~8h{~GUlK5#uT#zW<{bmP?oIFx>Z z|Bpv{>xnn5qEhG`TB9mxQcM+t05_>@u%b8MwG5rzKhqEP_Nu9Dvg~^R0R~cjL7wst zsUv#9TJef`!~5}1$*!FEpCGY^;y6X}MxHT zAhx;bZH{Rha}tR{Tr_R3mGA=791c&f;wcaJ$ZgcX-0rjG7_Zp| z;g@?{7U+r@44$D6?7ye@zZ$rOmneuA2DIyceh%ROkA=nPkN>ajLH}335=IN++B8$R z|KVW1WLbq9n}qlT&wiI{@0&QlrT_ts){v3*5-2SM_~%30S_X>1l+gh2XrZef$(x#g zOCP9h%7Ieag#14sso(DlPvN=)=r{E00{lG_Cg;#~0a4l!3zGU~-Wi(!Wx;Pnxp1vi zyMhQ!6if)7XFy;7l}*fXI%yH~{?}u>Ji+oMHLzfqqjd_om+XNc_JElClu=l*ZU^|p zW8>YNOX35d$VVm{L}?-dVj8qh#UP*`0=;%q@e%NgU7Za$n>D&zfrPYhZ!>uO1pu`f zfGTUG!a~vA&>PwVKkotmhm=vEMMt1}h9BA9e~B5+ckA#5?7?Y(WTIgF^d<<^*8ni!WuIg97bGv!H+7GpBLQm~4Fn+xTB2WO?dh02 zz_xneMC*E35a^`TAgJp)DyFK*(gXgj6D%EbB_S`EoLAxYY}|ADn^R4s3|8LmFTknQ zBYI6EU{VixmJImuMNwEaHceUP2q2Va{(ew(0z1l9I&Jw_>X(@lbfCDB9T6LbCVHZ@ zu4@O34%(XyK+H3cUHC#tw!As<)nW={1mCwH-^?}we-jq4A*Eyws{7br|4aP+Ckf zdGWfM8p+pMGh{>HD;dNaQaoL&c7=HhS$COrZUb~!)yJrlYpF}2CBYA5Fw4i;tT|Qe z-&z1g`9qU|wCqPK(TDt36Km(+((zY(fIIT+I%pIPK%CGWtjW71?hgeX!LptdJ`x_1 zpjvYOWYzri1M=IU-DMY_p^40M8!8^cNNCQ;z;+-cAO}hwzr=vm!uiIBV3q>tj$I`_YS;`OJEkb|NJqMy5B(e8(2Z2Ibp?gA zeDeFXKj9EoVZs2t%}~c85NPpHh(n8$5+N$%E0&SL|9=Nw+|9(!dj{!k_^%Sq` zFU@n`0BuJ~*Wh|w^Ru%r=oocQr;As57r94bN3Asm!C6b79z(%Z1tgBJ@ zzy$4-;x~kJgUCPyn}ei4Lj;19X(2;zxsbtyq9#7sPjm@SU0W_RRe8?Mw$LD!XXK*6 z&{xtOL@HqvUX*06AOkFrR|$k1v}0RPXbSa*{8V>@RomK$s>j;cc3haEoTo(LCGS!0_NjJMC0AU!Ok5wWe{y+1+?v(-s|5k(2HopSvd~-zf{fU8 zn5EMWwAbA+@j4gk&{Vsr8x$c;_nl`wMrh+M9q*MT_AK74EX*maZ8qk-3?OJL2n5}j}ABd^)Me0%DeE7rWff2bK*LmWrOBL({GKSR8r?r8= zZ$&Q&i$*1NGGU_JKtvF9WetcmlPu%$3=j<$b_7|;ctAmL=jYDY5x0UjG%VEJrWwW( zdq~Q4Jism8!TJ|M1u7yx1B+E1>H3A*0n$W~YvzaV>PiEYS4*34((ZnjCicR;SLI(K z{uE|sKE+)aStFvo1T0DVz_N-n4v!5!AquydTcGZtnc&^WoDF#(2yLQ83~5%Gi6tPJ zVR;NuS&Jq#DKH~d{nnMe6se7Uv;n;^Ty;?fuO_{kCP3g~TC5B5TGJfi7!_?B;l%rs zHIjr#O{|so6o%`YXO?HpD0ok&Hzj7@+m%nS=nX$qDgYMg3-(nuA--3WRFgR^`<=R^ z#{DNtr1VSntnaXIy%U_{0Q~9Ffte?*%@>$6_paql9B7O10 z0Z!SH1@HpMaoN=nuW14u{tEf>{>ptnK=0m$7lZEQ>|cvC|r!zAnB!^VXE6)F;3X+dC>m^dln*u#-ex&vdXTQM79GT^cggrM-9wo z4&jsH(Pb*@4H%rIq+U@q$nlVek1v#(>w{-jiIO5wmT%4jPxU+UI(_*H?QcklDmJxLS4Q22=%i!r8OJ(J`^L4@MXE(I(i!l4RuN!)ka(a>zKsy%PUP z`dhh^d8}34mGDEs>$Oi$PBN-gZX)g@~UGsZo$Q2Oy{T6!G#It{~8a@WGvYA03LluK?s!|UUWpO-))+4xu70gz9P z6kX74_b>9W;6bs+e=@(^zG8$hjJqOndV~*rK*1}<_6@pb#;q6m;mJ6hh>t8y7I0SG(&4NUF-7@t5|f0N zvT{1Go_H$0A!nb`mp#k7)f7SXYtrNm;v~~hd8%gtw8XYkZW=ryIE;#rR^x51-BWv&rQF;m@ojj4Yu^e1ynrbAKe;1x-NJzPbu95AC|i zTH&a#=vu;rxNcAqH?4{GJvH?!JMTD<9yoYmJTZ@Ex-be%xweiF)!$rq3NyuEESJ411(6`K~M0e z83B?t4W#GZGsyC>YGQWnl@fMx8?+_6pMebR;gOq_5JXo1<{^(Be5@hRzNT!bImS(Y zR*IRcfTp|OF7HVP2$ld1;<^dBB_&W}Dh~Cy2c}f7GEBB#c!;^r?MD%ta@4GY-}E-Kfr{%OMs^|R87LpdtkTzTuFuTYp*L{vs+}o{z-&FGfIwU4^ZBm}UaRYh4g;M` z-zx*EqkF#m1(ra6%U7#lDc|&PC z&%Lc`vJ!h~E9%tpNpGOu*xw2buK zv)4lJGoB$jm_W87a|h9)7%kvKURh95xi>htEPyYzvJMC{)Ax8Jsu(O9Yz5T>6~*;l z0SM`Gi8}-C`J~?Lwoi5FrpwFe^By5$!~%zVU^Zg*s*9h<9sBJSM`jG; zrVa7|R?kr{1mKW_!{@hMmqxjGbA+%B`OQMCe|j2_U9Zop&{H`M@+g z2avu=RIyBTM$5#~daf%?V&2`~W?g|v6z@mHYazn^9rZ4rbK77Mc5ce>T!?H3_OB(8 z&I6j6aY2c(u3dDA5AX9i%+wY#zo$gk&FSmbAs-OdJyNYbbA&zGcAFi7RLB6GU zX>DrR%dRrIjFnkH^{5(*!j3(O)K;C;^0>KdLjVcavJJW~4<=u0N>rMiFlaMh_P&xf zvIN#&cZX?k<(_J3!VQ6kdg%^2J}1yse$Rsq6U74(5D8PrT5X4&%rmU9Xka$hi-lwG zX#mar?K|Jl47kzv7zM!Gp5;_MBkDN>yK6#bLCX|h?GFgHTsbUB=pT@VGm+8<`7?f1 zfP>Dyf#NRiybc1uF-L&G#wOqov5rF}cx8)Pl$TTC54ZiSXl>-P1(`q)i1qt}sF23T zVa^?5TV~o_mZ5=50`ZQUg|CQx_xuuwuhr^6mXihlv!hd%wN2LRAAZ5-C2k6G{B|() zT-XHo7wZTyF`E;BNhI)_zuB1hyEwL6$_j3!EC>Vgtb%Hm1&fr9WvyxfBv3AmU3_K>Q1RQFb`cH8iRp#c9uib z-XHbJN9X>{{-0qUH==Wp*QWwRb$5ipT2OT9TG)L z#rCF$@jr=d-^rCITv-4C_YMC@$Sz5EGnt#$|9f&yJQ{}hO3RnfrOWwK0?g~)>IxS} z;vghmMKtBqT`NlU5W!$_f_GY+gV0f8(%4~-1t%6@UOwy2RH1Xozl)#vYHu9#h0fG$ zlpL`2_m&vns)}%58M3w7MVF}G&5Z5JSHM^PmgxeYcE9J0Y-ld+*6SJQ1pd{Cl0@f41r!L2GJgR}O? zwZhx2Oaam}U=Bio6@Z&czxxq!y5o|?SuKSF6?|2+}u-Jwh+G^rN@&O-r_=dtywoi zld*U-=8RsZ>SiY{odl=thfwZ#0=G$z$z~z!UK+&b%$MAuY<5qW+|V40;MLB!O(tpN zE{Z0uhF~uyfPLkvEA;?A4O;)W+$c{8U=@s>rf)8I8QV^vKcIUyGn+99NOUP zt!g8XBJ(Y^;_^D~>B^h2*MVU6(E^)odzjGo6IFLEs>O(S!z>66jBa=#x4WBiXYxf~ z%?`b`i=7J8%ycMXw9N)l7TLdbvbq#3M2;8m6)SHp#@sbDOxnGd#RBw|j~2WlwIQnR zt+;%@U}S?hbx=eo2zV9bXr+I91uN02unFF{5`hP!&IG9$v?v{$MZaBcr46%TCGXNP zt^pu|JXhfhCV9!eI!wNis}sxVJ>gItV9JE!W6 zkgO}CbPyS2%PhMD>aXe+dF)eMvEo!NhTr`GZ+EkD7f0778P>z;^Zo$w!$ZL^qQyyp z6x>mVlMd%=o6c);*NGiCa3scVZ$^W`*Psv?XA)wo%4O=1U{BWaMph;ciNc2Q4qa&yJ7`KFZn^QWqj_xl}g6C|;kPhdmNbN*z7z4W!_fP(5 z_fBbv79kh8UXH=<%UHCBZ5XTk0{DZ zDJ9oGch2awscxj1oWYYwP4;)T4;%GPvww9>$UU1cw30UWPN9Q29rAuIYB1z24X}oj zQhhSGmlJcFFUOm#0J&A&w_K-$g-ckh(ekylk${swyq75$XE+)Kp(Bj~Mh}a@DFk0j zg3N)YJ>4kstN_xXu**CT30{fKW@F|i`Cu>yFV$$t@eTM=3K<03Ty4sV%oagc>n5jO z6(+Go_?8Vvkbm(Bz@sAbUCF0#N1b}!w@KJ}ycBz9Kwpl4TV((A1xjRKT{{!xR}b=l zAFrfP#~uUiDo^94*}S@a@Ff@j$7se~e*xINK`foru^LR(d90cDd81l<4EC~|?xbX= zATfmxhT_{GFSwofPC}YdBP)0>y#?AckInH$VOUVhVLw)954zy^jRMcM17TO3*s!&l zJz^4i^gHz!AW`)DCFg^xd8km3&pZC~A&5imYFv|CoACn+SN|z~f}pmXK51(Y*9R9~ z(#LdVF17^yXUP>B9$Jao09*B_=3<^gJHYA?qbXjPt6wzB?~*$GKHe=Yg-LZqc8Nwx z?OUXbw(;N`q+}_Qr)(*o%D5WYcMQFPMQ~l)2iVT-0dV5IxctUHPuJ4pou`Xem2zU1GnbdW;bQf53PVP0QcouSrsexbyV#7 z?A@9-^Fwn5_hoj4v?oW!5_%QNo>D@sa!(ecLdIURc-E0*#X>DMTD5i|5Odw-D@r|M zjnYl3vxOVZw@NZtn(F+hH+C}Rh9tx&z43CjhU288ti~EjT3Y9VL*m?zjcq4Vx3#(C z%KE78@dA5dx1a(;(@npefpnUa-<7mZND%Lj=uBl=*y8mdBumQT=t)PGra$}P>e^9g zb7FI*gR;*g0qchwWD?J~%|_ZHKD7xz^)MmX96tjy++JC95epi70upkNBQGc$9+T>a zl((S;`c8S1@i|fDia_nsQ$E#`lS_2WlF>Rkk?L|1w3FbMwR}IyxQ7^o!#)RIb}php zd+yD8A{7R%ked@BOyJ(s)C-EdJ@M6dl{DjxCMD{FjC;yK z!<%yG&RX6f7TU1iCAxQCoXs22{^awxeGP>hI@V1+cL2#0=vJUFt@fPr2z34V@jJLF zh^LUdpH+m3)9)R<7}xsLgr7X#N%sVc!o#MVmcQ;vMOwGE&Jbm7*`pUSXvAohG*92a zQ(sw9`qY$jS^&B5m#-dBp~ol)Kp{BND7f_w)%PEDPkb@5CF`J`G`*I@^C|ioD^1OB z@6=%J-|*Pq|6;<(ul;1z1lmq~x6tnINqK?WTl~+J<+QfyS!0@gLBktL>P8Ummr30V z^?(=NNxYtt!iK+~)tTPiDIz*pmA90lfBReli2IvjK4|a$2?fNjv0f;qyBQ4QN4OrG zJ^xI<o&Js(h9nfy(J<|=Wa)@zE%(Kj(XPzr?_js!?$4aTm-$}l{qOd>ETkFKG7 zp0c`Clis3RiSEx}QD`Q`K$eQ`R-|cdIV@G*Rr`$kB=E&{@G*uPl#uBfCEDKrU?sxy z&rm*WDIQp`<8P}IxP{(*mPLY)DTT~$wSLDRaoKNj{lt?CE!LxS19 z{=$7S_`hF=U#!+8Q(Dwu(#T`5=E|FsE{gfga<5%c4=@gvlKMy=gLx{!3gGonN>g@C zB9C1NkMktt(-Z?qXAGiU(;Ugiq*A1=pl3uf>B7af5&ZGrMRxWbFSE~R!BG!N1WmR` zqY&+&_jv04X^`hRChE`%9x8ZDVzR9TGu(K{hZ|5IS1W?mVwSb_&dFfoa+ZeRb8v*Z zDly`#=`D|6*7}tRQg8#mBj%)8;wD-!zG^1rBaJPekjn8(HG7wCAyj)!#0`w`e9VL~ zd}f{hn4hG``5i7xgyxGxG zi=mV~F(0CXZV=%E2G6ah!vUcc&Vr+GcvnhtA0{~e- z-+#w0JDV|)(hbFzK;_pbj}R^>mgB;5KbFU9P|{^*}b&WJrk4tZIYAq`e8GdHEj0c8-fk^5V1HzsWvq`_308PsQ&; zj}^vgC^cLq`Tbm=-p36Mh*=hp>=2O|8rG-PnyTiEm(e;+gsg|^-`+m0Ft)fBr3dk1 z?NLwM2^gut>Z7tpWf_zoU>ZUDfL;54iELg_dbK1qkZyZ#jm?IoS z*;%@|L5Q;j(eAAGrCW8jb@Z^%$w3>N1+!8Mw!~)LW@thXRGIsXPkj9(dbsb+4xKU|8OgGX=+jD?O%F=l5rR8H!wxDBgV@Sz6q>Xa znkm{q;hGFTEZKwex=JBzHvg!E7UA15WN6W>2B02yNd+K9dWGWX3c*I9L7CRgcA)C@ zi|d7$G~iyTVK9M^TV&c0Q+5T>-t12PAtqt>F)z+WdpGRaoX<^gD~?d+5H_>H*jeai z0>1qlyY}GHV?v(joK|ZPqovk5&>3fKSbOhSmIY2^+RYe7Qd4qN*%!d7*hgG|3GfKi zE#XJ`Ogt*V7Pr#Iw3Yl@VK4$n!-k(np{U@q8R;`|Ci~tMlUw3;zINuk){1i11CWgm z!M8#D4zbRRn2hD;kW+L~ue9#VBoJiN-w zoMVsgrS{o`^`=!L26C2LW54T?B4yac;2m170NlRwLp+F{0GHDdmY6UPQ^&bKC#F-yvQ`FUJa* zwK(sB#k&oKl|YO2f=-;Yhfx^RG$)x^I)U5iFk}DkccNs`@96y>VZZ2~gem>w`1QW|qwrk7BGCDS-*z09FI!gGv*H-!k7pd$D~{ zHHT&5hrhs^xCOSbvV$k(i7UA~pHdJ{?Ej?p-YSD-2h|`kHI34NqEP_4MLpOPUB3yz zN3IRpz+Oav|Kt;7{+_WTr%=2Q4aZe?f%w zWdfo&T((g}hV804G)`b#_CWryYADX87|xy7A#2OI2tnuYBP%ig@#N!*`W!H?R|YWz z4iiwDK3lgPFa?nid^2L8bB(2;Q5=I>db>fBx5*R4W7Gp;V1MyU7E-B&|0sX`7Y^}L zyhY-`-RvBs6fK+g)*k5UnzoV9+mR_?wCMhE#e%_@)A|-5q5Lm8K-XXeo116bG)0pU zG5hp3Jo>a%b8yFb1oP6?qjVYFBC`R|Zu0vA<1!75%Po`rF@}7mRi(Zr#C(;iKrp`r z_kjs>#1UEYr%^f=U>m(qduKa~;Art5i3TzAY3Csn;XI2dPx2yPw?b=%LoraGQ?P3M zTcK$mGHs#A8kAlMEeWG#Er=woVWD2JT~Tf4*}NvCh(I}o4<94Z!=?ieff4JBjHi4O z0m_v3i$rz3$s9CIfZDz({^SRtX((wm_E8SVFdRAjL63SpR=4pbAcbEX0R}9up)6(W z&*(c5L){PZdwO<>4u@CZLNEfM-V6v0OF>a`L9OpPsGNdF>iYtJ!yBvgPa9EgotkL^ zsOjSLLJS0kBl%8$fEh?z63mY5e4m^-nh1i2AI?X<(4P#LV-M%_8`=XsD+xiUjuCxK z39!oDIJQ;^KgxJ+5kBi)WVo^D1eDDRC7I?MMrTzk1grCEXLS{7rr$$bIQhLMko5D^ zted7NH?|)HKpeBu*g>9jZ8&%#Np^5+K+v;m>~#nWRpLK)t+i16e7>FdSA+i4C~5vnHc1 z_TU^!KJQ=en&SC51-y&f`ZeYmj_oR54V9ioh-#ape6qAf2v%8lgD?ljWL|64a_>^) zs5TwegK*4#mz1Yby@zSZT6*VWjzrvqYkCDFh(?7bl_vGiLx^aXa$){Y0B-tMzAkP- zf9+WNS^$z>QT~!w#F(~?;*4eO9FR##E<1a3A>vu&zhr|pae;H!8U$%lr?1dH*BtJE zanc6Pe!nUAoyiec3H)jXhC1*J;(Du7bxj_gF0)T#WUYp8DIRS4x z8&It3CZ6Kc!rclD;z%ffddQe&2CVAD1wb_~#^xx?>81sp#p)w@0Y6JKENM_-{+xcs z(1j+cZ4t%KVFT`J1w}*1hPeENx<6XO9bj~yRO$`y^oQ*xj{eKXHl}h_%prN>FrULi zppz^5R_BJCph~#2mNOpBwAU~zhNrI9WSAi2gzF>3eo&$CsY1)vW^31|3MFYt?V=X} zRWS`Rf^0>qRa}%3*uQGN!P%*wL4YTg!j4epW+Cp>n_=k>$gwKzl}W?G7WF|X^(-;ZeSuUMSVmVLVpf{JL(TV)lcSA1 zRB##2+JHM&7H8d5tsV$|`a>VB8Cw|CB@gZa%2ieiC#$KKM|MDoEh$ZW%1=A9pzn0y zH5`wLL2*hE>or{p_QM2bm7iF(a3L#{#WTAGvIVMgNQr8XF_r`YTqyErs)BaB81&nf zDW~u(4ESelE-9RnSpC0LF$BS>s;NR()}k7Vb7&51br6tR1q1`)D>wQ$OK+x@#VYNx z^?!@KX|ws-iLrZZ$Z6+^#Nh7!n{pi_+`^Xumr3|8DYLlvD~1o>;#Vq>#tt}UkGOD% z>#}K1g_^@3M4CeuxIh62T8BV{Yh-`gwuAU=T3t{LdD4vHMIda#d013Hn^0l`dW(L% zWt*$*MIMD55EJstPN;xrVR9?C!<4w`y79+F8JSo=RDAI*>fJ^lS(olU^=-6Cm&ci; z4e?~mXR<6RU^#n5pH45CFNQg?#5u>$%sQwW6n;NS^l|42B^Gz-I2X%ah4c~>r=i>S zU@iU$jxOg0!z~Lj8G&I^U*o@THN52Cmcu4Nhz$W&9&>|<=o`_j$d?TOhgVV!K02Ox z@8}rz>|MI8@Utm6XyC3pt%f{j{5gVT1v3!YdYj@3uSnLzkP4(o2VmXueNc(UVA=lF zUNdqh(3@v2ltr(;xpInrGgzKBP0PZ_9+Dk?ZTYO1!5*LmBW`@+?@qWE%$!+s3ExOX zU^eHs{03bcC9{KmjJi%xypm`VB&=RfmbulEFza;b@ju^G(0g#K?7R`qM|p4y`2(d` z29t;QSsBa5z07_(5CspJ6wV2xHgAe+-<9${#K>6V$}lPKA1G(?pj#IOD>(M$*e439 zBSVU57aocZs2d0@4YH{<`Q~QxTGqe#1r%&R zr`}TBj}fqujBjnt1#UmAh=@(anP8rH;d|TCK+RO~(7KPO{+eNq4oR&mCYV>$thv&; zCj)MY)ckCrQuM&$(r)#w5whxIrmd?XHWZb{z1;>4vIFYaR;W@>?g zZVzS(*Pb*+!l*Ya=^L1`_8kB_ktjqa#QO7^G(w?*44le`A*nh-w;}m*1ws}oFlX4K zQ)?X9a`OL)7L>g$;i`7~pW{c&g$(7YGg_#X3g&rr6YZ|?2J5G;j!)uF^j8g0X}9ijmpY8y@jCPMs)J-fx_P|&@g(+*$m#k z^#jW=)BW<~^d>+{o`LYK{S9kS94re!*X=T>qxudhvE~snlvtA(&oL{lE6lB<*Tm|V$^SC1fC8jl8@hm6=Ux7i*5UTpg zH;$l7Hf5EKk@kariK?8>GR|xY;W85_1t10b;bO$E z|MPck!O)KN0qP zjbdu~ShJ=zNkT+SEJ~3CzP+X5m`@~klGj_QA!z+(&CG*^Y2~#t(^1`TJA{3RTl@;o zp-FfRbWv3^8Hd71-5tn&VpZ7P0n>5G&E_AJk590|4)jkSmym^IhUk#q)h?~c*$$qp z{@V!N1zx)Pvu1eeW=h0$64y5f8HyE|28*40Q%J~D&`ytZHv6*U_h|>2!g+RhA9k?N zErs$KXmDK9!7Q$F16zU(&6i~KMo5B03okdj1`&ndFXmFke9OsrCGkOnu8r|Brq!d zi2tQ!bs8EV5s-Svb9pSYVsl&YC%9&8f%R%o zw&$9R)cx3H$Q*|sPLZ1u?C~=HuKAPQOREE+7jb-nS)4eo$dVWwX94gpf}}nF#N`qR zpK&WnJHH467VPI?Wforsfpa%>ciO)0ZME>!450PTkXE5Sqx;W0&2FG0G*fIS}2mg6Jum2lrG4C~@hnj)Mqzt;K9yo>Fjx@s16;y)& ztN@h$_Lp~lBPv%2oFZ#(lZF4h#(!QN00)x)ricFb2VqM8uQ~t!|AYTN9Q4=!uRb6{ z6oJU~2;n4v(aaXFLif;&Hf3zD4{hf|cjy65HCT+bj3Oj#ZV$kMiSrP^`w?QKL6nA^ zZtqtUU>ONSx)zy&4y_Y}8F|10CYZ?(%A(86Av+l5_<2AXubzoL0N7CpB+HPITnLqw z836)OxSik@%&Ja0rF?iD z|Bmou8%}vS4CO{f?Vzhf+c>HNV5dSijMi2*J#o^X*33Qv{h|?2 z3U({6dBX=uMtiIocBQLEt$aP5q8il?P~$C#5G@1hy9ea)(~xvsSX;2N3K-zb8f}NP zf)c`7;KA9w6$sf5D2z{W4HD3Z!o6$_)^N~-s}QR zc&8_X;jF+AUA3YPqcB<%gn}c`mZgE)&@hw(Y$7o){wOq;{uIjn0-vg_3kRAiK?+? zAfAmUqT2ETJZE<4e^Fo|{ekh{Ftzjo>SP016>6{Du_$D!K+c#7X9ste<6gy)fK*Ej z$V+yP1w^>2{yDfw9u7pm9lCG(a2&e1wC5NcGzA_;|S&OD~Zx%LQwr7VuL* z`fwa@DisX49sp1K#Egc(qry4ZM}eDzgp<9y)WcUyiNm-rZYXG$Y8z03Z?IX$-sXwb zRUUHNx?hIaR%^-ltNhj;xK$3b$QsrK)S`rTY&0b%>MI!za}eS33(gsGWdMR}LD00X zKhtm!?OxjCu%+^vaeBQRLt_w~Pl$sY+V}Jh^u25f>;Obmd=ekcve%})qnYv<90mgp zY$w~fD0FXVl<9I6fjFxNQeT)@l64wStM<4I9{ z^MsQe&4Hp>H~%`odpUC{dZEXo1ZP{T^@!q;Yp#du>RoiSzF}wZwlkt%&}s;qx^6kp5jUstEAEdcco;kXkx|1Fs>vCo3?;Ot3nHY?DvRDnT*u$mY5$xGS0auPokFu(%{K0{jzivhYrjeRrG!E zUm#sCav&KtTZ1l}|6Fsr=)IMg#!X4mgS>@q!e{9WTZv<;Kg?nD*7#cwVy-iCt$Di` zWaSAGA4DjKv{#e!Ldn}yg&g969WL@nzf~!`v9%Np6txy-sE!|xxcuuhDc)cfeOetl z2a~l=yK@h}mT=uH#|_g?+?Uy-#ND{n<`=NlCo&cK`#{e5^DQ!c^7M+|HmNb3Y~<5_ z_B&8=?e5)`qi$HPoAzECiIMmJfTS2G3H=ds@U@PIlIm?9!j*@L2c}w{nr6=qwmkmFNY3Jd10iH zLvnO~0_6dQj{Y9*asf^;kp|$M>I&?1N z5o0}(;SL&u@OoNHz9zVnDsU>Oin3a)1!k;Ga4%Fq_DHq84JDFBT5h4Jx^8h<_$x4qF9Q_V3 u{w)TRuPO#k7bC!+AZ6Ct4%~#;JK0w@F?hay=|u#uWw?Kn z?g-GdRZXm)aKAidxsEFa>)0`uqSPj|)C_s(V?TU)(QqJ=)IK3jp5k$sfHbtj9}Afj zZ#$}9d^}hYKx9<4nH(t33+=8RM;#dXS6#2p&#Pm>&IG~$h0Isvb z0T+n+t8`(~9SWZjVd%9|+#Bd@JT87G&=>h_9aUr<=nG<7zPlS=T8)i7D^=8c?bsu% zZ_(JmF`MLs@W6r1qJut1tyOd_-Fjtu)s37Sd}l%x!lg0$P6%vgUwLiU*;8sOFNpbTk}?i zKDFFq1g$bGCFxSPZYL?jGKZoujY^SinjxU|*v6W2PqwSG)^%LSYtc_kz{zF;V>(OZ z;b%+*p1iy|dS5FD+EAig8EAK__q72v)FfWnshA>TtsHFKd${Uzk`$;!Y0sLjK$lk4 zQMgr~2;P{geY&QMvKc8)$PDg@{a*M38*-cy*Ug~|$k-KQ>IGPAs6;E%o=lxGlvdcM zc1vR4h?0An`=P9^CCb!}m(%-8+68~ggnw(U4cwN5kbS`rx^FfGw}Yz2e;V`&_fKO) z8atlPOOQqbx7U*|_9ThBq^+CxnbPNz>yHPDo^gLLRr~&aA!GhQtWXG9KU##`!B84A$kk1SAiYf zZJtTeU4x5%&yy#_B_XezlP_uV>-rJTL5r{u`gL%P|2ekgg*aX=Tw8e>YnvYW3;+$J16bR-PsCW#B3l z8HzSKtaR$`3Yl5Yf8aYSz-F(}N%4B*8^=MLV%6xV(#_X?l03_E}`#FDFl)Z28Tf60OT?KcmyWJT*}IH5?17+Tu?Wn`_*IcGJV) zno(k2=M%M+u}&cxRu?V8+Tjsnvc4VZAhuILe_VldYeevbTW@JaBgd zzkU;7A;p#c2U}qAncz}=(m?xTe*C@(H|0xB&fv7~wI409zS-L6Qr+V&54qa+#0ih+ zeeE2)8W+d-F(p!3AV}d4wkG|g5#o>oaf-$xUY6Y_L{wSuy)U@Tb@$~svgze0~UTk?nMwfpl~{0!s!$& zY`CXjnALJ+Y!(_VdfUK6@DvtGO(&BxJW6|a#VoOyOw-wS=|!Q$Cpt3SWl5dVDL>iHer_VZP$TZ`_-}a*8HS9s;){Q7UA@yr^YU0;J?G3x>}bxbOFda{3!?w1 zS1lVVD;$B0Xp1gl(M=U)%ucmfWW)@l|VI+@qJBmmjyk9C3(!zk@U1%?lZ; zxy`VpVk@!2#5q3K-8Dw4GbJ2Xn^RrCoGcN6K#Z2RzE00q@v&z0)phL|V=28&nr2;EEbI*?cc#E0Jvq_G zwzX(d6fbv)D|7B*29JjP7-5j*S>WyaEv_+G-LY&5u`cgonfgg+RuoG_mHoyy44YYW z%rtcE+#CyCb_bSFKJo<%)rPQis-RS2wOvFZ;Hlxg<`QLY5vIWI2RHG0HD6p2R(pC# zxGZBqZSLZ6Vc9Q3N#vWZf#29->^q*vue>VJ!R1`FzPpbz2S}GI#{>V0(41v>rXlfl zo@dfnrq}GU;x=ENCP*Fn_i80#uJBT&DBKh%G2lJ~>nA)|AGVQv^->;&+}vo`=aL#} zj1A7w*5y9^M9Ds9Bc8Hg15bT<;-%8pxvSr%-YW&4*fNx&)%&fsXH)K?gxsP)*7UV4 zH9dhgW7gsg_FR(H=+G_F#|&$i#}fG3df{zFE0A-A2npfHmkpn3g-C3QjC7`BBx4xo z$FF?iIP=WlLC7kpz?*y0j98~;9i#YhWazY!x`SsT%Or8YoKkMRcZlV8*ZX}L41rD@ zxV{%cOn09>?^wv|$U3$izWPXPki|>IFZ=QaIGYDSttg+Qfu#tn3UNn zJ=^h7frd?md`qY_^&EtR)w?^WW4HhArh|Bn@6_s&y`E;pS%KSj&L3W!ox`e6yqd(? zdN*j3ipN?neJj3xd9};jO!E^DS@V_P5_{^LUQAC%P$cEF*5r9C=A$@jXH!(?IiODF zsSZ0$ac5c%B&+5?;-2I1JG$|<;X76}dG^)Us3=m0@|sntcn2*WQ=bx!IwTP^f~U|w z@(QMCxmUkWezjjk!qn3X+3W?<5V6>n%x+>>p>yK{pK4kYQs=5FwXZD27VO5{v@aF0 zwj*~ctd+@YC61lmId&LDhE%C7S53_rmLg(eqgr9rWNyj>0mZLY;R0BSi5mUQ4K`+4 zfBsW4cbQsoZNTMA8Ix(xH?-^5QxV%M(ctBd&PChhe_@rlY zsT~i-E94Ak{*z`zGUL_33R!37XkBl-AJX7bB_DwYx7~&bFS*aLApb(X29e!pIF>r1j7`E$;N}HJiWLJN0DN8Dp%T zcd{nEqYiBJHtq7Un+7I&-qP} zC$_-Oc!1`+!s8ibv9RwJuvHJb)+sT>!2f+v?rlqo34XhIsC_ljXsj}I>N$C_{|{W4 zmmPK&yrQf@Nj)S7JFP%szV^#UGsOFwOfQ@r=(dS2ff^~>uhJIO1AkKsWPg^Srsdj= z4g)dbQUK6+5f%9|dw!{KAJ~czPtEaWxSTO^>4-`5-=s}9p>&R$*vr`oNhMN$FY4&N z5-%(rU}Q-zxntIpNy>%nPce;_LgaQIWzso_Q_U9h6(-cRyr!%`rde4puziPT;{vpJRy=vfZ=(znY;B^HKn(kWGTc46J(H<%uQs?zF9)6-h!6ga z29ut{v^q;=>5&fDEF6!}(ZFIOV(&s~xo`{`+`%nl;+TVQMt+2G&QW_W)=qa z4%ZSN^HtnJtZ6wPUvwb(RG~x2on3aPGHZ`WI3Mb5+?y!;%jp)E?z)=4%p|j>;ekMz zs5h4h6vht5%NcHy9nGq|6qqZ!Q44?Lvhlk^{z-XT-^s0p+0ec1J|($lU<>t0Ig9;d zMT*q1m#JfU%`Qnt%yU2gIEDWMvyAEXCK!wzz$;ql|D60XkpMm(IuVoLA=hPU^c#E4tv>e^tpThvRK`TAmbXk~pnGYAk^e zHD)d{?8{INUp@MQ+;sQi-@TrfvP-CxVsciC;%azJS@!qYe`r;jo&$oF4DM z7TtdrEhjYmzM%=?@ zBia=9-~T9c+!LjQSm4$vtoq;Gnl<;@0JI<5;)ksrJYk>AS6VL~Tk_9-dXzRw%fU&c z#%;jY3?Ht2K`rKen7YLY3}+SM5M-f=mXo(<;2C*xn#aq*MfK*Mv+XmQG}FNDp3rDp z2hFEPPoVC;%QF%JQ%;w9cj3McN@))%5$%T*D+R7bM|ZTQ^epk}rin(D&43)V|H_Hi z4I-&m9R%_1SjS^_){j-LlyR+3KSO2f*!m6Kw^Hk%OUz2`2xeEj5!s*- zKfz`T;Gi7$#Gr?g5{Ri?$=h(-)0lK^?fEkQ6KfRCCOE01HzCSyJ~{u`u3)BH?Fv&^ zhFkM7EVmENe7S<`uExK%@i)g0pH0lA;!t9(W4!W9)7c8V&Np^t;^<JcemjrPSa=deC|65!@ul0ley^5=Y}bq@lj53uKh z8hc1PDnu;_cIri^uzd+2^_~O}Z*TtN0LE((EC=US^EvdfX@3H5GT}L5r}b0a-sbfX z0w753vFq-_ZK7t}%}aKsd2U^Rk%Y8q$@8XX&P)ZGubEHC#2o5Kwz*asSSxfr?To^f zidcNXJjzPjr-q&7QFwPzbD@i|lcu~*uSBf{4BbwklXhcIGm|BR;~s$_O|G zwk*;#!Y@!?&T-Z2`(Vdo?faKX&FzFYkaxyf?aemUYy&XlUJCuis5357*zxD9iglo~ zdIjqdm(l%4wdxaOj%X2OYl)dDXhAiW{n}QF%naJjE7fCWKBRp00VB;uQKRMf!g#Vv zQ3D3+ws!IFE$~RT9b2z^weji#qvW{%)_b}DZP^5^w#k6G>US`RH~pd7AA?gKV)xku zhNZfr?wE+z<+`RrziX*6VbY~ETj;0+!zI#a%P?*-BtvD+8bxsF9O6!ylf!Qzr7Tss zO#7vQ#ss zlqgxU%f8DJMbaikT4Y~Rq^wEy{ap7~^E~rBzu!6M^*XQfI)5F1OfwT-pXL3&-`9QJ z*L~d_FYgwXN1?DPo?sO-=rN?fLoP);Xz4DNmweq{;(xi$OEn!hckf*ME_L1Jumd9} z*#w}1QE;QtIs-NZ-m=m@7xmC-lQnx_O~>N?i>l*{QH3Bq8I*wbZo6LIBEWL+&DS?M zb}$Z|%^G+>awX&XjX*hr8CqBPH8s&hapI%w2Z2WiuI-TBGIsoO6?{v0ueTqrSMJxh zFHao$79kQ@(Lbr}m!WzvNz~2h!pV+D1tekiOg2mCn6pPrJuvf3Q9!MNX+C<(&+PLV zIAsv~gRv#5vHHOCCNPdHGFN) zqAevS4VTy}3*1tu*DvjG=bFPde*~4G#_mbcU*+xR{}mndod0%Qapzro-`4xxIrR6U`J9~d86iD3z`Ej1xU(Oxd# zAkBi4ZzS7S950*Ft~_2QaDT6>O_4`>gwe`o_`d39b>pM?BGd3KD+sfE43?^r`fDiQR{cT z6rHhlAYJ%a&Z~*^Ghd;wynp65)BMVN2c0F~C<*wUbdQw{KAqNUAe`^_e8rA$3F5bs zxMcZT=WODE@MNW*vTy${(WRh$^e#p0Io&~{tDrcLX^VOJ9%ACi+Vdr9G2Z*7tk832UsZe+Gp8m@{}n?klEXwDilsBW$H#k}6m54dpaHv!-)zB=@ObO4uWDaLdXP zefzi|?@6optCG49K5q#t`4V$j$EJJuy=Sz-21csRj?*DkN_&PItN{4P`Sf@Y-J zCN_pT=jv{C`|Q!&^eXw4DdRx}*=2XT$F;54Q(6{ACvmiUEcJ1%0fg!?Z7V#eAoyhl`KoOMHm#_R5E zSKA}eZ}+U${X<-zPk}3%ZBX%b?aR?g_20IOsfX`8AJsxWp8K|Q*>$z8_~EX3SJ;3U z3vRt-6ixO#f4g_f0---&?=L^DsGTuh{2Uqx-RPRN60xoc^G`}v&0zT+&BRGF ztS+9gT$Z3jo<7yw{oL^m38VAvdgmzfsr#2CTnb379vLAaDiUUq(VgQn_67UJW!43c zb7;v|BD9I!KHRPC9&w>=!a{2-_vY*}a^z&_!mmK>C!1K3yTk5vtW6^IfgMSz+wHTE z>uI@3J&L$RhvcrayEfgxooqZYU%~ih()ru=B;7vph^u+?t|2X*n3iM9l=s&gvW2RJ zY>ibY+vrSSX{O|Dwc?12#?;GEeP08$^~+&CY@=^I{V%$*FG*9DD~n;g6gK6#vej05 z%N4dqs*L+)<{bx>W~|H8tF;%Kr~2koSWTE`!23?8m6`0k{bZFBnY2CZ2=B+ergPXO zV(fiRdLlPg24&oDK6rknu-}sB24?Go{1O#-`ux)RpDx;CSMLD9qY#UNx!636bezdw z;kl;HX0<}SI#+hxLH?}xU)}64o-DMOeX7lJ_PaiQ$AeprBwb6W_)0VYRB-YhI<)f+-wW3 z|7hE-6|q`aIzuL+?oBwhdVbF=*Et8+a+dD6f~Lx^v+cz456lRZI&J>IG>6331NPQw>v&u@Zzwo>H@V5NWx-jQ zH1P+sc`LXz&*}cL$D%3GohNN#wS40TC4q{+)FLj53MF)>PtGma!rI#IYQ8(U+iz9z z3YK-IgwWP!?@)_Ny`3TKZ~fk>g?jCZmMCRwT=zUX87pIIT5v^dWKV``Q^vJou!f$i z?er7eEU(>6SbFXGuKim+ZM0@ax>a9q!JlFGUyN%f(0yXvpw^>mY!ZARJhVk_Z`}La zMPFyInENWMa%`Qo~OB+bL+IaKM}2^isW-M|?>PVY_IzuF(a0|CU@va(%=>)z z6vUPsTqbco<^YX5kNQ(t3T>^6hYf0v#E$LjFELIHKboOyeE4u z{mEjkjS*W7CeHqm6`RIjp8O;YuD&!)e)V>>{6RQ6UT95}{NwYZ6PqwF^HxXj6s)*J zn3k#-BDz7NDiKxTG@&7^rp77BC7OJNN7xe7CLHLa=N=59KUl6;)u$@ViY#~8q7rVG z3tOL3Ox#2ckKV*kwv(P--Xy;iy#wWXUVkjYjZM4KJPZX#8k0rhHK=XXOv4yiqmYi> zMJK}3SZ%#r>#zv`i*FC94|Je_W(=IJh6=}@zmvLLOxXXvJ3UAq8e^QVahcDYgpjQp zuEuGZSL*J<)zr2{E(zBEmk!5kZ2wmuDD#4&4u7f8pVo#l{~P}}wBY}N6q9s>qGT&o zOSWDE@a{PV$3eAo{`kQ2j=Nk(vv;TaLl2wRrxU}?oaJ|@=Y(<0R^|x9#obo(FUI=@ z&5TD=N>GC^DbQmslWjHpYijEfFEb|>iY~2Nk?8aH5W~lQHh*6*^G{XJSL!ccMX()y zXGJ4Tz6BnxChv<$$oRzXYO&fplTkFNF=$^jCg}RRd+eUBtb4nheweHa;pZ7sTWjT& z{YTH@7}+&}hfYdtzQJju8z)n@!h9a{|8HUb|Em&0H>yq_j++vn{aF2_AT$vdI@v-? zcps}n7?}YV6WIq-a5BioocGPoyCI>@qJdV_Sx(AuSM=)ro?9K1^Bz8br+lP-T+Ww) zDLjq2UOKFN{isdj@k6WstM2(BpW%i3be`FG{yB=|-U`LXK@$1mOcjJW zSQ8_ePu6C}HbSPkuW--UP){xf(bZpMoo9(NA^`r2)clr;0)xZrU0-MkSIPmWIp-}R6!_eCHG!@udtP(r1 zxbp{Abrw_0Tz$$=*KH??!j`Ch`3#4`OJ%R&t67IE#;rGOZN7Iv=c!20Wc`X+D(m;T z!Tf5LwDJP*z&y_(w;&Cpr!!0&>1+Hr{IHl4Q{i=eaq}bd=W#+P5}&zQ4P)tz4seoV z9q56Gv`8t2W(OCLQSkSU!!Q>n4`iFSR*<3Qk}?Yw7xVY=D!DKr^U<5da_BwTsbX-N zSKkkmB-myt^K1%X#V_>5aHd0xdk*N;Mq{SRyA?ywt;jsL>aZlm|I&gYni7nb8O8oS z=Jm0BEPQFL|B-=>q?^O1q#q*XlvxUYlh2SM=Afg!2BG7Z3+kg&Gz$>cId$-|(!1`T z24TNsRaugk27Z=$WZ_>GAbrlE`c(GsPiG&?GP@=X83tPWg&h{LtSc+Obr3&biQj9p zFng7Zi(}N5Y@Zb(8|40aj?dfD=7e4PeJzH_oq~C}L)cNZJiGSoaghUN0!go$u<4fG zEowT~Lz8{pEFJi%As^nOzwSL^MXTr@6vOK$BB0bg*O3_CU_VgIdsN{9BP!(xq+55< zAW;uVP3t%HLZ6cFVSJ7jP=sFSq-ovfQW!Av2o|?mejP5oTi=)) z5}vdBUU~+-4M)oT=oAk4LY*&}1m3+ajV^`fo$!pGFr*7w$*jxbZ6){!6ByGd72@;PWvn7-;7(qqD<=91bM$(@oI+bNATeF}!&3Yxw>s zi<9VSY%H^N{CHuG?x=&ki$`1n$$)-o9mV?Pu9&-gtz?ka{f9GI+3wkWyJ2SS$YrIl zzNg?D7?CdMF;7=lUVk_G^C{%x?sdrcV|D`3iER%(<}II{wp>6{@I-THN%u%%;q%p@ z8tnx&4H!yFW2$#P3c*2iC;jU{4{xNRt1?>Am#oH=i~KZCml9hZXK%=>@6Rx2VNtud z*;C;35ViGV+{v^1x)INhCq1_X_34`;M2!-> zBEO<4f7bPgz8N2@I&$SQJ=iVRr_%gF=Li6=XAm~GZMG=(5S1Zo^lv*4KYloOu8!LG zOnvn??=p-J2JY*Cck5jeayOc5JRH>} zwM=i7&&^Yd?=&&6FPPXq>BpncI*P5+JgGH0gtqlfCr)-c?J!oo_6H8iqIMCSDu!OO zAEB0ef_U4D$O-9d-73hs7CH134AoJa^c4Trn5E}ztF(`^22hUpw&j86$)$mUYr?@X z9;F_jl^ay|yvHD2BdLSr;1GW9+uS^*eVQeX3t_934ZFj=wGRLQbWogJ)-P4^Ji(BU zbtfzk8inqCoVim!$~RwsaW%SGW)?O!`}g;eVdgwq#1cr z@-<9LtuV#v26dHxvmO5ad#=}HhkIDXLD5qmq{g65Z?LcI)qESk-6G7D(3z){#Xgb6 zhs6)F7O857p}us=;>_A45V7P{2!UBkOV!E{(m;5$O=KJ#}4-aAx_D{*ozIm9=H zw5rHah;=vLZ+^-(2Iso(B3MXYqEWzl``a_}9>51*Gg@xJ*^pIpN#m=d+}?;u305ct znivpF1H9%9@fbCK;Crq_H0=?eY0me+89)A9_%r}9xoAtPdD8hpJi zA-G?V|6sO9!Th7))ZP{>X5PG+>|3iGTUM`9RBeB7y1)L|z0<#Im2LDTn^Wp8c01|s z!GCVBYe})`_`Wg~=>f*uz6aErzFGtA;mZbK<-{VoK~!bEK4UGj4>D^pe7Kjitym`f zn_uAMjva~NxfgRbxlfLNOINGnUmfsa`r270N%?<_CIQz*!^`D3Dyhz3S2y7M z#(pUkSlmIbn^J>^Ya`izt%QYMJ&pUu$^9l5t>ZjKAN_WIgfV$$5^?kY!Mdm;x9XCuZhKD*|NO=r% zZPU)GQ`*`1S#)pvtT80y_JL?@$*;W-m7Okmqxz+jMDT(Hou=Dad=>8ozy;jv+@c(M z2Ly>Wj)g{^3gig7>-^@g7Jp@Zzct{IEBO|?A`X6YCKvRt03BtP_|BZupY{(GRv~WD z7dbLG%wTX2L}P-H#EJL3Clp+F;Pke?aJad{SS1D3%3)^S%cva#wPIH7h7 z8>?}E;xy-09rmMea*z{>{!3_5CV>XIbpoJfz=!32C%Pd|*B1LL{Wh@&!l5IuNV}nj z5Ujqz^d^mioIs+*?2msloS)dFnVxL`TSzN(Yo^6~gFkOulE(`$+shrfCuCmV5_zgO z)igDE91t`IYU$Sn0hzx`_r2O~JyR0*GPwwIH+Pzb>HsM2en~D1jW6>KCf3@Q>mj*v z+>>51!+C}l@M|t3at4(&Nr^mw;^+NT# zXX1r`E%&cR%=QSxRI`hjemuX`iXOPxg^nO=xcNC;IL6jI3f*$!qwjdk!JF!5!B=c? z``JFUz26~6e6mffFLW=2QG;lk&8RdS7VqY#U0E~vh?jMR0E><>4@{u!9&3`de8^{g z1TuD43|(L`UuinF_!Rty9D?F85apv}GFq0+ASus;8$mrv}x#%Oj}*_gMG6v_;CQWnfZe8`#zypsdxl9_K-@r>!9{I=+INK zCxqrjD}3%atD4Jq({J<5s~4}5ooE`ysy(9Ekk)BK1X8}+Xur8u?QexIaaY4CaqlG+ z_h+iIPXW3$*t7mfh~e5m4)kMWz9OzJ`S^cid!ItZt$ufp;c~gO?q_b|nSHrMbihVA zBFZDHdvA3^Blk>R->IJ^i@vFvfu}zuFI0{hf)QKY=kUnyYu&nmilZ zGupP1reB3g*lajmkBV>?dKz*Oz2C7)0|c~trPF)bbnN)_r>u)!|F@}tM=BgUu@B8q zJHSjQ!bKUy+-EMRQ*Sa5toG~BUEPGUpzplzrGa70nV3M-?UhbrbZ4g__jvLeGTz9s ztILJMXZ1{5YOoxN_?Q3EdNS#}wsy}4vp3;feyaQxAoVQ45877ur-oP zUJpEV21^9epYb`CluE^ZOh)#)x%S-88yCGeZAAt4q;4U%)CqVfXQDOh*w1gNt zp*0eOPm=y>Ah^nOgC>5>ls4`@952sT&06xB51_~Aarf#u5-NfBAGz|c{`p)?*iFK(ECt&3ddC;c$+gE# zs_m5Xb&N~`o>jC+2dH3K+D7&V=%INJ{qafQ(6iN;zhp==1|e8Hin4PGQbcQQ)}`2( z&slefS=PM8`ZgnxGDSF{#lK^Sm5p;5f=Mk}s8UXUdQt^@e+mq)Xa^XC+@cwKqe{jSW<6 zqt?!4+0b(KW|NZJc8s^NSntTMuxgFR70#@%`W%>CnH(Bn`wJ2Bem&Yxf6pJNIz}oLq5KNj<$5N|wkT&)Z}@C7DNKn0+5xAva}16;x{v%X zCK*Kwim=HYVYyi1kmwbVZCP8clWT4DcSFe?ZWhr1>Q;jai(|O!U{ueqJ{OZgUwlmK zsmXW>N+kV}E89aa#VWpCaPfNUO3lwaGdR}7epFSOulC4KVJy)k@?kU_U9D;GdMS#! zm3l*SPOZmfd@Sd$pj*dH`$#*IfBkiLDv&%{>J(Hc@w0NNT}r4BNdh~f{Wj^qsPy*-eXmO| zs`H({6Z5?S$LNsYqqk_aVtulV???ucyJAoQA@;8F>T_lm#p482v~Q3Z>sm%53ac)3 zAoTdaYs&OO5_!i%uc7VSfbH<0rZ`B-$#yW5?_N+|8~9GXc>}t@lD%)-C%2zVsc_}D zFHlrFSb(!!;#j$zD{60U?Z&azE|M)o2}*4DQ7q#p6Z=A*KyA`Po4)klsOr7#p-F7_|9Y`{-GE=`=m{IOH5)q z@|rM@!B`f}AF##WT0JzAJrE5G#TfLyCd4X78%36l^B8*0?Xel~L;W6*{`=hi>S@H6)OUIfHiJaj7zMt%uAr62mOM% z{ipVOVvd_&zmviRqEswyx9?Gmu|C_^^ffS|6jpZp)KZ6y>e>ZbK>OF^XJ`dhtXBV? zWy1Ez?i~6Pw|)-=&Oc>|fh6x>-_Pa1e-CL#)H$gvY&z)=2C?zrz8ysZ7W|h<`*X_D(rgW1xMUg5H<%?C`?T z%JfVjN1my_LfuB&V~eMN{Ag%~X=8GE;rfGI_Kci~doA7q3sf2bd$r?zDq4j+lCW>& zgY&XK+W>xDa|BVDIk2Nzcl%_okNW{w*cySZ5Wz$UL0xAwxdnEfWT>`F6-C8F>ZBOInmFV0O&E`j07V)#FwZ9kdyeAP)o2c3t$iG$^S zE>~}IZ&8ZypE(F5RJseNCPiSjMWbMwv4vhp9xUrQ|I5P5#@@R&A5+vVMqqu9Ha;(s{c8AH2j7$hS z%yJWx6}1)e3NZUy@~UfEzeM6{`Iml2`xN!`d{L}Pe{}X2_Nz}9?iREL4^m?mVwHKs zbadxt`=>;|H^ZPqx1(78adL&~sjFLaUgR~!7T1DZ%Cd4Ft8a?3sGRC~^P85gjjRh) za<$HEW&n0F$5e)3U8tTDeleo`{&J2@gcQ$Tk^743PNbS*7(?x}1$@wIvyYndC;LWL zjH|s}r}T0sQic>o4f&eLP5ok+3Q=Pks23n{O&>eg7=yAzFkDOmJ>tu2b~=9l*yVsg zbIiPH?e$Z%4RQn(*hI>jez#C9FElW>DDX{8_!5}cFo9VFZvu+$x5tHK#tKqg&B2wh z?9F)R1Z$~;3XJm#7nJaql%pY=rnBRHDSE#@<_eCp7c`UL;)e?FCwO?&4Yt1CH;2nl zX`Ul!(&r!_6mrP`!W$>O92T|*?FQUhXd7W-h7OttUO!QnXWL3vo2J}>aWCtzz+1ew zzrGCEv>nW5xV?AXiRf#YLTZWc6@xz{frgsb&;zrrD}b&d3wM3Ko+5BK6XdL!&&`=y ze24A=fED41wg8a&pdRteVDd+x?eOavc&wW%mtY9ORdVL?EFMI~9w;>@#bLy>_!tX| z;XYc;dt)gpdk&nLHGsLECW_h^Eg9?)57+b+$UeMi!V27X?&4YOS4HM$y#{cxwgPK} z3mePLJn5nDcLs{afhT~lC{*?WXvm>&Ka+2&y#E3WVvZIyVK~N-cQNZ2m|v19X(Ug2 zrF#g?C8oOgi}@5W&MPEiBZy%9qG{e6SUGb6e+;N`g~a`$>nFx)sDo$7X$q#<2F})U zz*_;M!^lwg06%xjva+0yW!Rf9!a}D{x#UZT-i6U|0hjwp0yiMNpZqg_e|N2 zUK~@;aB6a6vcu7DfjBvCD9*P?Wdp&$KlF9el$_U!jQtVEHNDaO?)Px*_2n{wpRc_! zp%n0x$2Vc-WiT93I`c$wDrc%?+x%|si_1uFe!{gMlJ;+(wL5 zW#iYYB@ga8(;QvuQzo-IPi4lDS4c3f2<{!gu;2p&9m*ptERTMC51~=ON7&U)Cn@Z3 z?_go!_rcCFBhx3tpfTv-o>;bHp57Ygw%e^zpeYhojLS}Y>_pC#?9P5)(jbG)?}#%k z8XKjS;bM-PEZO|?WgVFjhI7KyIszvZXEy`HQ53Jmio09H{Ey ziwT-(0O^HUCz5e&K$R3ccJVM)jxpF|Z{Wrvlw&z4qBrB{_ribNZ7BXpt+TMC+*05Q zN7P|dr0MN`4ganL9s76NSHyX%N4WfuCm!P+ib|ICLC3>a9uKvVBku8Cq5G5|V|qK` zVi;?#D$nP*j|+M3KG_#hgF|}x>d;YyvzEm8@GBVH@=Wz0mquOzmZc32H`7kUCBZzZfnh>=bdjY)DgmI6_7m70+~!{hHw9t|3bj#*c?V0;0^BzfdQ9Rxch8GX%io+xwS=5|Lsi{$e2VUKPvFNP>;1=&>qz$`{FyQatOc zz^UmCpJIs(zXY-1a>)tAx7hRF+#9|%3{{TQ=Cin7PAdR00$b^C z6|+T!Tw*G>BHVQd`c3Y2zqQ=1axnomUyk-G%ZjHLv>O5TWy;Hptqi`H>nS_BI?NB=={M9FKpVVyuvQ-<)`bxKCs6BPRV)_kb{?*1w|J z*A<>1?{dyKnZ@b49Y+Imq5dhpRh|N>!5Fe;6oZNBj6d6~R zKKemrQX?F}wWE})X(Ug&yDN}iFJ+Ul?mOM!7L;}M^i4RwiG~Pm+XY|M768hlX+!vHOxTW4b9f&Duf1nuVp*Iyp7x3W>Qj zaY~!!HQ`g$Msph~)+ad+m`*JlhCB-+N5Xjv-a5pGIp`R=gOSckW0+e!B08ehRUuC= z74>BsN^<)8u(Xe?@XzUEV7;^ud@opf!VLpKtR3=;IFFfZ4SLU^40@+*zgFMs-42!x z1Mlz*xZv?Dkek6=!|YiA!duUr=6Cx3curP5R{FEwFLCAQz6q0*_aAQ@21zd5cMC~~ zu-j06lfQEH*_S+lCY_1DX>=UED`kxAGjbchsUttf9c<%MFnmDGy>KtD1rkXcoGiy1 zV;1fi|4t)%s`N*&E4+|1#q{5U=<}IVcTUOE@YyAwc_~J{NN_XI;`74*Mw#rt8%Oo#F5hzi7z^!qP zPP-r{RKVE3sC=NvBb@Uq&h-~Uca$7Ji6v-fec9lX3fgLL=o9WZ-L9T&hjibdF1hy; zqvW;v%ixn~D}muk>V)8%#giY*%NhlyU>W=&nz96LDu7?x25n1_ebgw*j#&#C2>fS4 zG2&Z$C=mgXy?>gaOz0|Sy(13`#em(<7(Z6Xj~?r7j=5@!fb)qpM-SH~f>qV(j6Kc5 z7G3TTu0U)59!x+pP35ZqIw_egC5(tK!2-LClk&w0Ky!8(T+y6gLD>oGH5pyq%IVaA zxY7c8<|^ntQ-gTz`Q@)>W5tzDdWV{c;AbO^60*Yxh$pzvzU%taLZ>{tn+NXQ>Upj< zijvmyPqOk1{^_VYvdmbuOO|tfLNEB!+3$O;RN?F4ixEI|7$4d*R zCVD&ur-llr%B+#!azT9YX~WvDSf5)Lob1L<@5S0H#wP~2hPfHsni?X%!XK$>X9;pd z{#dcc%|#d0c7gsCSkA|cPA5SJCSqB0$0Ke)z7#GAgh{1UsfC`lqOMEF!f!QGE;YfWiZiwo!J$i=mw8NTD3g zs0`Wf+$I_yknCGnMZ2#bRg$M&e}2K1*84IU=1|8+37v5L7qJY-r%cD>~7Z{hg z>g?72KE4>|bi{=9^_V7MwbT5&;OQ0xPj>Bt$rYc7K=wvhC|yk{>ltRQ&7O)tAcVAF z32J^qVI}c`(~-U$fFdYt4K3mZ00vbYrbRL|ACdFc>v)`)u|Do4eDNlo81%CkGqX+Y zaMLEGQELz;Ycn_Dkr}s(Jup@38A95;b#OBAeFgTl?w5za(Prx*>tNvzNb=yupDZc- zu&?kz4Z!SiqLO&+CWQV=u@8GN?;zOThqs;o9dbUN567{uu0?xG^23cKqWmL6t^CG@ z5rosMdYqnH#j$lQYhi6<1bkIHi0d=uN>&&oimJ3Yaq1`0KXd* z0J9{G#aOGmPjnh&4G5=u-29X2(`KY^RS1jo z7y})p1~S+wUojH=te;x}QcTlatkBY#>V;#CJx16$wG|fuHeG4;)=u_^noBE9=UjWEnO3;fT0kNyK=Au2#anV>T?IlqCIQ@@44D)^SVc%EVAs}J1odIE)bC! zzqA-UWoEj{?(XawRDS};?lTejQPEG}2v}~t0emv9x$}E1^<{9Oj;Wtk{S|bUCvhV{ zH_7Gev^q9vpgV-sdpDM~F)DjHhV_v7Er(Bb<`5Hi8geY79e94^0c<#9J1H=yRhuE+ z6Zj?YJUF0pGS%Nc%L2q$Q1M!uXG`X}fN|-x)Pe2^Uz%HgUJcfKP-7JsN8(*f-K>D& zmTd_=z5j5q7~hyAYaGes2-22Hp$^w>gZ3%mJKZpGx{A3Ijt0}8mC=zrIv4-S@4NlA z#Kw@!Zpb@$8wU|#itz$+pN+wsTY8~%|Jx$&J}}Qa@fU@%-wMIO7-8AzJy*@jBs&T1 z>I9SbN;|Iw?>H&cH!(%)0R`p=S5m%W@!I9{f}FJ?^WIu#gvS zPJKHxi=_|KBK{fJq%SBbW{0l$MO72p6n{jekZruX%AZ)(smy-;2!UU?nLLa z4XPEd_S?%#x&cy@RISDSq})T%XEzXvF>>QtwJ7}p=M2K7oqRFX@R+o8#a{NJQVVo^ zOL0oR=Kgxhee9Sn?ku@2pEXi+c6^n2o#4X>3zyrM_A93}nuM*`Cg%!2(t z5JDlk8lUYyqLTtpYlHe@;Qe$&@!D<;Exo&p$ML?4mDpZbW|fKiPu_hK(%UlC^zyU8 z)M)ipsc%?tFD2!8e#YO8yVr;19WLv^w$o{Qhz#Vi`*_P*?eEr;h73p(qo^Rx?nX>& zdrLR)S?Lbfbu8m*9TNT*)k6#>k1`T9%jPj&mW6EJB9k{>({)C*3{rHKglE&#h!q$y z?eFG5cke)H&^Xk<(T9ANt%Tq6$9a> z#iSS=S)ObwaXA&vL*K$!&CtS;DFF&6D{Zf03o4ght4}=;3Axw}Un8B4uZo`%5vOFa zS+oo*1k*{?dyJDuDJw95rxvR@8mhi|hkF8LcAMuF#M)!Mnks5j;%Ld-<%X5aY@GjY z#iy^tvF<-ox@?#<^C;r<~I9`sMGJIH)C4BcTV;i$N<@-XW8;kxM0ghuY1#d=4I5duJ2Bd;lpv? zf^nlx>zO>AMdBZCGlM1VEu_$%Su19Nhoc$P|}xT{cQyaF61 ze6At(9aIM8#gXISS@?r;e4f1fbV*izPlTk(jh*0ul-A4KB<2T>)PbF65WoZ#DrLXn z!9^ZM>T5?0Q{Ivt38u~-WT8q_cQZP+4A%8CAd@4GD|3Jrraq}GSy*_tOkblRYKOfM zQ{!9cXakMct8lJc?O>AF0z+r8W%!D8`IWdM!9SoaGs^{;+ay|O4X-in`P@iGkfy8F`)Z!nB-l%F`Tx|!tNuAIUj&IO0g`*KNo@PSmv;-@Yt`>5}V!ergGCl$E4vL+k^$u_M&5?yl#u)479k?bkx;297 zY%iFCqCe*@YX)NTAtIf>g%g&8+1?5Xtk=7|Q-^c5NfG$suCzXtVT{==s&!s5!B z>(ievcjo>4tWK%Qv)8!z*CWh*z~Rk&8ZNMkFL+UHOpvW>azq-t&eU3^(Fw`^Jb(_d`+ z{_p6kbzfggw0PAZu$MIKG<$W60J;&*daz~~4T^aQ=N+ z>n`C}E`eUmw7;YB zU*|*HQT99>BEI$Spcjo^+<9E;IJ_jl6jNk3m63yFBCEb%0_juj-^`$G9H6{SqkXk3 z-aMz%D}obBWm*9QXFq@sNx;b?cItx*hd163zc>?3(C?!dsns@#u~!-)L@ zJ+A=kKBK%cKtsqL&Tf8wto>Y3qSk6eqkHr(@p?iw+1Bdrrc-285~5G zRO)1h%^|`JTX-Ujy|AT}Yab=T??nqLw7cn3j?Vh~vqD!(JNNIer(zzRTmc4^9QTB2 z$J;13D zXj+2u{#a-4{$T>HUT6+MROdKmnIRjB*4KA9kU6-g53Zki*yNO2Bq>AQK@cKpa1*nA zP~A;g0{OJ=2?NyB0z2%9Xl?ORSN24+aPfsX+$(T0zud1y)!CK}^KcZ~Q z6|S&lNx2$O$^J!1KzN%+4gXQhLUIs0=neFM!Z@olrQLw8sUj|QAHc# zyA(O^x|_$WJ4)R2*6dR#v!a6f+h6mSAcH%<#)cd4;mWanPt}R?*R_4gQ_InS9J(xR zmCBYS=nD+eb1Fbqczo+A6xGLukvlQ7$CrQ6IZgC9X)GK(hN;x|;uJnC;nBc8z@=t_ zpK;~KLQ&uBxLAq2a0SX7oZl3b=C}aCD9Pc&}=K^W%`S zP?33Py%;q_>aLsA0ss-#7*`hw#EK;L;4|b+!AgBYV5o>eZ_X{8=r&qwOxbuh7I?;6 zlyj@d&Ah@``yaP@lJ5Fle-?L?8r_r;^0L^my!t0Ow%J$yh215|@p z>rs)&OfGpZe*++3cYd>U*ou+*rb$k!O=wEcV^m)&s30ltD=8Pd%5XAqOwBQgGPU73 z^H~xcQWOkaPHMF8qQ0qI-wkDiB6;tk3>40Txx46Y{q2h!eY z_9><6z^7+!yAtav1swrVP!b&p(F(coHR$VVV3 zEj=Z1MD1XM33vzt9u&4&__f0Dou8$o1{866+T1sA60vE1)`nUa!6L$I9)s>UB1Q0P zE!``V<~?MXd-sXCkAN}0smFu;!sr?`v{Admr?+ws7<17f66MUZ3()a{H0Jb;^~t_g zC|w)O-3a>WN`W%oCI4`?YhgdBkftV@ru;k4=(~-yYD;tgo1FAQguOjM<$8yDBx|BQ z5i=PEZX>2SDd=cWba{wCEQe0Cs0Tr6#+9o@j1D}Gi|5T|qMzBHBbXI44Z#oTSBlT& z*g?Uv-2b-B(NO5X1!+$a50lkC;>M@P5OJTABrCt%44_irFVN__!NkuTL!(G&qcx(V zfPEJNnN1ar{Jl5q!xgl&+_8FFtBTr@yaC||9PE%Bc(zQ% zMBNFsNSWlWF&rFM;^|>sN$IP=yW)Z*&B*Rs!!GY#Uv3e2&AiE07%G&00O&Gc>^{{9 z?)@kcFvi>WhB>0(k9%mRG|Q!08!l|n*PB(b9I0gjFq|KBO_~LEQ7M@Y;r#6q{`$ZC zcr&Ka>-dJBvq!6_Mf#Xz`?I~lnQ2Em|A@U2o=XLrTjdNcC`8{VhsKzXQz6kK97OkD ztdeDMM=EhbMxk`7z4bZ}Lh=*T4DTuw0^UbTbO827 zA_K(GaZLxHyB@RB=9pzmaYFRk?x}{3)h~=i!Q*vv+&c(E292H`gl^1kCKhbMt%1ko zb!ZQ6L9d!Ooj)NC!X9y;22zQXXu*K%vYCv&b3n32m_?QW@+;u4cwegiFv|+%eU4Fc@RJc|+-YQ%x?^PtfMT~Rj z)+R*9Ta9_nw0F#0j|3ny6}C0#5Vwcvj!SkVbvbzO_FT+dIBzQz)XaY*G(6p#H@;mg;Pt_7FI7_^iS&2*lZpr4SH6QQ?ZZ z^%M109*?M!U^ZjQ*?ru{H;2d!mlY*>4!HxfO2N{^cW& z-h1B2Cem12q#V>;4c=p^)nTSG081DxGehV!JY)&7Q2Q;JHByESj~lD|*YE)NZ%S1m z*(45DS+YH*Ym>Z_IglZp ztyIYy{@;pLe)UJOA~Ro{bj4jT^l5$aD&L>0A`MNG2m)ITiXp@q*dT)xzB5H~yJgA} z>jjiCjd#+2a-1?rs*A%KA=qOu6}{F(_7;_co3r05m+CZ4mJ@e^=;e*T z^SlRz){9=yoA9pfzz?rGRe?x)BsA%u@*0!_NS@HvROn8zj?S(GP?z($B*oBYbKO2v z;c=qg*~T3LN9e4LrK;)=IXa_?_z<{!5kPlM_Q&s$1+74H7ohqV7fF2DZn3AV0()c${H2Oo8S5Z*80IwCmZk_)0*-SLHLK-Fufz+# zM-pUBGnF17$S^a&Baj3tT#GJfzP@3GUj?)TA&TQj83n(-!)+(~G8cQu{%Ss?b0u+8 zW2U?oZssnO_LMX7TZgoaQc|2AAwFICjUvfs zrQ4l;DC66B79S3m+Ldnwy=u@HsT-MME5!lK0P}ve->}sRLXOtS(9ccKk4FLRd2q|c z?u092`Oq&vo_zIv&>JN9fFn#iAL&Hqm(Ti863&yJkF7OlIxu|BQ0AMl=1P}6V1wfZPm#5z;! zFUbj`9<)ozY6HT@_AwUv{ZsmSS z6CAGu6s`JiZRnYXPyfCLm3%z!2dHt8`TOwdY*%6vFb-&J+(PoKl$v40h0Nsbz6kv^ zDS*ge?Tid4xv0frH3paK-oAvhHD&*FX!&t8o+R?M-DqbnX)wrijlL$%ABeMPuPkGS z${CL>?jxlJ&A?e@Rlt=a6l~}`%2!~@%dqJWVtvh%(m`!hV>&9{ES8MLHq1b`OEJ=w z#ZdJa7}8s(2>EI-9l1oSD2l@gmua(u+%m?Q;Iq+NVuOHKxl}{D#Wk_qLF5A(IDOv3dpZ%;`dRduMh5BrdJuob;&(llbdJB%2R(lLlMa z6*!ujQ^$`PZ^!baQ{$6){v8@Or`sz~b_fR*FKR8pkgk3fY;R689U0RGCX%1msuvPJ4~++!aOFiM75xwj%{ z@=u2x)p!cnbK`mZ|$><_AQ8LKFESH4O@WOG^Oe2y}tA3 z`3^#J&FM!oYSjYpA=^Q*sC_fWVr84&yVsZ~M#WlEWvk~$0<6rZQ{seumvGCr(#Zmi zfFb}%D|TAzz6#zUfQ&;c@6@-3`y$wJRHB-{V%s|8Y)9W_&<+S-?ttkdGy+E``uvwt zmJZn9oKYORR|A<%iIpyHYviBjmS2eZ7|GG;=pcw)mt(b>x??zfK@R`XJT=)f6$Ne= z+B6(f-p9k8;@&<5vd~=rUw1ly3WqEndNc?ns;+D%d5{F!oB{Ynh4*F^_fd86 zR6@B&+dk1%U;oEd$2Zu8!vR~@5>FS3I&+++Rpu6;e`~8zWCRVemXwSnk9rhMm%#HM zmoQhQ4BjWC79AY`yAlHV5Y>8fcR)^*Q)?D73>}SLq$+-C>po`eoPPcUd-+xjAejhs z(?e7`ikyM-R}~&)U6;Wwa~S@KC+0MLX8#NGRB#@%WQyN$=DNq$2k$(^z1Fo~m03ip zhY(;X$PT#+a^Q#k=KR-1asiQRm)4V-gC-VD!s{>KAISi>f6)dcA?B^a>=xo~Eo2%hi>M;U7j zbNMsDnE!8&pQxi{+Y`j47|MSkuF?Qv#Dw#&{r6=sKh1!kC0?EnxP*;(`F>XCh&jFf z+d}Q6i&UO#-KK!8C6&O0Ld;#=dgRXc3hK4Qi?Ve=t*O?R0%C2Z7}4*Mj{p6wpyyYU z%wrr2=o*6jE=mX>fjK$;X^}f`#dQRjng3WHCAV>i#*nN48W?}Kf-k`*<dDx+Q{I!`Wv9^C4yMA})C0?ykku z29SN?olzq99{Z+7l>0dEPeJ182`F{e;~}8DVe1(tb4iOE&(?#ch5n8IOv8D1fh1+t z*2S&r4xn_x%=b{sEk?;?6)~1wfZd9&PfT&!SLmJIo`4eYox&)I>2SL|#E!Bs>q5^! zIg*%9=krO_&a_M(Cz3_bLU2{(-Bfv|0t`-%P}amG&D6Y_>NBLeu*!P_Es=V=!8zvV z@x~NQX#nm;zQC-+|0<`|GThL0S3C1@q6y`iq>@Hg@yaP|mVl%_@_wRiqHW%U69Zp& zT-V}j(C!3CMLXnJu@$x*xm_l@o*>_Qo8xx<6jqecvBuwrHoVfIy? ze=L;brBtze9Zq1n%8qHFNF7yL;rKwf7Wy6S;Y5^=(DFggs2c*o<)pfY=F)6v0&-jq z5=YNR6D8$@u?vsH7AvZ=1mq$W^&dOq>kbjZW!xA0=liakwb>&Sf4l&$<9l}87V?YM zQ5XDL4oRsUFkq(S$OYMB1|&Yu!B`u#t;fz{_01iHyqS_k0=F&Hf7RTJGUK%c;x+3? ze?+vMIF}*XCF8@LEr>nDBX}s$>Ze}vdp{Pi2$RraU8qt3*%OEhA7=C?-FxL0b7s9D zzyH>Mpf>+}nL~QkYQybR*@mi>inJk(LNAdEglsMRTUKBMF@PrpGDWU%8Zv3lk}TE! z-cWLeQaZtiq#Mhq;YQQa1t_7ma-}@!sm%q-J^y4_ zrXYvzMU*8~iWc)M^3RsJC2Fb=diu#+?b9T=Ci<)hZ8w=ud_pAE(CfPAxi?q7+H-_; zMmQbbycfS$fUfXuoNN_#_e2d_{21xlqN&B1qp_X;8Xgos8bi29Uike4B6-QtwK2;! zQ7<8-q@APvh$+%IJ|0#Kscg|EhEVDKkRm9SYhWD@Amh~hk$@jFN+Y&;!(5dDR2i6u z>5_wMZg8xEx!0?-szd5Wm@T&=C~tZrpA!dO)|_fC=NJ|_ZDY~ z2wt@ljs0H)6Riqsb|9;HMVa`K1D_JXk*}{p#$29$X~Akv8Zj&0vBige#hqrrQ8AR6^L_IV z#h`jCUGsLBk07Bg6man zWqZ5D05uNN_Jp9wr3+{$d3=mc6E|8X?AnfmZC)!M`cIhI7a`#Ydb|aB!ByA6+W7OJ zURn5$xo(>hJBNg}ZG#WWE8PlD)!#_<9j4crFIuokiVPc4IvK$~gguhUsRL>0n?VCi zH_m|0G4)3eEgNs<;>rBZ0JhFrw)9`9)TlEx*Be+osh{ne*g?mC;?g521N)pC3=|&6 zWeZ-F^+*9F!%66l`U*KNF>7<7Npi|v5Gqp>$8jz5YNVYct47r5oLL+rkb@94Nrk0( z;!si;R{kdlge-f;oHf_Cn%+lzq_TRe%w|O7ZT~#4KF9Sjw2{g!1K-urA(_Yj9A&JNur(BBC) z!rJRW7kMW50Bmla1i&hW`N}Gdkr7sv4ZmJ3774rX zzaa;cv!i?_2M?9##pOHMVTb85KPEk^F^_uoID6ehWYjt(B~T+|ASsXxXP)WP20 z|A91YN|YQept|-Z#<%yL-C+Q-|C8@R1>q{RvAEsk@8XG9M=as%(YeQSE;N<$uOgOZ+{e zwh6wI6)e+1fLIlhG32}o*(r?daW!>e*?v$Sc}{EfuCQC{R{dk zsIW|>PU`N*Q}UAK)`G5uHgf6zq%UuVe6OGr11SnA@S(N(VC19piO;Z|-Wi>)mV%Kc&VZz_HBN<6Rsd0BRicK`BeLYnGXi+dJKy*e54xE`p3Q~Ip!uFk{ z0Yd!RN9A#<$cQj|T zRutKL8<389uREal*0&?w=&X9PUyINxO#{iGuC(4=@S<8lZDUwkfz~vlJ#$?pK}u57 zTi$s*`-47<;f?>^82dR3N6Jn$V>eW4vuQ@56jC52akoso-lQ>(A}KisR^3Pf!p;C| zDTiVu)wBf`C{KJlTX+?ZM4G$+X?|!{xA_!SX~wg!B*sF0pKm^uU&V3St)b9aRn`F_ z_zKi|MPu_>1gF`te^bHb3|D~98z0$IOvtp-;TT+$sJ}O}z0VhkPBH3NVX2Fd3L_^` zX2^H2`Cn9H#xnbuQRtRifc|uv`*5Ca?qv9nH%(0#G~mKwe^05nK^gWtsH=Onv)noQ zkFXL7=A{nL-x#_x?OxJF?O!zK%9F0ztSn`9gmJ6EGa5@n6@taBbn+&dtyE0!fc615 zz6ssUD5HN_8nIoeM=J_dxOrXqXl&*jmbA~7gcFnEQ(XiKm-c(4?I96E)__>pR{-~~ zO|w}N4ots=&jrfz?u*>ebd|Z11(;(s?_YO56%)RGUS(Amku8lZmPcM zqI%LBSU7Ha!PR_K!BUVjrPg%9b&1;tDSV1$H_L-64~P517ddA;D$G6KXp8C@$gk^Y zIP<1#G6A)?-Oott0NJ-dBb9TH?@@e-yIcdruAMUp66gP*?!DuB-s3;ebWR+FLmGA& z9qmX$S~B~lp&@A*g(xabY3RrZMH)sF+8P?#%4lacD3z#4llHFre0_`KoZs)>$G!Kj zdq0oI8OV2hKJWKyJfF|k^X)r1Tr>DS$iB2;(83x96j2p7$7+oZH!9?#ZM|$e_6>3_ z=~a&*xMNB`Ft48Y`q^(RZq-Gqjgi<)Ib;#UYviz(r-Jq&&kaEp_8l-+*za*$!uGv# z?lY2kRRH2k)F?X`Mo$yW zUJ{_gJ&(gT1@bhquh1K`h2v{t6>18CD(kR7i=L_oObT0Od#<@Ut&5Xy2wP3UQ2o?1 z?EE|3E%;=M2~Y2bWMdK1xKXm0NBRe#<`g#p-05%gSR%F+Wl$W8 zD6nM=RyAYBjg!DSa5%GfV1b(tAz1QIwL+4_4)kULRSt;lOA1vTno-Hw_ZB-TZ;hZ{ z?Mi;^#TinFD*8Nu3`0j!%a^pF3fuKIbUE8y&6JxBsPVl?g3#iLnf^kWZ80?Uao{wyLxYeF7heJgG*#GeP3OKcBKL;Jb3FJPb1cfu%Y^sNSGH% zQS;y0XhQmEY<5$H7J*5TtKaTg+QJgR-e1;!VswVy)&*9Zl8(GP8tVLwMKO2BhaXS9 zq=GT}K_4543_vO@pG{A^@T}B>#M}qcynww5y92(`6seT5&`lR!qFRr`|DNgWHXz!F zMB1BStHD!ooBaaUZN@E2AE_`Oac!!oo2(+&zrq3tU{k{Qn2_vdkIU)uBj7PFqD(Y3 zJ_g$F4bn`>2(>RXM@Qrd02gPP-LK?h(bQPMc7OEySp^ea3BdjKS6;1j-Fv&;m{V!H zf<9@#u~^5>)38jX_B8=r)6n)~W{k%e;*IFens5GKgS0I_i^e)Xdy3~8S6Y01vLAZQ zUvm`r>yr1~DHYvoY7wd)Dc-}PK3R-x4+4@v;DZ}Nn#*-Z9%!zKOb9uut&GH&O z4Eyq@dgu&Y;5d4bag)F<`~LhYTwfTaIC^QQWczPTfq5uk>S!k7tyME&M5K-BuqvrN z$1&@V2{{jU{XucIF~R8n2uDMwJ@qVjajZ|sAqmbHJkcMwm0rN99?OW;G@nMO2Gh0o z)lqtxFC&_6!nW}Y72z8^Ti@)+>>TAd$u4RLHAm=^$ZkDV2t~P0m?@Vux{e0r54cXf zJ;rx~OAuOko*up@!@3foZe_#XS6yB*LV{v^g8HOXKMDjGBs{(r1o-$+c~m*;B+;FL zW2qwl3<7`FveC|=(j$0Dxlof*Iaf2qJw^a03ef}75bv;PBv%ig$%fNz9=2xSt!qNx z?$0A0Uv!tI=5R<;QHj((fiC~$q}b@n64-FXhHXm2j=^c+8hey2n@Jo;yO~KSdEJNp zuW3*sZV>^mO(za-ezZ1ne;Ja=?U+B&OOT?ZB-TPGN2l|6GN}wcG$QwlIYVR^`Ao+e z9AF-GriyDsP=pJbFxT9hW?w0}TF^nW2otp1(Tt5BWcYlLkR9w%Qonj_oUzX2ymu+o z8t%^%Fid5t9Q0Y%IMqncH*nQI-V1sTX344J1WXyU`;Gmm!h<}nQO+U=JWbA?{RmA$ ztlazbG{FhX(Ne7LTq@GI7Zl!oU+Hfy(X{))T2HS!zeeMa$lP@$Gxu0`zuB5k5(hDX z5-C4YmPy|-9b894IrX6WJ>lbKI`)`)y-F(@RfE{XS1lzV?7K=$sOHL=NUiXacT3nf zOGof>7gF(K^{{(*?yim4Q>G-t{Z09OFE!NqaX=mHYJq0SkSmXQwa~eT5oXeQcBA#N z{|vwTslVCrhO)k!=gx4ZFBT}e8~|_|^~yEu(cp|_+~F*^ ziLNSF0meXV$s;{+u7+Pz3EI&W2z})Y-Xl?5M`+tMe@-EOKbf6=zlZxEbFI#ZLFaDf zAgp{VR>rRN8e4qHD?8=>D3hYcTwjF;2G#-HV7O+LysDi%-~6)ND|B!(No>rz8^Eaz z4^&#h-by5DVBwBbyAHJ_36gFK)gx~k28+BUbsvc%0s&gQfuA(Np9RMqNd8{na!0-G zs4MD|T%*b?;i^xuXe0G75HLDt#U90`vV9kB_{W7fy^;u5{9D!m{IP#zjn$1+dL?XfE7-1TR8}O(Kd?BqtmZI5j9O~zLT%z1CzNgaJuC)>UHx#eg!OTiUKz; z^~m%Av*uR?Zkm}Xqfl{t?lp+4dO>ucyQ@OO=@AOswx3~q@Jf;L}InnCVDy#2R7k|n>IG7}vt>-+l<*a)>3Wc~BntJFB<<@O!n{izXsixQ91W9W*U!LvAi4=seMKFC6(;|3sRE&5(g5HEDP{Vpnx=q{DWgi7qIGPR z^?P3xwk{jRMZ=+r2cIM#W~laMAZG6TIzT&KRL~Z;I3FnCd@5V1u9!S+&Skk#V9H!* z&WpU{0C?Ad`FozX7EUYXAHJ8x3)@C8Jhrm$Ly5?p^5X`hd*%j+_VT48bD#s?>cGNC zV91GU0cG#Urxi;fwZdjCK>MIk1G)dCdAOZ6z9)$}uojYWLE6qQRaYRgd@IkjL4=8g zbUXqg&SvaB8oUV259{b@tz31)VhBZ=>61w6o{l&>q0lj{C$#A3c$97NMWmA>i5`=Y zP;_V}RhZ{llA&^(G`y`Df1DfuihT%TB$-Y?aVi#MLLrm(TcJR>m-Y-$C}baVVPqbP zK-piqFYAbT>#WfG<)S7`$f*a0WXizv5w!kSgTj5gxi))@)0^0idq^dpCI1W8tmQU; zPofG57j(jG$(Nu?5RvDBH>pquw2&gO71Elq^z>5*YMA4gN||iXAME}p{S83uoOyY}4Y|va9Wf-tC*pexP!zY;T05usXl%4BDWEtr#v>s_`I1~PuSpm_8n*c#?)bUP`K@(-vJvB3K3bIQVZ&SDcZoc;xT2%i|!W^NC~#XrV3@Jl}*=I|5q4>W|v z-H({R_+S3uc_Z4ib0DbhJ%49HbGqmc^2ojs``DDwNGlu~oP&^^v_0+4dx8V zI6s62QvfcOk!TZ_-)dO^(b=Ig?cn3j!O7vcpEm-0#L1o~Kz@@88gEaCduYaf*Re-E z36D$Bm(K@iqfRRB%F)mcGH`+$1`WVkp>kyEsmR76G2aOWSUcbM0ThikV5;p2mJ+`8 zouGdj0=^*o1z4OX@fk$#S}&ZBs8QYXga%%HRK(ANY8LIhhH>^h-~~)oX*CIk00NM= z%{&4uGl5`}Z-GRIqvm7gP(D`pW*FlnG-i@d7U?bt?jir7=Z4V;ft{aZfvPcOVI}9? ziP%9vn};%a_t#B%q#zzjwumZ^9W>n5_aUfYC1gjS*0duH! zr9e_;3bVi_xbWiii^VkAcnIUzO}&`i$X%Jg;A&f`Gd5~xA@T7{+;5~SXpCvoL+xcY znBTXum5Jl<=89L~2B6STdVj)h5aA@|Br@Xb*BmTK}JArhP8(I91> z+(sYS&a`F6*gLKVV%|~PWPI@zqV?y5$itk;jbpAmKhCA+zIhd-R(w<#K!O=bAOVZ; zUS|!?=dd0*aD7N6`4zM}X?zFG`0yu!(V3(UB%L*|*SI>Izj}$h@G_bkXyidfJZ%|F zqW`9;wM3zHKqY-FjsSFHYJjxw4hBQgT-iq83ba3uAa}ap90<@U;7o*zqpfS7+Xq{LJ9Yp7&nHlYs>z?9#w0y!vudPn!XuOBN1KI6aG!r2Y&5GD3%2ZlPs#+Z zPzI^RT?~xq9jOAN=5)%LG9B@9L}S~ooGoAQg3XZGJ!KuUenvQ38Ix{*}v zt(6c1R#QaQ2n(XWg`_FaQj^yr4Ye`nq&u00HyJ0E8p8bzt39)P3UwFIXeiwD+K(8= zaZ7Q4gs>wQ<{u-s>sD#i&mAfNoL?SI0d3+ZV~1B=`{T8dtQ$0gl2Q>HbO(TQxIHc0 z?`#Q(lWDqz=hekpRr=1%39fkl{d0h-y$EADiME}PSq__9r1PU;$#|@lXrB%|_FUa? z1WRrdUnJ|p0!)4Cy$YiTsSQ3VJBv^sx;>5fqh|wIWTKzxz$c}Mh!s%MMu7s+YeeZ& zBxH9aXxxXT8(IZ~`=)aoO*@fiWrnq!n9w!9tg$3=b=Q$}p+Vj338Qa5lpYxE7=c82qAUt;|!lt~ilCuUGsVRP91FWIT^1 z+;YfeVsm>Ho_!(+n1VRja@tX|&V-lPa8m(Ez9&q_7+qso7%LDFL$*}=p%cACp`3;| z5n727^=zYEj7-0eL~YO@i%{57az>^6m@zI7`$qvHKz>3bP_08U@FJ23W&CUyro8eu zj>6+6+{UWm%GNy0f$3fDzWEjDxO3XPK@-rc8qI^EU zWXuUX$P#StoJHb@Uh=Qiq7R}H5wNy|w@Nd!fr%1Y0XnF%242X$?dD8z<3PaNV5QtMA zW$kOYcl*3CU~9t@j3wjwU+FpNzjHC%IG-k#FeaJ!d_SSX)cxGU&ipKoo+Nnq*OfpV z8(IP?G??+5xM0G0E8vcE7hN6GK!VJ0P_r-{2T>2)6l1EAc?~Q#Y{-W;DO&cw_YaXO zVP+X=6(5wLc)H&f7;a>$`; z<6r(uv$6JHa1|Ti@nI|=D$k% zPmzEf6Q6&lb5F>Wh#ai@QP`28%q37r(fnWmSc=a6vRO&=PlJ28?PK6?@9C!;B^^I0 zNQ(cbW>I^!FzM-u`dLWS2z1$E9*arRdEOhBt|(qNDWj(#78QM`#=1gfon znb{~_fm~nZ9%{XKe4ZQ}Fa^I-JlDtL_Xcw{j$iMby$k#x9{Me2g-p|`WH4E2z6jNl zEy+Zf9C^ea!rBhP*RkC&Qvc}nGwf|^Md}n`sL|{T&Mlvmiw~bvz;I zm?UP-+Tu$*GRVgxzH7JsI~E+#~APzZy-*aA(5&>x9gndg<4Y3c;W^bglA%jg8aG%TaJZ1@9mLn z4Wga%Iq4+PFRaK)FUm&TN#@L6*e+g+)nohJ4-QEM_k`PHw zAa9q@RQoaMin~NtV4gs)fxNG_KuuLgC@2No&obh)kH$&j!XvkQShWgKP1WP%qf7K` zNdx3u12fOHP%_q|xhPaJRGB?fd8BKQaI3&^7F~)R| zUvWJ!Cy$Qw4@4q-)Goo+jIJ+(Hilc`QMV?ohYQZp$jIW~9X@Yv6e=n~4}B}#Iyj_W z`-B3&1m;=i2Ngag`yv{RF&&R^VgwwJ+8N=U^U;~pY!!})xa{!=5y?a6QXmn) z`^e5b@uQ$siSqJXvWugGF8{mAsg}0FR%QbBF~%T6E0azUTZEc^Ir){+0DPJkBwfOu z2tj}!A8Cv2ha2)Dltzwe7x4#z)5fs^$2hIg!FSmQiV@mL(d7lhnmoucFWcTNAxy`5 ztwpT_orrKgH-{=5RE=-Y&0Eh$AEq%n{!qZ|Mq)%@0nBhJk$kitztn1KK5J8+M?*0M z`;P+noU`h{cGT}-|C^SiN!Av|b2+%JH(op&fyI=`q)76Ns??bW^7R7Ao&XD6GmLqI zNxOed1^hGG^@Syyq#V%qU3~KQlzhxU@KgjTX`?;ZTMaHwF9Kgh(C9zegF#RCD@L8k z@PtFd83DDjmzq&TSAW}kQ7A3$oW;HJ{p}xIMNmGCU0nc`izoCC6k9aXewBn28afDr zAKIP?c5$W4aKCv}7Rixm9*Bkw?;(hIEW)19@EX5$%x~b(AS_Drae`yqyG-aNUH}%`Rhv9obnv1#`H2e4^YEZ!*h^N!Bl3a_$QVfLZkup59C*F`Sppv70ClMewKF#uqpVjE=h@Py=;PBSX(ES(VQ zVI$l=MM4#n17snM%W0O{i{3JkrN2U29H6I>U$jlQqO zq-R=9o-HV^R?x)r=0#m-JMswNp#{J$r`tSlHHK|y7M@V5m$5U(;FviNHsVU8V5B@q z3dn^0$cnwKj80UzQdBuce?CexX6XF9CPw|UmG)zuuhh1eNiXg&y3u0y0UGS|3`)i7x%6jArO%ZJDW^ASvW;!=Z$5FPafcV`$;Cqel?Pse7ip&G@D)hvC23G6 zlzs0D<5J&%GBXYrGZ5WuEe}4fV!EOI@FYNjzTg>gmkG|WO;=wyF}W#vISEa?$*E8q z&OJb^Dw9q$F)nFahKQtVol00j%6rTV?JuKqXPgC&`>De`iSV0i^3Eri8d%xST?D$> zN!EjY$PY?>L&c~qiQjbzU-VvTOEgv-Pd&wrtM2ljr%S-;V_^*?L(qx zE^}DE(^<#Cv3yGLeaNdW#7OW6l+OaKR;^T3+Sd`TFd{Sx*gX$LYhu$7S=r{w@1%0k zdhyv>Js}m~5jMA%*rVQ93Ke+R%%DP>4V~kQvnrL?-^W6+_2n`kr703#h^(f)#Uu7lf6{>L zKveB-JLZ~EvWU(-i^sf;X>AK|IR81M$1l?Tz=V3xkuyT>8!+ctg(T=qTHseu* z)nxy+HrvLaqHh7@-YvR|Q`dtx&d<@-|9=04A@)~FB1|^{;mJRJySeM5zAKeYQ6U{i z>#{=fDl|0A5~aN>I>#?;l6vXM;S!+gzBMvU_zVMn`4n1q$?GgVsJSN+?%r15a7;># zse7pG1vO~yck6-j!tj@X=FLZt01wDNI3J|KmmL+^g|?Z(uBz8SM9hi}ORq!hu7 z8Z6I%fvhGcM$cAY>2&H$Xca8Q7m{3s3|Oqnc5K~Ddh+If;s^VpaZD865YLYyhV-SP zy-9R@szNGaiUz6Zp{Twy6w*DzgkZ^Dh3c>x`m!7g5}C)mm@zJmTX-|A1w8VxZ@6OR zckL8dt%Rb4*QuJg0Qgy*gU?#14V`tF@G`~hL>6RBeOBP64r-m_gHu6ep+AHb;`en> z;+-t~u~M1!HBI@G0jCu<0r|e5U{Mn!XI{Or4}Lg z3rc-YlC^ib_KRZx3MP9Um-;l59w|XCiXp}?oUFtK z#A=sOrPMNqj5kC(4|hnZZn=^74PA#5lj}lbTJpUdF+U(CeBe1Ex~D#$C>>5AUT#H* zZ?bPFRuZ#BoQAp+w*(LC24?<e+=MSVzue*3hbgLt@lPzbO+NAIum#VT^P&f$2c zytxONhRN`k^Ip91Ob(UDG>$_Z6NGJaoJFx5hEakaP`&oJL=$liO6<5zzPQUgf)eL!m(6cb>I_}+n_9H)^Ij$$vDj>Kq^hhd_f0S=ic9tp%pl5fFk zatjS*nhj_}@JOTt)qD$s$k2(YI9!8qNTvt{&K7#%;khrC0}0SR`L7DL)bTKBwCPlr zSg^7LcP5Lmg_6pR@;9lFIdpF!7t-7+_lZ>86X9P5(%=q&nJ$b=kQD+6*(tl6Cvylo zV-Z5&0#6@II&A_1#4fiTw{^iU_)tfo&batFLBRIL4IId3SA~QVVI*h&KL+c>(^Qw~ zz-N=j$5Cw+<6I{+Cco;*XQXj3hd`Lt-^c(2ca2W+}}f(LLTa|xn>)4>~P@7JQjrBe?)7J*CFnDMXT8Pb2)S~5jCNWwWZ zbWd^wniQzaf_+sU)S+`w5*r&}+r$_Cbu$M8X?kB59m$ot{`upG}9!nnmGOGhBAS>k;i6-6c>(0Ei| z|0l>JheYOz!)P3E?lw&-VoHvN^k~?i0}Cx5dM(4t)E0dHb>3I2r+{eE94JiYpd>UR z@SJ(SAmN!w^JzHiScV+ascDan&znbPSE81+ zAaBy(!q=7)mAK`TJoHAm8DpT z36C%bmW9u{dxX;|JP~MNm~$^ks3mR0OF^)=mC!y=!ulWODb|vfho+`jFU|=*NoSEV zkKe%!36MEtOQNQ(E((1wp#&p?mxMTPWGB%FJtWT`lJSV3w$RkYfMplYFArdywnW)% zS5Q!u+RiB723f0W5eZs1xe!S~9qU&YN{UE3z5sWMbeoIbVHof$QqfzBV`ia3y>Mnr zfBvcu-ORxaF%4w&N3D{6ElQBY?{Aq}D#J-X>=OQwp3sN|n!=>ly%7Eq4K{wXkXFQD zy{EskaH}?F=}O9(4W_K{2q3G6+z*YEZZo0c(R-Ig;+$fs7ax#bfZ#!RSJG3)y+W>i zm<}xyt?&zReTKX^pNLY5ibzX*AS=b&jCx2HADPd>^6MSog0zO~BGhp~Jnu(wlMXB& zBUV%LTR@oKVQ@C=&oB$q;X|>w>j3vjxf6BL0Z-BF+mo=9Gr#PXlj$dr~s(QJLnB2ude zYw2>#H}Ef}YNo4UtJj(%s~FFC5=yNm6IfO;|*>2iAaMH|Iq~JG102Q9Vm@Q4;0xk@cm>j0s-O08PrR zJ^a6XV_l0kG#`9KM&E?%m{-wGrah(NZ3Za;PX(hSyO7inJRcY&eXk%>e@HgQ%@iiM z#2zKN4oEs?V|8z<1~WgzuHl5W7Ry0tQqW<@;7=VJZQORL|W!;F5E z#L=ZY&0_&+1kl#w zB-9H4Mbf&n%+kw&vO2P5i?9g)OMJxs)-ejg0%nu7aJ%NAWhw9b~$}q%Xkd`-sufk^9Se1R;wK*%W-318h0Bu`FLJm_4r#gv z>u!kmlJ?ZI6@T}C>~Ujl~V zKBOHZw&QU;6$;d@Q#ti{*o!5i^_LY*bMZRXlJac#cmzB`2^_IrI9t)wCv0d4h{f%g zzHhY(H+_zcnrU4n-$jlD|5TKq^)#OH7JB;yPz$K?)R8C8UDT}{+u8p3R{-_YfAUNV z!@-+vuQmfOBC<}O4_ULRw@jxQHO0%fkV)l>O8F(ho}fi-HSE2~6u%xbHC~ALavYkW z7KI?ycMqip34!sOr@WDFz*t*ix9=a#$5 zf=}_KA-;KKLXu`FeOI84T3>LM&0hBB>^wXQ(l{~sD!$NjW>M!b5Ji=4zoPH9pw+ZcbtjXypsdn;yj&STd} zGUwtUtg=>60|IjoWbcj-Hn;DJqIoWH6rRG6QG)}0=MRS^I@1Y2Ou*2n%mk&Ry@xDH z?Vf%`_7yKkazfpGE4I(&!<9QGATkZ`Q|B;@8dGjMmfSz>?h z{SKpIojv~?)rc1eo+iXqjyvv=?7eWkG!qbq&juAosI^q$AW8Oxh58=0P%o|jQ&lEj z=J8WHS47g-pIos&MRMIB68e!1n6y8@REa3FtLa332|~lqydngncuq|GKmOwpycy`p zW62Ys=IZf)jT=V6S8RnnGVjnFQgV=7e7zmo-}on!=jTv-Cwl)8KHOvWDrl()1Tj(u z;=@XHu$!qAI`h_KG;uV5;n5q1w0q&v7;4D6hO=w9?w6{F|HW-e)v5^aEa%+h(99w`Iqc-Y>g0P?AXCIKX~7U(25PA!aKQUanBdl z-1PgVQ2WatDqlE_v^U-wzG`l_rZVg1x5jj93p>f{+rMP3y8hhxOW>Lh56Hv{?foHO za^qKmUiZNPboMO#X9SinuL7542HC@NjgP(wYC^?SW(=Rwy1OSDOh)lZ`5>|?yYOz_ zkH39-BeBVPt;j^l_?{UzC+BM&M-0qhPM1aayY_$Ii%1)2+g@D>!=BdLuF$4b;uxpI zJ#CX%n)DtX_FK5t@-aaAt9LH-pcl$ui3Ir|m%E>QpOie<ETK-0NQx0D7?WI4y=jBU(;DPdA z5H-rI02FWp?K{JEk?A}~eAlJy57-;wR`Gp`+Ui5EAERA07151Nf>171Qj`6*3}Qr# zcBJN97d{rZ0{3vZ=$MP?C)-?SdH4+nf+nMoeC5|o=b39cj{+a6t_4so-dZof@U7s_|Wo%ZTli<_V(0^pfH~j>Y^BgSCr*|udoN+myc@F>lwPrBnr8j+M zMWYS$V6IbUqR|usX(7`yj7yRmFD+PCf*j!c>D?fnSv0(@{e68K7=4a^H_s~=L%nZ- z=3V^UePBv+U8b`r4*n*`EwVESz+4U59L}+BPT3~sSQ17j8em76n;Q@IC zCfl~`bp7Z%xuYjL+`KZO{Tl=a1{wjFAj`ubrk03aLdeZAK&`e<3`!n3**l@clI;F4 ziJwO(!}sVNVcl4B==IN5=@P9RIayuk59yfg>x^D&$M4H6F$_S2+O4GI{#hzvm_HCo zv#XBmjx$C$9cSSm)uY|YhFoD?%=B`ZH>a_eD4v-MU`$|#$4j2j(O_aDU)qCFO$`pC z4(wSRHy_`z&qaR61#V$>)0qyUHt=*dtIc{;9&1=t^R64mv!o8O$HH?UH6u<@Lc%f% zAzvCwm1%JtEd09=7^-Wr(L&PAGOe9)#^;QoeyE`hgtF>#FC;qBhNP?DfC;U;tJpi{ zs8%ld3GP_(VgQ(`5pQ(Jq>ktZfcJtQha&@(0g$L0Grqo!Yejje3a{=Qp8v(-Hn!tq6eHqr_Iq%QTsJqD{+6iXp@q+){adu}8#91pJ0G2}hRH!7{%b6$wj*)XdvDr?e6P`~{CG3^dGk!pOUUp1e9*NrUSOZ@S?SP=VT@ zdJGPg9S1?zZ;D0DZwGxs6^LCeSr|6R_R-*Mq`dJ4-9dR$`aG34o}`V|yiPS%>(E=)Qb$zg3gnNAvL!bd4j$z;LFt;>5HE@bh^lp8An> z%^3ED8M=f7U|Szv5Ddz^68NqIX)V~gGuFVY7As66j8jNIIeKgq-^zVaRkmw3TEzVu zO5tbAjZmO09Vf=ZwH<8CUDfN7xqvQrS3Pn{QN6a}37os99H~l`&} zDXKz==fh$7y5ZGaY4d9_Mvvat=pLADx^JxpEJOB&Z3$ z>BHWyM9(+eg^IYw)fGrS#TM5vUu*$ASP$2X!Fn%%&NE-vAN%9uBdr}<4Cg^N*+aPf zVDM7XqIcklrMnJdmdG5p#HtUvdi4x6v^BEp?c2MjsM$7^!p1&UY$Nju45&DPbN>xJTE#ef`X@+6W2godG{2Tn@F6N<$wg8u5 z8I*#SarOsDFd8Kvf?bii`)&XvAgQVHIcxVyyxc$L6a)_W>9G z`Lss}>ReJNFGDgy4-RyGj+EaSsVUIc?<1h1DI4Y%3GV7mO^Kv+!!~=4gP`}?CpYj4 z8&{?rGv?*;o9FjHdyDjrFOlfDDi9D&vWGh_aGWlo4Hq^b0|kO@WhK|Hh{ripUEm0B zbCP9qKexbqkS0iFZnW<<8j*Dwm zA*8gkMs`e$Z{CK|7{4MqJ0#1!Q$!ZsmYFA2as^T8LU&BczwLGt1nD8 zSQKwRwEU!WbFI}~4j0YHBFrnDy`?@k@@=ePOZytY_iSp)00Lw1oZClHWPB?69=cgC zN){QtyH+<@=oK*a>RL2WX{If9XsZT`jfIc+F@_zz$-wz~#m^kcQlLglfQ4UuJ@#=M zqFjoQR#>W!|EFk!v=Y~BgEZ?6-nTG0I~p8@E-7?iccfVO$$?IxFHjHYN23UBA4Q>J z0n@|wEcZt)!yQ}gvnNuF;0M^BOkCESrbGDOQ3byPTGDp_Cg$0-S2wX9z?A4A@Yq*H z*m%Xn?qTvn{6QE-+oWBF9g=N*saAV`Z_|0srlT#7_uN-B;t}p(bcX)*z2ouc)B0PG zN;$5&BZZD%ZqbWc_kY}Iz_!wH#v<7{ z9oAe6xZ7PmO2SZPA5QejR!A&|d6PNDFxtl;!B6FM|M9jg1sPPav8+f$M^OoBS4;9{ z48gQv?D%_t$q*{dnz1+7X8z7-;mxZSAX3d58);~02+sHM@fq$z6Em;Fnl*?w_4#X4 z(6BEE4+}PDp|)KcfO9Lo2qTC;0|vH1#4@`o z%h_pD6+ZS`LCkf^4+pXr1y z08P>&U>qv)9|ltWgBTfu@?Y9@^yJunq_MjGFLaytL^d#_4X<|WDMw$;Bh>~;RlIrZVkzB6eLdvND4l&($UM-X>Lt(}2O#N+9O zTnh&)Y6r0SJ;36_@?t+3`q|mB#Yz*x$)dJva_FKg#ulR3%MxFG_)j zY#cnPZeja!J<~+@2xka__dZ@)a#!;bNCJCET1TB+oAMq%UT3#z5P%Y)1jXCWp_PrESv-W?7( ziHufB`>Yter!*bOt8WioALkP-7Po6Yfq|{8;q#_l-(xi(`gnOW%iPrC&ZF(E%}?0eHb=T{4>Rvqm+jc3U9A1~dEO(hv%NxerbW4IkxNu>oK^;w!{W%&b-(NZqn>%0+7>6Fg0zUZS5;V zO733NWRKpV=e<17a*!9X+AYu1GhDaYOEUFkG}4fCCAZ3R3KN) zmQua$Ws|F$B$T*!XQqRd6F4#h<($sX#rk)YJPZacP2DwlghB{W-P72&EUDnT`)&DZe zBcz!FHo+pm!1{a*G!b@q%fZG0RJBWY`zL&ES?~@T^h6716#S|o)%bfWKp5Sq1ZaH( z?n@U%%EPa+W7Hv@Bg+v;%ZO>^(;e5>nl9sHYZF}c22wfAkixepoBYmQ#e>Y4rd=fe znN2%VY5~qybZDEUcAitf95Jrl-=Fqv^u8h~9yRpQ)na03X>C$0xlV@9XMS9k%cWcm zA=|amzpL|_HM^^-W6g~8^|e;ce$z=+1PUZe-XP)i886RH9z<3#JmV~Jd!o_ewp)z!3&7_K>?l9pI^TU^(rD$1k< zH>E!nS<&B!*xfNj?L2D=ysP$rU%P8K{-Iblsm`SX6MG+clWX|s5X1}0_IfgLkt~8~ z5E-{(-jy*+))u!$A3bzUnwle0OO3~QnQxIpcll${_RLz##!_%qkklc2z7+M66vqy- zy}^{Tj96!kIkjiSMj_hj_#YMVn=;&RRef03&@pt-_k1$~;GG|z!{quvS7S!&&}}lU zelhST$|jWa2@MSH-|p&Wmu_gn3^1dT(MT9wNf0pb?G8?VeakRuO+qz$-z?YmNr-omTv zwvS@Vwn2h={D~^Xr&1WHjqO4Rs2QP!9^}4z`R__9kJ2&aSD6zi;VZKg`EVK?OqKe!6MQ9c8N;&j@59V*1 zGP6jj13#O7>J6rX^>aPBEg91O!mB_SOLm{2qNA-kt+eLbn|o(hANx>!OOdp3)k$d4mjhD0 z=v!*6@a2)nU3i*Icip42^L5Kf=&;*eJlMIbJ3Isv{Q@pAFa}YtraAFp6}O`DF=F6E5$nU;E4Ajtl`L8!%<*Bk$4t=?g@H0fiu@e!=ym=e0$ejHGhM4xVD zlQ~HnMQRJwbHDHyn#9aDZ5ev-GJIrEJGc7~w zG!iUp7(u$Gvyt7&Zw3eP z6l!XzE&ZJfNZz!f*KzzY0BTY-U9CafRpLim;TC7z=&wB)OF&`!adRGcsc|Mg(Hg8% z_qM+rT=3O0bN6Gc*Ozq<@lXnCZG4ukTUXgBBf%mqFYmg{r2NER%a%3t9v;7U{C`%( z??3Ad$@qrSMIgwyx^6+9(~HW#655l4D5?@~N;?nQ+(+G376*a1`g{07Rga)t-3`C4 z>UB?{^-pCHWiz1E4Sv!86Wlx%?ao(zj65{Cl$}?6L9f-Z^niGNIhof5m!60x)R`!3 zuDa?dEM2A2f6-Jyet-PP++ua5xTl^vGD4bnthuWatTrBvW1P}TKFL<~z)sbRPb*55 zSKQ#*y|{^te{lfy)p46v5(rA0l`OL#gGlNj2;PdCck4viEDRygup^N9TButLyoMSo z(kHRZoa!E4qJhStuMb?!UcSDt7Jj-1WA`p9ML?*y-^Zu5Z?SG#n@TS= zLbyW05OzlsoUY7Ka5j$Sflyuwh&1{ksyKikmPDgwG+{ds-ZJ*v zlo=JM{jb+p{LZ5IT!q0v;%Si0UWld4*}X>LoVRz%>C>mHjW9wsaq;WUl=Y({1Ifu^ zl@s!u1v+Ru9L)M2g?t+*`D93ZE>U}L&2Jf{^jSFBw0?53t+b$imb=BSj5934KVFI@ zXBocA4uB+1$eZRMJH^l8v7$yz0nb*6!@wr@f!8XKFIpS=ICApFzjiH{>yzLsAynwX zXYE&&XA<=DTPlh!acCaX&pPQCH9yj!qYlulqCJ*(+D0l@7o`7uOO^te4!u;-e}0Ys zD_FCw_LtvSjFwS!-MV#QDaBWoZgn5cOX%-+0A^IsR*pRT8&$zSF8*czd_#`FgwG-s zXC5B;Bbe2V^&Au5nE1Irg8x}U7J^>}CYF%P1}q^#2WDqG@sn;VxYlw9Jid&3#HNSU z@Z*if|JyM6*Eh%S(GC39kK`<4@BVpH8jhoqP6TsNfuSc0L=H-~h^4oC+H0z)1Fklb z!{6qAwPb<;#V-`)li|$Yf_%tvTNu!lAuJ-_#_!*%k25cOno9FkQSGqITtxWZ#MK#3 z#Xho08v&tE|Lig_2)yZnYVcOf0v){=F0tN06x5n z3sSITAg(q_Y6_siLYldHzlHFk1|7o8C$55gs6Q>xpv42R3Ui3I{PeW_8b%oUww&~j z8IL{mvYn@?Z@}U1D^<2{Pr_(~&l98*7JlyWZ{j9MEgmK8BI9UadDE53?(4pyFNRNI zw5b@=Ne`jZ6)X3dPj-N9^zHed%?&3!C1D%7g6AYgId@w(<)|So%Z?beGkmADvR#}d z)fc}iY;&2)feq8k(#bk#(oS0%#mIsF0VY1hqR7?TUUP&L*@ST43Vt&J@+MU=3LSd| zR1Ow23~{5lBHi{rN1*0o9o#fc*$=QveUHIi7R553t5+!-_M(^62Orc%{W}8a0|A66 zH>sK9#}Mzp$t8gtf$6XvHpdb6?Tn*jF5L!8{zTMsFA1wcc2>2WOXa5PYU2~9cH+fZ zW}e50zdoSTYLA7mUQIxAva;?X@M;!C(F^0v8H?Mn32vPD9>(9CXz6?b$YV0+Q^{|x zh(l)L`+<10A4Akla7Nn$sPp9D!n+(YTx)fJaymhLG8N}LH#h?5i)ZpWs0q0{x<*Hb zjs+NYz;eOn`lnZG>!)RavK!_VWmQZnnD|2Ac~eA6brVbqxx6Dq&$Qxv*%zkc-@3zHyI^Q5SwMMv@VxFKJ}a7#|HE9oRCESO9(1Qri)YhEsNszK$~-apNe z4+sga%WO8beW+XyfFVi7+l9!~;@u1%_^Nf%bwpOb8vps*yG=;?nI@7m(II zyi|_;DZA*xYg*%V)A`I}g+2mlcKdwf&zHTHxWfBJ;>x>*EkR`-Qwn6DN=by=GGl0- z=v%QsWQXgBuZpo$(mjHOi!_SC^(VRymAx9nifl#sZri8-@bG8WP!ezV!jm?&{sySx zjBJt_`^PD;ODEjPl^zeC{8x#R1V5NBx^m?kvUQZ25|n(j<}gN|JVk~86AOyyMESb1 zm_b`2ec<^VCt`g-Qw1}7xDb`Ov-~BDU;5M10?mvQT9&5|fseMXbc7~4;9L+BfuUMI zpk}L{Gi#I5CN|Z=_L>aC%jH1qT3M4D0A43!*sQRIhTz+v39)Oe_gy%OLBSMskfxv3 zZ+6)z{bPGwf4+9L%~y?pNr$1$5>l%^t$82{*ujci8#!ZvA>Vz&Fd#_+f*qotr7jdS zo0HQzbEKg2Z%YJO^)EsV>$+7MHshbwX>XSMdFcF&NY3BOq(|Y!p4zpCbVb4rQLx=; z|H7v1h}~_{)%dD!%s7<6y&mV_wL&AALPUY8*5rOPt&dT{?`Oidv%?Z}XRYPVC^$1& zJ#!3Q4_@|muN0`*){P_ z_g+ntvm|_{>{?PSn3R%M8+5P1zGn-JIbtb|8SU55>QRTpVUClM(~XZLV@=bFmaQUjdwEx48S za64F9bmq$rX(_hOrjsiwUt3RXteK|g(V)BtTvs}B%?0tRRIHqLo4`wPK2O9Tb%n*m$~f&nXg8LAH!#h%6n)6}HEYx`a%{!B z(v#80En!q`*IKccAVq{&eb^CJ<`pC`Kin0Qja}}8zkV(u7U2hFMkj1Kx1?AS+In>S zi?e7NFrP_xliXis?#EFZd(j4)?SUCI$K9 z#aUR)uESj;XZ|51?p`dkjG?Jhr=orLPJ1jgSJLEMv55ft>l46lD!-o-oPQ%BuqpZA zt3_+XVsMjE5aLqvpP6oSLUTIuN>@4wE9LdPG&iESe_s4s<S~In^#}1y6-Lc({C;6+>CETP1AI-g|4b|D znVz6N={i&X_4EA)+*rfwIOlxX`eDPm!ZDG>%0h+1`V5~(RK@?i?kcTQ=JM6_8LuvX z{5HR^@6}Bkganicsr~1YJZ$aMdlc{ktLGIRg0Zby7)k?cqf= zbFEZF8KTFA#2gmRTV|2G-}%b)w{2b`hgCR>*;GZE)|>QPmEM{;qnNX(!Yg&(hSI}- z9ZQ+*)3>#Ic%#*MX@&TGualPeo2YundOIxKDX`$!{CdV@)%!OxON*9&JMS=8fhX$5 zzTFq@9a-u!n%B>nEd1$ou-Wo~-dztv7S58`SoKMx;Pq!@lEXuNTa(3r+aIEHwqMh} zt3U%aze5JD?=Oq|INE31I#;!&-n-Hh>1^fEmXg!Cq13Z-){&Do`*T{p>pnf7ygJt5 zby9QT`e^gc59e;XN{N4PQI1|9rTX{RH%X1}`|HgOl_G=9xUbsle{L|GB~jm)Q#8im z^~Y-|vqu73<`~2aWTlSWvc&f($#S-1l=K?ZY&C~F934`FPhnP?hp{OxsWw194|-ab z4qXI|{T%d~&py64tzXZadv^y9ZQgQ=q$ODetjoqONgODiUG%%E$mjKYY<-fqWX@3G zEJ|~k6>+p=CvNMt`$Ojd(pLf4T5=-Gy#pDIM@qG}271p6coI^F_TUaoyRow{C105D zQl>h0ne*@Yv&NR+kVl&%hIp^{ItgH1g*zL^5~D4cm=;~dWDWk=82o<`CH%|9|JM%4 zBY#|PK`dVI_V!L~Rj2umIpFfzA=~n6wly^`TBNosH&BKxyn9Ev&chLU1SQ9 z>T8lJuX9If8(m)CUa>1&ph?xuFL7y|cy+ z?ohIvnt7<|_`&LxJDjDT_C$tGHI*z6Pr#!|GSfQpAv4X?H>>ns(>tUBp|F?>B75g= zEOO4PlXtH_H+HpBUI&E^l_ojG=ep-eul^_wzkt@rvr1Gh1Gi^pS#GH zhf7vTJY&<|EWptV)g9jz-nWY-TD$Pur4rmB0lc)KGfTry>OJr-Fb^tOX(p8)s;qon ze}7T$zo5Ev%D;R%dYJ#Oeq&s=gBs@|sj+~LY?e9(Qc+Y~T>Q{`%ziC(i6MY$`?S%i za~r$#+$Gl4nJ&`{)GLt58S+`ZdWrlh6)%po$=g~!E;@hF=taPJ+sh&5yUwmPw$|2N z6S`|kV#Ir;$(N**r8tAPlLXLA^x+cwjF$!J>4k&D{h-3-;SAs+B;j9mW3~= z(PiMFY~1;zu{Y%k1M;hs=+PPBavX21Hc1T?SvoWFL#`|eV0_KOXfkla*%NK0x+><6 z>z=$T7S9mP{n&!tJg4L15%j__*jO7%8C5R#9c>Qgv}6dzXSSt$v^{h=snV-wv-GZ% z^|29R%A&kdF(KbBm#e>D&>{p*=EYTG{Wz3KWhSK$3{jwfQ%E}zwM z>GE~9yZkIOEiQ`8m5MpssU{MXxxCJFcvr+>Pct#E*h~Eb^XpYAqE63t54+V+wZGiQ zZd2woi?bUShjUW{S(UAjV4)YkZ}WOgRk??eypCop5NjZxhwjfZDB@;PIb(3fmhR#I zKS#wY?|NsUAxr`7x~cmlTRoIrVux;IWrVBV(U$27KhPoE7qqG!}^D5_1gu$@A`gu$M{tkktCp2gLVb*=vjI=1`lxwEQW?K1s!bz(xt zk6dX={%-nY=nviy-s#gr4kR0Y@#uZd(W0R-sC^(M6#p7BO?8WkFnN-d5S(<-XxG`y z?*rOFMccErFB=*4V*_`oQQfoYz>u$(^1XH9s*Ix6dBpT7zWvmRVfGf^%2nIEXHHaw93ZW{Z z6saoGloFbBRDyt1rAo&NqM%YlL~6hQQX;)$0|P3(H&*F`GybrU1=gie8;qUYx9HObwW}i zhvhW?x099fP-cGlsi`s9-n+ax%COMsJFbZU2kUNO9eYySa-a0`K3++R#n}}6Qx!-t@-b}5*t=3pE%4Dte(3Z#CAJ{E$8s{TP{8y#is{|g^r}&iZ5e(ZVBII{-UJU_w%!)LjbGf+ zd86LBbB0#$CW)JU;`lPRA^7*f{hQY$%AX5s^6wUZ6F%+VqT0C+?gCZ*;T5I$VpU_f zA1uy&>ti=)_f;3-5r6ZXE9aB4#QA(ixwy$Rqmv&8K&jIKi-AK%cLKLI%LU9AZMA=! zoAF5gYwMA4>B9|i5}&1Bh_c{G~#;s?Q7PdEcEv0?f>o;LDX=2m`CmO z`6N~rdhBh*E{)tb&L3~5=xO#|CO%t&>m|u~W>NjhV7v`%7*k|#y6`-qUK8qe{s1!V z38>$LM-~>O1oZ@d%WkgTX;=`dGAViRB`j!QpzcYA~`?U)fm)TNoye?Jxs z%Uhzt{$1m*|KxvD&4f?ZD#CSl@olcLwN17bN}v0k_wN6qSalHkuf?h;u{=2`-*U|7 z6u2?7?IX=>J~` z=D&>FVd5*&XQ*~rZQUbaz>N0)K*~aBKRqurE zPF?%xi8tR2!JH=K;_&zo#erw%murIS=RR~CMgY2mt|7TP`#;|Z)+!w-mD^9hGiM#t zgR|vAS-Ggl*QYwl{LSU3K{u9r#uAm7DFCByjiPj z{E;+c=41FvPOOW^dpSe4#q4{a?K_O!xdQByJj2620w=InysQRJM4EY}beS0zhSp3^ za?qs@r*6)#nQ)KlY#N>;{y7o3iRNlHrqkg4DbYsL=WDZrr?c^%@Wr}`YwFd>4V+O& zj}YC!vHs7G*`p!{Lf_4gHX+S$9>~7hVNUM=lci8lv&+$p&{k0)fbIiuK4#=BfCX49 za>;<;HRtm)zH8&s=#$6azPB3vhKb6*bngJa&W4G3s}t#VX6dY&gybyzj>S*$fT{UX znSy@59qAR@ah5G$FQS3M>kTYTT0uv3+Ns~4m0PAA&G|V!*Ly7No=?Z&>-|F#?94YJ zn+c3D^O05#rrh5}pWOh7{w0{*A?)-`1hZq{|4sR^R0R{%%di&B0SZcSoz4viIk*Ia z#vZZLu49%k_^f%dgua${?J6hwca5Z&rcyts%5L~MmQ0>nt9FfkTZ6>H% zivz;6z<)}CRM3xKnxN*ugUyRnN+orv~;X6ZuJM^t*&p=}-?94X57g z+go$}#?7^X@6S0CMxtsS!avu4Khn-V_BbH$u&DPnY{iMNwv{73yU_o8-Fw-C^U&Ao z5#<-i766}UN- uo49f6Ty+vvOr=+7V{|Z%fo6|`h;Eo0aBhIS!W(VriE zwxiqyRR;8n!UA7nd|_DnYro|22BkbzC<{J*)INniKg$mT@8arya`kLt)i|2l?KRuRQ)J0MfC>k7`4(3tz_qt@spwG5{prD{HkH_GO+jMX2m{@p@ ze%AW@Kx*bR`u79kMorRL!z5&++keV#vlsC8u!4btEu{fZ+Oz>Ql>gEe(s>$*-q-s1A#C#p~~?%QzfhqlR9h_>j&Dz)>KU% zcO_r&<7R+5{S+uRadT*qsbAt|za()ImQJp7Ko;c42R*k+4CiR>b+{h7-CFD+V!_5i zl&Je_=wkYtFlSI`X8P^FUqT1rtb?4-VyO+7a@1ecdPA`FpSg0d(}-(wcW7oB7kup# z>6B--lG!p02Bn2>S;Ggf%c)bYR&VoyY&O&SJDnN8kb-Mg675mz#n>dj%bL%nq{51R zb$KWK+9(q3BVP|t^6aj67v`7(mu(R?mBnJ#hR$^f2yNA9w{PJYta(a27kq-+SAF6~ zLCK1x9yt%k$1GoYC4!NFH+qa`JVzKy&Tr&(%CIOw{ zcDouBhAI1bjS8I-P6s6PKSCNuR%uTV^Zj7n7lFIT^a_asYf(=>3CO+&wmS%XL42Sp zhKt2DfEN)kzeFib?o*j_0k%sEuv~s$&G$NGTCnKpG z#Y6b2vHlbv(T6op`j^ z-!HRWd4t_86Qm$C;{FAwD~o+dfvO9#tFY~A2VZB;(n)wD zcETHR|DQJ^3%wB=;Eez`u_YB~WF}UrV3A6e4$UlY<9&xxT;tP6ctA8G@gV*NAL4P> zw;bQ+4e*Jy5`wn)j)V(4zz^rd=hMV8G{cx0j){lwO2kQxPd&_aatwZ}kkcEop2fq0 z=fcTCL`8EoP&wqqpZ~JMWp6&H#YzIyHNkj>%%wfs z+pLh{x9=KNEqq^Iv=|5p&p#qQ@s89L&?HIW#0N1NL5oCaCft@lBONw+b(`oVZAGW= zcDdU%jZK4{OhJp=*^lE z)z3EYJ$Y8z=q(U51@=E^W;4JG1@+!OuyviL?@~xPem?L|NDlA7$q?5A631U{0?R2! ziV!RHaB}*k>JX0IZrrB!ueY9pGDb~%RV7+W+y|=od{8P&*nQH&1e!y zDU0juYm8~1>)mv_P_)uzCe_@EQn(&$-U+P=;RRzTWB9!S;x%ejwqVx25fHSmbZfF_ zM316ylsp%WMMgS>rQ}q1K_@=BeZ0s$EEIjmT74qRNIzA#Ms>bj+Yy<_! z^y)n;#ip4O>Js^n8+buRq8P*;{rjk2&R{7DnvvODrBl5t@`8iZ4wCiDQgc;Pr-TYMJ&S^B>^+gVD!H!fr3f%H}XitWeV4uh!t|{`@SvlfgcO_)wGRIZL zn(KSV&p2F>*$&oQN9o>}6Cn#$!+sAF++W44B-MQ{9H zHZ!K`ikyWPO;NVyl^d(X1I|pBIE~)x($S7LhWrUcDVJSZgJe*8%CK)F-QY%{>uG$u z3+%fh6J6q%R~J0z)N~FwPV$}IR0cEK5w*A0@dtDNbOD3!%;$2u-$KNcFD^&Q%j4w0 zRE=YLg?)`im;_S?q1qbvCZraG{a>UkL|12C41Xfqw7FP{8$=AwwSV2 z>a5}^v_ldjk0gg@y66w>vE;#+@;___>BgwsCa65JWPwXFwJ~3AyTI`i08zw!n`2&j zpBJ-j*Jg#f7}{s2Ne`e3p#F^B6mG9V zUAW*8P~l0jOG7JD2Y?&V#p+ z^t>Fv9LQKC_$BGtCrDom{jEOB>xE}4rjUUM*yh*riaBjTo~foolfxN_8qp0_?_xUR z1??>mFjhB1f)W#Z_nu3|NUbMgFFP6OzVz(7SOxylp<|mhn5NaNXRPpCqOO;7xbMDjlGd3dHQaTWqAhNiA9dSD(j!`BY`YL1y)Kt;>58r4pou|z5=6p?9Yg`7g> zED`&*4cc$wc#=6|&6bcS@w}IXHg^Q&hzz)f46}J})I4D9NfIOodi}h(q;}bZbn{(A zu8M|6KB)MYYt1%F19M;!nR;q%kmagV`$&c^g)dKWtEDip0^No`JQO!b2dMozKQ&A7 z1r^fK7B?MF3e?9P^1reKTVV1NmNd>$o7kgyj}UfmF#w8tXPQbD7aOMmr~>Vbr!TDv zkrqtZRAb1NAAn;qpccblbFt4+U@c`j%UZqZfEf7uK&YlW3isd%*SaT0i*ga}ppKDW z?q#~7g+4hU_3jar3y3raCLaLzT?etxto*BvpKQFlP)opa7@)oWwf!&lz|(bOO%gB-vA zUUoQjcoTksL~v=7{9VG11P=q9l&B5Lld0dHxnJ4u=_nsJyE+`$^B11bP56!fa5inMK|joNGA5ze6|r{d^D4W3dOG(K}Dvtou#|EfeC4 zl%+7{vE{zrm9J*LjW^%&qr21&R-b7^l|_Q(lHKkr`)|#hwSrneh2JYp1}yxj-&&hQ zAeyF#W#zT)oRVR3L7a5c_@~L#T&_$P@=Ee+J87C4gH!-KYU1cogZk=^|G$sgjX`Qr z9iQ6WNmd!J@CHSuKetLv4-3ao$>h=da51AbM1~R0{TG5KS zME5(*D3=qR3p8jACjetX0pebSf6BLR_3t(A;)RH_uAR#VH8nonXDk$vqbs9J1(zI9V7XhVq`~^O?fdtMs6nCSzwb@ zAGOJu#dt>Ve&USvMR*%K(6WNZB`^&)AFfx;X{#K;^an&ICZ|DHIV$2K<=_644NmUl z@E+icAcY!odDh9Bk;@efol6;$l?O9v^P|sxw1TH^ZTcb|O#5E&Sql+i)~Rm>fy6tj zB=e#hBpM9J4B?*RtlGN;yAR4!Q@H2Msy)oAz-CV|1q1d3+=!VW3cU2JQGuLc8<@&I z?EC}sx}N^q%7zD#O5Qfe*7Hd{!xBJdho%rL6iTfWAummp)28D9d+pb#-(C8Ijys%G z$w7K)P-)R$Sp&I@-NWY%!c!NDv`>@6BPhA}U)fMaNM#SzjQEP>F`dGAA`hLz8kzRu z9-Qf;B`S*dq&e20H^#79-Rjr|9)swiahq%c>7wqK3v4YN6h5a%Jqpq8Cq#=8yN4Bs zLQFOmh8vvrsURv&B-c%k4jQuK!d1B)F21d`aDwe1quF4_ed^u?xP*Ua-P#(#RxPM- zO9XO*N}!L_=`MDg9OE{K)SypujYC(v8z)J!^9Ip91qFVL9OoMMYJ&adg^G<~SN9%L zRD6GF2W8(?(;Cse-vrxtfsRV+7XtBR;@UwHZG+_WOrJo4>|#(khV7^f*cc(U4{j5P zDsmNhcZshx{Lxu*e}P@c4?@*-3vmJhM(kHG&9(?Y)T&tVA!_Hu#kLh>shjL6biV2r zbl5Tr+L3DF3PJS_*m8`4b%!REBD`le@KWDB)#rWWJA#5#GJ9OiHyo@fp?%H`lmyW; zd)p=ysd;Fet2T}Vr%flkYuCU|ey7$XnQ(zBmuvZXSKA)=9vzCaQZ3$q9=@fgVkGe) zg--wYj`uT6fhSN;F2UM}SNI}W{W@P~|JbEN>5rx6Wm7HeC*{i&_c7$)1U8dmbeDyrM>+cmOF%Z_Xb>X^>jm0pxp1 zfGOfCfyO1{QI9}TOJzSXBgr4TGzD4~IIg{X@@86CKNsd@c?nPpp4TUN=|!px?q$On zK&34tKlH0J2X0mU#B)J*p5_c!ewJ{mg!(q!A{}63h@Vb(lE^x2ug{sqU>F3=d?KMp zI==$Y{wC4(bdmZQUpTr=hsfYAtQXxd!2d}_tNz%YI}??c*k_qO&0+H;}BsIjlXU?3lGGzTVrqK6Qb z9su?mUm>lcu#tSI%W<_*Py>V3%wC*@Vi{NX?*J_hP5BV6N_Y;&AD4&QXLJCg?{ zYV9J$BM0}NunK|T8h-0?m|r1&P>(e4;+@4>&^dRZ=_Qz<--0#w5{$DUx{98K5=1V? z$%EE;sc@+2&#m72gtL?2YhXRSh78s4bmYUP!UzozVdH_=NuZg;zQ}^JDARFWe>^Mt zkG$~sB)VJBG!jq-7GMV2b_*>NU6RNRSCa> zklhagy07~g)1mj?4S@N{l`cq-%K(7IIzTXovFd0Yz z;pas3wO2W)R%x5(uN*@bEtttYycayx+~CFoh>>7k zZ6Aa53VfyEToep*7jzaX02IdjMgHoit!?a}>SkxpM(MCc<;XnTf!a0kX&9kRn@uB> z>=XO-rB%%>=km%EJJvtjAD)9*!x*mFGT?RKDT~%&i-0t_u?9FM@sBcMS3Yb;`Pmbo z3coq$h4^t%L1#K%1LWLr&4rj$41K#ju%R7(?Rk)59;`9QzgfXhHyIGMZg2x_`jQmC zn{`#NelRUPb5@h*q-}VuE@?s#`(w`95G&~cB6QPJdp=~s3mE6|V~ZEebxp!P!%S~9 z1%FA`hp>S#<_DjvU)Ls^R<)|U5e%!gjt}(0Imk}}WM{ASF{#@OzyJ9;M4Q!zk+NOS zo|+Q+EW~l<09LjJGeFMr01M01&_xxMZ1^oY!7BcJWeRm-+>l|L2F2ie`8DMC1+?hX zS^?4ppF{-#qPk9fTwprRUjU}v?uujta@f!ab5M$()QX+8e44KzYz{cPMAnaj48?VD z5V%ZNpozB!Ihe?fBVo)2C!NxG11c41S^{`dfC+1cz#+AGpy?Lc?STiC^+l#<-USSq z|2o?an~HhsW9GN#&5kL&hBFOjm_9>du^486J0v$t)Q01Ciz5JS=Ei!GO+T)a^N_#--@kNeBnmSfjNqBx`>tqOEd>dXQzv8uZl?HH@J2k! z&?P_ZaunDd!I;);aKywMp4_04yVB6lzkwGxb88I6(8gz{F`ZC#6MKaa7Q3R&pVggQ z9;d{SDDDRb6`?MDc${WTZ%y(g6VrL_6Z*Edt%192n6Y5qP&>?^aAEx*8ZZUlqrbO^ zyeZqOxK=Q1;Jm3j5;AbuT@Ac8yu=u-Q7)i%)}orN4^#*pkW691A;-E1J99B@e3F_Z z%uI{DE=JHd9Gr~8N^0BiV-#c|Z+LXE7UqwgXqrGt7Kd|ISclY%JX6`ZC;KQ>b0_-pOS$q=CV(3yRY}K6+Vtpwu%U5C;E%ez$*Bz~a1*94E1YFjVeTb({Y; zoiI3$i%+f`bmBNr7B7Q2p$hid^*sUU=zC%wOl`}n%jWh7&{O52;o3n&wOHUBh**%peCWqx393jQO0Dhz%1VXmNR^S^)?^4(jC>W!guGwIl1%F%%~ zLWxp>V0JKf1FR}Znl~y66*t_zzb{KT?PiUA{Zh`S`1EH_*pL)I8!0A$*gEqRq!H=% zm9#OV0;BXC34`pAY6Kh(VU)m!{Uw46fRh#4Zd^9tUkR=bZ~0Jb@&s;<{K5)>CvK1& zRye^1BqaChgUA)hVgwqHi^0Rlni8>X1^z0g(Sp2BeaRM2VT{m$;%Ke%xw6d5p*#|4 zPXnr1=A9R6WU}Rp$EvO&+-g3A0K;Bs)OyJ^jH40+ODkN7@~CKOHelPF)Oa1h+4oo+ zv~5n(-3a$i5?Qbk9)oWV`JBwB5hamP7>Op~JssNqO6`%_v@EohQ{{^f6L0tcv20Re z!8EgyK%~Ll8-q6Z7~Q*PD>gC=5JRpehdPI>!B$WqBhikB!SV@=Ixr-Mwk>9+Thgm2 zK*xFq$#g#eYi$9BrO)%@qu5#}CT`Cs8X`alXfh>p;A69!x44+*c*2su_*obX9ZM(M zbKV30oYxvC5t%p*Fj@-Bdq6>9kLXAP*A=x>E^r{HX!qxzC%#I)CyJwe?{E0{(K|fF zsQWDdkRHhjgMInm0!UAQw%4Q5Zd*t~>{N#txQ)-t@Q1WdMxmm;`kBC+)IPQ+^KhE{ zX+%9woOy+IxTh_;N{=Cr4*peoTdcfaxqV-GPlWQ>i5mD^Q`AM~sax@mSLjrkg! z?L6+^L#QjB6`Nq%I4N?bahi|#XNwEsoKpN(eC`Gczwlj#Mz{-ZuQA4O9l@A!ZJrWf z>YL_^W0djjQci2+QM1MJ-5Qcr7n>Z-F}kC!;atMv&myAAf_=9z)YR^+R^j@&ckR6- zv;QR}DD5nFVisj!UUHcSC}XeCET8na0xrp(s%usx{c>JWYEALjm?lO8R(rkow)0Fe zzLr6E6WHWRCUjsM>Vp3b&yMBVPqXNqvB$m$it|gu3X**ul%gjERgkd53oc31(dRHd z=pzEfh*w^VdCLs5(up&-mlAnrDl75YM+Jz>+O1n7R9Squru`gc{hL=vjD&fwGgTK5 zZ~g;)7HX;O*xX>Ge$fu2LbRcY+aL-NE8+z%9nr=hYQE}vG6u;8=kuSL4B=J>Leikd0p6E|f@ zR=76FbHpjmW4dgKxy%8qO{P|MtQl$MV2kveYZTqU;K}iGFZn9xZRf2FIgcnl3}H;A z6f5bV)QpYT#4}?qiKYhbV*{kjJ*-<_K1F(qXCpw=bCI_e=jTuf&@H~ZZR6lkjK8X) zK*z9lb~xn@UcnBuaYbd);0Uf0+&{>=cdtruPUm~8yj z3|_!6;!_1H4OUK6=JRLyh=7zbhF3ebgm5xU?AmA^JPxPb`?^-Cj5{uASim7XWqaNJ zdLCH~ixNr{C&6nvT}ZUyIx7n!wW;IwSz1_+#;XBD*6^TyNF8;aHk_t|0K%>7%;1%G z+BCr$#5#(6`I55*yav1l9!xpC&U)uPqJ;^ylphzjON@+jrCTbgUc*iJjuQ3tczye4 z(tYJ0JZ4*{cT9gy5eQ24 zZt5zY9Vu?3V85@`*G|*Lx5Iq%A~*6JDc!NyE26;`a98QLA$YSzhpcN-pW^~3umW?7HT1; z^fMQGFPQrkzctKMh~(Ehdlrhd^^V`F5`MsCn4{Tzm%aG)4|w%8;J+RBEy&V`w5{Z_ zvc{HjR%}Ncx6-*6eZSppbnJ$s{rBy>9bR+-y91ty^3@oUJNse__<;qQR;w+q^I3L* zi{9e-sQI+qPv+_L-p$43#7jKtk$+0<+OObBWWwA{T)% z%TXC-kgCDe2YR>Bxbho=YUWVl`@qa?3EJHgBT^@<>V83^{jDuob$1`EF%I4mg>k^m z@waH+5X!puSfy-A8^EZe{caHmPia7cc3=99rlHV_IBJ>CawAOIpC*`CjHS>a4s+E8 zNDWTYIUtjs%BfsRUTp(ol{w7qeBkb#fxl~wJK8ZX6+)R5@9G1kXwMvUeJk11H=)*< znn59;iGUqZaEOudq|l~rK@-~aQnT9l#WWyZHq(AXe&H}Tw5q!G^B|h}bg}LD1xff& zl%Hm92Rr-GUd)v|bLOu!xOO*PY|9yded-7|B7fdafee?vizsy^cyQE1t2z*VxfLu8 zKKzi=rpd^H)Fq3S3VzIHx<>OnVsl#?u1u2p4#QL|)o%r?Z$3~i->BGvkZ!oF9E&Q( z0wFrHlHFo0=%4y7wmJ5LOf0!51g?y>HgNn)RD88bzP9>jIddA7=ZY0H3A^_GNJtxE z>VrCyAfq|#GQV$Saw`Bk-b;R3JNEdAG>E)DNQ3EDVVi&<#j>6)h+3KJLvxEFDI4^r z*_n;}5x5TxY(>JYwwo$b?IIg&#rZ?nyN31rny{FdY+O`NFA2t!5E3=WzI|gn?glkI>+1tEVaa!5n z`?cic2bqNxqbV^aMm`bebz6^qgF){o#c|ZOx!z72RRz&UbhjxGXB&%+^uph$6#!!| z)st-kx(jZX!L-c!)`%RIJwi_3xFBOe%aDOMOjr~&#en~dT2IPgyTwBlOA)kmU3kO4 zJ3Mnm-@LI@Ky#!_wCo-woNO6j>zgh=hBYJBY!*Hagh`1`;O#yiYw3Y?yQ^Ow$p31# zhttnh%Tq^QPt!vhkB3n?1X)tcH$`|3LcQi5ZQvq<%S_HJziE(ZGD;%7ASDA3f^H>GPp zmjgW2KOBx_eF@uT=feaQ$ssjkUQ`oO-y70<#zgIbzP@Vh}Yq<)8&Ka zKh;ByzKcOi>)7L+QtMZmmJ?#NNU2mQbBk)J2B^Xrqj`K9nkf^hB3RwD(6eTpN41?E zF}`|-!6F$w(u(0~Z2^74vXKz%#oABC#ReAPMw)<*hP#c22lj@d4%Q4y3Xo@INXw03 zwi4Sdb40+r9WZ?C3gSR;bw5Z5u*BLm77O^R^Ic$@7$D7t=5nEFgTX13WsPTkjI$fS zh~G`JPqt54dd?mDYl#`tn0Vt+f_T5cK9kK`U(O;*j=dx6g^_cVz|>p^(B$1{!c83B z_j(8Bp-sP#Q`uQSnMtZ0+0|l>LLu07Np284^n}F{wVF^4#@xR z2%g>o$GH93-!^mYAALRmX`yaRtI}=_IpO6JCwHXpDoiN-DdyA1*wG;B>b(rdQOVV_ z3ldzr$DSK**dVQ;8#JUPJroI>>_-0M{OK#NMCP9(Ol|v57{<8yXja_U-DApLvC%B| zP?V`#w4i-jUvlUDv86P%dcnh=!)Te{KKsBX~~7-nvHVakZ<{wpZy+piX?^1I+{g za>SQb!@npR#vcR@53j^)a%B>5vywf!SyojAwfGyC3IX=KQ~853#5u;}m^`vt$9mo640JZnApZ*$-Y0-mD@R_jh0H;DGS~w;Q()yQq1&%$LCtuLXGD+zV9r zV^RV$!#Q@l|2@26I!-%r?T}ZLM2$2xw|V`uWUo-TZXW!57D5QkpN0|G=1fa)dTMHL@vCd0iLF z_}pc|*?LqGvoa!}(SwA#B{0?KSrOakPW}DX2JaO`(Zq+Tag@3cFZAmT z3Zy6Wi+`B|Q9%)5KQ(X>PUqqn>K@cN<6D&mw?il3gNF6X0p*+6gc38J+F;0*M=)Ic z^@i0R0e#;U92V6vE#p7@mAO6d+ew3(ZaGLW%tBnm14FMHYs(qBp43*%InR!?cc!Z8t7OlzVH-LMfo_``dker$3NV5;$s&x_ zlw(7pQV%OU##eszSBZ3`1l|`d9!a$GljRzJeK?c4iODm;eC)S*^&f$NJ+LYf120ByTOo|Qbol8i~vBxv=sP5!nmAJ8;p~emMo~J+a zQD+CVTQ_8Rn_2gVBYm#jP@+?Ga49hsB-)qNx$hXaqD3?u?T6X+N+yYUg24Hj7DoxJ zor(p$^|m=d?R7cpn3mQf3MYPC+i3j79>*TNTlIocd%q$Ml%Y)Q%M8$j6$cOnlAZx} zpZQ)RHDoXXKjVA&-Yr0znFhr|rF67s>Bk#`!pVqNl?#%9y$-IO-p9#%rk%xLL);$I2tu%@xG}uVb1?9OL)T(sy4@c{Majk4il9bw8Lt zFzAaU%*7!(Pa=QAQ6hSk^U#futADot+{=K)!l#*claNtv4N(h1O2Py^ky9J*IXAi5+^tm~kqwexMnKmUpdrUn@ zn$X0yFT<9HP?yvni*i~J-KSNRv_mIpLA&V8ndiYy~kNFmeV*Xt_OX)=Pl*+Vm#0_buU+;3dIqpR>1@-eIO7 z9;a>el08@uI$j+=qc}zVrJ8+wmQaDnRz1@@#g=(fX=b_e`wcsylgbn}Va}ROMnTFU zN)^3YR0arlOHjOrTs`)918<~rv>?nsp)g#hl84__HGBr%^U>`uMqz;%yxW%AI1klp z7s#x7^nw9~mbPAPYiZ-9F(ZHAZwJ9mt?|S7n@>~l56G~no63tQ8$)+|l_>VL_Kt@d z0)qWYhQYXVbC8HqR4D*+!_7m8hXk!wQ9N&imrnL$jdQTTX%5t!S8N75@qs(Iy@@N z4hWdC$Bb#XY*+ADbe1jk##nV(cUc&W(H9pu-dn!`D z>mTM5WZ$6oBKaPg?TLJ4m%g!;;XyE?QD*tdnC@+V-D$%PAKbBKC;+qPP-F%g7(^8* zZ_vhCXvgcobk4N@A!Re(LurG!*{??wEb1yF>UFZ8WmY13TF)HVW|Y{wlW%xY#gqGw zV5Ao&I+{%NG8q!l5;iWt27U|V;&8_dLm%3m3rN30hL;e?c;F-32-b*TgYptfV0kAm zU|wD-$mI^5RO;p(g!6)!L*xrD108W&lX)}dtTf)@)`hxgL0Tp3c=5=v4-cxrZBa+|)DDAB6Xt$BNpN|syeqQ0?!wW{Lf z0Dw1JqhuwPkw&*I${VzdLZeSyD=tr5Y`x_Id2*P(yMoI}g6Epcu}^grV)Fx(H`t7A zN}RYtj(Dh>FJ{kF<_j`o6M6mmOZ^l^b4|J?-3lh(5k0^Ntfh(tL86Kku!SQ*QILJ{ zrDQ|IXz@>;ps0h|W|pmJ2!GSd_1|Bkz2?4u}jXHgOZ)zL9))Gl7{NP^T4W!0?nhOI(6M znF!>aBDZU#=wIGH@4kPbCj+}-5SoXoF=Wm`q*pd!qh?s|+oORkI-?-UkqB~&1XLCm zm8D@?foF#pWg@RLV8o&riVi0foYcmQRw$^e2fOZ7!~z5*c@b|i{Q8!u!k^nt0~qBa z1!cp)hu#hVrA7Zxs(3#g8A1VY=m)P4d`A>mv2>3k(28Wz&IKmxt0Y zA{Ieg@x1oVos72-Kf}&Ry5UWvhHtrL2jBSAapH4Ct2OaAw&)LkUHStBtE}&?bp;;n ztp$SYBqf!c_4gzi*z+%4YF4rSE4+t4D5@x#Zx2u03Z9ENM+OeCAie+{_tkJ34KeEB z{9Cdwp9QpR(*$S{C~!{1d>dAGm({ge&qwkk6N!yKMN5AL`gKBsG@OFCr%3o&0!gKB zz$foO2Xplx<@jgVX;s*3k+daRpD~%P}zx*cvVU zfg`CMrb6$(o`9|Rc*on++E2|Hh9}z2oBJ&2wR=W5+x_`H11?bsi3-MCTsO&v9Kqb3 zefc)pEIPyhKcfcO*&qqL3lV@HTAwb3u4&b`KQ9i-vCh1pm8i(^yq^d=u$Z;$h+4Z6 zyXiouCzw2TmYrIEUlsI|VBv^K_f>3K$Q~nOL-ggQIjjQ*#QeKd|Eo@OpCRiHtfWBAke;lLw8Kx_yhMy66crs~l3h0b)3Oa9qo_rCa z%u+7BQd3WRv`~&Lm{a-4Mhef5?SC99&Sn;m1Fwpm=?KCdOR6NJ;{{Huf$gsiA>4DC2BZ4me+c|B&le&+Tg zT*yuEZgxN&X%a1?T^}Af12g!N3#Qn#T^Kuj;TULcoU2q`fse7sf}8j`_^^js?L`d@ zw*d15T>oLW0xKM_NjR-d|!n?`fw~f6oKnP-Kdw zfgN#ltE?Y_D0e*-lZ5g@lK> zVLNBIoB9y&HLijGC3K=6KsiJ8PbT;$X6Pq$WrD~;@tZ;9;Wyu4;lif<`Ak-Z3GC@~ zWQNMlTQQ}US{wvXO!)Efd;1-nmZCsy6ju%*cm@tLVAW&Af?Z1jbsSKy3l<>81TD~_qbyQfVOHok` zrs_@`)J8ktaG|9W+{t<0VBs(H7KY4`S6(ccwX8zZdUHu{o90>@+@JhT)SGD2WFdul zFKkj+e^JNgY?iK>B=sF>)-z$l6?CQ4nvv?XsDW%E{&ihnv9>x#bAT&RN34AMP^7p9 za}C2>f|+H^EVbXd{(yx2%SM9l6rTiX&9!#$Cvw~>r{M0^VsR>=_}XU@j{0+f35=eK z>fZzX8nM=BzTG}*w&Pi#w+wnbQFDaZL2iEOfc*j3;RqWL^PG3W*3u&{S4iKKKeA*v5vtQjPB;(eqY4sx8|HT3~XdBX;cC z#V)fu;SH_ca8U4ispI*>@1uA^RM(EDh>5wMAGJ+`GlB}+Q&(DyJ zAz#}+ltLhf$4&_{=^2$iz4@J@4q)?-5j8q)*^si) zp4Vm;Geki_M=<)%)6tY|!MG8{_eZ1M8~BFF4_^ITV8VFZorA8q1Cl#_Qk3xgwKpLO zqNuH5u^7C=_7I&dLenh_G3eX_{aslJey`e*L!bnW!MiNrl#ovMlp&*waOr1 zup#`B+F*M!j4x~=Cv~jik@c0?$S|WiJCfStxSGJQBHw! z%v6yh2WOeO*hcFux|u4^uR8vEmp7G}M7SG(yR-zoyb;{AZ@}$OE_VRhz6pmygd7@U z`xT2tj^+UuMRjQq<}EI&6+9v+XvU7iTe-eo2b z@H*roG&D2UhSX|75^4{IKapSjCy~6Rwq_oR%96=s`=mCV82&ID(5N_+gN>>W=5j+~ z%EWt}iX895Rnx%uC33wT=q}?cfq_pEGonDFcbxy`bH)h==m){rR4B;x+Mk4qDd=FQ z#FT7K@Q?3?hz6#6iwVCw7B3O=R!I00NBr=B^!Ux&fPLTNF$VWRcWMuc$|Z zSK{(T*IzgVjYIzsgL&h$!h!>z;pP)7Nl-)-+7Ym~w4DG@(Sxe+8;YkBaCNg)n|Rux z(76JoSDsfnqkXux>bDZ`T^usH4lVCq3_u;j4!Csm%rm;u92=%Wml;DvU;VtH@aK8S*{NU zfqRnX)~il`KV=MfeyumqN@o6v4NMM`qUxvk=fWtC#DC~-plq1VkDC(_f4>%DKcGey zaT=f8zfi3f}(%Jl~#&t2|g-{wcRpG&b}Nsw!!m%RFNd_&)%? C%B+(B literal 185152 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 T#-&j7yQ(T{D&?HHR diff --git a/pydeequ/engines/__init__.py b/pydeequ/engines/__init__.py index a165078..63a8327 100644 --- a/pydeequ/engines/__init__.py +++ b/pydeequ/engines/__init__.py @@ -400,3 +400,11 @@ def connect( # Factory function "connect", ] + + +# Lazy import for DuckDB config to avoid import errors when duckdb is not installed +def __getattr__(name: str) -> Any: + if name == "DuckDBEngineConfig": + from pydeequ.engines.duckdb_config import DuckDBEngineConfig + return DuckDBEngineConfig + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/pydeequ/engines/constraints/__init__.py b/pydeequ/engines/constraints/__init__.py index d475279..a93b3bf 100644 --- a/pydeequ/engines/constraints/__init__.py +++ b/pydeequ/engines/constraints/__init__.py @@ -48,6 +48,10 @@ BaseEvaluator, RatioCheckEvaluator, ) +from pydeequ.engines.constraints.batch_evaluator import ( + ConstraintBatchEvaluator, + SCAN_BASED_EVALUATORS, +) from pydeequ.engines.constraints.evaluators import ( ApproxCountDistinctEvaluator, ApproxQuantileEvaluator, @@ -88,6 +92,9 @@ "BaseEvaluator", "RatioCheckEvaluator", "AnalyzerBasedEvaluator", + # Batch evaluator + "ConstraintBatchEvaluator", + "SCAN_BASED_EVALUATORS", # Analyzer-based evaluators "SizeEvaluator", "CompletenessEvaluator", diff --git a/pydeequ/engines/constraints/batch_evaluator.py b/pydeequ/engines/constraints/batch_evaluator.py new file mode 100644 index 0000000..14eea45 --- /dev/null +++ b/pydeequ/engines/constraints/batch_evaluator.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +""" +Constraint batch evaluation for DuckDB performance optimization. + +This module provides functionality to batch constraint evaluations that can +share SQL queries, reducing the number of queries executed. + +Key optimizations: +1. Scan-based constraints (Size, Mean, Completeness, etc.) can be batched + when they use scan operators with compatible aggregations. +2. Ratio-check constraints (isPositive, isNonNegative, isContainedIn, etc.) + can be batched into a single query when they operate on the same table. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Type + +from pydeequ.engines.constraints.base import ( + AnalyzerBasedEvaluator, + BaseEvaluator, + RatioCheckEvaluator, +) +from pydeequ.engines.constraints.evaluators import ( + CompletenessEvaluator, + MaximumEvaluator, + MeanEvaluator, + MinimumEvaluator, + SizeEvaluator, + StandardDeviationEvaluator, + SumEvaluator, +) + +if TYPE_CHECKING: + import pandas as pd + + +# Evaluators that use scan operators (can be batched via aggregations) +SCAN_BASED_EVALUATORS: Tuple[Type[AnalyzerBasedEvaluator], ...] = ( + SizeEvaluator, + CompletenessEvaluator, + MeanEvaluator, + MinimumEvaluator, + MaximumEvaluator, + SumEvaluator, + StandardDeviationEvaluator, +) + + +class ConstraintBatchEvaluator: + """ + Batches constraint evaluations to minimize SQL queries. + + This class groups constraints by their evaluation pattern and executes + them in batches where possible: + - Scan-based evaluators: batched into single aggregation queries + - Ratio-check evaluators: batched into single ratio queries + - Other evaluators: executed individually + """ + + def __init__(self, evaluators: List[BaseEvaluator]): + """ + Initialize the batch evaluator. + + Args: + evaluators: List of constraint evaluators + """ + self.evaluators = evaluators + self._scan_based: List[AnalyzerBasedEvaluator] = [] + self._ratio_checks: List[RatioCheckEvaluator] = [] + self._other: List[BaseEvaluator] = [] + self._analyze() + + def _analyze(self) -> None: + """Categorize evaluators by type for batching.""" + for evaluator in self.evaluators: + if isinstance(evaluator, SCAN_BASED_EVALUATORS): + self._scan_based.append(evaluator) + elif isinstance(evaluator, RatioCheckEvaluator): + self._ratio_checks.append(evaluator) + else: + self._other.append(evaluator) + + def get_batch_info(self) -> Dict[str, int]: + """Return batch grouping information for debugging.""" + return { + "scan_based": len(self._scan_based), + "ratio_checks": len(self._ratio_checks), + "other": len(self._other), + } + + def execute( + self, + table: str, + execute_fn: Callable[[str], "pd.DataFrame"], + ) -> Dict[BaseEvaluator, Optional[float]]: + """ + Execute all evaluators with batching optimization. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Dictionary mapping evaluators to their computed values + """ + results: Dict[BaseEvaluator, Optional[float]] = {} + + # Batch scan-based evaluators + if self._scan_based: + scan_results = self._execute_scan_batch(table, execute_fn) + results.update(scan_results) + + # Batch ratio-check evaluators + if self._ratio_checks: + ratio_results = self._execute_ratio_batch(table, execute_fn) + results.update(ratio_results) + + # Execute other evaluators individually + for evaluator in self._other: + try: + value = evaluator.compute_value(table, execute_fn) + results[evaluator] = value + except Exception: + results[evaluator] = None + + return results + + def _execute_scan_batch( + self, + table: str, + execute_fn: Callable[[str], "pd.DataFrame"], + ) -> Dict[BaseEvaluator, Optional[float]]: + """ + Execute scan-based evaluators in a single batched query. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Dictionary mapping evaluators to their computed values + """ + results: Dict[BaseEvaluator, Optional[float]] = {} + + # Collect all aggregations from scan operators + operators = [] + operator_to_evaluator = {} + + for evaluator in self._scan_based: + operator = evaluator.get_operator() + if operator and hasattr(operator, "get_aggregations"): + operators.append(operator) + operator_to_evaluator[id(operator)] = evaluator + + if not operators: + # Fall back to individual execution + for evaluator in self._scan_based: + try: + value = evaluator.compute_value(table, execute_fn) + results[evaluator] = value + except Exception: + results[evaluator] = None + return results + + # Build batched query + aggregations = [] + for operator in operators: + aggregations.extend(operator.get_aggregations()) + + query = f"SELECT {', '.join(aggregations)} FROM {table}" + + try: + df = execute_fn(query) + + # Extract results for each operator + for operator in operators: + evaluator = operator_to_evaluator[id(operator)] + try: + metric_result = operator.extract_result(df) + results[evaluator] = metric_result.value + except Exception: + results[evaluator] = None + + except Exception: + # Fall back to individual execution on batch failure + for evaluator in self._scan_based: + try: + value = evaluator.compute_value(table, execute_fn) + results[evaluator] = value + except Exception: + results[evaluator] = None + + return results + + def _execute_ratio_batch( + self, + table: str, + execute_fn: Callable[[str], "pd.DataFrame"], + ) -> Dict[BaseEvaluator, Optional[float]]: + """ + Execute ratio-check evaluators in a single batched query. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + Dictionary mapping evaluators to their computed values + """ + results: Dict[BaseEvaluator, Optional[float]] = {} + + # Group evaluators by WHERE clause for proper batching + where_groups: Dict[Optional[str], List[RatioCheckEvaluator]] = {} + for evaluator in self._ratio_checks: + where = getattr(evaluator, "where", None) + if where not in where_groups: + where_groups[where] = [] + where_groups[where].append(evaluator) + + # Execute each where-group as a batch + for where, group_evaluators in where_groups.items(): + try: + group_results = self._execute_ratio_group( + table, execute_fn, group_evaluators, where + ) + results.update(group_results) + except Exception: + # Fall back to individual execution + for evaluator in group_evaluators: + try: + value = evaluator.compute_value(table, execute_fn) + results[evaluator] = value + except Exception: + results[evaluator] = None + + return results + + def _execute_ratio_group( + self, + table: str, + execute_fn: Callable[[str], "pd.DataFrame"], + evaluators: List[RatioCheckEvaluator], + where: Optional[str], + ) -> Dict[BaseEvaluator, Optional[float]]: + """ + Execute a group of ratio-check evaluators with the same WHERE clause. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + evaluators: List of ratio-check evaluators + where: WHERE clause (None if no filter) + + Returns: + Dictionary mapping evaluators to their computed values + """ + results: Dict[BaseEvaluator, Optional[float]] = {} + + # Build batched ratio query + cases = [] + for i, evaluator in enumerate(evaluators): + condition = evaluator.get_condition() + cases.append(f"SUM(CASE WHEN {condition} THEN 1 ELSE 0 END) as matches_{i}") + + # Add total count + if where: + query = f""" + SELECT + SUM(CASE WHEN {where} THEN 1 ELSE 0 END) as total, + {', '.join([f"SUM(CASE WHEN ({where}) AND ({evaluators[i].get_condition()}) THEN 1 ELSE 0 END) as matches_{i}" for i in range(len(evaluators))])} + FROM {table} + """ + else: + query = f""" + SELECT + COUNT(*) as total, + {', '.join(cases)} + FROM {table} + """ + + df = execute_fn(query) + total = float(df["total"].iloc[0]) if df["total"].iloc[0] else 0 + + for i, evaluator in enumerate(evaluators): + matches = float(df[f"matches_{i}"].iloc[0]) if df[f"matches_{i}"].iloc[0] else 0 + if total == 0: + results[evaluator] = 1.0 + else: + results[evaluator] = matches / total + + return results + + +__all__ = [ + "ConstraintBatchEvaluator", + "SCAN_BASED_EVALUATORS", +] diff --git a/pydeequ/engines/duckdb.py b/pydeequ/engines/duckdb.py index ef6ce92..e4b0200 100644 --- a/pydeequ/engines/duckdb.py +++ b/pydeequ/engines/duckdb.py @@ -15,10 +15,17 @@ engine = DuckDBEngine(con, table="test") metrics = engine.compute_metrics([Size(), Completeness("id"), Mean("value")]) + + # With profiling enabled + engine = DuckDBEngine(con, table="test", enable_profiling=True) + engine.compute_metrics([Size(), Completeness("id")]) + stats = engine.get_query_stats() + print(f"Total queries: {engine.get_query_count()}") """ from __future__ import annotations +import time from typing import TYPE_CHECKING, Dict, List, Optional, Sequence import pandas as pd @@ -32,10 +39,11 @@ CheckStatus, MetricResult, ) -from pydeequ.engines.operators import OperatorFactory +from pydeequ.engines.operators import GroupingOperatorBatcher, OperatorFactory if TYPE_CHECKING: import duckdb + from pydeequ.engines.duckdb_config import DuckDBEngineConfig from pydeequ.v2.analyzers import _ConnectAnalyzer from pydeequ.v2.checks import Check from pydeequ.v2.predicates import Predicate @@ -51,19 +59,35 @@ class DuckDBEngine(BaseEngine): Attributes: con: DuckDB connection table: Name of the table to analyze + enable_profiling: Whether to collect query timing statistics + config: Optional configuration for DuckDB optimization """ - def __init__(self, con: "duckdb.DuckDBPyConnection", table: str): + def __init__( + self, + con: "duckdb.DuckDBPyConnection", + table: str, + enable_profiling: bool = False, + config: Optional["DuckDBEngineConfig"] = None, + ): """ Create a new DuckDBEngine. Args: con: DuckDB connection object table: Name of the table to analyze + enable_profiling: Whether to collect query timing statistics + config: Optional DuckDB configuration for optimization """ self.con = con self.table = table self._schema: Optional[Dict[str, str]] = None + self._enable_profiling = enable_profiling + self._query_stats: List[Dict] = [] + + # Apply configuration if provided + if config is not None: + config.apply(con) def get_schema(self) -> Dict[str, str]: """Get the schema of the table.""" @@ -80,8 +104,34 @@ def get_schema(self) -> Dict[str, str]: def _execute_query(self, query: str) -> pd.DataFrame: """Execute a SQL query and return results as DataFrame.""" + if self._enable_profiling: + start = time.perf_counter() + result = self.con.execute(query).fetchdf() + elapsed = time.perf_counter() - start + self._query_stats.append({ + 'query': query[:200] + ('...' if len(query) > 200 else ''), + 'time_ms': elapsed * 1000, + 'rows': len(result), + }) + return result return self.con.execute(query).fetchdf() + def get_query_stats(self) -> pd.DataFrame: + """Return profiling statistics as DataFrame.""" + return pd.DataFrame(self._query_stats) + + def get_query_count(self) -> int: + """Return number of queries executed.""" + return len(self._query_stats) + + def explain_query(self, query: str) -> str: + """Get DuckDB query plan with EXPLAIN ANALYZE.""" + return self.con.execute(f"EXPLAIN ANALYZE {query}").fetchdf().to_string() + + def reset_profiling(self) -> None: + """Reset profiling statistics.""" + self._query_stats = [] + def _get_row_count(self, where: Optional[str] = None) -> int: """Get the row count, optionally filtered.""" if where: @@ -178,22 +228,45 @@ def compute_metrics( message=f"Batch query failed: {str(e)}" )) - # Execute grouping operators individually - for operator in grouping_operators: + # Execute grouping operators with batching optimization + if grouping_operators: + batcher = GroupingOperatorBatcher(grouping_operators) + + # Execute batched queries (fused operators with same columns/where) try: - query = operator.build_query(self.table) - df = self._execute_query(query) - result = operator.extract_result(df) - results.append(result) + batched_results = batcher.execute_batched( + self.table, self._execute_query + ) + results.extend(batched_results) except Exception as e: - results.append(MetricResult( - name=operator.metric_name, - instance=operator.instance, - entity=operator.entity, - value=None, - success=False, - message=str(e) - )) + # If batched execution fails, fall back to individual execution + for operator in grouping_operators: + if operator not in batcher.get_unbatchable_operators(): + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=f"Batched query failed: {str(e)}" + )) + + # Execute unbatchable operators individually + for operator in batcher.get_unbatchable_operators(): + try: + query = operator.build_query(self.table) + df = self._execute_query(query) + result = operator.extract_result(df) + results.append(result) + except Exception as e: + results.append(MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=None, + success=False, + message=str(e) + )) # Execute metadata operators using schema schema = self.get_schema() @@ -218,30 +291,56 @@ def compute_metrics( # ========================================================================= def run_checks(self, checks: Sequence["Check"]) -> List[ConstraintResult]: - """Run verification checks and return constraint results.""" + """Run verification checks and return constraint results. + + Uses ConstraintBatchEvaluator to batch compatible constraints, + reducing the number of SQL queries executed. + """ from pydeequ.v2.checks import CheckLevel - from pydeequ.engines.constraints import ConstraintEvaluatorFactory + from pydeequ.engines.constraints import ( + ConstraintBatchEvaluator, + ConstraintEvaluatorFactory, + ) results: List[ConstraintResult] = [] + # Phase 1: Create all evaluators and collect metadata + all_evaluators = [] + constraint_info = [] # (check, constraint, evaluator) tuples + + for check in checks: + for constraint in check._constraints: + evaluator = ConstraintEvaluatorFactory.create(constraint) + if evaluator: + all_evaluators.append(evaluator) + constraint_info.append((check, constraint, evaluator)) + else: + constraint_info.append((check, constraint, None)) + + # Phase 2: Batch execute all evaluators + computed_values: Dict = {} + if all_evaluators: + batcher = ConstraintBatchEvaluator(all_evaluators) + computed_values = batcher.execute(self.table, self._execute_query) + + # Phase 3: Process results by check + info_idx = 0 for check in checks: check_description = check.description check_level = check.level.value - - # Track overall check status check_has_failure = False for constraint in check._constraints: + _, _, evaluator = constraint_info[info_idx] + info_idx += 1 + constraint_message = None constraint_passed = False try: - # Create evaluator for this constraint - evaluator = ConstraintEvaluatorFactory.create(constraint) - if evaluator: - # Compute the metric value - value = evaluator.compute_value(self.table, self._execute_query) + # Get pre-computed value from batch execution + value = computed_values.get(evaluator) # Evaluate the constraint constraint_passed = evaluator.evaluate(value) @@ -304,9 +403,9 @@ def profile_columns( """ Profile columns in the table. - Uses ColumnProfileOperator to compute statistics for each column - including completeness, distinct values, and (for numeric columns) - min, max, mean, sum, stddev, and percentiles. + Uses MultiColumnProfileOperator to batch profile statistics across + multiple columns, significantly reducing the number of SQL queries + from 2-3 per column to 2-3 total. Args: columns: Optional list of columns to profile. If None, profile all. @@ -316,7 +415,10 @@ def profile_columns( Returns: List of ColumnProfile objects """ - from pydeequ.engines.operators.profiling_operators import ColumnProfileOperator + from pydeequ.engines.operators.profiling_operators import ( + ColumnProfileOperator, + MultiColumnProfileOperator, + ) schema = self.get_schema() @@ -326,46 +428,50 @@ def profile_columns( else: cols_to_profile = list(schema.keys()) - profiles: List[ColumnProfile] = [] + if not cols_to_profile: + return [] - for col in cols_to_profile: - col_type = schema[col] + # Use MultiColumnProfileOperator for batched profiling + operator = MultiColumnProfileOperator(cols_to_profile, schema) - # Create operator for this column - operator = ColumnProfileOperator( - column=col, - column_type=col_type, - compute_percentiles=True, - compute_histogram=(low_cardinality_threshold > 0), - histogram_limit=low_cardinality_threshold, - ) + # Query 1: Completeness and distinct counts for all columns + completeness_query = operator.build_completeness_query(self.table) + completeness_df = self._execute_query(completeness_query) - # Execute base query and extract stats - base_query = operator.build_base_query(self.table) - base_result = self._execute_query(base_query) - base_stats = operator.extract_base_result(base_result) + # Query 2: Numeric stats for all numeric columns (if any) + numeric_df = None + numeric_query = operator.build_numeric_stats_query(self.table) + if numeric_query: + numeric_df = self._execute_query(numeric_query) - # Execute percentile query for numeric columns - percentiles = None - if operator.compute_percentiles: - try: - percentile_query = operator.build_percentile_query(self.table) - percentile_result = self._execute_query(percentile_query) - percentiles = operator.extract_percentile_result(percentile_result) - except Exception: - # Percentile computation may fail for some types - pass - - # Execute histogram query for low cardinality columns - histogram = None - if operator.compute_histogram and base_stats["distinct_count"] <= low_cardinality_threshold: - hist_query = operator.build_histogram_query(self.table) - hist_result = self._execute_query(hist_query) - histogram = operator.extract_histogram_result(hist_result) - - # Build and append profile - profile = operator.build_profile(base_stats, percentiles, histogram) - profiles.append(profile) + # Query 3: Percentiles for all numeric columns (if any) + percentile_df = None + percentile_query = operator.build_percentile_query(self.table) + if percentile_query: + try: + percentile_df = self._execute_query(percentile_query) + except Exception: + # Percentile computation may fail for some types + pass + + # Extract profiles from batched results + profiles = operator.extract_profiles(completeness_df, numeric_df, percentile_df) + + # Add histograms for low cardinality columns (requires per-column queries) + if low_cardinality_threshold > 0: + for profile in profiles: + if profile.approx_distinct_values <= low_cardinality_threshold: + col_type = schema.get(profile.column, "VARCHAR") + col_operator = ColumnProfileOperator( + column=profile.column, + column_type=col_type, + compute_percentiles=False, + compute_histogram=True, + histogram_limit=low_cardinality_threshold, + ) + hist_query = col_operator.build_histogram_query(self.table) + hist_result = self._execute_query(hist_query) + profile.histogram = col_operator.extract_histogram_result(hist_result) return profiles diff --git a/pydeequ/engines/duckdb_config.py b/pydeequ/engines/duckdb_config.py new file mode 100644 index 0000000..b470ae0 --- /dev/null +++ b/pydeequ/engines/duckdb_config.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +""" +DuckDB engine configuration for PyDeequ. + +This module provides configuration options to optimize DuckDB performance +for analytical workloads like data quality checks. + +Example usage: + import duckdb + from pydeequ.engines.duckdb import DuckDBEngine + from pydeequ.engines.duckdb_config import DuckDBEngineConfig + + # Create config with optimizations + config = DuckDBEngineConfig( + threads=8, + memory_limit="8GB", + preserve_insertion_order=False, # Better parallelism + ) + + con = duckdb.connect() + config.apply(con) + + engine = DuckDBEngine(con, table="test") +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Dict, Optional + +if TYPE_CHECKING: + import duckdb + + +@dataclass +class DuckDBEngineConfig: + """ + Configuration for DuckDB engine optimization. + + Attributes: + threads: Number of threads to use. None = auto (all cores). + memory_limit: Memory limit string (e.g., "8GB"). None = auto. + preserve_insertion_order: If False, allows better parallelism. + Set to False for read-only analytical workloads. + parquet_metadata_cache: Cache Parquet metadata for faster repeated reads. + enable_object_cache: Enable object caching for repeated queries. + enable_progress_bar: Show progress bar for long-running queries. + default_null_order: How to order NULLs (NULLS_FIRST, NULLS_LAST). + custom_settings: Additional DuckDB settings as key-value pairs. + """ + + threads: Optional[int] = None + memory_limit: Optional[str] = None + preserve_insertion_order: bool = False + parquet_metadata_cache: bool = True + enable_object_cache: bool = True + enable_progress_bar: bool = False + default_null_order: str = "NULLS_LAST" + custom_settings: Dict[str, str] = field(default_factory=dict) + + def apply(self, con: "duckdb.DuckDBPyConnection") -> None: + """ + Apply configuration settings to a DuckDB connection. + + Args: + con: DuckDB connection to configure + """ + # Thread configuration + if self.threads is not None: + con.execute(f"SET threads = {self.threads}") + + # Memory configuration + if self.memory_limit is not None: + con.execute(f"SET memory_limit = '{self.memory_limit}'") + + # Parallelism optimization + con.execute( + f"SET preserve_insertion_order = {str(self.preserve_insertion_order).lower()}" + ) + + # Caching optimization + if self.parquet_metadata_cache: + con.execute("SET parquet_metadata_cache = true") + + if self.enable_object_cache: + con.execute("SET enable_object_cache = true") + + # Progress bar (useful for debugging long queries) + con.execute( + f"SET enable_progress_bar = {str(self.enable_progress_bar).lower()}" + ) + + # NULL ordering + con.execute(f"SET default_null_order = '{self.default_null_order}'") + + # Apply custom settings + for key, value in self.custom_settings.items(): + # Determine if value needs quoting (strings vs numbers/booleans) + if value.lower() in ("true", "false") or value.isdigit(): + con.execute(f"SET {key} = {value}") + else: + con.execute(f"SET {key} = '{value}'") + + @classmethod + def default(cls) -> "DuckDBEngineConfig": + """Create a default configuration.""" + return cls() + + @classmethod + def high_performance(cls) -> "DuckDBEngineConfig": + """ + Create a high-performance configuration for analytical workloads. + + This configuration prioritizes read performance over write safety: + - Disables insertion order preservation for better parallelism + - Enables all caching options + - Uses all available cores + """ + return cls( + threads=None, # Use all cores + preserve_insertion_order=False, + parquet_metadata_cache=True, + enable_object_cache=True, + ) + + @classmethod + def memory_constrained(cls, memory_limit: str = "4GB") -> "DuckDBEngineConfig": + """ + Create a configuration for memory-constrained environments. + + Args: + memory_limit: Memory limit string (e.g., "4GB") + """ + return cls( + memory_limit=memory_limit, + enable_object_cache=False, # Reduce memory usage + ) + + +__all__ = ["DuckDBEngineConfig"] diff --git a/pydeequ/engines/operators/__init__.py b/pydeequ/engines/operators/__init__.py index 93b3957..a76f8b9 100644 --- a/pydeequ/engines/operators/__init__.py +++ b/pydeequ/engines/operators/__init__.py @@ -42,6 +42,10 @@ from pydeequ.engines.operators.base import GroupingOperator, ScanOperator from pydeequ.engines.operators.factory import OperatorFactory +from pydeequ.engines.operators.grouping_batcher import ( + GroupingOperatorBatcher, + BATCHABLE_OPERATORS, +) from pydeequ.engines.operators.grouping_operators import ( DistinctnessOperator, EntropyOperator, @@ -120,6 +124,9 @@ "EntropyOperator", "MutualInformationOperator", "HistogramOperator", + # Grouping operator batching + "GroupingOperatorBatcher", + "BATCHABLE_OPERATORS", # Metadata operators "DataTypeOperator", # Profiling operators diff --git a/pydeequ/engines/operators/grouping_batcher.py b/pydeequ/engines/operators/grouping_batcher.py new file mode 100644 index 0000000..db75641 --- /dev/null +++ b/pydeequ/engines/operators/grouping_batcher.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +""" +Grouping operator batching for DuckDB performance optimization. + +This module provides functionality to batch grouping operators that share +identical CTEs (same columns and where clause) into single queries. + +Key insight: DistinctnessOperator, UniquenessOperator, and UniqueValueRatioOperator +all use the same frequency CTE: + WITH freq AS (SELECT cols, COUNT(*) AS cnt FROM table GROUP BY cols) + +By fusing operators with matching (columns, where_clause), we can: +- Compute all metrics in a single query +- Reduce the number of table scans +- Improve performance by 20-40% for checks with multiple grouping operators +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple + +from pydeequ.engines import MetricResult +from pydeequ.engines.operators.grouping_operators import ( + DistinctnessOperator, + UniquenessOperator, + UniqueValueRatioOperator, +) + +if TYPE_CHECKING: + import pandas as pd + from pydeequ.engines.operators.base import GroupingOperator + + +# Operators that can be batched together (share same freq CTE structure) +BATCHABLE_OPERATORS = (DistinctnessOperator, UniquenessOperator, UniqueValueRatioOperator) + + +class GroupingOperatorBatcher: + """ + Batches grouping operators with matching (columns, where) into single queries. + + This class analyzes grouping operators and fuses compatible ones to reduce + the number of SQL queries executed. + """ + + def __init__(self, operators: List["GroupingOperator"]): + """ + Initialize the batcher with operators to analyze. + + Args: + operators: List of grouping operators + """ + self.operators = operators + self._batched_groups: Dict[Tuple, List["GroupingOperator"]] = {} + self._unbatchable: List["GroupingOperator"] = [] + self._analyze() + + def _get_batch_key(self, operator: "GroupingOperator") -> Optional[Tuple]: + """ + Get the batch key for an operator (columns tuple + where clause). + + Returns None if the operator cannot be batched. + """ + if not isinstance(operator, BATCHABLE_OPERATORS): + return None + + # Create key from (columns tuple, where clause) + cols = tuple(operator.columns) + where = operator.where or "" + return (cols, where) + + def _analyze(self) -> None: + """Analyze operators and group batchable ones by key.""" + for operator in self.operators: + key = self._get_batch_key(operator) + if key is None: + self._unbatchable.append(operator) + else: + if key not in self._batched_groups: + self._batched_groups[key] = [] + self._batched_groups[key].append(operator) + + def get_unbatchable_operators(self) -> List["GroupingOperator"]: + """Return operators that cannot be batched.""" + return self._unbatchable + + def get_batch_count(self) -> int: + """Return the number of batched query groups.""" + return len(self._batched_groups) + + def execute_batched( + self, + table: str, + execute_fn, + ) -> List[MetricResult]: + """ + Execute batched queries and return results. + + Args: + table: Name of the table to query + execute_fn: Function to execute SQL and return DataFrame + + Returns: + List of MetricResult objects for all batched operators + """ + results: List[MetricResult] = [] + + for (cols, where), operators in self._batched_groups.items(): + # Build fused query + query = self._build_fused_query(table, cols, where, operators) + + # Execute query + df = execute_fn(query) + + # Extract results for each operator + for operator in operators: + result = self._extract_result(df, operator) + results.append(result) + + return results + + def _build_fused_query( + self, + table: str, + cols: Tuple[str, ...], + where: str, + operators: List["GroupingOperator"], + ) -> str: + """ + Build a fused query that computes metrics for all operators in a batch. + + Args: + table: Name of the table to query + cols: Tuple of column names + where: WHERE clause (empty string if none) + operators: List of operators to fuse + + Returns: + SQL query string + """ + cols_str = ", ".join(cols) + where_clause = f"WHERE {where}" if where else "" + + # Determine which metrics we need to compute + needs_distinct = any(isinstance(op, DistinctnessOperator) for op in operators) + needs_unique = any(isinstance(op, UniquenessOperator) for op in operators) + needs_unique_ratio = any(isinstance(op, UniqueValueRatioOperator) for op in operators) + + # Build SELECT clause + select_parts = [] + + # Always need total_count for Distinctness and Uniqueness + if needs_distinct or needs_unique: + select_parts.append("SUM(cnt) AS total_count") + + # distinct_count needed for Distinctness and UniqueValueRatio + if needs_distinct or needs_unique_ratio: + select_parts.append("COUNT(*) AS distinct_count") + + # unique_count needed for Uniqueness and UniqueValueRatio + if needs_unique or needs_unique_ratio: + select_parts.append("SUM(CASE WHEN cnt = 1 THEN 1 ELSE 0 END) AS unique_count") + + return f""" + WITH freq AS ( + SELECT {cols_str}, COUNT(*) AS cnt + FROM {table} + {where_clause} + GROUP BY {cols_str} + ) + SELECT {', '.join(select_parts)} + FROM freq + """ + + def _extract_result( + self, + df: "pd.DataFrame", + operator: "GroupingOperator", + ) -> MetricResult: + """ + Extract the metric result for a specific operator from the fused query result. + + Args: + df: DataFrame containing fused query results + operator: The operator to extract result for + + Returns: + MetricResult for the operator + """ + if isinstance(operator, DistinctnessOperator): + distinct = operator.safe_float(df, "distinct_count") or 0 + total = operator.safe_float(df, "total_count") or 0 + value = distinct / total if total > 0 else 0.0 + + elif isinstance(operator, UniquenessOperator): + unique = operator.safe_float(df, "unique_count") or 0 + total = operator.safe_float(df, "total_count") or 0 + value = unique / total if total > 0 else 0.0 + + elif isinstance(operator, UniqueValueRatioOperator): + distinct = operator.safe_float(df, "distinct_count") or 0 + unique = operator.safe_float(df, "unique_count") or 0 + value = unique / distinct if distinct > 0 else 0.0 + + else: + # Fallback (shouldn't happen for batchable operators) + value = 0.0 + + return MetricResult( + name=operator.metric_name, + instance=operator.instance, + entity=operator.entity, + value=value, + ) + + +__all__ = [ + "GroupingOperatorBatcher", + "BATCHABLE_OPERATORS", +] diff --git a/pydeequ/engines/operators/profiling_operators.py b/pydeequ/engines/operators/profiling_operators.py index 93cbfbb..9e4a892 100644 --- a/pydeequ/engines/operators/profiling_operators.py +++ b/pydeequ/engines/operators/profiling_operators.py @@ -354,10 +354,34 @@ def build_numeric_stats_query(self, table: str) -> str: return f"SELECT {', '.join(aggregations)} FROM {table}" + def build_percentile_query(self, table: str) -> str: + """ + Build query for percentiles of all numeric columns. + + Args: + table: Table name to query + + Returns: + SQL query string (empty if no numeric columns) + """ + if not self.numeric_columns: + return "" + + aggregations = [] + for col in self.numeric_columns: + aggregations.extend([ + f"QUANTILE_CONT({col}, 0.25) as p25_{col}", + f"QUANTILE_CONT({col}, 0.50) as p50_{col}", + f"QUANTILE_CONT({col}, 0.75) as p75_{col}", + ]) + + return f"SELECT {', '.join(aggregations)} FROM {table}" + def extract_profiles( self, completeness_df: "pd.DataFrame", numeric_df: Optional["pd.DataFrame"] = None, + percentile_df: Optional["pd.DataFrame"] = None, ) -> List[ColumnProfile]: """ Extract column profiles from query results. @@ -365,6 +389,7 @@ def extract_profiles( Args: completeness_df: DataFrame with completeness statistics numeric_df: DataFrame with numeric statistics (optional) + percentile_df: DataFrame with percentile statistics (optional) Returns: List of ColumnProfile objects @@ -400,6 +425,17 @@ def extract_profiles( profile.sum = self.safe_float(numeric_df, f"sum_{col}") profile.std_dev = self.safe_float(numeric_df, f"stddev_{col}") + # Add percentiles if applicable + if col in self.numeric_columns and percentile_df is not None: + p25 = self.safe_float(percentile_df, f"p25_{col}") + p50 = self.safe_float(percentile_df, f"p50_{col}") + p75 = self.safe_float(percentile_df, f"p75_{col}") + profile.approx_percentiles = json.dumps({ + "0.25": p25, + "0.50": p50, + "0.75": p75, + }) + profiles.append(profile) return profiles From a17bfa0405e058b1293ae767d024c328d62f12d3 Mon Sep 17 00:00:00 2001 From: Peter Liu Date: Wed, 21 Jan 2026 14:41:18 -0500 Subject: [PATCH 7/7] fix spark benchmark for 100k rows that suffers from a cold start issue --- BENCHMARK.md | 74 +++++++++++++++++++-------------------- imgs/benchmark_chart.png | Bin 186257 -> 185536 bytes 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/BENCHMARK.md b/BENCHMARK.md index ca9b309..025c159 100644 --- a/BENCHMARK.md +++ b/BENCHMARK.md @@ -58,36 +58,36 @@ Benchmark run on Apple M3 Max (14 cores), macOS Darwin 25.2.0. | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.022 | 1.171 | **54.4x** | -| 1M | 0.064 | 1.829 | **28.6x** | -| 5M | 0.170 | 2.474 | **14.6x** | -| 10M | 0.267 | 3.033 | **11.3x** | -| 50M | 1.132 | 10.593 | **9.4x** | -| 130M | 2.712 | 27.074 | **10.0x** | +| 100K | 0.034 | 0.662 | **19.5x** | +| 1M | 0.071 | 1.648 | **23.2x** | +| 5M | 0.167 | 2.470 | **14.8x** | +| 10M | 0.268 | 3.239 | **12.1x** | +| 50M | 1.114 | 12.448 | **11.2x** | +| 130M | 2.752 | 28.404 | **10.3x** | ### Experiment 2: Varying Columns | Cols | Checks | DuckDB (s) | Spark (s) | Speedup | |------|--------|------------|-----------|---------| -| 10 | 16 | 0.090 | 1.556 | **17.2x** | -| 20 | 46 | 0.111 | 2.169 | **19.5x** | -| 40 | 106 | 0.143 | 2.878 | **20.2x** | -| 80 | 226 | 0.253 | 4.474 | **17.7x** | +| 10 | 16 | 0.076 | 1.619 | **21.3x** | +| 20 | 46 | 0.081 | 2.078 | **25.7x** | +| 40 | 106 | 0.121 | 2.781 | **23.0x** | +| 80 | 226 | 0.177 | 4.258 | **24.1x** | ### Experiment 3: Column Profiling | Rows | DuckDB (s) | Spark (s) | Speedup | |------|------------|-----------|---------| -| 100K | 0.044 | 0.638 | **14.5x** | -| 1M | 0.297 | 0.701 | **2.4x** | -| 5M | 1.521 | 1.886 | **1.2x** | -| 10M | 2.902 | 3.406 | **1.2x** | +| 100K | 0.045 | 0.585 | **13.0x** | +| 1M | 0.288 | 0.720 | **2.5x** | +| 5M | 1.524 | 2.351 | **1.5x** | +| 10M | 2.993 | 3.975 | **1.3x** | ### Key Takeaways -1. **DuckDB is 10-54x faster** for row-scaling validation workloads -2. **Consistent speedup across complexity** - 17-20x speedup regardless of column count -3. **Profiling converges** - at 10M rows, DuckDB is still 1.2x faster +1. **DuckDB is 10-23x faster** for row-scaling validation workloads +2. **Consistent speedup across complexity** - 21-26x speedup regardless of column count +3. **Profiling converges** - at 10M rows, DuckDB is still 1.3x faster 4. **No JVM overhead** - DuckDB runs natively in Python, no startup cost ## Performance Optimizations @@ -208,49 +208,49 @@ plan = engine.explain_query("SELECT COUNT(*) FROM test") ### Measured Performance Improvements -Benchmark comparison: Baseline (2026-01-20) vs After Optimization (2026-01-21) +Benchmark comparison: Baseline (2026-01-20) vs After Optimization (2026-01-21, 5-run average) #### Experiment 2: Varying Columns (KEY METRIC - Speedup Degradation Fix) | Cols | Checks | Before DuckDB | After DuckDB | Spark | Before Speedup | After Speedup | |------|--------|---------------|--------------|-------|----------------|---------------| -| 10 | 16 | 0.118s | 0.090s | 1.556s | 14.1x | **17.2x** | -| 20 | 46 | 0.286s | 0.111s | 2.169s | 7.5x | **19.5x** | -| 40 | 106 | 0.713s | 0.143s | 2.878s | 4.0x | **20.2x** | -| 80 | 226 | 2.214s | 0.253s | 4.474s | 2.0x | **17.7x** | +| 10 | 16 | 0.118s | 0.076s | 1.619s | 14.1x | **21.3x** | +| 20 | 46 | 0.286s | 0.081s | 2.078s | 7.5x | **25.7x** | +| 40 | 106 | 0.713s | 0.121s | 2.781s | 4.0x | **23.0x** | +| 80 | 226 | 2.214s | 0.177s | 4.258s | 2.0x | **24.1x** | **Key Achievement**: The speedup degradation problem is **SOLVED**. - **Before**: Speedup degraded from 14x (10 cols) down to 2x (80 cols) -- **After**: Speedup is consistent **~17-20x** across ALL column counts +- **After**: Speedup is consistent **~21-26x** across ALL column counts #### DuckDB-Only Performance Gains | Cols | Before | After | Improvement | |------|--------|-------|-------------| -| 10 | 0.118s | 0.090s | 24% faster | -| 20 | 0.286s | 0.111s | 61% faster | -| 40 | 0.713s | 0.143s | 80% faster | -| 80 | 2.214s | 0.253s | **89% faster (~9x)** | +| 10 | 0.118s | 0.076s | 36% faster | +| 20 | 0.286s | 0.081s | 72% faster | +| 40 | 0.713s | 0.121s | 83% faster | +| 80 | 2.214s | 0.177s | **92% faster (~12x)** | #### Experiment 1: Varying Rows (16 checks) | Rows | Before | After | Improvement | |------|--------|-------|-------------| -| 100K | 0.052s | 0.022s | 58% faster | -| 1M | 0.090s | 0.064s | 29% faster | -| 5M | 0.221s | 0.170s | 23% faster | -| 10M | 0.335s | 0.267s | 20% faster | -| 50M | 1.177s | 1.132s | 4% faster | -| 130M | 2.897s | 2.712s | 6% faster | +| 100K | 0.052s | 0.034s | 35% faster | +| 1M | 0.090s | 0.071s | 21% faster | +| 5M | 0.221s | 0.167s | 24% faster | +| 10M | 0.335s | 0.268s | 20% faster | +| 50M | 1.177s | 1.114s | 5% faster | +| 130M | 2.897s | 2.752s | 5% faster | #### Experiment 3: Column Profiling (10 columns) | Rows | Before | After | Change | |------|--------|-------|--------| -| 100K | 0.086s | 0.044s | 49% faster | -| 1M | 0.388s | 0.297s | 23% faster | -| 5M | 1.470s | 1.521s | ~same | -| 10M | 2.659s | 2.902s | 9% slower | +| 100K | 0.086s | 0.045s | 48% faster | +| 1M | 0.388s | 0.288s | 26% faster | +| 5M | 1.470s | 1.524s | ~same | +| 10M | 2.659s | 2.993s | 13% slower | Note: Profiling shows slight regression at very high row counts due to batched query overhead, which is a trade-off for the significant gains in column scaling. diff --git a/imgs/benchmark_chart.png b/imgs/benchmark_chart.png index e1551e3ad4a7ff6d5a627fd2141b9886119856fc..a6dc1ff8f574169d5c231cb0c9102b62b21c4b95 100644 GIT binary patch literal 185536 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 C%B+(B