2026, Jan 02 15:01
Как унифицировать создание DataFrame в databricks-connect и PySpark
Покажем несовместимости создания DataFrame между databricks-connect и PySpark: почему ломается вывод схемы, строгая типизация, как писать стабильные тесты.
Запуск одних и тех же модульных тестов для ETL‑пайплайна через databricks-connect в VS Code и на «чистом» pyspark в Docker-контейнере может выявить тонкие несовместимости. Один из таких случаев — создание DataFrame: код, который без проблем работает с databricks-connect и даже в блокноте Databricks, в pyspark может падать с ошибками вроде CANNOT_INFER_SCHEMA_FOR_TYPE или CANNOT_ACCEPT_OBJECT_IN_TYPE. Источник проблемы — различия в выводе типов и в строгости соблюдения схемы.
Воспроизводим проблему
Сессию Spark инициализируем так, чтобы она работала в обоих окружениях. В VS Code используется databricks-connect; в Docker — локальный сеанс pyspark.
from pyspark.sql import SparkSession
def init_session() -> SparkSession:
try:
from databricks.connect import DatabricksSession
cfg = load_profile()
return DatabricksSession.builder.remote(
host=cfg.host,
token=cfg.token,
cluster_id=cfg.cluster_id,
).getOrCreate()
except ImportError:
return (
SparkSession.builder
.master("local[4]")
.config("spark.databricks.clusterUsageTags.clusterAllTags", "{}")
.getOrCreate()
)
Первый сбой — создание одноколоночного DataFrame напрямую из списка значений. С databricks-connect это проходит, а в pyspark падает с ошибкой вывода схемы.
spark_ctx = init_session()
frame_values = spark_ctx.createDataFrame(
[
"123",
123456,
12345678912345,
],
["my_str_column"],
)
Второй случай — передача целого числа в поле типа FloatType. В databricks-connect это принимается, тогда как pyspark строго отвергает int для FloatType.
from pyspark.sql.types import StructType, StructField, FloatType
rows_raw = [
(102),
]
schema_spec = StructType([
StructField("my_float_column", FloatType()),
])
frame_strict = spark_ctx.createDataFrame(rows_raw, schema_spec)
В чём дело на самом деле
Разница сводится к двум моментам. Для одноколоночного ввода databricks-connect принимает «голый» список и выводит схему из одного столбца, а pyspark ожидает, что каждая строка будет кортежем, даже если столбец всего один. Кроме того, при явно заданной схеме databricks-connect терпимее к неявным преобразованиям, тогда как pyspark строго придерживается объявленных типов и требует точного соответствия входных данных.
Решение, работающее в обоих окружениях
Переносимый подход прост: всегда передавайте строки как кортежи, даже если колонка одна, и заранее приводите значения к объявленным типам перед созданием DataFrame.
# Одноколоночный DataFrame: используйте кортежи для каждой строки
stable_one_col = spark_ctx.createDataFrame(
[
("123",),
(123456,),
(12345678912345,),
],
["my_str_column"],
)
stable_one_col.show()
+--------------+
| my_str_column|
+--------------+
| 123|
| 123456|
|12345678912345|
+--------------+
# Соблюдение схемы: приводите к точному ожидаемому типу
from pyspark.sql.types import StructType, StructField, FloatType
rows_casted = [
(102.0,),
]
schema_spec = StructType([
StructField("my_float_column", FloatType()),
])
stable_with_schema = spark_ctx.createDataFrame(rows_casted, schema_spec)
stable_with_schema.show()
+---------------+
|my_float_column|
+---------------+
| 102.0|
+---------------+
Почему это важно
Если модульные тесты в GitLab CI запускаются на pyspark, а локальная разработка ведётся с databricks-connect, несогласованное создание DataFrame быстро приводит к нестабильным тестам и ложным падениям. Нормализуйте форму входных данных и явно задавайте типы — это делает тесты детерминированными и позволяет одному и тому же коду без проблем выполняться и в VS Code, и в Docker‑базированном CI. Такой подход также лучше согласуется с тем, как те же синтаксические конструкции исполняются в блокнотах Databricks.
Заключение
При работе сразу с databricks-connect и pyspark используйте согласованный ввод для DataFrame. Для данных с одной колонкой передавайте кортежи, а не голые значения. Если указываете схему, заранее приводите значения к точным целевым типам. Эти два правила убирают наблюдавшиеся расхождения, делают модульные тесты переносимыми между окружениями и уменьшают сюрпризы в CI‑конвейерах.