typedspark 0.0.0__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.
- typedspark/__init__.py +54 -0
- typedspark/_core/__init__.py +0 -0
- typedspark/_core/column.py +97 -0
- typedspark/_core/column_meta.py +26 -0
- typedspark/_core/dataset.py +302 -0
- typedspark/_core/datatypes.py +106 -0
- typedspark/_core/literaltype.py +41 -0
- typedspark/_core/validate_schema.py +120 -0
- typedspark/_schema/__init__.py +0 -0
- typedspark/_schema/dlt_kwargs.py +20 -0
- typedspark/_schema/get_schema_definition.py +160 -0
- typedspark/_schema/get_schema_imports.py +146 -0
- typedspark/_schema/schema.py +208 -0
- typedspark/_schema/structfield.py +155 -0
- typedspark/_transforms/__init__.py +0 -0
- typedspark/_transforms/rename_duplicate_columns.py +122 -0
- typedspark/_transforms/structtype_column.py +56 -0
- typedspark/_transforms/transform_to_schema.py +74 -0
- typedspark/_transforms/utils.py +46 -0
- typedspark/_utils/__init__.py +0 -0
- typedspark/_utils/camelcase.py +6 -0
- typedspark/_utils/create_dataset.py +131 -0
- typedspark/_utils/create_dataset_from_structtype.py +71 -0
- typedspark/_utils/databases.py +229 -0
- typedspark/_utils/load_table.py +90 -0
- typedspark/_utils/register_schema_to_dataset.py +95 -0
- typedspark/py.typed +0 -0
- typedspark-0.0.0.dist-info/LICENSE.txt +201 -0
- typedspark-0.0.0.dist-info/METADATA +93 -0
- typedspark-0.0.0.dist-info/RECORD +32 -0
- typedspark-0.0.0.dist-info/WHEEL +5 -0
- typedspark-0.0.0.dist-info/top_level.txt +1 -0
typedspark/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Typedspark: column-wise type annotations for pyspark DataFrames."""
|
|
2
|
+
|
|
3
|
+
from typedspark._core.column import Column
|
|
4
|
+
from typedspark._core.column_meta import ColumnMeta
|
|
5
|
+
from typedspark._core.dataset import DataSet, DataSetImplements
|
|
6
|
+
from typedspark._core.datatypes import (
|
|
7
|
+
ArrayType,
|
|
8
|
+
DayTimeIntervalType,
|
|
9
|
+
DecimalType,
|
|
10
|
+
MapType,
|
|
11
|
+
StructType,
|
|
12
|
+
)
|
|
13
|
+
from typedspark._core.literaltype import IntervalType
|
|
14
|
+
from typedspark._schema.schema import MetaSchema, Schema
|
|
15
|
+
from typedspark._transforms.structtype_column import structtype_column
|
|
16
|
+
from typedspark._transforms.transform_to_schema import transform_to_schema
|
|
17
|
+
from typedspark._utils.create_dataset import (
|
|
18
|
+
create_empty_dataset,
|
|
19
|
+
create_partially_filled_dataset,
|
|
20
|
+
create_structtype_row,
|
|
21
|
+
)
|
|
22
|
+
from typedspark._utils.databases import Catalogs, Database, Databases
|
|
23
|
+
from typedspark._utils.load_table import create_schema, load_table
|
|
24
|
+
from typedspark._utils.register_schema_to_dataset import (
|
|
25
|
+
register_schema_to_dataset,
|
|
26
|
+
register_schema_to_dataset_with_alias,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ArrayType",
|
|
31
|
+
"Catalogs",
|
|
32
|
+
"Column",
|
|
33
|
+
"ColumnMeta",
|
|
34
|
+
"Database",
|
|
35
|
+
"Databases",
|
|
36
|
+
"DataSet",
|
|
37
|
+
"DayTimeIntervalType",
|
|
38
|
+
"DecimalType",
|
|
39
|
+
"IntervalType",
|
|
40
|
+
"MapType",
|
|
41
|
+
"MetaSchema",
|
|
42
|
+
"DataSetImplements",
|
|
43
|
+
"Schema",
|
|
44
|
+
"StructType",
|
|
45
|
+
"create_empty_dataset",
|
|
46
|
+
"create_partially_filled_dataset",
|
|
47
|
+
"create_structtype_row",
|
|
48
|
+
"create_schema",
|
|
49
|
+
"load_table",
|
|
50
|
+
"register_schema_to_dataset",
|
|
51
|
+
"register_schema_to_dataset_with_alias",
|
|
52
|
+
"structtype_column",
|
|
53
|
+
"transform_to_schema",
|
|
54
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Module containing classes and functions related to TypedSpark Columns."""
|
|
2
|
+
|
|
3
|
+
from logging import warn
|
|
4
|
+
from typing import Generic, Optional, TypeVar, Union, get_args, get_origin
|
|
5
|
+
|
|
6
|
+
from pyspark.sql import Column as SparkColumn
|
|
7
|
+
from pyspark.sql import DataFrame, SparkSession
|
|
8
|
+
from pyspark.sql.functions import col
|
|
9
|
+
from pyspark.sql.types import DataType
|
|
10
|
+
|
|
11
|
+
from typedspark._core.datatypes import StructType
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T", bound=DataType)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmptyColumn(SparkColumn):
|
|
17
|
+
"""Column object to be instantiated when there is no active Spark session."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, *args, **kwargs) -> None: # pragma: no cover
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Column(SparkColumn, Generic[T]):
|
|
24
|
+
"""Represents a ``Column`` in a ``Schema``. Can be used as:
|
|
25
|
+
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
class A(Schema):
|
|
29
|
+
a: Column[IntegerType]
|
|
30
|
+
b: Column[StringType]
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __new__(
|
|
34
|
+
cls,
|
|
35
|
+
name: str,
|
|
36
|
+
dataframe: Optional[DataFrame] = None,
|
|
37
|
+
curid: Optional[int] = None,
|
|
38
|
+
dtype: Optional[T] = None,
|
|
39
|
+
parent: Union[DataFrame, "Column", None] = None,
|
|
40
|
+
alias: Optional[str] = None,
|
|
41
|
+
):
|
|
42
|
+
"""``__new__()`` instantiates the object (prior to ``__init__()``).
|
|
43
|
+
|
|
44
|
+
Here, we simply take the provided ``name``, create a pyspark
|
|
45
|
+
``Column`` object and cast it to a typedspark ``Column`` object.
|
|
46
|
+
This allows us to bypass the pypsark ``Column`` constuctor in
|
|
47
|
+
``__init__()``, which requires parameters that may be difficult
|
|
48
|
+
to access.
|
|
49
|
+
"""
|
|
50
|
+
# pylint: disable=unused-argument
|
|
51
|
+
|
|
52
|
+
if dataframe is not None and parent is None:
|
|
53
|
+
parent = dataframe
|
|
54
|
+
warn("The use of Column(dataframe=...) is deprecated, use Column(parent=...) instead.")
|
|
55
|
+
|
|
56
|
+
column: SparkColumn
|
|
57
|
+
if SparkSession.getActiveSession() is None:
|
|
58
|
+
column = EmptyColumn() # pragma: no cover
|
|
59
|
+
elif alias is not None:
|
|
60
|
+
column = col(f"{alias}.{name}")
|
|
61
|
+
elif parent is not None:
|
|
62
|
+
column = parent[name]
|
|
63
|
+
else:
|
|
64
|
+
column = col(name)
|
|
65
|
+
|
|
66
|
+
column.__class__ = Column
|
|
67
|
+
return column
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
dataframe: Optional[DataFrame] = None,
|
|
73
|
+
curid: Optional[int] = None,
|
|
74
|
+
dtype: Optional[T] = None,
|
|
75
|
+
parent: Union[DataFrame, "Column", None] = None,
|
|
76
|
+
alias: Optional[str] = None,
|
|
77
|
+
):
|
|
78
|
+
# pylint: disable=unused-argument
|
|
79
|
+
self.str = name
|
|
80
|
+
self._dtype = dtype if dtype is not None else DataType
|
|
81
|
+
self._curid = curid
|
|
82
|
+
|
|
83
|
+
def __hash__(self) -> int:
|
|
84
|
+
return hash((self.str, self._curid))
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def dtype(self) -> T:
|
|
88
|
+
"""Get the datatype of the column, e.g. Column[IntegerType] -> IntegerType."""
|
|
89
|
+
dtype = self._dtype
|
|
90
|
+
|
|
91
|
+
if get_origin(dtype) == StructType:
|
|
92
|
+
return StructType(
|
|
93
|
+
schema=get_args(dtype)[0],
|
|
94
|
+
parent=self,
|
|
95
|
+
) # type: ignore
|
|
96
|
+
|
|
97
|
+
return dtype() # type: ignore
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Metadata for ``Column`` objects that can be accessed during runtime."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ColumnMeta:
|
|
9
|
+
"""Contains the metadata for a ``Column``. Used as:
|
|
10
|
+
|
|
11
|
+
.. code-block:: python
|
|
12
|
+
|
|
13
|
+
class A(Schema):
|
|
14
|
+
a: Annotated[
|
|
15
|
+
Column[IntegerType],
|
|
16
|
+
ColumnMeta(
|
|
17
|
+
comment="This is a comment",
|
|
18
|
+
)
|
|
19
|
+
]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
comment: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
def get_metadata(self) -> Optional[Dict[str, str]]:
|
|
25
|
+
"""Returns the metadata of this column."""
|
|
26
|
+
return {"comment": self.comment} if self.comment else None
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Module containing classes and functions related to TypedSpark DataSets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import Callable, Generic, List, Literal, Optional, Type, TypeVar, Union, cast, overload
|
|
7
|
+
|
|
8
|
+
from pyspark.sql import Column as SparkColumn
|
|
9
|
+
from pyspark.sql import DataFrame
|
|
10
|
+
from typing_extensions import Concatenate, ParamSpec
|
|
11
|
+
|
|
12
|
+
from typedspark._core.validate_schema import validate_schema
|
|
13
|
+
from typedspark._schema.schema import Schema
|
|
14
|
+
|
|
15
|
+
_Schema = TypeVar("_Schema", bound=Schema)
|
|
16
|
+
_Protocol = TypeVar("_Protocol", bound=Schema, covariant=True)
|
|
17
|
+
_Implementation = TypeVar("_Implementation", bound=Schema, covariant=True)
|
|
18
|
+
|
|
19
|
+
P = ParamSpec("P")
|
|
20
|
+
_ReturnType = TypeVar("_ReturnType", bound=DataFrame) # pylint: disable=C0103
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DataSetImplements(DataFrame, Generic[_Protocol, _Implementation]):
|
|
24
|
+
"""DataSetImplements allows us to define functions such as:
|
|
25
|
+
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
class Age(Schema, Protocol):
|
|
28
|
+
age: Column[LongType]
|
|
29
|
+
|
|
30
|
+
def birthday(df: DataSetImplements[Age, T]) -> DataSet[T]:
|
|
31
|
+
return transform_to_schema(
|
|
32
|
+
df,
|
|
33
|
+
df.typedspark_schema,
|
|
34
|
+
{Age.age: Age.age + 1},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
Such a function:
|
|
38
|
+
1. Takes as an input ``DataSetImplements[Age, T]``: a ``DataSet`` that implements the protocol
|
|
39
|
+
``Age`` as ``T``.
|
|
40
|
+
2. Returns a ``DataSet[T]``: a ``DataSet`` of the same type as the one that was provided.
|
|
41
|
+
|
|
42
|
+
``DataSetImplements`` should solely be used as a type annotation, it is never initialized."""
|
|
43
|
+
|
|
44
|
+
_schema_annotations: Type[_Implementation]
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
raise NotImplementedError(
|
|
48
|
+
"DataSetImplements should solely be used as a type annotation, it is never initialized."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def typedspark_schema(self) -> Type[_Implementation]:
|
|
53
|
+
"""Returns the ``Schema`` of the ``DataSet``."""
|
|
54
|
+
return self._schema_annotations
|
|
55
|
+
|
|
56
|
+
"""The following functions are equivalent to their parents in ``DataFrame``, but since they
|
|
57
|
+
don't affect the ``Schema``, we can add type annotations here. We're omitting docstrings,
|
|
58
|
+
such that the docstring from the parent will appear."""
|
|
59
|
+
|
|
60
|
+
def alias(self, alias: str) -> DataSet[_Implementation]:
|
|
61
|
+
return DataSet[self._schema_annotations](super().alias(alias)) # type: ignore
|
|
62
|
+
|
|
63
|
+
def distinct(self) -> DataSet[_Implementation]: # pylint: disable=C0116
|
|
64
|
+
return DataSet[self._schema_annotations](super().distinct()) # type: ignore
|
|
65
|
+
|
|
66
|
+
def filter(self, condition) -> DataSet[_Implementation]: # pylint: disable=C0116
|
|
67
|
+
return DataSet[self._schema_annotations](super().filter(condition)) # type: ignore
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
def join( # type: ignore
|
|
71
|
+
self,
|
|
72
|
+
other: DataFrame,
|
|
73
|
+
on: Optional[ # pylint: disable=C0103
|
|
74
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
75
|
+
] = ...,
|
|
76
|
+
how: None = ...,
|
|
77
|
+
) -> DataFrame: ... # pragma: no cover
|
|
78
|
+
|
|
79
|
+
@overload
|
|
80
|
+
def join(
|
|
81
|
+
self,
|
|
82
|
+
other: DataFrame,
|
|
83
|
+
on: Optional[ # pylint: disable=C0103
|
|
84
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
85
|
+
] = ...,
|
|
86
|
+
how: Literal["semi"] = ...,
|
|
87
|
+
) -> DataSet[_Implementation]: ... # pragma: no cover
|
|
88
|
+
|
|
89
|
+
@overload
|
|
90
|
+
def join(
|
|
91
|
+
self,
|
|
92
|
+
other: DataFrame,
|
|
93
|
+
on: Optional[ # pylint: disable=C0103
|
|
94
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
95
|
+
] = ...,
|
|
96
|
+
how: Optional[str] = ...,
|
|
97
|
+
) -> DataFrame: ... # pragma: no cover
|
|
98
|
+
|
|
99
|
+
def join( # pylint: disable=C0116
|
|
100
|
+
self,
|
|
101
|
+
other: DataFrame,
|
|
102
|
+
on: Optional[ # pylint: disable=C0103
|
|
103
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
104
|
+
] = None,
|
|
105
|
+
how: Optional[str] = None,
|
|
106
|
+
) -> DataFrame:
|
|
107
|
+
return super().join(other, on, how) # type: ignore
|
|
108
|
+
|
|
109
|
+
def orderBy(self, *args, **kwargs) -> DataSet[_Implementation]: # type: ignore # noqa: N802, E501 # pylint: disable=C0116, C0103
|
|
110
|
+
return DataSet[self._schema_annotations](super().orderBy(*args, **kwargs)) # type: ignore
|
|
111
|
+
|
|
112
|
+
def transform(
|
|
113
|
+
self,
|
|
114
|
+
func: Callable[Concatenate[DataSet[_Implementation], P], _ReturnType],
|
|
115
|
+
*args: P.args,
|
|
116
|
+
**kwargs: P.kwargs,
|
|
117
|
+
) -> _ReturnType:
|
|
118
|
+
return super().transform(func, *args, **kwargs) # type: ignore
|
|
119
|
+
|
|
120
|
+
@overload
|
|
121
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
122
|
+
self,
|
|
123
|
+
other: DataSet[_Implementation],
|
|
124
|
+
allowMissingColumns: Literal[False] = ..., # noqa: N803
|
|
125
|
+
) -> DataSet[_Implementation]: ... # pragma: no cover
|
|
126
|
+
|
|
127
|
+
@overload
|
|
128
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
129
|
+
self,
|
|
130
|
+
other: DataFrame,
|
|
131
|
+
allowMissingColumns: bool = ..., # noqa: N803
|
|
132
|
+
) -> DataFrame: ... # pragma: no cover
|
|
133
|
+
|
|
134
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
135
|
+
self,
|
|
136
|
+
other: DataFrame,
|
|
137
|
+
allowMissingColumns: bool = False, # noqa: N803
|
|
138
|
+
) -> DataFrame:
|
|
139
|
+
res = super().unionByName(other, allowMissingColumns)
|
|
140
|
+
if isinstance(other, DataSet) and other._schema_annotations == self._schema_annotations:
|
|
141
|
+
return DataSet[self._schema_annotations](res) # type: ignore
|
|
142
|
+
return res # pragma: no cover
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class DataSet(DataSetImplements[_Schema, _Schema]):
|
|
146
|
+
"""``DataSet`` subclasses pyspark ``DataFrame`` and hence has all the same
|
|
147
|
+
functionality, with in addition the possibility to define a schema.
|
|
148
|
+
|
|
149
|
+
.. code-block:: python
|
|
150
|
+
|
|
151
|
+
class Person(Schema):
|
|
152
|
+
name: Column[StringType]
|
|
153
|
+
age: Column[LongType]
|
|
154
|
+
|
|
155
|
+
def foo(df: DataSet[Person]) -> DataSet[Person]:
|
|
156
|
+
# do stuff
|
|
157
|
+
return df
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __new__(cls, dataframe: DataFrame) -> DataSet[_Schema]:
|
|
161
|
+
"""``__new__()`` instantiates the object (prior to ``__init__()``).
|
|
162
|
+
|
|
163
|
+
Here, we simply take the provided ``df`` and cast it to a
|
|
164
|
+
``DataSet``. This allows us to bypass the ``DataFrame``
|
|
165
|
+
constuctor in ``__init__()``, which requires parameters that may
|
|
166
|
+
be difficult to access. Subsequently, we perform schema validation, if
|
|
167
|
+
the schema annotations are provided.
|
|
168
|
+
"""
|
|
169
|
+
dataframe = cast(DataSet, dataframe)
|
|
170
|
+
dataframe.__class__ = DataSet
|
|
171
|
+
|
|
172
|
+
# first we reset the schema annotations to None, in case they are inherrited through the
|
|
173
|
+
# passed DataFrame
|
|
174
|
+
dataframe._schema_annotations = None # type: ignore
|
|
175
|
+
|
|
176
|
+
# then we use the class' schema annotations to validate the schema and add metadata
|
|
177
|
+
if hasattr(cls, "_schema_annotations"):
|
|
178
|
+
dataframe._schema_annotations = cls._schema_annotations # type: ignore
|
|
179
|
+
dataframe._validate_schema()
|
|
180
|
+
dataframe._add_schema_metadata()
|
|
181
|
+
|
|
182
|
+
return dataframe # type: ignore
|
|
183
|
+
|
|
184
|
+
def __init__(self, dataframe: DataFrame):
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
def __class_getitem__(cls, item):
|
|
188
|
+
"""Allows us to define a schema for the ``DataSet``.
|
|
189
|
+
|
|
190
|
+
To make sure that the DataSet._schema_annotations variable isn't reused globally, we
|
|
191
|
+
generate a subclass of the ``DataSet`` with the schema annotations as a class variable.
|
|
192
|
+
"""
|
|
193
|
+
subclass_name = f"{cls.__name__}[{item.__name__}]"
|
|
194
|
+
subclass = type(subclass_name, (cls,), {"_schema_annotations": item})
|
|
195
|
+
return subclass
|
|
196
|
+
|
|
197
|
+
def _validate_schema(self) -> None:
|
|
198
|
+
"""Validates the schema of the ``DataSet`` against the schema annotations."""
|
|
199
|
+
validate_schema(
|
|
200
|
+
self._schema_annotations.get_structtype(),
|
|
201
|
+
deepcopy(self.schema),
|
|
202
|
+
self._schema_annotations.get_schema_name(),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def _add_schema_metadata(self) -> None:
|
|
206
|
+
"""Adds the ``ColumnMeta`` comments as metadata to the ``DataSet``.
|
|
207
|
+
|
|
208
|
+
Previously set metadata is deleted. Hence, if ``foo(dataframe: DataSet[A]) -> DataSet[B]``,
|
|
209
|
+
then ``DataSet[B]`` will not inherrit any metadata from ``DataSet[A]``.
|
|
210
|
+
|
|
211
|
+
Assumes validate_schema() in __setattr__() has been run.
|
|
212
|
+
"""
|
|
213
|
+
for field in self._schema_annotations.get_structtype().fields:
|
|
214
|
+
self.schema[field.name].metadata = field.metadata
|
|
215
|
+
|
|
216
|
+
"""The following functions are equivalent to their parents in ``DataSetImplements``. However,
|
|
217
|
+
to support functions like ``functools.reduce(DataSet.unionByName, datasets)``, we also add them
|
|
218
|
+
here. Unfortunately, this leads to some code redundancy, but we'll take that for granted."""
|
|
219
|
+
|
|
220
|
+
def alias(self, alias: str) -> DataSet[_Schema]:
|
|
221
|
+
return DataSet[self._schema_annotations](super().alias(alias)) # type: ignore
|
|
222
|
+
|
|
223
|
+
def distinct(self) -> DataSet[_Schema]: # pylint: disable=C0116
|
|
224
|
+
return DataSet[self._schema_annotations](super().distinct()) # type: ignore
|
|
225
|
+
|
|
226
|
+
def filter(self, condition) -> DataSet[_Schema]: # pylint: disable=C0116
|
|
227
|
+
return DataSet[self._schema_annotations](super().filter(condition)) # type: ignore
|
|
228
|
+
|
|
229
|
+
@overload
|
|
230
|
+
def join( # type: ignore
|
|
231
|
+
self,
|
|
232
|
+
other: DataFrame,
|
|
233
|
+
on: Optional[ # pylint: disable=C0103
|
|
234
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
235
|
+
] = ...,
|
|
236
|
+
how: None = ...,
|
|
237
|
+
) -> DataFrame: ... # pragma: no cover
|
|
238
|
+
|
|
239
|
+
@overload
|
|
240
|
+
def join(
|
|
241
|
+
self,
|
|
242
|
+
other: DataFrame,
|
|
243
|
+
on: Optional[ # pylint: disable=C0103
|
|
244
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
245
|
+
] = ...,
|
|
246
|
+
how: Literal["semi"] = ...,
|
|
247
|
+
) -> DataSet[_Schema]: ... # pragma: no cover
|
|
248
|
+
|
|
249
|
+
@overload
|
|
250
|
+
def join(
|
|
251
|
+
self,
|
|
252
|
+
other: DataFrame,
|
|
253
|
+
on: Optional[ # pylint: disable=C0103
|
|
254
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
255
|
+
] = ...,
|
|
256
|
+
how: Optional[str] = ...,
|
|
257
|
+
) -> DataFrame: ... # pragma: no cover
|
|
258
|
+
|
|
259
|
+
def join( # pylint: disable=C0116
|
|
260
|
+
self,
|
|
261
|
+
other: DataFrame,
|
|
262
|
+
on: Optional[ # pylint: disable=C0103
|
|
263
|
+
Union[str, List[str], SparkColumn, List[SparkColumn]]
|
|
264
|
+
] = None,
|
|
265
|
+
how: Optional[str] = None,
|
|
266
|
+
) -> DataFrame:
|
|
267
|
+
return super().join(other, on, how) # type: ignore
|
|
268
|
+
|
|
269
|
+
def orderBy(self, *args, **kwargs) -> DataSet[_Schema]: # type: ignore # noqa: N802, E501 # pylint: disable=C0116, C0103
|
|
270
|
+
return DataSet[self._schema_annotations](super().orderBy(*args, **kwargs)) # type: ignore
|
|
271
|
+
|
|
272
|
+
def transform(
|
|
273
|
+
self,
|
|
274
|
+
func: Callable[Concatenate[DataSet[_Schema], P], _ReturnType],
|
|
275
|
+
*args: P.args,
|
|
276
|
+
**kwargs: P.kwargs,
|
|
277
|
+
) -> _ReturnType:
|
|
278
|
+
return super().transform(func, *args, **kwargs) # type: ignore
|
|
279
|
+
|
|
280
|
+
@overload
|
|
281
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
282
|
+
self,
|
|
283
|
+
other: DataSet[_Schema],
|
|
284
|
+
allowMissingColumns: Literal[False] = ..., # noqa: N803
|
|
285
|
+
) -> DataSet[_Schema]: ... # pragma: no cover
|
|
286
|
+
|
|
287
|
+
@overload
|
|
288
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
289
|
+
self,
|
|
290
|
+
other: DataFrame,
|
|
291
|
+
allowMissingColumns: bool = ..., # noqa: N803
|
|
292
|
+
) -> DataFrame: ... # pragma: no cover
|
|
293
|
+
|
|
294
|
+
def unionByName( # noqa: N802 # pylint: disable=C0116, C0103
|
|
295
|
+
self,
|
|
296
|
+
other: DataFrame,
|
|
297
|
+
allowMissingColumns: bool = False, # noqa: N803
|
|
298
|
+
) -> DataFrame:
|
|
299
|
+
res = super().unionByName(other, allowMissingColumns)
|
|
300
|
+
if isinstance(other, DataSet) and other._schema_annotations == self._schema_annotations:
|
|
301
|
+
return DataSet[self._schema_annotations](res) # type: ignore
|
|
302
|
+
return res # pragma: no cover
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Here, we make our own definitions of ``MapType``, ``ArrayType`` and ``StructType`` in
|
|
2
|
+
order to allow e.g. for ``ArrayType[StringType]``."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, Generic, Type, TypeVar
|
|
7
|
+
|
|
8
|
+
from pyspark.sql.types import DataType
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
11
|
+
from typedspark._core.column import Column
|
|
12
|
+
from typedspark._schema.schema import Schema
|
|
13
|
+
|
|
14
|
+
_Schema = TypeVar("_Schema", bound=Schema)
|
|
15
|
+
else:
|
|
16
|
+
_Schema = TypeVar("_Schema")
|
|
17
|
+
|
|
18
|
+
_KeyType = TypeVar("_KeyType", bound=DataType) # pylint: disable=invalid-name
|
|
19
|
+
_ValueType = TypeVar("_ValueType", bound=DataType) # pylint: disable=invalid-name
|
|
20
|
+
_Precision = TypeVar("_Precision", bound=int) # pylint: disable=invalid-name
|
|
21
|
+
_Scale = TypeVar("_Scale", bound=int) # pylint: disable=invalid-name
|
|
22
|
+
_StartField = TypeVar("_StartField", bound=int) # pylint: disable=invalid-name
|
|
23
|
+
_EndField = TypeVar("_EndField", bound=int) # pylint: disable=invalid-name
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TypedSparkDataType(DataType):
|
|
27
|
+
"""Base class for typedspark specific ``DataTypes``."""
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_name(cls) -> str:
|
|
31
|
+
"""Return the name of the type."""
|
|
32
|
+
return cls.__name__
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StructTypeMeta(type):
|
|
36
|
+
"""Initializes the schema attribute as None.
|
|
37
|
+
|
|
38
|
+
This allows for auto-complete in Databricks notebooks (uninitialized variables don't
|
|
39
|
+
show up in auto-complete there).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __new__(cls, name: str, bases: Any, dct: Dict[str, Any]):
|
|
43
|
+
dct["schema"] = None
|
|
44
|
+
return super().__new__(cls, name, bases, dct)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class StructType(Generic[_Schema], TypedSparkDataType, metaclass=StructTypeMeta):
|
|
48
|
+
"""Allows for type annotations such as:
|
|
49
|
+
|
|
50
|
+
.. code-block:: python
|
|
51
|
+
|
|
52
|
+
class Job(Schema):
|
|
53
|
+
position: Column[StringType]
|
|
54
|
+
salary: Column[LongType]
|
|
55
|
+
|
|
56
|
+
class Person(Schema):
|
|
57
|
+
job: Column[StructType[Job]]
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
schema: Type[_Schema],
|
|
63
|
+
parent: Column,
|
|
64
|
+
) -> None:
|
|
65
|
+
self.schema = schema
|
|
66
|
+
self.schema._parent = parent
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MapType(Generic[_KeyType, _ValueType], TypedSparkDataType):
|
|
70
|
+
"""Allows for type annotations such as.
|
|
71
|
+
|
|
72
|
+
.. code-block:: python
|
|
73
|
+
|
|
74
|
+
class Basket(Schema):
|
|
75
|
+
items: Column[MapType[StringType, StringType]]
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ArrayType(Generic[_ValueType], TypedSparkDataType):
|
|
80
|
+
"""Allows for type annotations such as.
|
|
81
|
+
|
|
82
|
+
.. code-block:: python
|
|
83
|
+
|
|
84
|
+
class Basket(Schema):
|
|
85
|
+
items: Column[ArrayType[StringType]]
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class DecimalType(Generic[_Precision, _Scale], TypedSparkDataType):
|
|
90
|
+
"""Allows for type annotations such as.
|
|
91
|
+
|
|
92
|
+
.. code-block:: python
|
|
93
|
+
|
|
94
|
+
class Numbers(Schema):
|
|
95
|
+
number: Column[DecimalType[Literal[10], Literal[0]]]
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DayTimeIntervalType(Generic[_StartField, _EndField], TypedSparkDataType):
|
|
100
|
+
"""Allows for type annotations such as.
|
|
101
|
+
|
|
102
|
+
.. code-block:: python
|
|
103
|
+
|
|
104
|
+
class TimeInterval(Schema):
|
|
105
|
+
interval: Column[DayTimeIntervalType[IntervalType.HOUR, IntervalType.SECOND]
|
|
106
|
+
"""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Defines ``LiteralTypes``, e.g. ``IntervalType.DAY``, that map their class attribute
|
|
2
|
+
to a ``Literal`` integer.
|
|
3
|
+
|
|
4
|
+
Can be used for example in ``DayTimeIntervalType``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LiteralType:
|
|
11
|
+
"""Base class for literal types, that map their class attribute to a Literal
|
|
12
|
+
integer."""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_dict(cls) -> Dict[str, str]:
|
|
16
|
+
"""Returns a dictionary mapping e.g. "IntervalType.DAY" -> "Literal[0]"."""
|
|
17
|
+
dictionary = {}
|
|
18
|
+
for key, value in cls.__dict__.items():
|
|
19
|
+
if key.startswith("_"):
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
key = f"{cls.__name__}.{key}"
|
|
23
|
+
value = str(value).replace("typing.", "")
|
|
24
|
+
|
|
25
|
+
dictionary[key] = value
|
|
26
|
+
|
|
27
|
+
return dictionary
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_inverse_dict(cls) -> Dict[str, str]:
|
|
31
|
+
"""Returns a dictionary mapping e.g. "Literal[0]" -> "IntervalType.DAY"."""
|
|
32
|
+
return {v: k for k, v in cls.get_dict().items()}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IntervalType(LiteralType):
|
|
36
|
+
"""Interval types for ``DayTimeIntervalType``."""
|
|
37
|
+
|
|
38
|
+
DAY = Literal[0]
|
|
39
|
+
HOUR = Literal[1]
|
|
40
|
+
MINUTE = Literal[2]
|
|
41
|
+
SECOND = Literal[3]
|