From e899d7bc8204947eef9c25a6df7a59a4628ab8c4 Mon Sep 17 00:00:00 2001 From: Cenz Wong <44856918+cenzwong@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:30:39 +0000 Subject: [PATCH] feat: changing column enabler from decorator to function --- dist/pysparky-0.1.0-py3-none-any.whl | Bin 17803 -> 18580 bytes dist/pysparky-0.1.0.tar.gz | Bin 18254 -> 18722 bytes example/dev.ipynb | 105 ++++++++++++++++++++------- pysparky.egg-info/PKG-INFO | 1 - pysparky.egg-info/SOURCES.txt | 3 + pysparky/enabler.py | 25 +++++++ pysparky/functions/conditions.py | 11 +-- pysparky/functions/general.py | 36 +++++---- pysparky/functions/math_.py | 35 +++++---- pysparky/typing.py | 5 ++ requirements.txt | 1 + tests/functions/test_general.py | 8 -- tests/test_enabler.py | 27 +++++++ 13 files changed, 185 insertions(+), 72 deletions(-) create mode 100644 pysparky/enabler.py create mode 100644 pysparky/typing.py create mode 100644 tests/test_enabler.py diff --git a/dist/pysparky-0.1.0-py3-none-any.whl b/dist/pysparky-0.1.0-py3-none-any.whl index 512b0aa5d511d82b374369a5aa5e8fc053ba8688..ca7c4e606a9ca9ed1f39751c954a57a1d27425ee 100644 GIT binary patch delta 6522 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 delta 5765 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 NQ2gOE3)#N_{|7NZXg&Y{ diff --git a/dist/pysparky-0.1.0.tar.gz b/dist/pysparky-0.1.0.tar.gz index 03f8cedb4218832959794e33c6392ab0f97b6e34..d13b1aede2e1c2036dac17832c295049ae639701 100644 GIT binary patch literal 18722 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 M113OFasV&_01@$E3;+NC literal 18254 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 RpMSB>{|Ef#1Umo-0s!}BW+4Co diff --git a/example/dev.ipynb b/example/dev.ipynb index 2b1897f..95b4b41 100644 --- a/example/dev.ipynb +++ b/example/dev.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -23,25 +23,6 @@ "text": [ "3.5.2\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "24/10/03 15:42:41 WARN Utils: Your hostname, codespaces-0aafae resolves to a loopback address: 127.0.0.1; using 10.0.1.110 instead (on interface eth0)\n", - "24/10/03 15:42:41 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n", - "Setting default log level to \"WARN\".\n", - "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n", - "24/10/03 15:42:42 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n", - "24/10/03 15:42:44 WARN Utils: Service 'SparkUI' could not bind on port 4040. Attempting port 4041.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "24/10/03 15:42:56 WARN GarbageCollectionMetrics: To enable non-built-in garbage collector(s) List(G1 Concurrent GC), users should configure it(them) to spark.eventLog.gcMetrics.youngGenerationGarbageCollectors or spark.eventLog.gcMetrics.oldGenerationGarbageCollectors\n" - ] } ], "source": [ @@ -60,6 +41,15 @@ "spark = SparkSession.builder.getOrCreate()" ] }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from pysparky import functions as F_" + ] + }, { "cell_type": "code", "execution_count": null, @@ -67,6 +57,46 @@ "outputs": [], "source": [] }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Column<'trim(regexp_replace(hi, \\s+, , 1))'>" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "F_.single_space_and_trim(\"hi\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Column<'CASE WHEN (hi = 1) THEN Ture END'>" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "F_.when_mapping(\"hi\", {1: \"Ture\"})" + ] + }, { "cell_type": "code", "execution_count": 3, @@ -128,11 +158,7 @@ "se.convert_1d_list_to_dataframe(\n", " spark, my_list, [\"ID1\", \"ID2\", \"ID3\", \"ID4\"], axis=\"row\"\n", ").show()\n", - "se.convert_1d_list_to_dataframe(spark, my_list, \"ID\", axis=\"column\").show()\n", - "spark.convert_1d_list_to_dataframe(\n", - " my_list, [\"ID1\", \"ID2\", \"ID3\", \"ID4\"], axis=\"row\"\n", - ").show()\n", - "spark.convert_1d_list_to_dataframe(my_list, \"ID\", axis=\"column\").show()" + "se.convert_1d_list_to_dataframe(spark, my_list, \"ID\", axis=\"column\").show()" ] }, { @@ -201,6 +227,33 @@ "result == [(3, 4)]" ] }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(Column<'hello'>,)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hello_columns" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/pysparky.egg-info/PKG-INFO b/pysparky.egg-info/PKG-INFO index 06f471c..99550c5 100644 --- a/pysparky.egg-info/PKG-INFO +++ b/pysparky.egg-info/PKG-INFO @@ -35,7 +35,6 @@ python -m build # TODO - Change pytest test case -- Build mkdocs -> to make it standard to ingest to MkDocs - Build wheels for PyPi # Reference: diff --git a/pysparky.egg-info/SOURCES.txt b/pysparky.egg-info/SOURCES.txt index 6f5ae2e..1948cd6 100644 --- a/pysparky.egg-info/SOURCES.txt +++ b/pysparky.egg-info/SOURCES.txt @@ -4,11 +4,13 @@ pyproject.toml pysparky/__init__.py pysparky/debug.py pysparky/decorator.py +pysparky/enabler.py pysparky/quality.py pysparky/reader_options.py pysparky/schema_ext.py pysparky/spark_ext.py pysparky/transformation_ext.py +pysparky/typing.py pysparky/utils.py pysparky.egg-info/PKG-INFO pysparky.egg-info/SOURCES.txt @@ -21,6 +23,7 @@ pysparky/functions/general.py pysparky/functions/math_.py tests/test_debug.py tests/test_decorator.py +tests/test_enabler.py tests/test_quality.py tests/test_schema_ext.py tests/test_spark_ext.py diff --git a/pysparky/enabler.py b/pysparky/enabler.py new file mode 100644 index 0000000..bfcd85a --- /dev/null +++ b/pysparky/enabler.py @@ -0,0 +1,25 @@ +from pyspark.sql import Column +from pyspark.sql import functions as F + +from pysparky.typing import ColumnOrName + + +def column_or_name_enabler(*columns: ColumnOrName) -> tuple[Column, ...]: + """ + Enables PySpark functions to accept either column names (as strings) or Column objects. + + Parameters: + columns (ColumnOrName): Column names (as strings) or Column objects to be converted. + + Returns: + tuple[Column]: A tuple of Column objects. + + Example: + >>> column_or_name_enabler("col1", "col2", F.col("col3")) + (Column, Column, Column) + """ + return tuple( + map( + lambda column: F.col(column) if isinstance(column, str) else column, columns + ) + ) diff --git a/pysparky/functions/conditions.py b/pysparky/functions/conditions.py index ee98090..c151057 100644 --- a/pysparky/functions/conditions.py +++ b/pysparky/functions/conditions.py @@ -1,17 +1,18 @@ from functools import reduce from operator import and_, or_ -from typing import Union from pyspark.sql import Column from pyspark.sql import functions as F +from pysparky.typing import ColumnOrName -def condition_and(*conditions: Union[Column, str]) -> Column: + +def condition_and(*conditions: ColumnOrName) -> Column: """ Combines multiple conditions using logical AND. Args: - *conditions (Union[Column, str]): Multiple PySpark Column objects or SQL expression strings representing conditions. + *conditions (ColumnOrName): Multiple PySpark Column objects or SQL expression strings representing conditions. Returns: Column: A single PySpark Column object representing the combined condition. @@ -29,12 +30,12 @@ def condition_and(*conditions: Union[Column, str]) -> Column: return reduce(and_, parsed_conditions, F.lit(True)) -def condition_or(*conditions: Union[Column, str]) -> Column: +def condition_or(*conditions: ColumnOrName) -> Column: """ Combines multiple conditions using logical OR. Args: - *conditions (Union[Column, str]): Multiple PySpark Column objects or SQL expression strings representing conditions. + *conditions (ColumnOrName): Multiple PySpark Column objects or SQL expression strings representing conditions. Returns: Column: A single PySpark Column object representing the combined condition. diff --git a/pysparky/functions/general.py b/pysparky/functions/general.py index b6eb28b..03d31b0 100644 --- a/pysparky/functions/general.py +++ b/pysparky/functions/general.py @@ -7,6 +7,8 @@ from pyspark.sql import functions as F from pysparky import decorator, utils +from pysparky.enabler import column_or_name_enabler +from pysparky.typing import ColumnOrName @decorator.extension_enabler(Column) @@ -67,7 +69,7 @@ def chain(self, func, *args, **kwargs) -> Column: @decorator.extension_enabler(Column) @decorator.pyspark_column_or_name_enabler("column_or_name") def startswiths( - column_or_name: str | Column, list_of_strings: list[str] + column_or_name: ColumnOrName, list_of_strings: list[str] ) -> pyspark.sql.Column: """ Creates a PySpark Column expression to check if the given column starts with any string in the list. @@ -88,9 +90,8 @@ def startswiths( @decorator.extension_enabler(Column) -@decorator.pyspark_column_or_name_enabler("column_or_name") def replace_strings_to_none( - column_or_name: str | Column, + column_or_name: ColumnOrName, list_of_null_string: list[str], customize_output: Any = None, ) -> pyspark.sql.Column: @@ -104,14 +105,13 @@ def replace_strings_to_none( Column: A Spark DataFrame column with the values replaced. """ - return F.when(column_or_name.isin(list_of_null_string), customize_output).otherwise( - column_or_name - ) + (column,) = column_or_name_enabler(column_or_name) + + return F.when(column.isin(list_of_null_string), customize_output).otherwise(column) @decorator.extension_enabler(Column) -@decorator.pyspark_column_or_name_enabler("column_or_name") -def single_space_and_trim(column_or_name: str | Column) -> Column: +def single_space_and_trim(column_or_name: ColumnOrName) -> Column: """ Replaces multiple white spaces with a single space and trims the column. @@ -126,8 +126,7 @@ def single_space_and_trim(column_or_name: str | Column) -> Column: @decorator.extension_enabler(Column) -@decorator.pyspark_column_or_name_enabler("column_or_name") -def get_value_from_map(column_or_name: str | Column, dict_: dict) -> Column: +def get_value_from_map(column_or_name: ColumnOrName, dict_: dict) -> Column: """ Retrieves a value from a map (dictionary) using a key derived from a specified column in a DataFrame. @@ -153,12 +152,13 @@ def get_value_from_map(column_or_name: str | Column, dict_: dict) -> Column: | 2| b| +----------+-----+ """ - return utils.create_map_from_dict(dict_)[column_or_name] + (column,) = column_or_name_enabler(column_or_name) + + return utils.create_map_from_dict(dict_)[column] @decorator.extension_enabler(Column) -@decorator.pyspark_column_or_name_enabler("column_or_name") -def when_mapping(column_or_name: Column, dict_: dict) -> Column: +def when_mapping(column_or_name: ColumnOrName, dict_: dict) -> Column: """ Applies a series of conditional mappings to a PySpark Column based on a dictionary of conditions and values. @@ -169,7 +169,11 @@ def when_mapping(column_or_name: Column, dict_: dict) -> Column: Returns: Column: A new PySpark Column with the conditional mappings applied. """ - result_column = F # initiate as an functions - for condition, value in dict_.items(): - result_column = result_column.when(column_or_name == condition, value) + (column,) = column_or_name_enabler(column_or_name) + + def reducer(result_column: Column, condition_value: tuple[Any, Any]) -> Column: + condition, value = condition_value + return result_column.when(column == condition, value) + + result_column: Column = functools.reduce(reducer, dict_.items(), F) # type: ignore return result_column diff --git a/pysparky/functions/math_.py b/pysparky/functions/math_.py index 273a08c..58c8ba1 100644 --- a/pysparky/functions/math_.py +++ b/pysparky/functions/math_.py @@ -2,24 +2,26 @@ from pyspark.sql import functions as F from pysparky import decorator +from pysparky.enabler import column_or_name_enabler +from pysparky.typing import ColumnOrName @decorator.extension_enabler(Column) @decorator.pyspark_column_or_name_enabler("lat1", "long1", "lat2", "long2") def haversine_distance( - lat1: Column, - long1: Column, - lat2: Column, - long2: Column, + lat1: ColumnOrName, + long1: ColumnOrName, + lat2: ColumnOrName, + long2: ColumnOrName, ) -> Column: """ Calculates the Haversine distance between two sets of latitude and longitude coordinates. Args: - lat1 (Column): The column containing the latitude of the first coordinate. - long1 (Column): The column containing the longitude of the first coordinate. - lat2 (Column): The column containing the latitude of the second coordinate. - long2 (Column): The column containing the longitude of the second coordinate. + lat1 (ColumnOrName): The column containing the latitude of the first coordinate. + long1 (ColumnOrName): The column containing the longitude of the first coordinate. + lat2 (ColumnOrName): The column containing the latitude of the second coordinate. + long2 (ColumnOrName): The column containing the longitude of the second coordinate. Returns: Column: The column containing the calculated Haversine distance. @@ -54,11 +56,10 @@ def haversine_distance( return distance.alias("haversine_distance") -@decorator.pyspark_column_or_name_enabler("columnOrName") def cumsum( - columnOrName: Column, - partition_by: list[Column] = None, - order_by_column: Column = None, + column_or_name: ColumnOrName, + partition_by: list[Column] | None = None, + order_by_column: Column | None = None, is_normalized: bool = False, is_descending: bool = False, alias: str = "cumsum", @@ -67,7 +68,7 @@ def cumsum( Calculate the cumulative sum of a column, optionally partitioned by other columns. Args: - columnOrName (Column): The column for which to calculate the cumulative sum. + column_or_name (Column): The column for which to calculate the cumulative sum. partition_by (list[Column], optional): A list of columns to partition by. Defaults to an empty list. order_by_column: The Column for order by, null for using the same column. is_normalized (bool, optional): Whether to normalize the cumulative sum. Defaults to False. @@ -82,13 +83,15 @@ def cumsum( >>> result_df = df.select("id", "category", "value", cumsum(F.col("value"), partition_by=[F.col("category")], is_descending=True)) >>> result_df.display() """ + (column,) = column_or_name_enabler(column_or_name) + if partition_by is None: partition_by = [] if order_by_column is None: - order_by_column = columnOrName + order_by_column = column if is_normalized: - total_sum = F.sum(columnOrName).over(Window.partitionBy(partition_by)) + total_sum = F.sum(column).over(Window.partitionBy(partition_by)) else: total_sum = F.lit(1) @@ -97,7 +100,7 @@ def cumsum( else: order_by_column_ordered = order_by_column.asc() - cumsum_ = F.sum(columnOrName).over( + cumsum_ = F.sum(column).over( Window.partitionBy(partition_by).orderBy(order_by_column_ordered) ) diff --git a/pysparky/typing.py b/pysparky/typing.py new file mode 100644 index 0000000..9d48cf7 --- /dev/null +++ b/pysparky/typing.py @@ -0,0 +1,5 @@ +from typing import TypeAlias + +from pyspark.sql import Column + +ColumnOrName: TypeAlias = Column | str diff --git a/requirements.txt b/requirements.txt index 471c83a..282173c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ pyspark pytest isort pyarrow +mypy # build mkdocs-material mkdocstrings-python \ No newline at end of file diff --git a/tests/functions/test_general.py b/tests/functions/test_general.py index 9cb91bd..db0f125 100644 --- a/tests/functions/test_general.py +++ b/tests/functions/test_general.py @@ -45,14 +45,6 @@ def test_replace_strings_to_none(spark): ] ).select(F_.replace_strings_to_none("_1", ["", " "]).alias("output")) - test2_sdf = spark.createDataFrame( - [ - ("data",), - ("",), - (" ",), - ] - ).select(F.col("_1").replace_strings_to_none(["", " "]).alias("output")) - # it will raise error with assertDataFrameEqual if there is an error assert test_sdf.collect() == target_sdf.collect() diff --git a/tests/test_enabler.py b/tests/test_enabler.py new file mode 100644 index 0000000..459ac49 --- /dev/null +++ b/tests/test_enabler.py @@ -0,0 +1,27 @@ +import pytest +from pyspark.sql import Column +from pyspark.sql import functions as F + +from pysparky import enabler + + +def test_column_or_name_enabler_with_strings(spark): + col1, col2, col3 = enabler.column_or_name_enabler("col1", "col2", "col3") + assert isinstance(col1, Column) + assert isinstance(col2, Column) + assert isinstance(col3, Column) + + +def test_column_or_name_enabler_with_columns(spark): + col1, col2 = enabler.column_or_name_enabler(F.col("col1"), F.col("col2")) + assert isinstance(col1, Column) + assert isinstance(col2, Column) + + +def test_column_or_name_enabler_with_1(spark): + (col1,) = enabler.column_or_name_enabler("col1") + assert isinstance(col1, Column) + + +if __name__ == "__main__": + pytest.main()