forked from awslabs/python-deequ
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscala_utils.py
More file actions
167 lines (133 loc) · 5.95 KB
/
Copy pathscala_utils.py
File metadata and controls
167 lines (133 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# -*- coding: utf-8 -*-
"""A collection of utility functions and classes for manipulating with scala objects anc classes through py4j
"""
from py4j.java_gateway import JavaObject
class PythonCallback:
# https://www.py4j.org/advanced_topics.html#py4j-memory-model
# https://stackoverflow.com/questions/50878834/py4j-error-while-obtaining-a-new-communication-channel-on-multithreaded-java
def __init__(self, gateway):
self.gateway = gateway
# P4j will return false if the callback server is already started
# https://github.com/bartdag/py4j/blob/master/py4j-python/src/py4j/java_gateway.py
callback_server = self.gateway.get_callback_server()
# TODO clean
if callback_server is None:
self.gateway.start_callback_server()
print("Python Callback server started!") # TODO Logging
elif callback_server.is_shutdown:
callback_server.close()
self.gateway.restart_callback_server()
# Have you tried turning it off and on again?
# TODO why do we need to restart this every time?
# TODO Will this break during chained function calls?
print("PythonCallback server restarted!")
class ScalaFunction1(PythonCallback):
"""Implements scala.Function1 interface so we can pass lambda functions to Check
https://www.scala-lang.org/api/current/scala/Function1.html"""
def __init__(self, gateway, lambda_function):
super().__init__(gateway)
self.lambda_function = lambda_function
def apply(self, arg):
"""Implements the apply function"""
return self.lambda_function(arg)
class Java:
"""scala.Function1: a function that takes one argument"""
implements = ["scala.Function1"]
class ScalaFunction2(PythonCallback):
"""Implements scala.Function2 interface
https://www.scala-lang.org/api/current/scala/Function2.html"""
def __init__(self, gateway, lambda_function):
super().__init__(gateway)
self.lambda_function = lambda_function
def apply(self, t1, t2):
"""Implements the apply function"""
return self.lambda_function(t1, t2)
class Java:
"""scala.Function2: a function that takes two arguments"""
implements = ["scala.Function2"]
def get_or_else_none(scala_option):
# TODO Checks
if scala_option.isEmpty():
return None
return scala_option.get()
# Cache per JVM instance so version detection only happens once per session.
_jvm_converters_cache: dict = {}
def _get_converters(jvm):
"""
Return (style, converters) for the running Scala version.
style='jdk' → scala.jdk.javaapi.CollectionConverters (Scala 2.13, Spark 4+)
style='legacy' → scala.collection.JavaConverters (Scala 2.12, Spark 3.x)
"""
key = id(jvm)
if key not in _jvm_converters_cache:
try:
converters = jvm.scala.jdk.javaapi.CollectionConverters
# On Scala 2.12, the path resolves to a JavaPackage placeholder (no class
# exists), so attribute access succeeds but any method call raises TypeError.
# Probe with an actual call to confirm the class is genuinely usable.
converters.asScala(jvm.java.util.ArrayList())
_jvm_converters_cache[key] = ("jdk", converters)
except Exception:
_jvm_converters_cache[key] = ("legacy", jvm.scala.collection.JavaConverters)
return _jvm_converters_cache[key]
def to_scala_seq(jvm, iterable):
"""
Helper method to take an iterable and turn it into a Scala sequence
Args:
jvm: Spark session's JVM
iterable: your iterable
Returns:
Scala sequence
"""
style, converters = _get_converters(jvm)
if style == "jdk":
return converters.asScala(iterable).toSeq()
return converters.iterableAsScalaIterableConverter(iterable).asScala().toSeq()
def empty_scala_seq(jvm):
"""
Returns an empty Scala immutable List (Nil), usable as Seq[_].
Converts an empty ArrayList via .asScala().toList() to produce an immutable.List
rather than a Stream, which is required for Py4J constructor/method lookup to
succeed across both Scala 2.12 (Spark 3.x) and Scala 2.13 (Spark 4+).
"""
style, converters = _get_converters(jvm)
if style == "jdk":
return converters.asScala(jvm.java.util.ArrayList()).toList()
return converters.iterableAsScalaIterableConverter(jvm.java.util.ArrayList()).asScala().toList()
def to_scala_map(spark_session, d):
"""
Convert a dict into a JVM Map.
Args:
spark_session: Spark session
d: Python dictionary
Returns:
Scala map
"""
jvm = spark_session._jvm
try:
# PythonUtils.toScalaMap is a PySpark internal that may be removed in future versions.
return jvm.PythonUtils.toScalaMap(d)
except Exception:
style, converters = _get_converters(jvm)
if style == "jdk":
return converters.asScala(d).toMap()
return converters.mapAsScalaMapConverter(d).asScala().toMap()
def scala_map_to_dict(jvm, scala_map):
style, converters = _get_converters(jvm)
if style == "jdk":
return dict(converters.asJava(scala_map))
return dict(converters.mapAsJavaMapConverter(scala_map).asJava())
def scala_map_to_java_map(jvm, scala_map):
style, converters = _get_converters(jvm)
if style == "jdk":
return converters.asJava(scala_map)
return converters.mapAsJavaMapConverter(scala_map).asJava()
def java_list_to_python_list(java_list: str, datatype):
# TODO: Improve
# Currently turn the java object into a string and pass it through
start = java_list.index("(")
end = java_list.index(")")
empty_val = "" if datatype == str else None
return [datatype(i) if i != "" else empty_val for i in java_list[start + 1 : end].split(",")]
def scala_get_default_argument(java_object, argument_idx: int) -> JavaObject:
return getattr(java_object, f"apply$default${argument_idx}")()