[{"data":1,"prerenderedAt":1672},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda":3,"surround:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda":1638,"ancestors:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda":1647,"children:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda":1652,"related:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda":1653},{"id":4,"title":5,"body":6,"dateModified":1603,"datePublished":1603,"description":1604,"extension":1605,"faq":1606,"keyword":1615,"kind":1616,"meta":1617,"navigation":299,"path":1627,"seo":1628,"slug":1631,"stem":1632,"topics":1633,"type":1636,"__hash__":1637},"docs\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda\u002Findex.md","Run a Python Excel Report in AWS Lambda",{"type":7,"value":8,"toc":1591},"minimark",[9,19,179,184,218,233,237,248,732,756,760,765,984,996,1000,1003,1064,1071,1157,1161,1164,1207,1251,1255,1261,1332,1347,1354,1462,1466,1512,1516,1526,1530,1536,1542,1548,1554,1558,1587],[10,11,12,13,18],"p",{},"A report that runs for thirty seconds once a day does not need a server. AWS Lambda runs the Python function on a schedule, bills for the seconds it uses, and writes the workbook to S3 where people or other systems pick it up. The friction is in the details: Lambda's defaults — 128 MB of memory, a 3-second timeout, automatic retries — suit small event handlers, not a pandas report, and the deployment package has size limits that pandas comes close to. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002F","Scheduling Python Excel Scripts with cron",", builds the function, sizes it with measured numbers and tests it locally without an AWS account.",[20,21,29,30,29,34,29,38,29,46,29,56,29,67,29,74,29,79,29,86,29,91,29,94,29,100,29,104,29,110,29,112,29,114,29,119,29,124,29,132,29,138,29,143,29,146,29,153,29,159,29,164,29,167,29,174],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 274","img",[25,26],"lm-seq-t","lm-seq-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"A scheduled report in Lambda",[35,36,37],"desc",{"id":26},"EventBridge Scheduler invokes the function at 07:30 London time on weekdays, passing an empty event. The function builds the workbook in memory and writes it to S3 with put_object. S3 confirms, and the function returns the bucket, key and size, which appear in the logs.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","274","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"20.0","16.0","180.0","78.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[57,58,60],"g",{"transform":59},"translate(98.0,30.5) scale(1.000)",[61,62],"path",{"d":63,"fill":64,"stroke":65,"style":66},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[68,69,73],"text",{"x":70,"y":71,"style":72},"110.0","76.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","EventBridge Scheduler",[39,75],{"x":76,"y":49,"width":50,"height":51,"rx":52,"fill":77,"stroke":78,"style":55},"290.0","#e7ebef","var(--line,#cdd5e6)",[57,80,82],{"transform":81},"translate(368.0,30.5) scale(1.000)",[61,83],{"d":84,"fill":64,"stroke":85,"style":66},"M13 2L4 14h7l-1 8 9-12h-7z","var(--text,#172033)",[68,87,90],{"x":88,"y":71,"style":89},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Lambda function",[39,92],{"x":93,"y":49,"width":50,"height":51,"rx":52,"fill":77,"stroke":78,"style":55},"560.0",[57,95,97],{"transform":96},"translate(638.0,30.5) scale(1.000)",[61,98],{"d":99,"fill":64,"stroke":85,"style":66},"M5 6c0-1.7 3.1-3 7-3s7 1.3 7 3-3.1 3-7 3-7-1.3-7-3z M5 6v12c0 1.7 3.1 3 7 3s7-1.3 7-3V6 M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3",[68,101,103],{"x":102,"y":71,"style":89},"650.0","S3 bucket",[105,106],"line",{"x1":70,"y1":107,"x2":70,"y2":108,"stroke":78,"style":109},"94.0","258.0","stroke-width:1.5px;stroke-dasharray:5 4",[105,111],{"x1":88,"y1":107,"x2":88,"y2":108,"stroke":78,"style":109},[105,113],{"x1":102,"y1":107,"x2":102,"y2":108,"stroke":78,"style":109},[105,115],{"x1":116,"y1":117,"x2":118,"y2":117,"stroke":54,"style":55},"115.0","134.0","371.0",[120,121],"polygon",{"points":122,"fill":123},"375.0,134.0 366.0,139.0 366.0,129.0","#5b5cf0",[39,125],{"x":126,"y":127,"width":128,"height":129,"rx":130,"fill":53,"stroke":54,"style":131},"138.2","105.0","213.7","21.0","10","stroke-width:1.2px",[68,133,137],{"x":134,"y":135,"style":136},"245.0","119.5","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","1. 07:30 Europe\u002FLondon, event {}",[105,139],{"x1":140,"y1":141,"x2":142,"y2":141,"stroke":54,"style":55},"385.0","182.0","641.0",[120,144],{"points":145,"fill":123},"645.0,182.0 636.0,187.0 636.0,177.0",[39,147],{"x":148,"y":149,"width":150,"height":129,"rx":130,"fill":151,"stroke":152,"style":131},"382.3","153.0","265.3","#fdefd8","var(--gold,#b4740a)",[68,154,158],{"x":155,"y":156,"style":157},"515.0","167.5","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","2. workbook built in memory → put_object",[105,160],{"x1":161,"y1":162,"x2":163,"y2":162,"stroke":54,"style":55},"645.0","230.0","389.0",[120,165],{"points":166,"fill":123},"385.0,230.0 394.0,225.0 394.0,235.0",[39,168],{"x":169,"y":170,"width":171,"height":129,"rx":130,"fill":172,"stroke":173,"style":131},"476.3","201.0","77.4","#d9f4f1","var(--teal,#0f9488)",[68,175,178],{"x":155,"y":176,"style":177},"215.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","3. 200 OK",[180,181,183],"h2",{"id":182},"prerequisites","Prerequisites",[185,186,191],"pre",{"className":187,"code":188,"language":189,"meta":190,"style":190},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install boto3 pandas openpyxl \"moto[s3]\"\n","bash","",[192,193,194],"code",{"__ignoreMap":190},[195,196,198,202,206,209,212,215],"span",{"class":105,"line":197},1,[195,199,201],{"class":200},"sMTad","pip",[195,203,205],{"class":204},"srMev"," install",[195,207,208],{"class":204}," boto3",[195,210,211],{"class":204}," pandas",[195,213,214],{"class":204}," openpyxl",[195,216,217],{"class":204}," \"moto[s3]\"\n",[10,219,220,221,224,225,228,229,232],{},"An S3 bucket for the reports and an execution role for the function that allows ",[192,222,223],{},"s3:PutObject"," on it. The Lambda Python runtime already includes ",[192,226,227],{},"boto3","; ",[192,230,231],{},"moto"," is only for local tests.",[180,234,236],{"id":235},"step-1-write-a-handler-that-works-in-memory","Step 1 — Write a handler that works in memory",[10,238,239,240,243,244,247],{},"Lambda's file system is read-only except for ",[192,241,242],{},"\u002Ftmp",". Rather than writing a file and uploading it, build the workbook in a ",[192,245,246],{},"BytesIO"," buffer and send the bytes straight to S3. Create the S3 client at module level, outside the handler, so warm invocations reuse it:",[185,249,253],{"className":250,"code":251,"language":252,"meta":190,"style":190},"language-python shiki shiki-themes github-light github-dark-high-contrast","# report_lambda.py\nimport datetime as dt\nimport io\nimport os\n\nimport boto3\nimport pandas as pd\n\ns3 = boto3.client(\"s3\")                      # created once per container, reused\n\ndef build_report(day):\n    frame = pd.DataFrame({\"region\": [\"North\", \"South\", \"East\", \"West\"],\n                          \"sales\": [182_400, 141_950, 97_300, 120_880]})\n    buffer = io.BytesIO()\n    with pd.ExcelWriter(buffer, engine=\"openpyxl\") as writer:\n        frame.to_excel(writer, sheet_name=f\"Sales {day:%Y-%m-%d}\", index=False)\n    return buffer.getvalue()\n\ndef handler(event, context):\n    day = dt.date.fromisoformat(event.get(\"date\") or dt.date.today().isoformat())\n    data = build_report(day)\n    key = f\"reports\u002Fdaily\u002F{day:%Y\u002F%m}\u002Fsales_{day:%Y-%m-%d}.xlsx\"\n    s3.put_object(Bucket=os.environ[\"REPORT_BUCKET\"], Key=key, Body=data,\n                  ContentType=\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\")\n    return {\"bucket\": os.environ[\"REPORT_BUCKET\"], \"key\": key, \"bytes\": len(data)}\n","python",[192,254,255,261,278,286,294,301,309,322,327,348,353,366,405,435,446,472,532,541,546,557,579,590,647,683,696],{"__ignoreMap":190},[195,256,257],{"class":105,"line":197},[195,258,260],{"class":259},"s-wDw","# report_lambda.py\n",[195,262,264,268,272,275],{"class":105,"line":263},2,[195,265,267],{"class":266},"s-kum","import",[195,269,271],{"class":270},"skGVy"," datetime ",[195,273,274],{"class":266},"as",[195,276,277],{"class":270}," dt\n",[195,279,281,283],{"class":105,"line":280},3,[195,282,267],{"class":266},[195,284,285],{"class":270}," io\n",[195,287,289,291],{"class":105,"line":288},4,[195,290,267],{"class":266},[195,292,293],{"class":270}," os\n",[195,295,297],{"class":105,"line":296},5,[195,298,300],{"emptyLinePlaceholder":299},true,"\n",[195,302,304,306],{"class":105,"line":303},6,[195,305,267],{"class":266},[195,307,308],{"class":270}," boto3\n",[195,310,312,314,317,319],{"class":105,"line":311},7,[195,313,267],{"class":266},[195,315,316],{"class":270}," pandas ",[195,318,274],{"class":266},[195,320,321],{"class":270}," pd\n",[195,323,325],{"class":105,"line":324},8,[195,326,300],{"emptyLinePlaceholder":299},[195,328,330,333,336,339,342,345],{"class":105,"line":329},9,[195,331,332],{"class":270},"s3 ",[195,334,335],{"class":266},"=",[195,337,338],{"class":270}," boto3.client(",[195,340,341],{"class":204},"\"s3\"",[195,343,344],{"class":270},")                      ",[195,346,347],{"class":259},"# created once per container, reused\n",[195,349,351],{"class":105,"line":350},10,[195,352,300],{"emptyLinePlaceholder":299},[195,354,356,359,363],{"class":105,"line":355},11,[195,357,358],{"class":266},"def",[195,360,362],{"class":361},"s_Opv"," build_report",[195,364,365],{"class":270},"(day):\n",[195,367,369,372,374,377,380,383,386,389,392,394,397,399,402],{"class":105,"line":368},12,[195,370,371],{"class":270},"    frame ",[195,373,335],{"class":266},[195,375,376],{"class":270}," pd.DataFrame({",[195,378,379],{"class":204},"\"region\"",[195,381,382],{"class":270},": [",[195,384,385],{"class":204},"\"North\"",[195,387,388],{"class":270},", ",[195,390,391],{"class":204},"\"South\"",[195,393,388],{"class":270},[195,395,396],{"class":204},"\"East\"",[195,398,388],{"class":270},[195,400,401],{"class":204},"\"West\"",[195,403,404],{"class":270},"],\n",[195,406,408,411,413,417,419,422,424,427,429,432],{"class":105,"line":407},13,[195,409,410],{"class":204},"                          \"sales\"",[195,412,382],{"class":270},[195,414,416],{"class":415},"sP0c6","182_400",[195,418,388],{"class":270},[195,420,421],{"class":415},"141_950",[195,423,388],{"class":270},[195,425,426],{"class":415},"97_300",[195,428,388],{"class":270},[195,430,431],{"class":415},"120_880",[195,433,434],{"class":270},"]})\n",[195,436,438,441,443],{"class":105,"line":437},14,[195,439,440],{"class":270},"    buffer ",[195,442,335],{"class":266},[195,444,445],{"class":270}," io.BytesIO()\n",[195,447,449,452,455,459,461,464,467,469],{"class":105,"line":448},15,[195,450,451],{"class":266},"    with",[195,453,454],{"class":270}," pd.ExcelWriter(buffer, ",[195,456,458],{"class":457},"sa561","engine",[195,460,335],{"class":266},[195,462,463],{"class":204},"\"openpyxl\"",[195,465,466],{"class":270},") ",[195,468,274],{"class":266},[195,470,471],{"class":270}," writer:\n",[195,473,475,478,481,483,486,489,493,496,499,502,505,508,510,513,516,519,521,524,526,529],{"class":105,"line":474},16,[195,476,477],{"class":270},"        frame.to_excel(writer, ",[195,479,480],{"class":457},"sheet_name",[195,482,335],{"class":266},[195,484,485],{"class":266},"f",[195,487,488],{"class":204},"\"Sales ",[195,490,492],{"class":491},"sSjpA","{",[195,494,495],{"class":270},"day:",[195,497,498],{"class":266},"%",[195,500,501],{"class":270},"Y",[195,503,504],{"class":266},"-%",[195,506,507],{"class":270},"m",[195,509,504],{"class":266},[195,511,512],{"class":270},"d",[195,514,515],{"class":491},"}",[195,517,518],{"class":204},"\"",[195,520,388],{"class":270},[195,522,523],{"class":457},"index",[195,525,335],{"class":266},[195,527,528],{"class":415},"False",[195,530,531],{"class":270},")\n",[195,533,535,538],{"class":105,"line":534},17,[195,536,537],{"class":266},"    return",[195,539,540],{"class":270}," buffer.getvalue()\n",[195,542,544],{"class":105,"line":543},18,[195,545,300],{"emptyLinePlaceholder":299},[195,547,549,551,554],{"class":105,"line":548},19,[195,550,358],{"class":266},[195,552,553],{"class":361}," handler",[195,555,556],{"class":270},"(event, context):\n",[195,558,560,563,565,568,571,573,576],{"class":105,"line":559},20,[195,561,562],{"class":270},"    day ",[195,564,335],{"class":266},[195,566,567],{"class":270}," dt.date.fromisoformat(event.get(",[195,569,570],{"class":204},"\"date\"",[195,572,466],{"class":270},[195,574,575],{"class":266},"or",[195,577,578],{"class":270}," dt.date.today().isoformat())\n",[195,580,582,585,587],{"class":105,"line":581},21,[195,583,584],{"class":270},"    data ",[195,586,335],{"class":266},[195,588,589],{"class":270}," build_report(day)\n",[195,591,593,596,598,601,604,606,609,612,614,616,619,621,623,626,628,630,632,634,636,638,640,642,644],{"class":105,"line":592},22,[195,594,595],{"class":270},"    key ",[195,597,335],{"class":266},[195,599,600],{"class":266}," f",[195,602,603],{"class":204},"\"reports\u002Fdaily\u002F",[195,605,492],{"class":491},[195,607,608],{"class":270},"day",[195,610,611],{"class":266},":",[195,613,498],{"class":270},[195,615,501],{"class":266},[195,617,618],{"class":270},"\u002F%",[195,620,507],{"class":266},[195,622,515],{"class":491},[195,624,625],{"class":204},"\u002Fsales_",[195,627,492],{"class":491},[195,629,495],{"class":270},[195,631,498],{"class":266},[195,633,501],{"class":270},[195,635,504],{"class":266},[195,637,507],{"class":270},[195,639,504],{"class":266},[195,641,512],{"class":270},[195,643,515],{"class":491},[195,645,646],{"class":204},".xlsx\"\n",[195,648,650,653,656,658,661,664,667,670,672,675,678,680],{"class":105,"line":649},23,[195,651,652],{"class":270},"    s3.put_object(",[195,654,655],{"class":457},"Bucket",[195,657,335],{"class":266},[195,659,660],{"class":270},"os.environ[",[195,662,663],{"class":204},"\"REPORT_BUCKET\"",[195,665,666],{"class":270},"], ",[195,668,669],{"class":457},"Key",[195,671,335],{"class":266},[195,673,674],{"class":270},"key, ",[195,676,677],{"class":457},"Body",[195,679,335],{"class":266},[195,681,682],{"class":270},"data,\n",[195,684,686,689,691,694],{"class":105,"line":685},24,[195,687,688],{"class":457},"                  ContentType",[195,690,335],{"class":266},[195,692,693],{"class":204},"\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"",[195,695,531],{"class":270},[195,697,699,701,704,707,710,712,714,717,720,723,726,729],{"class":105,"line":698},25,[195,700,537],{"class":266},[195,702,703],{"class":270}," {",[195,705,706],{"class":204},"\"bucket\"",[195,708,709],{"class":270},": os.environ[",[195,711,663],{"class":204},[195,713,666],{"class":270},[195,715,716],{"class":204},"\"key\"",[195,718,719],{"class":270},": key, ",[195,721,722],{"class":204},"\"bytes\"",[195,724,725],{"class":270},": ",[195,727,728],{"class":415},"len",[195,730,731],{"class":270},"(data)}\n",[10,733,734,735,738,739,742,743,746,747,750,751,755],{},"The optional ",[192,736,737],{},"date"," in the event lets you rerun a specific day by invoking the function by hand with ",[192,740,741],{},"{\"date\": \"2026-09-22\"}",". Lambda runs in UTC, so ",[192,744,745],{},"date.today()"," near midnight may not be the business day you expect; pass the date explicitly or convert with ",[192,748,749],{},"zoneinfo"," when it matters. Settings such as the bucket name come from environment variables on the function, not from the code. More on S3 uploads is in ",[14,752,754],{"href":753},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-amazon-s3-with-boto3\u002F","Upload an Excel Report to Amazon S3 with boto3",".",[180,757,759],{"id":758},"step-2-test-it-locally-with-moto","Step 2 — Test it locally with moto",[10,761,762,764],{},[192,763,231],{}," intercepts boto3's calls and fakes S3 in memory, so the handler can be tested on any machine and in CI:",[185,766,768],{"className":250,"code":767,"language":252,"meta":190,"style":190},"import os\nos.environ.update(REPORT_BUCKET=\"acme-reports\", AWS_DEFAULT_REGION=\"eu-west-2\",\n                  AWS_ACCESS_KEY_ID=\"testing\", AWS_SECRET_ACCESS_KEY=\"testing\")\n\nimport boto3\nfrom moto import mock_aws\n\nwith mock_aws():\n    boto3.client(\"s3\").create_bucket(Bucket=\"acme-reports\",\n                                     CreateBucketConfiguration={\"LocationConstraint\": \"eu-west-2\"})\n    import report_lambda\n    print(report_lambda.handler({\"date\": \"2026-09-23\"}, None))\n    head = boto3.client(\"s3\").head_object(Bucket=\"acme-reports\",\n                                          Key=\"reports\u002Fdaily\u002F2026\u002F09\u002Fsales_2026-09-23.xlsx\")\n    print(head[\"ContentType\"])\n# {'bucket': 'acme-reports', 'key': 'reports\u002Fdaily\u002F2026\u002F09\u002Fsales_2026-09-23.xlsx', 'bytes': 4935}\n# application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\n",[192,769,770,776,802,823,827,833,846,850,858,876,895,903,927,949,961,974,979],{"__ignoreMap":190},[195,771,772,774],{"class":105,"line":197},[195,773,267],{"class":266},[195,775,293],{"class":270},[195,777,778,781,784,786,789,791,794,796,799],{"class":105,"line":263},[195,779,780],{"class":270},"os.environ.update(",[195,782,783],{"class":457},"REPORT_BUCKET",[195,785,335],{"class":266},[195,787,788],{"class":204},"\"acme-reports\"",[195,790,388],{"class":270},[195,792,793],{"class":457},"AWS_DEFAULT_REGION",[195,795,335],{"class":266},[195,797,798],{"class":204},"\"eu-west-2\"",[195,800,801],{"class":270},",\n",[195,803,804,807,809,812,814,817,819,821],{"class":105,"line":280},[195,805,806],{"class":457},"                  AWS_ACCESS_KEY_ID",[195,808,335],{"class":266},[195,810,811],{"class":204},"\"testing\"",[195,813,388],{"class":270},[195,815,816],{"class":457},"AWS_SECRET_ACCESS_KEY",[195,818,335],{"class":266},[195,820,811],{"class":204},[195,822,531],{"class":270},[195,824,825],{"class":105,"line":288},[195,826,300],{"emptyLinePlaceholder":299},[195,828,829,831],{"class":105,"line":296},[195,830,267],{"class":266},[195,832,308],{"class":270},[195,834,835,838,841,843],{"class":105,"line":303},[195,836,837],{"class":266},"from",[195,839,840],{"class":270}," moto ",[195,842,267],{"class":266},[195,844,845],{"class":270}," mock_aws\n",[195,847,848],{"class":105,"line":311},[195,849,300],{"emptyLinePlaceholder":299},[195,851,852,855],{"class":105,"line":324},[195,853,854],{"class":266},"with",[195,856,857],{"class":270}," mock_aws():\n",[195,859,860,863,865,868,870,872,874],{"class":105,"line":329},[195,861,862],{"class":270},"    boto3.client(",[195,864,341],{"class":204},[195,866,867],{"class":270},").create_bucket(",[195,869,655],{"class":457},[195,871,335],{"class":266},[195,873,788],{"class":204},[195,875,801],{"class":270},[195,877,878,881,883,885,888,890,892],{"class":105,"line":350},[195,879,880],{"class":457},"                                     CreateBucketConfiguration",[195,882,335],{"class":266},[195,884,492],{"class":270},[195,886,887],{"class":204},"\"LocationConstraint\"",[195,889,725],{"class":270},[195,891,798],{"class":204},[195,893,894],{"class":270},"})\n",[195,896,897,900],{"class":105,"line":355},[195,898,899],{"class":266},"    import",[195,901,902],{"class":270}," report_lambda\n",[195,904,905,908,911,913,915,918,921,924],{"class":105,"line":368},[195,906,907],{"class":415},"    print",[195,909,910],{"class":270},"(report_lambda.handler({",[195,912,570],{"class":204},[195,914,725],{"class":270},[195,916,917],{"class":204},"\"2026-09-23\"",[195,919,920],{"class":270},"}, ",[195,922,923],{"class":415},"None",[195,925,926],{"class":270},"))\n",[195,928,929,932,934,936,938,941,943,945,947],{"class":105,"line":407},[195,930,931],{"class":270},"    head ",[195,933,335],{"class":266},[195,935,338],{"class":270},[195,937,341],{"class":204},[195,939,940],{"class":270},").head_object(",[195,942,655],{"class":457},[195,944,335],{"class":266},[195,946,788],{"class":204},[195,948,801],{"class":270},[195,950,951,954,956,959],{"class":105,"line":437},[195,952,953],{"class":457},"                                          Key",[195,955,335],{"class":266},[195,957,958],{"class":204},"\"reports\u002Fdaily\u002F2026\u002F09\u002Fsales_2026-09-23.xlsx\"",[195,960,531],{"class":270},[195,962,963,965,968,971],{"class":105,"line":448},[195,964,907],{"class":415},[195,966,967],{"class":270},"(head[",[195,969,970],{"class":204},"\"ContentType\"",[195,972,973],{"class":270},"])\n",[195,975,976],{"class":105,"line":474},[195,977,978],{"class":259},"# {'bucket': 'acme-reports', 'key': 'reports\u002Fdaily\u002F2026\u002F09\u002Fsales_2026-09-23.xlsx', 'bytes': 4935}\n",[195,980,981],{"class":105,"line":534},[195,982,983],{"class":259},"# application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\n",[10,985,986,987,991,992,995],{},"Import the handler module ",[988,989,990],"em",{},"inside"," ",[192,993,994],{},"mock_aws()",": its module-level client must be created while the mock is active, or it would try to reach real AWS. Fake credentials guarantee a mistake cannot touch a real account.",[180,997,999],{"id":998},"step-3-package-it-within-lambdas-limits","Step 3 — Package it within Lambda's limits",[10,1001,1002],{},"A zip deployment can be at most 50 MB when uploaded directly and 250 MB unzipped, including layers. Install the dependencies for Lambda's platform, not your laptop's, and measure:",[185,1004,1006],{"className":187,"code":1005,"language":189,"meta":190,"style":190},"pip install --platform manylinux2014_x86_64 --implementation cp --python-version 3.12 \\\n            --only-binary=:all: --target package pandas openpyxl\ndu -sh package          # 154 MB with numpy\n",[192,1007,1008,1035,1051],{"__ignoreMap":190},[195,1009,1010,1012,1014,1017,1020,1023,1026,1029,1032],{"class":105,"line":197},[195,1011,201],{"class":200},[195,1013,205],{"class":204},[195,1015,1016],{"class":415}," --platform",[195,1018,1019],{"class":204}," manylinux2014_x86_64",[195,1021,1022],{"class":415}," --implementation",[195,1024,1025],{"class":204}," cp",[195,1027,1028],{"class":415}," --python-version",[195,1030,1031],{"class":415}," 3.12",[195,1033,1034],{"class":491}," \\\n",[195,1036,1037,1040,1043,1046,1048],{"class":105,"line":263},[195,1038,1039],{"class":415},"            --only-binary=:all:",[195,1041,1042],{"class":415}," --target",[195,1044,1045],{"class":204}," package",[195,1047,211],{"class":204},[195,1049,1050],{"class":204}," openpyxl\n",[195,1052,1053,1056,1059,1061],{"class":105,"line":280},[195,1054,1055],{"class":200},"du",[195,1057,1058],{"class":415}," -sh",[195,1060,1045],{"class":204},[195,1062,1063],{"class":259},"          # 154 MB with numpy\n",[10,1065,1066,1067,755],{},"pandas, numpy and openpyxl came to 154 MB unzipped and 44 MB zipped — within both limits, but with less room than it seems once other libraries join. Three ways to keep it comfortable: put the dependencies in a layer shared by several report functions; use a container image, which allows up to 10 GB; or drop pandas when the report is simple, since openpyxl alone was 3 MB and XlsxWriter 2 MB. Building workbooks without pandas is covered in ",[14,1068,1070],{"href":1069},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a Formatted Excel Report with XlsxWriter",[20,1072,29,1078,29,1081,29,1084,29,1088,29,1094,29,1100,29,1105,29,1110,29,1113,29,1117,29,1120,29,1125,29,1128,29,1132,29,1135,29,1140,29,1143,29,1147,29,1149,29,1152],{"viewBox":1073,"role":23,"ariaLabelledBy":1074,"xmlns":27,"style":1077},"4 0 744 206",[1075,1076],"lm-sizes-t","lm-sizes-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1079,1080],{"id":1075},"Unzipped package size against the 250 MB limit",[35,1082,1083],{"id":1076},"Dependencies installed for Lambda's Linux x86_64 platform and Python 3.12. pandas with numpy and openpyxl takes 154 MB unzipped, leaving under 100 MB of the 250 MB limit for everything else. openpyxl alone takes 3 MB and XlsxWriter 2 MB.",[39,1085],{"x":41,"y":42,"width":1086,"height":1087,"fill":45},"744","206",[39,1089],{"x":48,"y":49,"width":1090,"height":1091,"rx":1092,"fill":77,"stroke":78,"style":1093},"209.7","28.0","7","stroke-width:1px",[68,1095,1099],{"x":1096,"y":1097,"style":1098},"124.9","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Lambda unzipped limit",[39,1101],{"x":1102,"y":49,"width":1103,"height":1091,"rx":1092,"fill":77,"stroke":78,"style":1104},"239.7","436.9","stroke-width:1.5px",[68,1106,1109],{"x":1107,"y":1097,"style":1108},"684.7","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","250 MB",[39,1111],{"x":48,"y":1112,"width":1090,"height":1091,"rx":1092,"fill":77,"stroke":78,"style":1093},"56.0",[68,1114,1116],{"x":1096,"y":1115,"style":1098},"74.5","pandas + numpy + openpyxl",[39,1118],{"x":1102,"y":1112,"width":1119,"height":1091,"rx":1092,"fill":151,"stroke":152,"style":1104},"269.1",[68,1121,1124],{"x":1122,"y":1115,"style":1123},"516.9","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","154 MB",[39,1126],{"x":48,"y":1127,"width":1090,"height":1091,"rx":1092,"fill":77,"stroke":78,"style":1093},"96.0",[68,1129,1131],{"x":1096,"y":1130,"style":1098},"114.5","openpyxl only",[39,1133],{"x":1102,"y":1127,"width":1134,"height":1091,"rx":1092,"fill":172,"stroke":173,"style":1104},"6.0",[68,1136,1139],{"x":1137,"y":1130,"style":1138},"253.7","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","3 MB",[39,1141],{"x":48,"y":1142,"width":1090,"height":1091,"rx":1092,"fill":77,"stroke":78,"style":1093},"136.0",[68,1144,1146],{"x":1096,"y":1145,"style":1098},"154.5","XlsxWriter only",[39,1148],{"x":1102,"y":1142,"width":1134,"height":1091,"rx":1092,"fill":172,"stroke":173,"style":1104},[68,1150,1151],{"x":1137,"y":1145,"style":1138},"2 MB",[68,1153,1156],{"x":88,"y":1154,"style":1155},"186.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","measured with pip install --platform manylinux2014_x86_64 --target",[180,1158,1160],{"id":1159},"step-4-replace-the-defaults-that-do-not-fit","Step 4 — Replace the defaults that do not fit",[10,1162,1163],{},"Four settings decide whether the function works at all:",[1165,1166,1167,1183,1189,1197],"ul",{},[1168,1169,1170,1174,1175,1178,1179,1182],"li",{},[1171,1172,1173],"strong",{},"Memory."," The default is 128 MB. Importing pandas, openpyxl and boto3 alone peaked at 129 MB in a local test, before reading any data. Start at 512 MB; Lambda also allocates CPU in proportion to memory, so the report runs faster. After a few runs, the ",[192,1176,1177],{},"REPORT"," line in the logs shows ",[988,1180,1181],{},"Max Memory Used"," — set the memory comfortably above it.",[1168,1184,1185,1188],{},[1171,1186,1187],{},"Timeout."," The default is 3 seconds, and a cold start that imports pandas can use a good part of that. Set it to a few minutes, well above the slowest real run; the maximum is 15 minutes.",[1168,1190,1191,991,1194,1196],{},[1171,1192,1193],{},"Ephemeral storage.",[192,1195,242],{}," holds 512 MB by default, configurable up to 10 GB. Building in memory avoids it, but large input files downloaded for processing need space there.",[1168,1198,1199,1202,1203,1206],{},[1171,1200,1201],{},"Architecture."," arm64 functions cost less per second. Build the package for ",[192,1204,1205],{},"manylinux2014_aarch64"," if you switch — x86_64 wheels do not run on arm64.",[185,1208,1210],{"className":187,"code":1209,"language":189,"meta":190,"style":190},"aws lambda update-function-configuration --function-name sales-report \\\n    --memory-size 512 --timeout 300 --environment \"Variables={REPORT_BUCKET=acme-reports}\"\n",[192,1211,1212,1231],{"__ignoreMap":190},[195,1213,1214,1217,1220,1223,1226,1229],{"class":105,"line":197},[195,1215,1216],{"class":200},"aws",[195,1218,1219],{"class":204}," lambda",[195,1221,1222],{"class":204}," update-function-configuration",[195,1224,1225],{"class":415}," --function-name",[195,1227,1228],{"class":204}," sales-report",[195,1230,1034],{"class":491},[195,1232,1233,1236,1239,1242,1245,1248],{"class":105,"line":263},[195,1234,1235],{"class":415},"    --memory-size",[195,1237,1238],{"class":415}," 512",[195,1240,1241],{"class":415}," --timeout",[195,1243,1244],{"class":415}," 300",[195,1246,1247],{"class":415}," --environment",[195,1249,1250],{"class":204}," \"Variables={REPORT_BUCKET=acme-reports}\"\n",[180,1252,1254],{"id":1253},"step-5-schedule-it-in-local-time-without-duplicate-runs","Step 5 — Schedule it in local time, without duplicate runs",[10,1256,1257,1258,611],{},"EventBridge Scheduler invokes the function on a cron expression and, unlike older EventBridge rules, takes a time zone, so the report stays at 07:30 local time across daylight saving changes. Its cron format has six fields, and one of day-of-month and day-of-week must be ",[192,1259,1260],{},"?",[185,1262,1264],{"className":187,"code":1263,"language":189,"meta":190,"style":190},"aws scheduler create-schedule --name sales-report-weekdays \\\n    --schedule-expression \"cron(30 7 ? * MON-FRI *)\" \\\n    --schedule-expression-timezone \"Europe\u002FLondon\" \\\n    --flexible-time-window Mode=OFF \\\n    --target '{\"Arn\": \"arn:aws:lambda:eu-west-2:123456789012:function:sales-report\",\n               \"RoleArn\": \"arn:aws:iam::123456789012:role\u002Fscheduler-invoke-sales-report\",\n               \"Input\": \"{}\"}'\n",[192,1265,1266,1284,1294,1304,1314,1322,1327],{"__ignoreMap":190},[195,1267,1268,1270,1273,1276,1279,1282],{"class":105,"line":197},[195,1269,1216],{"class":200},[195,1271,1272],{"class":204}," scheduler",[195,1274,1275],{"class":204}," create-schedule",[195,1277,1278],{"class":415}," --name",[195,1280,1281],{"class":204}," sales-report-weekdays",[195,1283,1034],{"class":491},[195,1285,1286,1289,1292],{"class":105,"line":263},[195,1287,1288],{"class":415},"    --schedule-expression",[195,1290,1291],{"class":204}," \"cron(30 7 ? * MON-FRI *)\"",[195,1293,1034],{"class":491},[195,1295,1296,1299,1302],{"class":105,"line":280},[195,1297,1298],{"class":415},"    --schedule-expression-timezone",[195,1300,1301],{"class":204}," \"Europe\u002FLondon\"",[195,1303,1034],{"class":491},[195,1305,1306,1309,1312],{"class":105,"line":288},[195,1307,1308],{"class":415},"    --flexible-time-window",[195,1310,1311],{"class":204}," Mode=OFF",[195,1313,1034],{"class":491},[195,1315,1316,1319],{"class":105,"line":296},[195,1317,1318],{"class":415},"    --target",[195,1320,1321],{"class":204}," '{\"Arn\": \"arn:aws:lambda:eu-west-2:123456789012:function:sales-report\",\n",[195,1323,1324],{"class":105,"line":303},[195,1325,1326],{"class":204},"               \"RoleArn\": \"arn:aws:iam::123456789012:role\u002Fscheduler-invoke-sales-report\",\n",[195,1328,1329],{"class":105,"line":311},[195,1330,1331],{"class":204},"               \"Input\": \"{}\"}'\n",[10,1333,1334,1335,1338,1339,1342,1343,755],{},"The scheduler invokes the function asynchronously, and Lambda retries a failed asynchronous invocation twice by default. For this handler a retry simply writes the same key again, which is harmless. For a report that emails people, three runs mean three emails; set ",[192,1336,1337],{},"--maximum-retry-attempts 0"," with ",[192,1340,1341],{},"aws lambda put-function-event-invoke-config"," and rely on an alarm instead, or make the send step check whether it already ran, as in ",[14,1344,1346],{"href":1345},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fskip-a-report-when-the-input-file-has-not-changed\u002F","Skip a Report Run When the Input File Has Not Changed",[10,1348,1349,1350,1353],{},"Point a CloudWatch alarm at the function's ",[192,1351,1352],{},"Errors"," metric to hear about failures; the traceback is in the function's log group.",[20,1355,29,1360,29,1363,29,1366,29,1369,29,1373,29,1377,29,1382,29,1385,29,1389,29,1392,29,1397,29,1399,29,1404,29,1406,29,1409,29,1412,29,1416,29,1418,29,1422,29,1424,29,1427,29,1430,29,1434,29,1436,29,1440,29,1442,29,1445,29,1448,29,1451,29,1453,29,1457,29,1459],{"viewBox":1356,"role":23,"ariaLabelledBy":1357,"xmlns":27,"style":28},"4 0 752 208",[1358,1359],"lm-defaults-t","lm-defaults-d",[31,1361,1362],{"id":1358},"Lambda defaults against what a pandas report needs",[35,1364,1365],{"id":1359},"Memory defaults to 128 MB, which pandas imports alone exceed; start at 512 MB. The timeout defaults to 3 seconds; set a few minutes. Ephemeral storage defaults to 512 MB, enough when the workbook is built in memory. Asynchronous invocations are retried twice; keep that only when the report is idempotent.",[39,1367],{"x":41,"y":42,"width":43,"height":1368,"fill":45},"208",[39,1370],{"x":48,"y":49,"width":1371,"height":1372,"rx":42,"fill":45,"stroke":78,"style":1093},"111.9","40.0",[39,1374],{"x":1375,"y":49,"width":1376,"height":1372,"rx":42,"fill":53,"stroke":54,"style":131},"131.9","304.0",[68,1378,1381],{"x":1379,"y":1380,"style":72},"283.9","41.0","Default",[39,1383],{"x":1384,"y":49,"width":1376,"height":1372,"rx":42,"fill":53,"stroke":54,"style":131},"436.0",[68,1386,1388],{"x":1387,"y":1380,"style":72},"588.0","For an Excel report",[39,1390],{"x":48,"y":1112,"width":1371,"height":1391,"rx":42,"fill":77,"stroke":78,"style":1093},"34.0",[68,1393,1396],{"x":1394,"y":51,"style":1395},"32.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","memory",[39,1398],{"x":1375,"y":1112,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1400,1403],{"x":1379,"y":1401,"style":1402},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","128 MB",[39,1405],{"x":1384,"y":1112,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1407,1408],{"x":1387,"y":1401,"style":1402},"512 MB, then tune",[39,1410],{"x":48,"y":1411,"width":1371,"height":1391,"rx":42,"fill":77,"stroke":78,"style":1093},"90.0",[68,1413,1415],{"x":1394,"y":1414,"style":1395},"112.0","timeout",[39,1417],{"x":1375,"y":1411,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1419,1421],{"x":1379,"y":1420,"style":1402},"111.0","3 seconds",[39,1423],{"x":1384,"y":1411,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1425,1426],{"x":1387,"y":1420,"style":1402},"a few minutes",[39,1428],{"x":48,"y":1429,"width":1371,"height":1391,"rx":42,"fill":77,"stroke":78,"style":1093},"124.0",[68,1431,1433],{"x":1394,"y":1432,"style":1395},"146.0","\u002Ftmp storage",[39,1435],{"x":1375,"y":1429,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1437,1439],{"x":1379,"y":1438,"style":1402},"145.0","512 MB",[39,1441],{"x":1384,"y":1429,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1443,1444],{"x":1387,"y":1438,"style":1402},"enough when building in memory",[39,1446],{"x":48,"y":1447,"width":1371,"height":1391,"rx":42,"fill":77,"stroke":78,"style":1093},"158.0",[68,1449,1450],{"x":1394,"y":50,"style":1395},"async retries",[39,1452],{"x":1375,"y":1447,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1454,1456],{"x":1379,"y":1455,"style":1402},"179.0","2",[39,1458],{"x":1384,"y":1447,"width":1376,"height":1391,"rx":42,"fill":45,"stroke":78,"style":1093},[68,1460,1461],{"x":1387,"y":1455,"style":1402},"0, or an idempotent handler",[180,1463,1465],{"id":1464},"common-pitfalls","Common pitfalls",[1165,1467,1468,1480,1494,1500,1506],{},[1168,1469,1470,991,1473,1476,1477,1479],{},[1171,1471,1472],{},"Writing next to the code.",[192,1474,1475],{},"to_excel(\"report.xlsx\")"," fails with a read-only file system error. Use ",[192,1478,242],{}," or, better, memory.",[1168,1481,1482,1485,1486,1489,1490,1493],{},[1171,1483,1484],{},"Wheels built on macOS or Windows."," A package zipped from a laptop's ",[192,1487,1488],{},"site-packages"," fails on Lambda with import errors from numpy. Always install with ",[192,1491,1492],{},"--platform"," or build in a container.",[1168,1495,1496,1499],{},[1171,1497,1498],{},"Secrets in environment variables."," They are visible to anyone who can read the function's configuration. Keep database passwords in Secrets Manager or Parameter Store and fetch them at start-up.",[1168,1501,1502,1505],{},[1171,1503,1504],{},"Measuring only warm runs."," The first invocation in a new container imports pandas before doing any work — 0.33 seconds on a fast laptop, and noticeably longer on a function with little memory, and therefore little CPU. A warm run of the handler above took 4 milliseconds. Time the cold start when choosing the timeout; for a once-a-day report, nearly every run is a cold start.",[1168,1507,1508,1511],{},[1171,1509,1510],{},"Reports that need Excel itself."," Lambda cannot run desktop Excel; recalculation or PDF export needs a different approach, such as LibreOffice in a container image.",[180,1513,1515],{"id":1514},"conclusion","Conclusion",[10,1517,1518,1519,1521,1522,1525],{},"Build the workbook in a ",[192,1520,246],{}," buffer and ",[192,1523,1524],{},"put_object"," it to S3 with the spreadsheet content type, test the handler with moto, package pandas for Lambda's platform (154 MB unzipped, inside the 250 MB limit) or drop it for lighter reports, raise memory and timeout well above the defaults, schedule with EventBridge Scheduler in local time, and decide deliberately whether a failed run may be retried.",[180,1527,1529],{"id":1528},"frequently-asked-questions","Frequently asked questions",[10,1531,1532,1535],{},[1171,1533,1534],{},"Can I use pandas in AWS Lambda?","\nYes. pandas, numpy and openpyxl together measured 154 MB unzipped and 44 MB zipped for Python 3.12 on x86_64, inside Lambda's 250 MB unzipped limit. Package them in a layer, use a container image, or use openpyxl or XlsxWriter alone when the report does not need pandas.",[10,1537,1538,1541],{},[1171,1539,1540],{},"How much memory does a Lambda function need for a pandas Excel report?","\nMore than the 128 MB default. Importing pandas, openpyxl and boto3 alone peaked at 129 MB in a local test. Start at 512 MB, which also gives the function more CPU, and adjust from the Max Memory Used figure in the logs.",[10,1543,1544,1547],{},[1171,1545,1546],{},"Where can a Lambda function write an Excel file?","\nOnly under \u002Ftmp, which holds 512 MB by default. It is simpler to build the workbook in a BytesIO buffer and upload the bytes to S3 directly, so nothing is written to disk.",[10,1549,1550,1553],{},[1171,1551,1552],{},"How do I schedule a Lambda function at a local time?","\nUse EventBridge Scheduler with a cron expression such as cron(30 7 ? * MON-FRI *) and a schedule time zone such as Europe\u002FLondon. The run then stays at 07:30 local time across daylight saving changes.",[180,1555,1557],{"id":1556},"related","Related",[1165,1559,1560,1566,1573,1580],{},[1168,1561,1562,1563,1565],{},"Up one level: ",[14,1564,17],{"href":16}," — running reports on a schedule.",[1168,1567,1568,1572],{},[14,1569,1571],{"href":1570},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-a-python-excel-report-in-docker\u002F","Run a Python Excel Report in Docker"," — the container image route, also usable in Lambda.",[1168,1574,1575,1579],{},[14,1576,1578],{"href":1577},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fschedule-an-excel-report-with-systemd-timers\u002F","Schedule an Excel Report with systemd Timers"," — the same job on a server you run.",[1168,1581,1582,1586],{},[14,1583,1585],{"href":1584},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Freduce-memory-when-reading-excel-with-pandas\u002F","Reduce Memory When Reading Excel with pandas"," — keeping memory, and the Lambda bill, down.",[1588,1589,1590],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":190,"searchDepth":263,"depth":263,"links":1592},[1593,1594,1595,1596,1597,1598,1599,1600,1601,1602],{"id":182,"depth":263,"text":183},{"id":235,"depth":263,"text":236},{"id":758,"depth":263,"text":759},{"id":998,"depth":263,"text":999},{"id":1159,"depth":263,"text":1160},{"id":1253,"depth":263,"text":1254},{"id":1464,"depth":263,"text":1465},{"id":1514,"depth":263,"text":1515},{"id":1528,"depth":263,"text":1529},{"id":1556,"depth":263,"text":1557},"2026-09-23","Build an Excel report in an AWS Lambda function and save it to S3 on a schedule — a handler that works in memory, a package that fits Lambda's size limits, memory and timeout settings that suit pandas, an EventBridge Scheduler rule in local time, retries that do not produce duplicate reports, and tests that run without an AWS account.","md",[1607,1609,1611,1613],{"q":1534,"a":1608},"Yes. pandas, numpy and openpyxl together measured 154 MB unzipped and 44 MB zipped for Python 3.12 on x86_64, inside Lambda's 250 MB unzipped limit. Package them in a layer, use a container image, or use openpyxl or XlsxWriter alone when the report does not need pandas.",{"q":1540,"a":1610},"More than the 128 MB default. Importing pandas, openpyxl and boto3 alone peaked at 129 MB in a local test. Start at 512 MB, which also gives the function more CPU, and adjust from the Max Memory Used figure in the logs.",{"q":1546,"a":1612},"Only under \u002Ftmp, which holds 512 MB by default. It is simpler to build the workbook in a BytesIO buffer and upload the bytes to S3 directly, so nothing is written to disk.",{"q":1552,"a":1614},"Use EventBridge Scheduler with a cron expression such as cron(30 7 ? * MON-FRI *) and a schedule time zone such as Europe\u002FLondon. The run then stays at 07:30 local time across daylight saving changes.","run a python excel report in aws lambda",null,{"breadcrumb":1618},[1619,1622,1625],{"name":1620,"item":1621},"Home","\u002F",{"name":1623,"item":1624},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":1626,"item":16},"Scheduling Python Excel Scripts with Cron","\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda",{"title":1629,"description":1630},"Run a Python Excel Report in AWS Lambda (pandas, openpyxl, S3)","Generate an xlsx report in AWS Lambda with Python — BytesIO instead of files, put_object to S3 with the spreadsheet content type, measured package sizes for pandas and openpyxl against the 250 MB limit, memory above the 128 MB default, timeouts, EventBridge Scheduler cron with a time zone, async retry settings and moto tests.","run-an-excel-report-in-aws-lambda","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda\u002Findex",[1634,1635],"scheduling","publishing","how-to","wb_EnfUsjY-JdUpynUe8selnuCf0he-cg45Ag5hKtwk",[1639,1643],{"title":1640,"path":1641,"stem":1642,"children":-1},"Run an Excel Report as a Kubernetes CronJob","\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-as-a-kubernetes-cronjob","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-as-a-kubernetes-cronjob\u002Findex",{"title":1644,"path":1645,"stem":1646,"children":-1},"Run an Excel Report in Azure Functions","\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions\u002Findex",[1648,1650],{"path":1649,"title":1623},"\u002Fautomating-reporting-workflows",{"path":1651,"title":1626},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron",[],[1654,1657,1660,1663,1666,1669],{"path":1655,"title":1656,"kind":1636},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fdownload-new-excel-files-from-an-sftp-server","Download New Excel Files from an SFTP Server",{"path":1658,"title":1659,"kind":1636},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fread-excel-attachments-into-a-database-nightly","Read Excel Attachments into a Database Nightly",{"path":1661,"title":1662,"kind":1636},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Frefresh-an-excel-report-from-a-database-on-a-schedule","Refresh an Excel Report from a Database on a Schedule",{"path":1664,"title":1665,"kind":1636},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive","Run a Report When an Excel File Changes in OneDrive",{"path":1667,"title":1668,"kind":1636},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-report-emails-at-each-recipients-local-time","Send Report Emails at Each Recipient's Local Time",{"path":1670,"title":1671,"kind":1636},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fdetect-a-scheduled-report-that-did-not-run","Detect a Scheduled Report That Did Not Run",1790972138834]