Conversation
There was a problem hiding this comment.
Code Review
This pull request transitions the project's dependency from the full pyspark distribution to the lightweight pyspark-client package, updating setup.py, requirements-dev.txt, and documentation to reflect this change and prevent namespace shadowing. The reviewer suggests utilizing extras_require in setup.py to handle remote-only and local Spark runtime configurations more cleanly, and updating the README installation instructions accordingly.
| If you also need a local Spark runtime, install the full distribution instead: | ||
|
|
||
| ```sh | ||
| pip uninstall pyspark-client | ||
| pip install 'pyspark[connect]~=4.0.0' | ||
| ``` |
There was a problem hiding this comment.
If we transition to using extras_require in setup.py to avoid dependency conflicts and namespace shadowing, we should update the installation instructions here to guide users on using the appropriate extra.
For example:
# For remote-only (lightweight, ~14 MB)
pip install google-cloud-spark-connect[client]
# For local Spark runtime support or Dataproc Serverless environments
pip install google-cloud-spark-connect[local]
Fixing the integration failure (cd23806)The first integration run came back 44 passed, 1 skipped, 1 error, stopping at the error because the job runs with Root cause
The test is not wrong and the library code is not wrong. The test environment stopped providing a capability the test depends on. Everything else in the suite passed on the slim install, including both sparksql-magic tests, which confirms the The fix
The integration suite deliberately stays on the slim install, so the 44 Connect tests keep exercising the dependency set we actually ship. Only the one test that genuinely needs a JVM gets an environment with one. Why the real Dataproc batch runtime is unaffected
try:
from pyspark import core # noqa: F401
_is_remote_only = False
except ImportError:
_is_remote_only = True
Verification
Integration suite is re-running. |
The client only ever talks to a remote Spark Connect endpoint, so it has no use for the Spark JVM jars that make up 442 MB of a 461 MB pyspark install. Apache Spark 4.0 publishes pyspark-client for exactly this case: the same Python tree, remote-only, without the jars. Installs drop to around 14 MB. sparksql-magic declares a dependency on the full pyspark distribution, which pip would install alongside pyspark-client and shadow it, so it is now installed with --no-deps wherever it is needed.
cd23806 to
0fb998a
Compare
test_create_local_spark_session covers the Dataproc batch path, where the builder hands off to a local classic Spark session. pyspark-client ships no JVM jars and cannot start one, so the test errored with CONNECT_URL_NOT_SET once the dependency was swapped. Guard the test on pyspark.util.is_remote_only() so a pyspark-client environment skips it with a clear reason, and add a CI job that installs the full distribution so the path keeps its coverage. The test needs no GCP credentials, so it runs there rather than in the integration suite.
0fb998a to
6564108
Compare
…s file The full pyspark distribution is needed by one test, not by the test suite, so it does not belong in requirements-test.txt: both the unit and integration jobs install that file, and giving them a JVM Spark would stop them exercising the pyspark-client install we actually ship. Move it out of the workflow's inline pip invocation and into requirements-local-spark.txt, consumed only by the local-spark job. It now participates in the cache key and is discoverable for local development.
Because the two distributions have different names but provide the same pyspark module, pip cannot see them as conflicting and installs both. An ordinary `pip install sparksql-magic` next to this library is enough to produce pyspark 4.2.0 alongside pyspark-client 4.0.4, and the first import then fails with "[PACKAGE_NOT_INSTALLED] zstandard >= 0.25.0 must be installed", which names neither package. Check for the mismatch before importing anything from pyspark and point at the fix. Same versions are left alone: their shared files are byte-identical, so that combination works.
What
Swaps the runtime dependency from
pyspark[connect]~=4.0.0topyspark-client~=4.0.0.The client only ever talks to a remote Spark Connect endpoint, so the Spark JVM jars bundled in the full distribution are dead weight. Apache Spark 4.0 publishes
pyspark-clientfor exactly this case — the same Python tree, remote-only, withjars/stripped.pyspark[connect]~=4.0.0pyspark/jars/)pyspark-client~=4.0.0Every symbol this library imports is present in
pyspark-client:pyspark.sql.connect.client.DefaultChannelBuilder,pyspark.sql.connect.session.SparkSession,pyspark.sql.utils.to_str,pyspark.sql.connect.shell.progress.StageInfo.Guarding the failure mode this introduces
The two distributions have different names but provide the same
pysparkmodule, so pip cannot see them as conflicting and installs both side by side. That turns a resolver-visible conflict into a silent file overlay.It is easy to hit. Starting from a clean install of this library,
pip install sparksql-magic— which the README instructed until this PR — yieldspyspark 4.2.0next topyspark-client 4.0.4, and the first import then fails with:which names neither package. Any dependency declaring
pysparkdoes the same._check_pyspark_installation()in__init__.pynow detects the mismatch and points at the fix. It runs before thefrom .session import ...line, since that import is what fails first; the existing conflicting-package check below it would never be reached. Identical versions are left alone — every shared.pyfile is byte-identical at the same version, verified by comparing the two trees at 4.0.4, so that combination genuinely works in either install order.Four unit tests cover mismatch, match, and each distribution alone.
sparksql-magic
sparksql-magicdeclarespyspark>=2.3.0in its wheel metadata andsetup.py, so it is now installed with--no-depsin the integration workflow, the README and DEVELOPING.md, and dropped fromrequirements-dev.txtwith a comment.Safe because the pin is over-broad rather than load-bearing:
sparksql_magic/sparksql.py:7imports onlyfrom pyspark.sql import SparkSession, whichpyspark-clientsatisfies. Both sparksql-magic integration tests pass under the slim install.Local Spark handoff
ManagedSparkSession.Builder.getOrCreate()has ais_dataproc_batch()branch (session.py:627) that hands off to a local classic Spark session.pyspark-clientcannot start one, sotest_create_local_spark_session— the only coverage of that branch — errored on the first CI run.It is now guarded on
pyspark.util.is_remote_only()so slim environments skip it with a clear reason, and alocal-sparkCI job installs the full distribution and runs it. The test needs no GCP access and takes ~7s, so it does not belong behind 80 minutes of cloud provisioning. Its dependency lives inrequirements-local-spark.txt, consumed only by that job, deliberately not inrequirements-test.txt— the unit and integration jobs install that file and must keep running against the slim install we ship.The real Dataproc batch runtime is unaffected.
is_remote_only()is decided by whetherpyspark.coreimports. Installingpyspark-clientover a fullpysparkleavespyspark/coreandpyspark/jarsintact, so it staysFalseand classic mode keeps working.Verification
test_create_session_without_application_default_credentials, reproduces identically onmainand passes in CI.local-sparkjob: passes in 40s in CI, reproduced locally in an exact environment simulation with all GCP env vars unset.python -m build+twine checkpass; wheel metadata showsRequires-Dist: pyspark-client~=4.0.0.pyinkclean.An earlier integration run failed on
test_dpip_install_success[None]withRETRIES_EXCEEDEDbehind a websocket handshake timeout inproxy.py. That path uses thewebsocketsdependency directly and the same test passed before and after, so it is flaky infrastructure rather than a regression here.