【问题标题】:Lambda Python Dependency Package ERROR Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'surveys'Lambda Python 依赖包错误 Runtime.ImportModuleError:无法导入模块“lambda_function”:没有名为“surveys”的模块
【发布时间】:2021-11-09 17:37:52
【问题描述】:

编辑 3:
所以问题可能出在我的 Lambda 层依赖项的设置和配置中。我有一个包含 3 个文件的 /bin 目录:

  • lambdazip.sh
  • pdftk
  • libgcj.so.10

pdftk 是一个 pdf 库,而 libgcj 是 PDFtk 的依赖项。
lambdazip.sh 似乎可以设置和修改 PATH 变量。

已经尝试将所有 3 个作为 1 个 lambda 层上传。
已经尝试将所有 3 个作为 3 个单独的 lambda 层上传。
没有尝试自定义 .zip 文件名,我知道有时 Lambda 层希望您根据语言将 .zip 文件命名为特定名称。
没有尝试自定义“兼容架构”和“兼容运行时”lambda 层设置。

编辑 2:
我尝试将 Lambda 层重命名为 Python.zip,因为我听说有时您需要特定的命名约定才能使 Lambda 层正常工作。这也失败并产生了同样的错误。

编辑:
我尝试将 .py 文件从 /surveys 目录中拉出,因此当它们被压缩时,它们位于根文件夹中,但我仍然收到相同的错误:Runtime.ImportModuleError: Unable to import module 'lambda_function': No module名为“调查”

我需要压缩哪些文件?我需要将某些文件移动到根目录吗?
我了解到我不小心压缩了通常导致此错误的目录。
我需要压缩目录的内容,这是一个常见的解决方案。
不幸的是,这对我不起作用。

我有一个 Lambda 函数,我上传的代码是我的 /Archive 目录的压缩文件夹。
据我了解,许多遇到此 "[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function':" 的人都因为他们的 Lambda 处理程序而遇到问题。

我的 Lambda 处理程序是:lambda_function.lambda_handler 所以这似乎不是我的问题。

我在 Stackoverflow 上注意到的另一个常见问题似乎是人们如何压缩和压缩他们上传到 Lambda 函数的文件。

我需要移动我的 lambda_function.py 吗?有时会出现此 CloudWatch 错误,因为 lambda_function.py 不在 ROOT 目录中。

我的调查目录需要移动吗?

我认为我在这里拥有的文件夹和目录可能会导致我的问题。

我需要单独压缩目录吗?

我可以通过压缩整个项目来解决这个错误吗?

有关更多信息,我还有一个用于 PDF 工具包的 Lambda 层,在代码库中称为 pyPDFtk。在那个 Lambda 层中是一个压缩的 /bin,里面有二进制文件。

如果我可以在我的代码或 AWS 配置中更改/更改任何内容,请告诉我,我可以为您返回新的 CloudWatch 错误日志。

lambda_function.py

"""
cl_boost-pdfgen manages form to
pdf merge and mail
"""
import json, base64
import os, sys
from string import Template
from boost import PageCalc, AwsWrapper
from boost.tools import helper 
from boost.surveys import ALLOWED_SURVEYS

os.environ['LAMBDA_TASK_ROOT'] = os.environ['LAMBDA_TASK_ROOT'] if 'LAMBDA_TASK_ROOT' in os.environ else '/usr/local'
os.environ['PDFTK_PATH'] = os.environ['LAMBDA_TASK_ROOT'] + '/bin/pdftk'
os.environ['LD_LIBRARY_PATH'] = os.environ['LAMBDA_TASK_ROOT'] + '/bin'
# must import after setting env vars for pdftk
import pypdftk

# Constants
BUCKET_NAME = os.environ['BUCKET_NAME'] if 'BUCKET_NAME' in os.environ else 'cl-boost-us-east-1-np'

RAW_MESSAGE = Template(b'''From: ${from}
To: ${to}
Subject: MySteadyMind Survey results for ${subjname}
MIME-Version: 1.0
Content-type: Multipart/Mixed; boundary = "NextPart"

--NextPart
Content-Type: multipart/alternative; boundary="AlternativeBoundaryString"

--AlternativeBoundaryString
Content-Type: text/plain;charset="utf-8"
Content-Transfer-Encoding: quoted-printable

See attachment for MySteadyMind report on ${subjname}

--AlternativeBoundaryString
Content-Type: text/html;charset="utf-8"
Content-Transfer-Encoding: quoted-printable

<html>
  <body>=0D
    <p>See attachment for MySteadyMind Report on </b> ${subjname} </b>.</p>=0D
  </body>=0D
</html>=0D

--AlternativeBoundaryString--

--NextPart
Content-type: application / pdf
Content-Type: application/pdf;name="${filename}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;filename="${filename}"

${pdfdata}

--NextPart--''')

EMAIL_TAKER = True
#DEFAULT_EMAIL = os.environ['DEFAULT_EMAIL'] if 'DEFAULT_EMAIL' in os.environ else 'support@mysteadymind.com'
DEFAULT_EMAIL = os.environ['DEFAULT_EMAIL'] if 'DEFAULT_EMAIL' in os.environ else 'marshall@volociti.com'
SUBJECT = 'Evaluation for %s'
NAME_PATH = ['Entry', 'Name']
#EXTRA_EMAILS = os.environ['EXTRA_EMAILS'].split(",") if 'EXTRA_EMAILS' in os.environ else ['seth@mysteadymind.com']
EXTRA_EMAILS = os.environ['EXTRA_EMAILS'].split(",") if 'EXTRA_EMAILS' in os.environ else ['marshall@volociti.com']

#  Lambda response
def respond(err, res=None):
    """
    parameters are expected to either be
    None or valid JSON ready to go.

    :param err:
    :param res:
    :return:
    """
    return {
        'statusCode': '400' if err else '200',
        'body': err if err else res,
        'headers': {
            'Content-Type': 'application/json',
        },
    }


def check_basic_auth(headers):
    """
    pull out the auth header and validate.

    :param headers:
    :return:

    # Retrieve values from env
    vid = os.environ['uid']
    vpw = os.environ['pwd']

    encoded = "Basic " + base64.b64encode("%s:%s" % (vid,vpw))

    # compare
    return headers['Authorization'] == encoded
    """
    return True


def lambda_handler(event, context):
    """
    receive JSON, produce PDF, save to S3,
    email via SES.... bring it!
    """
    err = None
    rsp = None

    #Have to none out addresses for future lambda runs to not cause issues with appending.
    ADDRESSES = None
    ADDRESSES = {'from': "marshall@volociti.com",
                 'to': [DEFAULT_EMAIL] + EXTRA_EMAILS}
    """
    ADDRESSES = {'from': "support@mysteadymind.com",
                 'to': [DEFAULT_EMAIL] + EXTRA_EMAILS}
    """

    # check auth
    if not check_basic_auth(event['headers']):
        print ("Failed to authenticate")
        return False

    # get dataq
    data = json.loads(event['body'])

    # make sure its legit
    if (data['Form']['InternalName'] not in ALLOWED_SURVEYS):
        return False

    # read in template and prep survey type and scoreit
    pcalc = PageCalc(data, data['Form']['InternalName'])
    pcalc.score()

    pcalc.flat['Name'] = data['Section']['FirstName'] + \
        " " + data['Section']['LastName']

    # output pdf to temp space
    # baseName = str(data['Entry']['Number']) + "-" + pcalc.survey + "-" + \
    #     data['Section']['LastName'].replace(' ','') + ".pdf"
    baseName = str(data['Entry']['Number']) + "-MySteadyMind-" + \
         data['Section']['LastName'].replace(' ','') + ".pdf"
    filename = "/tmp/" + baseName

    pypdftk.fill_form(pcalc.pdf_path, pcalc.flat, out_file=filename)

    # -- Post Processing after PDF Generation -- #
    # fetch the client wrapper(s)
    aws = AwsWrapper()

    # get PDF data and prep for email
    try:
        # save the pdf to S3
        print("save %s to S3" % filename)
        aws.save_file(BUCKET_NAME, pcalc.survey,filename)

        # read in the pdf file data and
        # base64 encode it
        buff = open(filename, "rb")
        pdfdata = base64.b64encode(buff.read())
        buff.close()

        ADDRESSES['to'].append(data['Section']['Email']) if EMAIL_TAKER else None

        # gather data needed for email body
        data = {"from": ADDRESSES['from'],
                "to": ', '.join(ADDRESSES['to']),
                "subjname": pcalc.flat["Name"],
                "filename": baseName,
                "pdfdata": pdfdata
               }

        print("sending email via SES to: %s" % ', '.join(ADDRESSES['to']))

        # build MMM email and send via SES
        response = aws.send_raw_mail(ADDRESSES['from'],
                                     ADDRESSES['to'],
                                     RAW_MESSAGE.substitute(data))

        # send JSON response
        rsp = '{"Code": 200, "Message": "%s"}' % response

    except Exception as ex:
        # error trap for all occassions
        errmsg = "Exception Caught: %s" % ex

        # notify local log
        print(errmsg)

        # and lambda response
        err = '{"Code":500, "Message":"%s"}' % errmsg

    # done
    return respond(err, rsp)


if __name__ == '__main__':
    # use this option to manually generate from raw csv of cognitoforms
    if len(sys.argv) > 1:
        import csv
        with open(sys.argv[1], 'rU') as csvfile:
            csvreader = csv.DictReader(csvfile, delimiter=',',)
            for row in csvreader:
                jsondata = helper.create_json(row)
                fakeevent = {'body': json.dumps(jsondata), "headers": []}
                lambda_handler(fakeevent, None)

    # use this option to manually generate from raw json webhook response from cognito in a dir called generated/
    else:
        rng = range(3,4)
        print (rng)
        print ("Attempting to parse files: " + str(rng))
        for i in rng:
            try:
                print ('./generated/queue/' + str(i) + '.json')
                f = open('./generated/queue/' + str(i) + '.json', 'r')
                jsondata = f.read().replace('\n', '')
                f.close()

                #jdata = json.loads(jsondata)
                fakeevent = {'body': jsondata, "headers": []}
                lambda_handler(fakeevent, None)
            except:
                print ("error. file not found: " + str(i))

lambdazip.sh

#!/bin/bash
PYTHON_PATH=$VIRTUAL_ENV/..
BASE_PATH=$PWD


cd $VIRTUAL_ENV/lib/python3.9/site-packages/
zip -x "*pyc" -r9 $BASE_PATH/dist/cl-boost.zip pypdftk*

cd $BASE_PATH
zip -r $BASE_PATH/dist/cl-boost.zip bin

cd $BASE_PATH
zip -x "*pyc" -r9 $BASE_PATH/dist/cl-boost.zip boost* pdf_surveys

【问题讨论】:

  • 我建议你分享 lambda 代码,或者至少分享足够多的代码,以便它是一个可重现的示例
  • 添加了 lambda_function.py & 我可以根据要求添加更多内容。
  • 我了解到我错误地压缩了项目。最初,我只是压缩根目录。现在我已经正确压缩了每个文件,但我得到了相同的“没有名为 /surveys 的模块”错误
  • “未命名模块”错误似乎暗示您配置的层没有所需的库。见stackoverflow.com/a/64462403/3390419
  • 'surveys' 是我项目中的一个目录,包含 5 个 .py 文件。我会查看我的图层和您提供的链接。

标签: python amazon-web-services aws-lambda


【解决方案1】:

我试图重现该问题,但一切按预期工作。我的设置是(使用 Python 3.9 的 Lambda):

在我看来,您的目录结构不是您在问题中发布的内容。同样,您在 SO 中呈现的真实代码可能会有所不同。

【讨论】:

  • 我已将 lambdazip.sh 文件内容添加到原始帖子的底部。也许我们可以在这个文件中找到一些错误,或者帮助我们朝着正确的方向前进。据我了解,过去的开发人员可能在部署到 AWS 之前使用了这个 lambdazip.sh 文件在本地测试了 lambda 函数。之前有人指示我不要将此 .sh 文件上传到 Lambda 层。你认为这个 .sh 文件需要以 .zip 格式上传到 Lambda 层吗?
  • @StephenStilwell 遗憾的是,您的新代码无济于事。我没有这样的库也没有项目设置,因此无法运行您的lambdazip.sh。换句话说,lambdazip.sh 是不可重现的。
【解决方案2】:

考虑到 Marcin 无法重现该错误:此错误可能与多个安装及其库有关。如果您的路径变量中有错误的变量,则无法找到正确的变量。

【讨论】:

  • 我已将 lambdazip.sh 文件内容添加到原始帖子的底部。也许我们可以在这个文件中找到一些错误,或者帮助我们朝着正确的方向前进。据我了解,过去的开发人员可能在部署到 AWS 之前使用了这个 lambdazip.sh 文件在本地测试了 lambda 函数。之前有人指示我不要将此 .sh 文件上传到 Lambda 层。你认为这个 .sh 文件需要以 .zip 格式上传到 Lambda 层吗?
【解决方案3】:

我的代码是用 Python 2.7 编写的,这是一种 AWS Lambda 已弃用的语言。

在将代码更新到 Python 3.9 时,我了解到我还需要更新我的依赖项、二进制文件和 AWS Lambda 层。

我用于 Python PDF Toolkit 或 PDFtk 的二进制文件也已过时且不兼容。

所以我需要启动一个新的 CentOS 6 EC2 实例来生成二进制文件。

这有其自身的问题,但这些细节最好用于另一个线程。

主要是,CentOS 6 已停产,用于生成二进制文件的说明不再有效。

我需要更改 /etc/yum.repos.d/ 中的基本 url 以访问 vault.centos.org

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-28
    • 2023-03-18
    • 1970-01-01
    • 2018-08-01
    • 2018-11-16
    • 1970-01-01
    • 1970-01-01
    • 2019-11-21
    相关资源
    最近更新 更多