sparkleframe 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ """
2
+ Sparkleframe
3
+ """
4
+
5
+ import os
6
+
7
+ with open(os.path.join(os.path.dirname(__file__), "version.txt"), "r", encoding="utf-8") as f:
8
+ __version__ = f.read()
@@ -0,0 +1,96 @@
1
+ """
2
+ Sparkleframe
3
+ """
4
+
5
+ # ruff: noqa: F401
6
+ from __future__ import annotations
7
+
8
+ import importlib
9
+ import sys
10
+ from contextlib import contextmanager
11
+ from unittest.mock import MagicMock
12
+
13
+ # Helps users to use `from sparkleframe import Engine` instead of `from sparkleframe.engine import Engine`
14
+ from .engine import Engine
15
+
16
+
17
+ NAME_TO_FILE_OVERRIDE = {
18
+ # "DataFrameNaFunctions": "dataframe",
19
+ # "DataFrameStatFunctions": "dataframe",
20
+ # "DataFrameReader": "readwriter",
21
+ # "DataFrameWriter": "readwriter",
22
+ # "GroupedData": "group",
23
+ "SparkSession": "session",
24
+ # "WindowSpec": "window",
25
+ # "UDFRegistration": "udf",
26
+ }
27
+
28
+ ACTIVATE_CONFIG = {}
29
+
30
+
31
+ def activate() -> None:
32
+
33
+ pyspark_mock = MagicMock()
34
+ pyspark_mock.__file__ = "pyspark"
35
+ sys.modules["pyspark"] = pyspark_mock
36
+ # pyspark_mock.testing = testing
37
+ # sys.modules["pyspark.testing"] = testing
38
+
39
+ engine = "polarsdf"
40
+ prefix = "Polars"
41
+ engine_module = importlib.import_module(f"sparkleframe.{engine}")
42
+
43
+ sys.modules["pyspark.sql"] = engine_module
44
+ pyspark_mock.sql = engine_module
45
+ types = engine_module.__dict__.copy()
46
+ resolved_files = set()
47
+ for name, obj in types.items():
48
+ # print("name, obj, name.startswith(prefix): ",name, obj, name.startswith(prefix))
49
+ if name.startswith(prefix) or name in [
50
+ "Column",
51
+ "Window",
52
+ "WindowSpec",
53
+ "functions",
54
+ "types",
55
+ "Column",
56
+ "DataFrame",
57
+ "SparkSession",
58
+ ]:
59
+
60
+ name_without_prefix = name.replace(prefix, "")
61
+ if name_without_prefix == "Session":
62
+ name_without_prefix = "SparkSession"
63
+
64
+ setattr(engine_module, name_without_prefix, obj)
65
+
66
+ file = NAME_TO_FILE_OVERRIDE.get(name_without_prefix, name_without_prefix).lower()
67
+
68
+ engine_file = importlib.import_module(f"sparkleframe.{engine}.{file}")
69
+
70
+ if engine_file not in resolved_files:
71
+ sys.modules[f"pyspark.sql.{file}"] = engine_file
72
+ resolved_files.add(engine_file)
73
+
74
+ setattr(engine_file, name_without_prefix, obj)
75
+
76
+
77
+ def deactivate() -> None:
78
+ pyspark_imports = [k for k in sys.modules if k.startswith("pyspark")]
79
+
80
+ for k, v in sys.modules.copy().items():
81
+ if k in pyspark_imports:
82
+ del sys.modules[k]
83
+ # Try importing the pyspark imports again and see if pyspark is installed and therefore available
84
+ # if not then nothing will change
85
+ for k in pyspark_imports:
86
+ try:
87
+ sys.modules[k] = importlib.import_module(k)
88
+ except ImportError:
89
+ pass
90
+ ACTIVATE_CONFIG.clear()
91
+
92
+
93
+ @contextmanager
94
+ def activate_context():
95
+ activate()
96
+ yield
File without changes
@@ -0,0 +1,20 @@
1
+ from typing import Any
2
+
3
+ from pandas import DataFrame as PandasDataFrame
4
+
5
+
6
+ class DataFrame:
7
+ def __init__(self, df: Any):
8
+ self._alias = None
9
+ self.df = df
10
+
11
+ def pandas_api(self) -> PandasDataFrame:
12
+ """Mimics Pandas On Spark dataframe"""
13
+ return self.toPandas()
14
+
15
+ def to_native_df(self) -> "DataFrame":
16
+ """Return the underlying Polars DataFrame."""
17
+ return self.df
18
+
19
+ def to_spark(self) -> "DataFrame":
20
+ return self
@@ -0,0 +1,13 @@
1
+ import sys
2
+
3
+ import pytest
4
+
5
+ # Skip writing pyc files on a readonly filesystem.
6
+ sys.dont_write_bytecode = True
7
+
8
+
9
+ @pytest.fixture(scope="function", autouse=False)
10
+ def spark():
11
+ from sparkleframe.tests.spark import spark as spark_session
12
+
13
+ return spark_session
@@ -0,0 +1,86 @@
1
+ import importlib
2
+ import sys
3
+ import types
4
+ from contextlib import contextmanager
5
+ from typing import Callable
6
+
7
+ from sparkleframe import Engine
8
+
9
+ SPARKLE_MODULES = [
10
+ "functions",
11
+ "types",
12
+ "session",
13
+ "dataframe",
14
+ ]
15
+
16
+
17
+ @contextmanager
18
+ def mock_pyspark_modules(mock_map):
19
+ # Save original pyspark modules
20
+ original_modules = {k: sys.modules[k] for k in list(sys.modules) if k.startswith("pyspark")}
21
+
22
+ # Remove existing pyspark.* modules
23
+ for key in original_modules:
24
+ del sys.modules[key]
25
+
26
+ # Make sure root pyspark module exists
27
+ if "pyspark" not in sys.modules:
28
+ sys.modules["pyspark"] = types.ModuleType("pyspark")
29
+
30
+ # Add parent modules for each mock
31
+ for full_name in mock_map:
32
+ parts = full_name.split(".")
33
+ for i in range(1, len(parts)):
34
+ parent = ".".join(parts[:i])
35
+ if parent not in sys.modules:
36
+ sys.modules[parent] = types.ModuleType(parent)
37
+
38
+ # Inject mocks
39
+ sys.modules.update(mock_map)
40
+
41
+ try:
42
+ yield
43
+ finally:
44
+ # Clean up mocked modules
45
+ for key in mock_map:
46
+ pyspark_module_name = key
47
+ if pyspark_module_name in sys.modules:
48
+ del sys.modules[pyspark_module_name]
49
+
50
+ sparkle_module_name = mock_map[key].__name__
51
+ if sparkle_module_name in sys.modules:
52
+ del sys.modules[sparkle_module_name]
53
+
54
+ # Restore and reload original modules
55
+ for name, module in original_modules.items():
56
+ sys.modules[name] = module
57
+ importlib.import_module(name)
58
+
59
+
60
+ def get_mock_map(engine: Engine) -> dict:
61
+
62
+ sparkleframe_module = f"sparkleframe.{engine.module}"
63
+ engine_module = importlib.import_module(sparkleframe_module)
64
+
65
+ mock_map = {"pyspark.sql": engine_module}
66
+ types = engine_module.__dict__.copy()
67
+ for name, obj in types.items():
68
+ name = engine.clean_class_name(name)
69
+
70
+ if name in SPARKLE_MODULES:
71
+ module_name = obj.__name__.split(".")[-1]
72
+ pyspark_module = f"pyspark.sql.{module_name}"
73
+ sparkle_module = f"{sparkleframe_module}.{module_name}"
74
+ mock_map[pyspark_module] = importlib.import_module(sparkle_module)
75
+
76
+ return mock_map
77
+
78
+
79
+ def run_with_context(func: Callable, engine: Engine = None):
80
+ engine = engine or Engine.POLARS
81
+
82
+ with mock_pyspark_modules(get_mock_map(engine)):
83
+ for name in SPARKLE_MODULES:
84
+ importlib.import_module(f"sparkleframe.{engine.module}.{name}")
85
+
86
+ return func()
sparkleframe/engine.py ADDED
@@ -0,0 +1,16 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Engine(Enum):
5
+ POLARS = ("polarsdf", "Polars")
6
+
7
+ @property
8
+ def module(self):
9
+ return self.value[0]
10
+
11
+ @property
12
+ def class_prefix(self):
13
+ return self.value[1]
14
+
15
+ def clean_class_name(self, klass):
16
+ return klass.replace(self.class_prefix, "") if klass.startswith(self.class_prefix) else klass
@@ -0,0 +1,91 @@
1
+ """
2
+ this is my <file_name.py>
3
+
4
+ <content of the file>
5
+
6
+ implement the pyspark <function> function like as defined in <function_url> into my <file_name.py>
7
+
8
+ """
9
+
10
+ import os
11
+
12
+
13
+ def generate_pyspark_function_instruction(file_path: str, function_name: str, function_url: str) -> str:
14
+ """
15
+ Generates a formatted message including the contents of a Python file and an instruction
16
+ to implement a specific PySpark function based on its official documentation.
17
+
18
+ Args:
19
+ file_path (str): Path to the Python file.
20
+ function_name (str): Name of the PySpark function to implement.
21
+ function_url (str): URL to the official PySpark documentation for the function.
22
+
23
+ Returns:
24
+ str: Formatted message.
25
+ """
26
+ if not os.path.isfile(file_path):
27
+ raise FileNotFoundError(f"File not found: {file_path}")
28
+
29
+ with open(file_path, "r") as file:
30
+ file_contents = file.read()
31
+
32
+ file_name = os.path.basename(file_path)
33
+
34
+ message = f"""\
35
+ this is the content of my {file_name}:
36
+
37
+ {file_contents}
38
+
39
+ implement the pyspark {function_name} function like as defined in {function_url} into my {file_name}
40
+ """
41
+ return message
42
+
43
+
44
+ def generate_test_instruction(file_path: str, function_name: str, compare_output_spark=False) -> str:
45
+ """
46
+ Generates a formatted message including the contents of a Python file and an instruction
47
+ to implement a specific PySpark function based on its official documentation.
48
+
49
+ Args:
50
+ file_path (str): Path to the Python file.
51
+ function_name (str): Name of the PySpark function to implement.
52
+ function_url (str): URL to the official PySpark documentation for the function.
53
+
54
+ Returns:
55
+ str: Formatted message.
56
+ """
57
+ if not os.path.isfile(file_path):
58
+ raise FileNotFoundError(f"File not found: {file_path}")
59
+
60
+ with open(file_path, "r") as file:
61
+ file_contents = file.read()
62
+
63
+ file_name = os.path.basename(file_path)
64
+
65
+ spark_msg_output = (
66
+ ", create a spark dataframe build from PolarDataframe and use another spark dataframe to compare them "
67
+ if compare_output_spark
68
+ else ""
69
+ )
70
+
71
+ message = f"""\
72
+ this is the content of my test file {file_name}:
73
+
74
+ {file_contents}
75
+
76
+ generate a pytest test case, using pytest.parametrize to test the logic `{function_name}` that has been just implemented{spark_msg_output}
77
+ """
78
+ return message
79
+
80
+
81
+ file_path = "./polarsdf/dataframe.py"
82
+ function_name = "fillna"
83
+ pyspark_function_url = (
84
+ "https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.fillna.html"
85
+ )
86
+
87
+ msg = generate_pyspark_function_instruction(file_path, function_name, pyspark_function_url)
88
+
89
+ msg = generate_test_instruction(file_path, function_name, compare_output_spark=True)
90
+
91
+ print(msg)
@@ -0,0 +1,12 @@
1
+ # ruff: noqa: F401
2
+ from sparkleframe.polarsdf.column import Column
3
+ from sparkleframe.polarsdf.dataframe import DataFrame
4
+ from sparkleframe.polarsdf.functions import col
5
+ from sparkleframe.polarsdf.session import SparkSession
6
+ from sparkleframe.polarsdf.types import StringType
7
+
8
+ __all__ = [
9
+ "Column",
10
+ "SparkSession",
11
+ "DataFrame",
12
+ ]
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ import polars as pl
6
+
7
+ from sparkleframe.polarsdf.types import DataType
8
+
9
+
10
+ class Column:
11
+ def __init__(self, expr_or_name):
12
+ if isinstance(expr_or_name, str):
13
+ self.expr = pl.col(expr_or_name)
14
+ else:
15
+ self.expr = expr_or_name
16
+
17
+ # Arithmetic operations
18
+ def __mul__(self, other):
19
+ return Column(self.expr * _to_expr(other))
20
+
21
+ def __add__(self, other):
22
+ return Column(self.expr + _to_expr(other))
23
+
24
+ def __sub__(self, other):
25
+ return Column(self.expr - _to_expr(other))
26
+
27
+ def __truediv__(self, other):
28
+ return Column(self.expr / _to_expr(other))
29
+
30
+ def __radd__(self, other):
31
+ return Column(_to_expr(other) + self.expr)
32
+
33
+ def __rsub__(self, other):
34
+ return Column(_to_expr(other) - self.expr)
35
+
36
+ def __rmul__(self, other):
37
+ return Column(_to_expr(other) * self.expr)
38
+
39
+ def __rtruediv__(self, other):
40
+ return Column(_to_expr(other) / self.expr)
41
+
42
+ # Comparison operations
43
+ def __eq__(self, other):
44
+ return Column(self.expr == _to_expr(other))
45
+
46
+ def __ne__(self, other):
47
+ return Column(self.expr != _to_expr(other))
48
+
49
+ def __lt__(self, other):
50
+ return Column(self.expr < _to_expr(other))
51
+
52
+ def __le__(self, other):
53
+ return Column(self.expr <= _to_expr(other))
54
+
55
+ def __gt__(self, other):
56
+ return Column(self.expr > _to_expr(other))
57
+
58
+ def __ge__(self, other):
59
+ return Column(self.expr >= _to_expr(other))
60
+
61
+ # Logical operations
62
+ def __and__(self, other):
63
+ return Column(self.expr & _to_expr(other))
64
+
65
+ def __rand__(self, other):
66
+ return Column(_to_expr(other) & self.expr)
67
+
68
+ def __or__(self, other):
69
+ return Column(self.expr | _to_expr(other))
70
+
71
+ def __ror__(self, other):
72
+ return Column(_to_expr(other) | self.expr)
73
+
74
+ def alias(self, name: str) -> Column:
75
+ """
76
+ Mimics pyspark.sql.Column.alias
77
+
78
+ Args:
79
+ name (str): Alias name for the column expression
80
+
81
+ Returns:
82
+ Column: A new Column with the alias applied
83
+ """
84
+ return Column(self.expr.alias(name))
85
+
86
+ def cast(self, data_type: DataType) -> Column:
87
+ """
88
+ Mimics pyspark.sql.Column.cast using Polars' cast().
89
+
90
+ Args:
91
+ data_type (DataType): A sparkleframe-defined DataType object.
92
+
93
+ Returns:
94
+ Column: A new Column with the expression casted.
95
+ """
96
+ if not isinstance(data_type, DataType):
97
+ raise TypeError(f"cast() expects a DataType, got {type(data_type)}")
98
+ return Column(self.expr.cast(data_type.to_native()))
99
+
100
+ def isin(self, *values) -> Column:
101
+ """
102
+ Mimics pyspark.sql.Column.isin and supports both:
103
+ col("x").isin("a", "b") and col("x").isin(["a", "b"])
104
+
105
+ Args:
106
+ *values: A list of values or individual arguments.
107
+
108
+ Returns:
109
+ Column: A Column representing a boolean expression.
110
+ """
111
+ # If a single iterable (non-str) is passed, use that directly
112
+ if len(values) == 1 and isinstance(values[0], Iterable) and not isinstance(values[0], str):
113
+ value_list = list(values[0])
114
+ else:
115
+ value_list = list(values)
116
+
117
+ return Column(self.expr.is_in(value_list))
118
+
119
+ def isNotNull(self) -> Column:
120
+ """
121
+ Mimics pyspark.sql.Column.isNotNull
122
+
123
+ Returns:
124
+ Column: A Column representing the non-null condition.
125
+ """
126
+ return Column(self.expr.is_not_null())
127
+
128
+ def rlike(self, pattern: str) -> Column:
129
+ """
130
+ Mimics pyspark.sql.Column.rlike using Polars' regex matching.
131
+
132
+ Args:
133
+ pattern (str): Regular expression pattern to match.
134
+
135
+ Returns:
136
+ Column: A new Column representing a boolean expression.
137
+ """
138
+ if not isinstance(pattern, str):
139
+ raise TypeError(f"rlike() expects a string pattern, got {type(pattern)}")
140
+
141
+ return Column(self.expr.str.contains(pattern))
142
+
143
+ def to_native(self) -> pl.Expr:
144
+ return self.expr
145
+
146
+
147
+ def _to_expr(value):
148
+ if isinstance(value, Column):
149
+ return value.to_native()
150
+ elif isinstance(value, pl.Expr):
151
+ return value
152
+ else:
153
+ return pl.lit(value)