腾讯云Serverless架构安装Python依赖的小工具(包括对外的API,基于SCF)

2020-01-03 13:44:45 浏览数 (1)

很久很久之前,做了一个在线下载依赖包的工具,但是由于是放在了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部分的目录并为给我们权限。

有问题随时沟通。欢迎提出问题。


0 人点赞