Skip to content

@tracer.capture_lambda_handler does not support functions that return generator expressions #112

Closed
@michaelbrewer

Description

@michaelbrewer

I have an async lambda listening to S3 bucket notifications and i wanted to track the performance of downloading a large json file. Original this was a regular function that worked but later when refactoring it to yield the file pointer it failed when deployed. Nothing was found during unit testing.

What were you trying to accomplish?
Add a @tracer.capture_method to a function that yields a result.

Expected Behavior

Either fail the same way during testing when POWERTOOLS_TRACE_DISABLED='true' or support generator expression

Current Behavior

Either results in a RuntimeError("generator didn't yield") or a Task timed out

Possible Solution

Detect and support generator expression, or have a better emulation during testing which returns an error when a return
returns a generator expression.

Steps to Reproduce (for bugs)

import contextlib
import json
import tempfile
from typing import IO

import boto3
from aws_lambda_powertools import Tracer

bucket_name = "some_bucket"
key = "file.json"
tracer = Tracer()
s3 = boto3.client("s3")


@contextlib.contextmanager
def yield_without_capture() -> str:
    yield "[]"


@tracer.capture_method
@contextlib.contextmanager
def yield_with_capture() -> str:
    yield "[]"


@tracer.capture_method
@contextlib.contextmanager
def boto_yield_with_capture() -> IO:
    with tempfile.TemporaryFile() as fp:
        s3.download_fileobj(bucket_name, key, fp)
        print("download complete")  # Never prints
        fp.seek(0)
        yield fp


@contextlib.contextmanager
def boto_yield_without_capture() -> IO:
    with tempfile.TemporaryFile() as fp:
        s3.download_fileobj(bucket_name, key, fp)
        fp.seek(0)
        print("download complete")
        yield fp


@tracer.capture_lambda_handler
def lambda_handler(event: dict, _):
    if event["case"] == "yield_without_capture":
        with yield_without_capture() as yielded_value:
            result = yielded_value
    if event["case"] == "yield_with_capture":
        with yield_with_capture() as yielded_value:
            result = yielded_value
    if event["case"] == "boto_yield_without_capture":
        with boto_yield_without_capture() as fp:
            result = fp.read().decode("UTF-8")
    if event["case"] == "boto_yield_with_capture":
        with boto_yield_with_capture() as fp:
            result = fp.read().decode("UTF-8")  # "Task timed out after 10.01 seconds"

    return {"statusCode": 200, "result": json.loads(result)}
  1. case yield_without_capture returns the expected value
  2. case yield_with_capture returns a RuntimeError("generator didn't yield")
  3. case boto_yield_without_capture returns the expected value
  4. case boto_yield_with_capture returns a "Task timed out after 900.01 seconds"

Environment

  • Powertools version used: 1.1.2
  • Packaging format (Layers, PyPi): PyPi
  • AWS Lambda function runtime: Python 3.8

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions