kel*_*oti 5 python python-3.5 mypy databricks
我正在尝试对从 Databricks 导出的笔记本进行类型检查。这些笔记本是*.py带有特殊注释格式的纯文件,用于指示单元格的开始和结束位置。mypy 没有理由不能对这些文件进行类型检查,除了一些丢失的名称:
sparkscdbutilsdisplaydisplayHTML我知道该python命令将在将您转储到交互模式之前运行环境变量指定的文件PYTHONSTARTUP。这就是这些名称的定义方式。
mypy 中是否有一个钩子可以让您在代码之外定义类似的名称?
这是我想到的一个答案。它很脏,但很管用。我希望有一个更好的答案,但在那之前,这就是有效的方法。
该策略是使用 shell 脚本将“PYTHONSTARTUP”文件添加到每个笔记本,然后减去最终输出中的行号。
类型检查.sh:
#!/bin/bash
TARGET=$1
# Define the contents of "PYTHONSTARTUP" file inline. This just
# makes it easier to copy & paste this script elsewhere. You could also
# make it a separate *.py file.
PRELUDE="$(cat <<EOF
import typing
import pyspark.SparkContext
import pyspark.sql.SparkSession
spark = None # type: pyspark.sql.SparkSession
sc = None # type: pyspark.SparkContext
def display(expr):
pass
def displayHTML(expr):
pass
class dbutils:
class fs:
def help(): pass
def cp(from_: str, to: str, recurse: bool = False) -> bool: pass
def head(file: str, maxBytes: int) -> str: pass
def ls(dir: str) -> typing.List[str]: pass
def mkdirs(dir: str) -> bool: pass
def put(file: str, contents: str, overwrite: bool = False) -> bool: pass
def rm(dir: str, recurse: bool) -> bool: pass
def mount(source: str, mountPoint: str, encryptionType: str = "", owner: str = "", extraConfigs: typing.Map[str, str] = {}) -> bool: pass
def mounts() -> typing.List[str]: pass
def refreshMounts() -> bool: pass
def unmount(mountPoint: str) -> bool: pass
class notebook:
def exit(value: str): pass
def run(path: str, timeout: int, arguments: typing.Map[str, str]) -> str: pass
class widgets:
def combobox(name: str, defaultValue: str, choices: typing.List[str], label: str = ""): pass
def dropdown(name: str, defaultValue: str, choices: typing.List[str], label: str = ""): pass
def get(name: str) -> str: pass
def multiselect(name: str, defaultValue: str, choices: typing.List[str], label: str = ""): pass
def remove(name: str): pass
def removeAll(): pass
def text(name: str, defaultValue: str, label: str = ""): pass
def getArgument(name: str) -> str: pass
EOF
)"
# Remember the length of $PRELUDE so that we can subtract the line number
LEN="$(echo "$PRELUDE" | wc -l | awk '{ print $1 }')"
for file in $(find $TARGET -name '*.py'); do
# run mypy for the two files concatenated together (with a blank line
# for good measure)
OUTPUT=$(mypy -c "$(cat <<EOF
$PRELUDE
$(cat $file)
EOF
)")
# awk: Take only output where the line number is after the PRELUDE. Also, fix the file name and line number
FILE_OUTPUT="$(echo "$OUTPUT" | awk -F: '$2 > '$LEN' { line=($2-'$LEN')-1; $1=""; $2=""; print "'$file':" line ":" $0 }')"
# Remove blank lines from output before printing
if [[ $(echo "$FILE_OUTPUT" | sed '/^$/d' | wc -l) -gt 0 ]]; then
echo "$FILE_OUTPUT"
fi
# Keep track of all output, so we can decide the exit code
ALL_OUTPUT+="$FILE_OUTPUT"
done
# propagate errors to the exit code, but ignore errors in the prelude. This
# makes it easier to use in a CI pipeline.
if [[ $(echo "$ALL_OUTPUT" | wc -l) -gt 1 ]]; then
exit 1
else
exit 0
fi
Run Code Online (Sandbox Code Playgroud)
用法:
./typecheck.sh notebooks/
Run Code Online (Sandbox Code Playgroud)