很久很久之前,做了一个在线下载依赖包的工具,但是由于是放在了CVM上,收费比较高昂,而自己比较清贫,所以没能坚持多久,那个工具就被我下掉了,后来有小伙伴就给我留言问我为啥工具不能用了?对啊,有Serverless架构,为什么要用CVM这种鬼东西呢?那么今天我就弄一个Python安装依赖的小工具。
众所周知,在SCF的Runtime中,实际上,并不能很好的执行pip,也就是说并没有默认的安装pip,而且就算已经默认安装了pip,我们也不能每次函数启动都去pip一下,这样会导致程序进入状态变得很慢,所以我们通常都是把依赖打到我们的程序包中,例如在安装的时候: pip install ***==*** -t . 但是,有一种情况是非常尴尬的,那就是有一些依赖是需要编译的,例如opencv这些,可能在不同的系统上或者python版本中编译出来的文件是不同的,这就导致部分依赖如果想要放在SCF Runtime中正常执行,就要在Centos 对应的版本,例如Python2.7,Python3.6等指定环境上进行操作。那么问题来了,我们有多少人是在Centos上开发呢?难不成要弄一个虚拟机或者什么东西单独来做这个处理么?这显然不是很好的操作。所以,我就做了这样一个小工具: http://serverless.0duzhan.com/app/new_year_greeting_card/
选择好Python版本之后,输入包名和版本信息(版本信息可以不写),输入之后点击确定:
会看到下面提示系统处理中,稍等片刻:
点击下载压缩包,就可以获得到对应的package在100% SCF的环境下生成的安装包。
当然,这个是简单的网页工具,如果要用一个工具批量处理也是ok的,可以考虑这个接口:
请求方法:POST Python2: http://service-8d3fi753-1256773370.bj.apigw.tencentcs.com/release/scf_python2_package_download Python3: http://service-8d3fi753-1256773370.bj.apigw.tencentcs.com/release/scf_python3_package_download 输入参数: name: 包名 versioin: 版本 输出参数: error: 真/假,表示是否有错误 result: 结果,如果error为False,则此处输出下载地址,如果error为True,此处输出错误信息 效果:
接下来,分享一下代码(Python2/3代码基本一致,只是稍微修改一下Python2/3的标记):
代码语言:javascript复制# -*- coding: utf8 -*-
import pip._internal.main
import json
import os
import zipfile
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
secret_id = '' # 替换为用户的 secretId
secret_key = '' # 替换为用户的 secretKey
region = 'ap-shanghai' # 替换为用户的 Region
token = None # 使用临时密钥需要传入 Token,默认为空,可不填
scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)
client = CosS3Client(config)
bucket_name = 'serverless-cache-1256773370'
def zipDir(dirpath, outFullName):
"""
压缩指定文件夹
:param dirpath: 目标文件夹路径
:param outFullName: 压缩文件保存路径 xxxx.zip
:return: 无
"""
zip = zipfile.ZipFile(outFullName, "w", zipfile.ZIP_DEFLATED)
for path, dirnames, filenames in os.walk(dirpath):
# 去掉目标跟路径,只对目标文件夹下边的文件及文件夹进行压缩
fpath = path.replace(dirpath, '')
for filename in filenames:
zip.write(os.path.join(path, filename), os.path.join(fpath, filename))
zip.close()
def main_handler(event, context):
try:
packageName = json.loads(event["body"])["name"]
except:
packageName = None
try:
packageVersion = json.loads(event["body"])["version"]
except:
packageVersion = None
if packageName:
if packageVersion:
packageInfor = "%s==%s" % (packageName, packageVersion)
else:
packageInfor = "%s" % (packageName)
response = client.list_objects(
Bucket=bucket_name,
Prefix="python2_%s" % packageInfor
)
if 'Contents' in response and response['Contents'] and len(response['Contents']) > 0:
for eve in response['Contents']:
response = client.get_presigned_download_url(
Bucket=bucket_name,
Key=eve['Key']
)
return {
"error": False,
"result": str(response)
}
try:
os.makedirs("/tmp/%s" % packageName)
except:
pass
try:
install_list = ["install", packageInfor, "-t", "/tmp/%s"%(packageName), "-i", "https://pypi.tuna.tsinghua.edu.cn/simple"]
pip._internal.main.main(install_list)
if os.listdir("/tmp/%s" % packageName):
zipDir("/tmp/%s" % packageName, "/tmp/%s.zip" % packageInfor)
response = client.upload_file(
Bucket=bucket_name,
LocalFilePath="/tmp/%s.zip" % packageInfor,
Key="python2_%s.zip" % packageInfor,
)
print(response['ETag'])
response = client.get_presigned_download_url(
Bucket=bucket_name,
Key="/python2_%s.zip" % packageInfor
)
return {
"error": False,
"result": str(response)
}
else:
return {
"error": True,
"result": "依赖安装失败"
}
except Exception as e:
print(e)
return {
"error": True,
"result": str(e)
}
else:
return {
"error": True,
"result": "未获得到包名,请检查输入"
}
整个逻辑就是,接收到数据,去存储桶寻找是否已存在,如果已存在直接返回,如果不存在则通过pip下载,保存到存储桶,然后返回。
另外还需要对PIP的部分内容修改,主要有cache_dir等。例如在:
以及:
修改的原因是因为SCF部分的目录并为给我们权限。
有问题随时沟通。欢迎提出问题。