Azure Synapse中编写可调用mssparkutils的外部Python库的可行方案咨询
我完全理解你的困扰——在本地开发Synapse依赖的Python库时,mssparkutils的本地模拟和Synapse原生环境的兼容性问题确实很头疼。你遇到的dummy-notebookutils和原生mssparkutils冲突的问题,本质是因为在Synapse环境中,这个dummy包会覆盖掉原生的mssparkutils对象,导致调用credentials.getPropertiesAll返回空值。下面给你几个可行的解决方案,尽量减少对现有代码结构的改动:
方案1:环境检测+条件导入
在你的Python库中,通过检测当前运行环境来决定导入哪个版本的mssparkutils。Synapse环境中会有特定的环境变量或者标识,可以用来区分本地和Synapse环境:
# 在你的库文件中 import sys from pathlib import Path # 检测是否在Synapse环境中运行 def is_synapse_environment(): # Synapse环境中通常会有这些特征,你可以根据实际情况调整 return "SPARK_HOME" in sys.environ or any("synapse" in key.lower() for key in sys.environ) # 条件导入/获取mssparkutils if is_synapse_environment(): # 在Synapse中直接使用全局注入的原生mssparkutils import __main__ mssparkutils = __main__.mssparkutils else: # 本地环境使用dummy包 from notebookutils import mssparkutils def copy_file_to_folder(path_to_file: str, path_to_folder: str) -> str: mssparkutils.fs.cp(path_to_file, f"file:{path_to_folder}") result = str(Path(path_to_folder)/Path(path_to_file).name) return result
这样做的好处是:在Synapse环境中,我们直接获取全局的原生mssparkutils对象,不会被dummy包覆盖;本地环境则使用dummy-notebookutils来模拟,不影响本地开发和测试。
方案2:依赖注入(最小改动版)
你提到的把mssparkutils传入类的构造函数是个思路,但其实不需要重构整个库为类,只需要给函数添加一个可选的mssparkutils参数,默认值在不同环境中自动适配:
# 在你的库文件中 from pathlib import Path from typing import Optional # 本地环境默认导入dummy try: from notebookutils import mssparkutils as default_mssparkutils except ImportError: # Synapse环境中不需要dummy,这里留空,后续自动获取全局对象 default_mssparkutils = None def copy_file_to_folder(path_to_file: str, path_to_folder: str, mssparkutils: Optional[object] = None) -> str: # 优先级:显式传入的对象 > 本地dummy对象 > Synapse全局对象 utils = mssparkutils or default_mssparkutils or globals().get("mssparkutils") utils.fs.cp(path_to_file, f"file:{path_to_folder}") result = str(Path(path_to_folder)/Path(path_to_file).name) return result
在Synapse的runbook中调用时,你可以选择:
- 不传参数,函数会自动获取全局的原生
mssparkutils - 显式传入:
copy_file_to_folder(..., mssparkutils=mssparkutils)
这种方式几乎不需要改动现有函数的逻辑,只是添加了一个可选参数,对现有代码结构的影响极小。
方案3:Synapse环境中排除dummy包
如果你使用的是Synapse的工作区包池或者通过%pip安装库,可以在Synapse环境中安装库时排除dummy-notebookutils依赖。比如在你的库的pyproject.toml中把dummy-notebookutils设为开发依赖:
# pyproject.toml [project] name = "your-synapse-library" # 其他基础配置... [project.optional-dependencies] dev = [ "dummy-notebookutils" ]
本地开发时安装核心包+开发依赖:pip install -e ".[dev]",而在Synapse中安装时只安装核心包:%pip install your-synapse-library。同时,在库中做如下调整:
# 在你的库文件中 from pathlib import Path try: # 优先尝试使用Synapse全局的原生mssparkutils mssparkutils except NameError: try: # 本地环境使用dummy包 from notebookutils import mssparkutils except ImportError: raise RuntimeError("mssparkutils not found - please ensure you're running in Synapse or have dummy-notebookutils installed locally") def copy_file_to_folder(path_to_file: str, path_to_folder: str) -> str: mssparkutils.fs.cp(path_to_file, f"file:{path_to_folder}") result = str(Path(path_to_folder)/Path(path_to_file).name) return result
这样在Synapse环境中,因为原生的mssparkutils是全局可用的,所以不会触发导入dummy包的逻辑,也就不会有冲突了。
额外小提醒
在Synapse runbook中,如果你之前安装过dummy-notebookutils,记得先卸载它避免残留影响:
%pip uninstall -y dummy-notebookutils
这些方案都可以解决你遇到的冲突问题,其中方案2的改动最小,适合不想大幅调整现有代码结构的场景;方案1的环境检测更自动化,适合需要完全透明适配环境的场景。
备注:内容来源于stack exchange,提问作者Tom Hosker

