diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fc020f..e320874 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ jobs: - name: Check lock file run: poetry lock --check - - name: Run pre-commit hooks - run: poetry run pre-commit run -a + - name: Run code quality checks + run: poetry run make check test: runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index c1f8aed..1afed5f 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ install: ## Install the Poetry environment check: ## Run code quality checks @echo "Running pre-commit hooks" @poetry run pre-commit run -a + @poetry run mypy chispa .PHONY: test test: ## Run unit tests diff --git a/chispa/__init__.py b/chispa/__init__.py index cef3d31..706acc1 100644 --- a/chispa/__init__.py +++ b/chispa/__init__.py @@ -3,6 +3,9 @@ import os import sys from glob import glob +from typing import Callable + +from pyspark.sql import DataFrame # Add PySpark to the library path based on the value of SPARK_HOME if pyspark is not already in our path try: @@ -10,7 +13,7 @@ except ImportError: # We need to add PySpark, try use findspark, or failback to the "manually" find it try: - import findspark + import findspark # type: ignore[import-untyped] findspark.init() except ImportError: @@ -46,7 +49,7 @@ class Chispa: - def __init__(self, formats: FormattingConfig | None = None, default_output=None): + def __init__(self, formats: FormattingConfig | None = None) -> None: if not formats: self.formats = FormattingConfig() elif isinstance(formats, FormattingConfig): @@ -54,20 +57,18 @@ def __init__(self, formats: FormattingConfig | None = None, default_output=None) else: self.formats = FormattingConfig._from_arbitrary_dataclass(formats) - self.default_outputs = default_output - def assert_df_equality( self, - df1, - df2, - ignore_nullable=False, - transforms=None, - allow_nan_equality=False, - ignore_column_order=False, - ignore_row_order=False, - underline_cells=False, - ignore_metadata=False, - ): + df1: DataFrame, + df2: DataFrame, + ignore_nullable: bool = False, + transforms: list[Callable] | None = None, # type: ignore[type-arg] + allow_nan_equality: bool = False, + ignore_column_order: bool = False, + ignore_row_order: bool = False, + underline_cells: bool = False, + ignore_metadata: bool = False, + ) -> None: return assert_df_equality( df1, df2, diff --git a/chispa/bcolors.py b/chispa/bcolors.py index 2ced6cd..1912ff0 100644 --- a/chispa/bcolors.py +++ b/chispa/bcolors.py @@ -33,7 +33,7 @@ class bcolors: Bold = "\033[1m" Underline = "\033[4m" - def __init__(self): + def __init__(self) -> None: warnings.warn("The `bcolors` class is deprecated and will be removed in a future version.", DeprecationWarning) diff --git a/chispa/column_comparer.py b/chispa/column_comparer.py index 5ab1e0e..c31d85a 100644 --- a/chispa/column_comparer.py +++ b/chispa/column_comparer.py @@ -1,6 +1,7 @@ from __future__ import annotations from prettytable import PrettyTable +from pyspark.sql import DataFrame from chispa.formatting import blue @@ -11,12 +12,12 @@ class ColumnsNotEqualError(Exception): pass -def assert_column_equality(df, col_name1, col_name2): - elements = df.select(col_name1, col_name2).collect() - colName1Elements = list(map(lambda x: x[0], elements)) - colName2Elements = list(map(lambda x: x[1], elements)) - if colName1Elements != colName2Elements: - zipped = list(zip(colName1Elements, colName2Elements)) +def assert_column_equality(df: DataFrame, col_name1: str, col_name2: str) -> None: + rows = df.select(col_name1, col_name2).collect() + col_name_1_elements = [x[0] for x in rows] + col_name_2_elements = [x[1] for x in rows] + if col_name_1_elements != col_name_2_elements: + zipped = list(zip(col_name_1_elements, col_name_2_elements)) t = PrettyTable([col_name1, col_name2]) for elements in zipped: if elements[0] == elements[1]: @@ -26,18 +27,18 @@ def assert_column_equality(df, col_name1, col_name2): raise ColumnsNotEqualError("\n" + t.get_string()) -def assert_approx_column_equality(df, col_name1, col_name2, precision): - elements = df.select(col_name1, col_name2).collect() - colName1Elements = list(map(lambda x: x[0], elements)) - colName2Elements = list(map(lambda x: x[1], elements)) +def assert_approx_column_equality(df: DataFrame, col_name1: str, col_name2: str, precision: float) -> None: + rows = df.select(col_name1, col_name2).collect() + col_name_1_elements = [x[0] for x in rows] + col_name_2_elements = [x[1] for x in rows] all_rows_equal = True - zipped = list(zip(colName1Elements, colName2Elements)) + zipped = list(zip(col_name_1_elements, col_name_2_elements)) t = PrettyTable([col_name1, col_name2]) for elements in zipped: first = blue(str(elements[0])) second = blue(str(elements[1])) # when one is None and the other isn't, they're not equal - if (elements[0] is None and elements[1] is not None) or (elements[0] is not None and elements[1] is None): + if (elements[0] is None) != (elements[1] is None): all_rows_equal = False t.add_row([str(elements[0]), str(elements[1])]) # when both are None, they're equal diff --git a/chispa/dataframe_comparer.py b/chispa/dataframe_comparer.py index 167ceb2..fdc7201 100644 --- a/chispa/dataframe_comparer.py +++ b/chispa/dataframe_comparer.py @@ -1,6 +1,9 @@ from __future__ import annotations from functools import reduce +from typing import Callable + +from pyspark.sql import DataFrame from chispa.formatting import FormattingConfig from chispa.row_comparer import are_rows_approx_equal, are_rows_equal_enhanced @@ -18,17 +21,17 @@ class DataFramesNotEqualError(Exception): def assert_df_equality( - df1, - df2, - ignore_nullable=False, - transforms=None, - allow_nan_equality=False, - ignore_column_order=False, - ignore_row_order=False, - underline_cells=False, - ignore_metadata=False, + df1: DataFrame, + df2: DataFrame, + ignore_nullable: bool = False, + transforms: list[Callable] | None = None, # type: ignore[type-arg] + allow_nan_equality: bool = False, + ignore_column_order: bool = False, + ignore_row_order: bool = False, + underline_cells: bool = False, + ignore_metadata: bool = False, formats: FormattingConfig | None = None, -): +) -> None: if not formats: formats = FormattingConfig() elif not isinstance(formats, FormattingConfig): @@ -48,7 +51,7 @@ def assert_df_equality( df1.collect(), df2.collect(), are_rows_equal_enhanced, - [True], + {"allow_nan_equality": True}, underline_cells=underline_cells, formats=formats, ) @@ -61,7 +64,7 @@ def assert_df_equality( ) -def are_dfs_equal(df1, df2): +def are_dfs_equal(df1: DataFrame, df2: DataFrame) -> bool: if df1.schema != df2.schema: return False if df1.collect() != df2.collect(): @@ -70,16 +73,16 @@ def are_dfs_equal(df1, df2): def assert_approx_df_equality( - df1, - df2, - precision, - ignore_nullable=False, - transforms=None, - allow_nan_equality=False, - ignore_column_order=False, - ignore_row_order=False, + df1: DataFrame, + df2: DataFrame, + precision: float, + ignore_nullable: bool = False, + transforms: list[Callable] | None = None, # type: ignore[type-arg] + allow_nan_equality: bool = False, + ignore_column_order: bool = False, + ignore_row_order: bool = False, formats: FormattingConfig | None = None, -): +) -> None: if not formats: formats = FormattingConfig() elif not isinstance(formats, FormattingConfig): @@ -99,10 +102,12 @@ def assert_approx_df_equality( df1.collect(), df2.collect(), are_rows_approx_equal, - [precision, allow_nan_equality], - formats, + {"precision": precision, "allow_nan_equality": allow_nan_equality}, + formats=formats, ) elif allow_nan_equality: - assert_generic_rows_equality(df1.collect(), df2.collect(), are_rows_equal_enhanced, [True], formats) + assert_generic_rows_equality( + df1.collect(), df2.collect(), are_rows_equal_enhanced, {"allow_nan_equality": True}, formats=formats + ) else: - assert_basic_rows_equality(df1.collect(), df2.collect(), formats) + assert_basic_rows_equality(df1.collect(), df2.collect(), formats=formats) diff --git a/chispa/default_formats.py b/chispa/default_formats.py index ab1f292..5d3f555 100644 --- a/chispa/default_formats.py +++ b/chispa/default_formats.py @@ -15,7 +15,7 @@ class DefaultFormats: mismatched_cells: list[str] = field(default_factory=lambda: ["red", "underline"]) matched_cells: list[str] = field(default_factory=lambda: ["blue"]) - def __post_init__(self): + def __post_init__(self) -> None: warnings.warn( "DefaultFormats is deprecated. Use `chispa.formatting.FormattingConfig` instead.", DeprecationWarning ) diff --git a/chispa/formatting/format_string.py b/chispa/formatting/format_string.py index b343a3a..e0b72db 100644 --- a/chispa/formatting/format_string.py +++ b/chispa/formatting/format_string.py @@ -21,5 +21,5 @@ def format_string(input_string: str, format: Format) -> str: return formatted_string -def blue(string: str): +def blue(string: str) -> str: return Color.LIGHT_BLUE + string + Color.LIGHT_RED diff --git a/chispa/formatting/formats.py b/chispa/formatting/formats.py index 76064d8..98337a5 100644 --- a/chispa/formatting/formats.py +++ b/chispa/formatting/formats.py @@ -57,7 +57,7 @@ class Format: style: list[Style] | None = None @classmethod - def from_dict(cls, format_dict: dict) -> Format: + def from_dict(cls, format_dict: dict[str, str | list[str]]) -> Format: """ Create a Format instance from a dictionary. @@ -72,7 +72,10 @@ def from_dict(cls, format_dict: dict) -> Format: if invalid_keys: raise ValueError(f"Invalid keys in format dictionary: {invalid_keys}. Valid keys are {valid_keys}") - color = cls._get_color_enum(format_dict.get("color")) + if isinstance(format_dict.get("color"), list): + raise TypeError("The value for key 'color' should be a string, not a list!") + color = cls._get_color_enum(format_dict.get("color")) # type: ignore[arg-type] + style = format_dict.get("style") if isinstance(style, str): styles = [cls._get_style_enum(style)] @@ -81,7 +84,7 @@ def from_dict(cls, format_dict: dict) -> Format: else: styles = None - return cls(color=color, style=styles) + return cls(color=color, style=styles) # type: ignore[arg-type] @classmethod def from_list(cls, values: list[str]) -> Format: diff --git a/chispa/formatting/formatting_config.py b/chispa/formatting/formatting_config.py index 055428f..48c1fc3 100644 --- a/chispa/formatting/formatting_config.py +++ b/chispa/formatting/formatting_config.py @@ -16,10 +16,10 @@ class FormattingConfig: def __init__( self, - mismatched_rows: Format | dict = Format(Color.RED), - matched_rows: Format | dict = Format(Color.BLUE), - mismatched_cells: Format | dict = Format(Color.RED, [Style.UNDERLINE]), - matched_cells: Format | dict = Format(Color.BLUE), + mismatched_rows: Format | dict[str, str | list[str]] = Format(Color.RED), + matched_rows: Format | dict[str, str | list[str]] = Format(Color.BLUE), + mismatched_cells: Format | dict[str, str | list[str]] = Format(Color.RED, [Style.UNDERLINE]), + matched_cells: Format | dict[str, str | list[str]] = Format(Color.BLUE), ): """ Initializes the FormattingConfig with given or default formatting. @@ -46,7 +46,7 @@ def __init__( self.mismatched_cells: Format = self._parse_format(mismatched_cells) self.matched_cells: Format = self._parse_format(matched_cells) - def _parse_format(self, format: Format | dict) -> Format: + def _parse_format(self, format: Format | dict[str, str | list[str]]) -> Format: if isinstance(format, Format): return format elif isinstance(format, dict): diff --git a/chispa/number_helpers.py b/chispa/number_helpers.py index a49c84a..1bd9d00 100644 --- a/chispa/number_helpers.py +++ b/chispa/number_helpers.py @@ -1,18 +1,20 @@ from __future__ import annotations import math +from decimal import Decimal +from typing import Any -def isnan(x): +def isnan(x: Any) -> bool: try: return math.isnan(x) except TypeError: return False -def nan_safe_equality(x, y) -> bool: +def nan_safe_equality(x: int | float, y: int | float | Decimal) -> bool: return (x == y) or (isnan(x) and isnan(y)) -def nan_safe_approx_equality(x, y, precision) -> bool: +def nan_safe_approx_equality(x: int | float, y: int | float, precision: float | Decimal) -> bool: return (abs(x - y) <= precision) or (isnan(x) and isnan(y)) diff --git a/chispa/row_comparer.py b/chispa/row_comparer.py index 7835e5d..86d005d 100644 --- a/chispa/row_comparer.py +++ b/chispa/row_comparer.py @@ -11,10 +11,10 @@ def are_rows_equal(r1: Row, r2: Row) -> bool: return r1 == r2 -def are_rows_equal_enhanced(r1: Row, r2: Row, allow_nan_equality: bool) -> bool: +def are_rows_equal_enhanced(r1: Row | None, r2: Row | None, allow_nan_equality: bool) -> bool: if r1 is None and r2 is None: return True - if (r1 is None and r2 is not None) or (r2 is None and r1 is not None): + if r1 is None or r2 is None: return False d1 = r1.asDict() d2 = r2.asDict() @@ -27,10 +27,10 @@ def are_rows_equal_enhanced(r1: Row, r2: Row, allow_nan_equality: bool) -> bool: return r1 == r2 -def are_rows_approx_equal(r1: Row, r2: Row, precision: float, allow_nan_equality=False) -> bool: +def are_rows_approx_equal(r1: Row | None, r2: Row | None, precision: float, allow_nan_equality: bool = False) -> bool: if r1 is None and r2 is None: return True - if (r1 is None and r2 is not None) or (r2 is None and r1 is not None): + if r1 is None or r2 is None: return False d1 = r1.asDict() d2 = r2.asDict() diff --git a/chispa/rows_comparer.py b/chispa/rows_comparer.py index 30fdc96..c296e9c 100644 --- a/chispa/rows_comparer.py +++ b/chispa/rows_comparer.py @@ -1,14 +1,18 @@ from __future__ import annotations from itertools import zip_longest +from typing import Any, Callable from prettytable import PrettyTable +from pyspark.sql import Row import chispa from chispa.formatting import FormattingConfig, format_string -def assert_basic_rows_equality(rows1, rows2, underline_cells=False, formats: FormattingConfig | None = None): +def assert_basic_rows_equality( + rows1: list[Row], rows2: list[Row], underline_cells: bool = False, formats: FormattingConfig | None = None +) -> None: if not formats: formats = FormattingConfig() elif not isinstance(formats, FormattingConfig): @@ -47,13 +51,13 @@ def assert_basic_rows_equality(rows1, rows2, underline_cells=False, formats: For def assert_generic_rows_equality( - rows1, - rows2, - row_equality_fun, - row_equality_fun_args, - underline_cells=False, + rows1: list[Row], + rows2: list[Row], + row_equality_fun: Callable, # type: ignore[type-arg] + row_equality_fun_args: dict[str, Any], + underline_cells: bool = False, formats: FormattingConfig | None = None, -): +) -> None: if not formats: formats = FormattingConfig() elif not isinstance(formats, FormattingConfig): @@ -73,7 +77,7 @@ def assert_generic_rows_equality( format_string(str(r2), formats.mismatched_rows), ]) # rows are equal - elif row_equality_fun(r1, r2, *row_equality_fun_args): + elif row_equality_fun(r1, r2, **row_equality_fun_args): r1_string = ", ".join(map(lambda f: f"{f}={r1[f]}", r1.__fields__)) r2_string = ", ".join(map(lambda f: f"{f}={r2[f]}", r2.__fields__)) t.add_row([ @@ -83,18 +87,18 @@ def assert_generic_rows_equality( # otherwise, rows aren't equal else: r_zipped = list(zip_longest(r1.__fields__, r2.__fields__)) - r1_string = [] - r2_string = [] + r1_string_list: list[str] = [] + r2_string_list: list[str] = [] for r1_field, r2_field in r_zipped: if r1[r1_field] != r2[r2_field]: all_rows_equal = False - r1_string.append(format_string(f"{r1_field}={r1[r1_field]}", formats.mismatched_cells)) - r2_string.append(format_string(f"{r2_field}={r2[r2_field]}", formats.mismatched_cells)) + r1_string_list.append(format_string(f"{r1_field}={r1[r1_field]}", formats.mismatched_cells)) + r2_string_list.append(format_string(f"{r2_field}={r2[r2_field]}", formats.mismatched_cells)) else: - r1_string.append(format_string(f"{r1_field}={r1[r1_field]}", formats.matched_cells)) - r2_string.append(format_string(f"{r2_field}={r2[r2_field]}", formats.matched_cells)) - r1_res = ", ".join(r1_string) - r2_res = ", ".join(r2_string) + r1_string_list.append(format_string(f"{r1_field}={r1[r1_field]}", formats.matched_cells)) + r2_string_list.append(format_string(f"{r2_field}={r2[r2_field]}", formats.matched_cells)) + r1_res = ", ".join(r1_string_list) + r2_res = ", ".join(r2_string_list) t.add_row([r1_res, r2_res]) if all_rows_equal is False: diff --git a/chispa/schema_comparer.py b/chispa/schema_comparer.py index c0e619b..3773989 100644 --- a/chispa/schema_comparer.py +++ b/chispa/schema_comparer.py @@ -1,8 +1,10 @@ from __future__ import annotations +import typing from itertools import zip_longest from prettytable import PrettyTable +from pyspark.sql.types import StructField, StructType from chispa.formatting import blue @@ -13,15 +15,19 @@ class SchemasNotEqualError(Exception): pass -def assert_schema_equality(s1, s2, ignore_nullable=False, ignore_metadata=False): +def assert_schema_equality( + s1: StructType, s2: StructType, ignore_nullable: bool = False, ignore_metadata: bool = False +) -> None: if not ignore_nullable and not ignore_metadata: assert_basic_schema_equality(s1, s2) else: assert_schema_equality_full(s1, s2, ignore_nullable, ignore_metadata) -def assert_schema_equality_full(s1, s2, ignore_nullable=False, ignore_metadata=False): - def inner(s1, s2, ignore_nullable, ignore_metadata): +def assert_schema_equality_full( + s1: StructType, s2: StructType, ignore_nullable: bool = False, ignore_metadata: bool = False +) -> None: + def inner(s1: StructType, s2: StructType, ignore_nullable: bool, ignore_metadata: bool) -> bool: if len(s1) != len(s2): return False zipped = list(zip_longest(s1, s2)) @@ -44,7 +50,7 @@ def inner(s1, s2, ignore_nullable, ignore_metadata): # deprecate this # perhaps it is a little faster, but do we really need this? # I think schema equality operations are really fast to begin with -def assert_basic_schema_equality(s1, s2): +def assert_basic_schema_equality(s1: StructType, s2: StructType) -> None: if s1 != s2: t = PrettyTable(["schema1", "schema2"]) zipped = list(zip_longest(s1, s2)) @@ -57,7 +63,7 @@ def assert_basic_schema_equality(s1, s2): # deprecate this. ignore_nullable should be a flag. -def assert_schema_equality_ignore_nullable(s1, s2): +def assert_schema_equality_ignore_nullable(s1: StructType, s2: StructType) -> None: if not are_schemas_equal_ignore_nullable(s1, s2): t = PrettyTable(["schema1", "schema2"]) zipped = list(zip_longest(s1, s2)) @@ -70,7 +76,7 @@ def assert_schema_equality_ignore_nullable(s1, s2): # deprecate this. ignore_nullable should be a flag. -def are_schemas_equal_ignore_nullable(s1, s2, ignore_metadata=False): +def are_schemas_equal_ignore_nullable(s1: StructType, s2: StructType, ignore_metadata: bool = False) -> bool: if len(s1) != len(s2): return False zipped = list(zip_longest(s1, s2)) @@ -81,7 +87,9 @@ def are_schemas_equal_ignore_nullable(s1, s2, ignore_metadata=False): # "ignore_nullability" should be "ignore_nullable" for consistent terminology -def are_structfields_equal(sf1, sf2, ignore_nullability=False, ignore_metadata=False): +def are_structfields_equal( + sf1: StructField | None, sf2: StructField | None, ignore_nullability: bool = False, ignore_metadata: bool = False +) -> bool: if not ignore_nullability and not ignore_metadata: return sf1 == sf2 else: @@ -95,11 +103,12 @@ def are_structfields_equal(sf1, sf2, ignore_nullability=False, ignore_metadata=F if not ignore_metadata and sf1.metadata != sf2.metadata: return False else: - return are_datatypes_equal_ignore_nullable(sf1.dataType, sf2.dataType, ignore_metadata) + return are_datatypes_equal_ignore_nullable(sf1.dataType, sf2.dataType, ignore_metadata) # type: ignore[no-any-return, no-untyped-call] # deprecate this -def are_datatypes_equal_ignore_nullable(dt1, dt2, ignore_metadata=False): +@typing.no_type_check +def are_datatypes_equal_ignore_nullable(dt1, dt2, ignore_metadata: bool = False) -> bool: """Checks if datatypes are equal, descending into structs and arrays to ignore nullability. """ diff --git a/poetry.lock b/poetry.lock index 8612623..e26e9b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -705,6 +705,64 @@ files = [ griffe = ">=0.47" mkdocstrings = ">=0.25" +[[package]] +name = "mypy" +version = "1.11.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"}, + {file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"}, + {file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"}, + {file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"}, + {file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"}, + {file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"}, + {file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"}, + {file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"}, + {file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"}, + {file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"}, + {file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"}, + {file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"}, + {file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"}, + {file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"}, + {file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"}, + {file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"}, + {file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"}, + {file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"}, + {file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1296,4 +1354,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "9fde9a932fca40538936262263439debec9162feea75797fc973fe6a92a770b1" +content-hash = "74fd71f11fb19994adf4b8a836e4eed342eb54e5c2e5a69c391156f16b4a020d" diff --git a/pyproject.toml b/pyproject.toml index 674fa9c..d03ffef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ findspark = "1.4.2" pytest-describe = "^2.1.0" pytest-cov = "^5.0.0" pre-commit = "3.3.3" +mypy = "^1.11.0" [tool.poetry.group.mkdocs.dependencies] mkdocs = "^1.6.0" @@ -82,3 +83,16 @@ strict = true [tool.ruff.lint.isort] required-imports = ["from __future__ import annotations"] + +[tool.mypy] +files = ["chispa"] +explicit_package_bases = true +disallow_any_unimported = true +enable_error_code = [ + "ignore-without-code", + "redundant-expr", + "truthy-bool", +] +strict = true +pretty = true +show_error_codes = true