[{"data":1,"prerenderedAt":1744},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions":3,"surround:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions":1710,"ancestors:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions":1719,"children:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions":1724,"related:\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions":1725},{"id":4,"title":5,"body":6,"dateModified":1676,"datePublished":1676,"description":1677,"extension":1678,"faq":1679,"keyword":1688,"kind":1689,"meta":1690,"navigation":315,"path":1699,"seo":1700,"slug":1703,"stem":1704,"topics":1705,"type":1708,"__hash__":1709},"docs\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions\u002Findex.md","Run an Excel Report in Azure Functions",{"type":7,"value":8,"toc":1664},"minimark",[9,24,29,70,96,279,283,286,751,777,781,789,970,987,1095,1099,1110,1311,1319,1323,1337,1372,1387,1391,1402,1534,1538,1586,1590,1599,1603,1609,1615,1621,1627,1631,1660],[10,11,12,13,17,18,23],"p",{},"For organisations on Microsoft Azure, a timer-triggered Azure Function is the lightweight way to run a report on a schedule: no virtual machine to patch, no cron to maintain, billing per execution, and the output written straight into a Storage account where Power BI, Logic Apps or SharePoint can pick it up. The Python v2 programming model keeps everything in one file, ",[14,15,16],"code",{},"function_app.py",", with decorators declaring the trigger and the output. This page, part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002F","Scheduling Python Excel Scripts with Cron",", builds a weekly sales report as a function, tests it without Azure, and covers the limits that decide whether a report fits in a function at all.",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install azure-functions pandas openpyxl croniter\n# to run and deploy: Azure Functions Core Tools and the Azure CLI\n","bash","",[14,37,38,63],{"__ignoreMap":35},[39,40,43,47,51,54,57,60],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," azure-functions",[39,55,56],{"class":49}," pandas",[39,58,59],{"class":49}," openpyxl",[39,61,62],{"class":49}," croniter\n",[39,64,66],{"class":41,"line":65},2,[39,67,69],{"class":68},"s-wDw","# to run and deploy: Azure Functions Core Tools and the Azure CLI\n",[10,71,72,73,75,76,79,80,83,84,87,88,91,92,95],{},"A function app project is a folder with ",[14,74,16],{},", ",[14,77,78],{},"requirements.txt"," listing the packages the function needs, and ",[14,81,82],{},"host.json",". ",[14,85,86],{},"local.settings.json"," holds settings for local runs, including ",[14,89,90],{},"AzureWebJobsStorage",", the storage connection the function uses for its own bookkeeping and — here — for the report output. ",[14,93,94],{},"sales_source.csv"," stands in for the database the real report queries.",[97,98,106,107,106,111,106,115,106,123,106,133,106,144,106,151,106,156,106,160,106,166,106,171,106,175,106,182,106,188,106,192,106,196,106,199,106,202,106,208,106,212,106,216,106,219,106,223,106,226,106,231,106,238,106,243,106,246,106,249,106,253,106,256,106,261,106,268,106,273,106,276],"svg",{"viewBox":99,"role":100,"ariaLabelledBy":101,"xmlns":104,"style":105},"4 0 752 142","img",[102,103],"az-flow-t","az-flow-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  ",[108,109,110],"title",{"id":102},"One scheduled execution",[112,113,114],"desc",{"id":103},"The Functions host fires the timer trigger on the NCRONTAB schedule, the handler reads the source data and builds the workbook in memory, returns the bytes to the blob output binding, and the host writes them to a dated blob.",[116,117],"rect",{"x":118,"y":119,"width":120,"height":121,"fill":122},"4","0","752","142","#ffffff",[116,124],{"x":125,"y":126,"width":127,"height":128,"rx":129,"fill":130,"stroke":131,"style":132},"20.0","16.0","115.2","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[134,135,137],"g",{"transform":136},"translate(65.6,29.0) scale(1.000)",[138,139],"path",{"d":140,"fill":141,"stroke":142,"style":143},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[145,146,150],"text",{"x":147,"y":148,"style":149},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Timer fires",[145,152,155],{"x":147,"y":153,"style":154},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","0 0 7 * * 1",[145,157,159],{"x":147,"y":158,"style":154},"112.5","(UTC)",[41,161],{"x1":162,"y1":163,"x2":164,"y2":163,"stroke":165,"style":132},"140.2","71.2","162.2","var(--brand,#5b5cf0)",[167,168],"polygon",{"points":169,"fill":170},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[116,172],{"x":173,"y":126,"width":127,"height":128,"rx":129,"fill":174,"stroke":165,"style":132},"171.2","#f0f4ff",[134,176,178],{"transform":177},"translate(216.8,37.8) scale(1.000)",[138,179],{"d":180,"fill":141,"stroke":181,"style":143},"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","var(--brand-strong,#4338ca)",[145,183,187],{"x":184,"y":185,"style":186},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Read data",[145,189,191],{"x":184,"y":190,"style":154},"103.8","source query",[41,193],{"x1":194,"y1":163,"x2":195,"y2":163,"stroke":165,"style":132},"291.4","313.4",[167,197],{"points":198,"fill":170},"317.4,71.2 308.4,76.2 308.4,66.2",[116,200],{"x":201,"y":126,"width":127,"height":128,"rx":129,"fill":174,"stroke":165,"style":132},"322.4",[134,203,205],{"transform":204},"translate(368.0,29.0) scale(1.000)",[138,206],{"d":207,"fill":141,"stroke":181,"style":143},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[145,209,211],{"x":210,"y":148,"style":186},"380.0","Build",[145,213,215],{"x":210,"y":214,"style":186},"93.0","workbook",[145,217,218],{"x":210,"y":158,"style":154},"in memory",[41,220],{"x1":221,"y1":163,"x2":222,"y2":163,"stroke":165,"style":132},"442.6","464.6",[167,224],{"points":225,"fill":170},"468.6,71.2 459.6,76.2 459.6,66.2",[116,227],{"x":228,"y":126,"width":127,"height":128,"rx":129,"fill":229,"stroke":230,"style":132},"473.6","#d9f4f1","var(--teal,#0f9488)",[134,232,234],{"transform":233},"translate(519.2,29.0) scale(1.000)",[138,235],{"d":236,"fill":141,"stroke":237,"style":143},"M7 18h10a4 4 0 0 0 .6-8A6 6 0 0 0 6.1 11.4 3.4 3.4 0 0 0 7 18z","var(--teal-ink,#0b6157)",[145,239,242],{"x":240,"y":148,"style":241},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Output",[145,244,245],{"x":240,"y":214,"style":241},"binding",[145,247,248],{"x":240,"y":158,"style":154},"report.set(bytes)",[41,250],{"x1":251,"y1":163,"x2":252,"y2":163,"stroke":165,"style":132},"593.8","615.8",[167,254],{"points":255,"fill":170},"619.8,71.2 610.8,76.2 610.8,66.2",[116,257],{"x":258,"y":126,"width":127,"height":128,"rx":129,"fill":259,"stroke":260,"style":132},"624.8","#fdefd8","var(--gold,#b4740a)",[134,262,264],{"transform":263},"translate(670.4,29.0) scale(1.000)",[138,265],{"d":266,"fill":141,"stroke":267,"style":143},"M6 3h9l4 4v14H6z M15 3v4h4","var(--gold-ink,#7a4e06)",[145,269,272],{"x":270,"y":148,"style":271},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Dated blob",[145,274,275],{"x":270,"y":153,"style":154},"one .xlsx per",[145,277,278],{"x":270,"y":158,"style":154},"run",[25,280,282],{"id":281},"step-1-write-the-function","Step 1 — Write the function",[10,284,285],{},"The report logic lives in an ordinary function; the Azure-specific part is the decorated handler that calls it. The timer trigger declares the schedule and the blob output binding declares where the bytes go:",[30,287,291],{"className":288,"code":289,"language":290,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import io\nimport logging\n\nimport azure.functions as func\nimport pandas as pd\n\napp = func.FunctionApp()\n\ndef build_report() -> bytes:\n    df = pd.read_csv(\"sales_source.csv\")\n    df[\"Variance\"] = df[\"Sales\"] - df[\"Target\"]\n    buf = io.BytesIO()\n    with pd.ExcelWriter(buf, engine=\"openpyxl\") as xw:\n        df.sort_values(\"Variance\").to_excel(xw, sheet_name=\"Weekly sales\", index=False)\n    return buf.getvalue()\n\n@app.retry(strategy=\"exponential_backoff\", max_retry_count=\"3\",\n           minimum_interval=\"00:00:30\", maximum_interval=\"00:05:00\")\n@app.timer_trigger(schedule=\"0 0 7 * * 1\", arg_name=\"timer\", run_on_startup=False, use_monitor=True)\n@app.blob_output(arg_name=\"report\", path=\"reports\u002Fweekly_sales_{DateTime:yyyy-MM-dd}.xlsx\",\n                 connection=\"AzureWebJobsStorage\")\ndef weekly_sales(timer: func.TimerRequest, report: func.Out[bytes]) -> None:\n    if timer.past_due:\n        logging.warning(\"timer is running late\")\n    data = build_report()\n    report.set(data)\n    logging.info(\"weekly sales report written, %d bytes\", len(data))\n","python",[14,292,293,303,310,317,331,344,349,361,366,386,403,436,447,473,504,513,518,548,571,618,657,670,691,700,711,722,728],{"__ignoreMap":35},[39,294,295,299],{"class":41,"line":42},[39,296,298],{"class":297},"s-kum","import",[39,300,302],{"class":301},"skGVy"," io\n",[39,304,305,307],{"class":41,"line":65},[39,306,298],{"class":297},[39,308,309],{"class":301}," logging\n",[39,311,313],{"class":41,"line":312},3,[39,314,316],{"emptyLinePlaceholder":315},true,"\n",[39,318,320,322,325,328],{"class":41,"line":319},4,[39,321,298],{"class":297},[39,323,324],{"class":301}," azure.functions ",[39,326,327],{"class":297},"as",[39,329,330],{"class":301}," func\n",[39,332,334,336,339,341],{"class":41,"line":333},5,[39,335,298],{"class":297},[39,337,338],{"class":301}," pandas ",[39,340,327],{"class":297},[39,342,343],{"class":301}," pd\n",[39,345,347],{"class":41,"line":346},6,[39,348,316],{"emptyLinePlaceholder":315},[39,350,352,355,358],{"class":41,"line":351},7,[39,353,354],{"class":301},"app ",[39,356,357],{"class":297},"=",[39,359,360],{"class":301}," func.FunctionApp()\n",[39,362,364],{"class":41,"line":363},8,[39,365,316],{"emptyLinePlaceholder":315},[39,367,369,372,376,379,383],{"class":41,"line":368},9,[39,370,371],{"class":297},"def",[39,373,375],{"class":374},"s_Opv"," build_report",[39,377,378],{"class":301},"() -> ",[39,380,382],{"class":381},"sP0c6","bytes",[39,384,385],{"class":301},":\n",[39,387,389,392,394,397,400],{"class":41,"line":388},10,[39,390,391],{"class":301},"    df ",[39,393,357],{"class":297},[39,395,396],{"class":301}," pd.read_csv(",[39,398,399],{"class":49},"\"sales_source.csv\"",[39,401,402],{"class":301},")\n",[39,404,406,409,412,415,417,420,423,425,428,430,433],{"class":41,"line":405},11,[39,407,408],{"class":301},"    df[",[39,410,411],{"class":49},"\"Variance\"",[39,413,414],{"class":301},"] ",[39,416,357],{"class":297},[39,418,419],{"class":301}," df[",[39,421,422],{"class":49},"\"Sales\"",[39,424,414],{"class":301},[39,426,427],{"class":297},"-",[39,429,419],{"class":301},[39,431,432],{"class":49},"\"Target\"",[39,434,435],{"class":301},"]\n",[39,437,439,442,444],{"class":41,"line":438},12,[39,440,441],{"class":301},"    buf ",[39,443,357],{"class":297},[39,445,446],{"class":301}," io.BytesIO()\n",[39,448,450,453,456,460,462,465,468,470],{"class":41,"line":449},13,[39,451,452],{"class":297},"    with",[39,454,455],{"class":301}," pd.ExcelWriter(buf, ",[39,457,459],{"class":458},"sa561","engine",[39,461,357],{"class":297},[39,463,464],{"class":49},"\"openpyxl\"",[39,466,467],{"class":301},") ",[39,469,327],{"class":297},[39,471,472],{"class":301}," xw:\n",[39,474,476,479,481,484,487,489,492,494,497,499,502],{"class":41,"line":475},14,[39,477,478],{"class":301},"        df.sort_values(",[39,480,411],{"class":49},[39,482,483],{"class":301},").to_excel(xw, ",[39,485,486],{"class":458},"sheet_name",[39,488,357],{"class":297},[39,490,491],{"class":49},"\"Weekly sales\"",[39,493,75],{"class":301},[39,495,496],{"class":458},"index",[39,498,357],{"class":297},[39,500,501],{"class":381},"False",[39,503,402],{"class":301},[39,505,507,510],{"class":41,"line":506},15,[39,508,509],{"class":297},"    return",[39,511,512],{"class":301}," buf.getvalue()\n",[39,514,516],{"class":41,"line":515},16,[39,517,316],{"emptyLinePlaceholder":315},[39,519,521,524,527,530,532,535,537,540,542,545],{"class":41,"line":520},17,[39,522,523],{"class":374},"@app.retry",[39,525,526],{"class":301},"(",[39,528,529],{"class":458},"strategy",[39,531,357],{"class":297},[39,533,534],{"class":49},"\"exponential_backoff\"",[39,536,75],{"class":301},[39,538,539],{"class":458},"max_retry_count",[39,541,357],{"class":297},[39,543,544],{"class":49},"\"3\"",[39,546,547],{"class":301},",\n",[39,549,551,554,556,559,561,564,566,569],{"class":41,"line":550},18,[39,552,553],{"class":458},"           minimum_interval",[39,555,357],{"class":297},[39,557,558],{"class":49},"\"00:00:30\"",[39,560,75],{"class":301},[39,562,563],{"class":458},"maximum_interval",[39,565,357],{"class":297},[39,567,568],{"class":49},"\"00:05:00\"",[39,570,402],{"class":301},[39,572,574,577,579,582,584,587,589,592,594,597,599,602,604,606,608,611,613,616],{"class":41,"line":573},19,[39,575,576],{"class":374},"@app.timer_trigger",[39,578,526],{"class":301},[39,580,581],{"class":458},"schedule",[39,583,357],{"class":297},[39,585,586],{"class":49},"\"0 0 7 * * 1\"",[39,588,75],{"class":301},[39,590,591],{"class":458},"arg_name",[39,593,357],{"class":297},[39,595,596],{"class":49},"\"timer\"",[39,598,75],{"class":301},[39,600,601],{"class":458},"run_on_startup",[39,603,357],{"class":297},[39,605,501],{"class":381},[39,607,75],{"class":301},[39,609,610],{"class":458},"use_monitor",[39,612,357],{"class":297},[39,614,615],{"class":381},"True",[39,617,402],{"class":301},[39,619,621,624,626,628,630,633,635,637,639,642,646,649,652,655],{"class":41,"line":620},20,[39,622,623],{"class":374},"@app.blob_output",[39,625,526],{"class":301},[39,627,591],{"class":458},[39,629,357],{"class":297},[39,631,632],{"class":49},"\"report\"",[39,634,75],{"class":301},[39,636,138],{"class":458},[39,638,357],{"class":297},[39,640,641],{"class":49},"\"reports\u002Fweekly_sales_",[39,643,645],{"class":644},"sSjpA","{DateTime",[39,647,648],{"class":297},":",[39,650,651],{"class":644},"yyyy-MM-dd}",[39,653,654],{"class":49},".xlsx\"",[39,656,547],{"class":301},[39,658,660,663,665,668],{"class":41,"line":659},21,[39,661,662],{"class":458},"                 connection",[39,664,357],{"class":297},[39,666,667],{"class":49},"\"AzureWebJobsStorage\"",[39,669,402],{"class":301},[39,671,673,675,678,681,683,686,689],{"class":41,"line":672},22,[39,674,371],{"class":297},[39,676,677],{"class":374}," weekly_sales",[39,679,680],{"class":301},"(timer: func.TimerRequest, report: func.Out[",[39,682,382],{"class":381},[39,684,685],{"class":301},"]) -> ",[39,687,688],{"class":381},"None",[39,690,385],{"class":301},[39,692,694,697],{"class":41,"line":693},23,[39,695,696],{"class":297},"    if",[39,698,699],{"class":301}," timer.past_due:\n",[39,701,703,706,709],{"class":41,"line":702},24,[39,704,705],{"class":301},"        logging.warning(",[39,707,708],{"class":49},"\"timer is running late\"",[39,710,402],{"class":301},[39,712,714,717,719],{"class":41,"line":713},25,[39,715,716],{"class":301},"    data ",[39,718,357],{"class":297},[39,720,721],{"class":301}," build_report()\n",[39,723,725],{"class":41,"line":724},26,[39,726,727],{"class":301},"    report.set(data)\n",[39,729,731,734,737,740,743,745,748],{"class":41,"line":730},27,[39,732,733],{"class":301},"    logging.info(",[39,735,736],{"class":49},"\"weekly sales report written, ",[39,738,739],{"class":644},"%d",[39,741,742],{"class":49}," bytes\"",[39,744,75],{"class":301},[39,746,747],{"class":381},"len",[39,749,750],{"class":301},"(data))\n",[10,752,753,754,757,758,761,762,765,766,769,770,773,774,776],{},"The workbook is built in a ",[14,755,756],{},"BytesIO"," buffer and never touches disk: the function's file system is temporary and, on some plans, shared or read-only outside the temp folder, so in-memory output is the reliable choice. ",[14,759,760],{},"report.set(data)"," hands the bytes to the binding, and the host writes them to the blob path — ",[14,763,764],{},"{DateTime:yyyy-MM-dd}"," is a binding expression the host fills in with the current UTC date, giving each run its own file. ",[14,767,768],{},"use_monitor=True"," makes the host record the schedule's status in storage so that a run missed while the app was down is noticed, and ",[14,771,772],{},"timer.past_due"," is ",[14,775,615],{}," when the trigger fires later than planned.",[25,778,780],{"id":779},"step-2-read-the-schedule-correctly","Step 2 — Read the schedule correctly",[10,782,783,784,788],{},"Azure Functions timer schedules are NCRONTAB expressions with ",[785,786,787],"strong",{},"six"," fields — seconds come first. Checking an expression before deploying saves a week of waiting to find out it was wrong:",[30,790,792],{"className":288,"code":791,"language":290,"meta":35,"style":35},"from datetime import datetime\nfrom croniter import croniter\n\ndef next_runs(expr, start, n=3):\n    it = croniter(expr, start, second_at_beginning=True)\n    return [it.get_next(datetime).strftime(\"%a %d %b %H:%M:%S\") for _ in range(n)]\n\nprint(next_runs(\"0 0 7 * * 1\", datetime(2026, 9, 29, 12, 0)))\nprint(next_runs(\"0 30 6 * * 1-5\", datetime(2026, 9, 29, 12, 0)))\n",[14,793,794,807,818,822,840,859,898,902,939],{"__ignoreMap":35},[39,795,796,799,802,804],{"class":41,"line":42},[39,797,798],{"class":297},"from",[39,800,801],{"class":301}," datetime ",[39,803,298],{"class":297},[39,805,806],{"class":301}," datetime\n",[39,808,809,811,814,816],{"class":41,"line":65},[39,810,798],{"class":297},[39,812,813],{"class":301}," croniter ",[39,815,298],{"class":297},[39,817,62],{"class":301},[39,819,820],{"class":41,"line":312},[39,821,316],{"emptyLinePlaceholder":315},[39,823,824,826,829,832,834,837],{"class":41,"line":319},[39,825,371],{"class":297},[39,827,828],{"class":374}," next_runs",[39,830,831],{"class":301},"(expr, start, n",[39,833,357],{"class":297},[39,835,836],{"class":381},"3",[39,838,839],{"class":301},"):\n",[39,841,842,845,847,850,853,855,857],{"class":41,"line":333},[39,843,844],{"class":301},"    it ",[39,846,357],{"class":297},[39,848,849],{"class":301}," croniter(expr, start, ",[39,851,852],{"class":458},"second_at_beginning",[39,854,357],{"class":297},[39,856,615],{"class":381},[39,858,402],{"class":301},[39,860,861,863,866,869,872,875,878,881,883,886,889,892,895],{"class":41,"line":346},[39,862,509],{"class":297},[39,864,865],{"class":301}," [it.get_next(datetime).strftime(",[39,867,868],{"class":49},"\"",[39,870,871],{"class":644},"%a",[39,873,874],{"class":644}," %d",[39,876,877],{"class":644}," %b",[39,879,880],{"class":49}," %H:%M:%S\"",[39,882,467],{"class":301},[39,884,885],{"class":297},"for",[39,887,888],{"class":301}," _ ",[39,890,891],{"class":297},"in",[39,893,894],{"class":381}," range",[39,896,897],{"class":301},"(n)]\n",[39,899,900],{"class":41,"line":351},[39,901,316],{"emptyLinePlaceholder":315},[39,903,904,907,910,912,915,918,920,923,925,928,930,932,934,936],{"class":41,"line":363},[39,905,906],{"class":381},"print",[39,908,909],{"class":301},"(next_runs(",[39,911,586],{"class":49},[39,913,914],{"class":301},", datetime(",[39,916,917],{"class":381},"2026",[39,919,75],{"class":301},[39,921,922],{"class":381},"9",[39,924,75],{"class":301},[39,926,927],{"class":381},"29",[39,929,75],{"class":301},[39,931,129],{"class":381},[39,933,75],{"class":301},[39,935,119],{"class":381},[39,937,938],{"class":301},")))\n",[39,940,941,943,945,948,950,952,954,956,958,960,962,964,966,968],{"class":41,"line":368},[39,942,906],{"class":381},[39,944,909],{"class":301},[39,946,947],{"class":49},"\"0 30 6 * * 1-5\"",[39,949,914],{"class":301},[39,951,917],{"class":381},[39,953,75],{"class":301},[39,955,922],{"class":381},[39,957,75],{"class":301},[39,959,927],{"class":381},[39,961,75],{"class":301},[39,963,129],{"class":381},[39,965,75],{"class":301},[39,967,119],{"class":381},[39,969,938],{"class":301},[10,971,972,974,975,978,979,982,983,986],{},[14,973,155],{}," is 07:00:00 every Monday; ",[14,976,977],{},"0 30 6 * * 1-5"," is 06:30 on weekdays. A five-field expression copied from a Linux crontab does not mean the same thing in a six-field format — the positions no longer line up with seconds, minutes and hours — so always write all six fields and check them as above rather than relying on how the host interprets a shorter line. The croniter check is cheap insurance: ",[14,980,981],{},"second_at_beginning=True"," tells it the seconds field comes first, exactly as in Azure. Schedules are evaluated in UTC by default; the ",[14,984,985],{},"WEBSITE_TIME_ZONE"," app setting changes that on the plans that support it, and without it a 07:00 UTC run is 08:00 in London in summer and 07:00 in winter.",[97,988,106,994,106,997,106,1000,106,1005,106,1011,106,1017,106,1021,106,1025,106,1029,106,1033,106,1037,106,1041,106,1045,106,1049,106,1053,106,1057,106,1060,106,1064,106,1067,106,1070,106,1072,106,1076,106,1078,106,1081,106,1083,106,1085,106,1087,106,1090],{"viewBox":989,"role":100,"ariaLabelledBy":990,"xmlns":104,"style":993},"83 0 594 114",[991,992],"az-ncrontab-t","az-ncrontab-d","width:100%;max-width:594px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[108,995,996],{"id":991},"The six NCRONTAB fields",[112,998,999],{"id":992},"A table of the six fields of an Azure Functions timer schedule — second, minute, hour, day of month, month and day of week — with the values of the weekly Monday 07:00 schedule.",[116,1001],{"x":1002,"y":119,"width":1003,"height":1004,"fill":122},"83","594","114",[116,1006],{"x":1007,"y":126,"width":1008,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"190.5","69.5","28.0","stroke-width:1.5px",[145,1012,1016],{"x":1013,"y":1014,"style":1015},"225.2","35.0","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","second",[116,1018],{"x":1019,"y":126,"width":1020,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"260.0","66.7",[145,1022,1024],{"x":1023,"y":1014,"style":1015},"293.3","minute",[116,1026],{"x":1027,"y":126,"width":1028,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"326.7","52.6",[145,1030,1032],{"x":1031,"y":1014,"style":1015},"353.0","hour",[116,1034],{"x":1035,"y":126,"width":1036,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"379.3","46.8",[145,1038,1040],{"x":1039,"y":1014,"style":1015},"402.7","day",[116,1042],{"x":1043,"y":126,"width":1044,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"426.1","63.5",[145,1046,1048],{"x":1047,"y":1014,"style":1015},"457.9","month",[116,1050],{"x":1051,"y":126,"width":1052,"height":1009,"rx":119,"fill":174,"stroke":165,"style":1010},"489.6","79.9",[145,1054,1056],{"x":1055,"y":1014,"style":1015},"529.6","weekday",[116,1058],{"x":1007,"y":1059,"width":1008,"height":1009,"rx":119,"fill":259,"stroke":260,"style":1010},"44.0",[145,1061,119],{"x":1013,"y":1062,"style":1063},"63.0","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[116,1065],{"x":1019,"y":1059,"width":1020,"height":1009,"rx":119,"fill":122,"stroke":131,"style":1066},"stroke-width:1px",[145,1068,119],{"x":1023,"y":1062,"style":1069},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[116,1071],{"x":1027,"y":1059,"width":1028,"height":1009,"rx":119,"fill":229,"stroke":230,"style":1010},[145,1073,1075],{"x":1031,"y":1062,"style":1074},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","7",[116,1077],{"x":1035,"y":1059,"width":1036,"height":1009,"rx":119,"fill":122,"stroke":131,"style":1066},[145,1079,1080],{"x":1039,"y":1062,"style":1069},"*",[116,1082],{"x":1043,"y":1059,"width":1044,"height":1009,"rx":119,"fill":122,"stroke":131,"style":1066},[145,1084,1080],{"x":1047,"y":1062,"style":1069},[116,1086],{"x":1051,"y":1059,"width":1052,"height":1009,"rx":119,"fill":229,"stroke":230,"style":1010},[145,1088,1089],{"x":1055,"y":1062,"style":1074},"1",[145,1091,1094],{"x":210,"y":1092,"style":1093},"94.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","The seconds field comes first — a five-field Linux cron line means something else entirely.",[25,1096,1098],{"id":1097},"step-3-test-the-handler-and-its-bindings-locally","Step 3 — Test the handler and its bindings locally",[10,1100,1101,1102,1105,1106,1109],{},"The decorated function is registered on the ",[14,1103,1104],{},"FunctionApp"," object, which can be inspected without the Functions host: its trigger and binding definitions are exactly what the host will read, and the handler itself can be called with a real ",[14,1107,1108],{},"TimerRequest"," and a stand-in for the output binding:",[30,1111,1113],{"className":288,"code":1112,"language":290,"meta":35,"style":35},"import io\nimport json\nimport importlib\nfrom azure.functions.timer import TimerRequest\n\nmodule = importlib.import_module(\"function_app\")\nfn = module.app.get_functions()[0]\nprint(fn.get_function_name())\nprint(json.dumps(json.loads(fn.get_function_json())[\"bindings\"], indent=None))\nprint([s.get_dict_repr() for s in fn._settings])\n\nclass CapturedOutput:\n    def set(self, value):\n        self.value = value\n\nout = CapturedOutput()\nfn.get_user_function()(TimerRequest(past_due=True), out)\nprint(pd.read_excel(io.BytesIO(out.value))[[\"Region\", \"Variance\"]].values.tolist())\n",[14,1114,1115,1121,1128,1135,1147,1151,1166,1180,1187,1210,1227,1231,1241,1252,1265,1269,1279,1294],{"__ignoreMap":35},[39,1116,1117,1119],{"class":41,"line":42},[39,1118,298],{"class":297},[39,1120,302],{"class":301},[39,1122,1123,1125],{"class":41,"line":65},[39,1124,298],{"class":297},[39,1126,1127],{"class":301}," json\n",[39,1129,1130,1132],{"class":41,"line":312},[39,1131,298],{"class":297},[39,1133,1134],{"class":301}," importlib\n",[39,1136,1137,1139,1142,1144],{"class":41,"line":319},[39,1138,798],{"class":297},[39,1140,1141],{"class":301}," azure.functions.timer ",[39,1143,298],{"class":297},[39,1145,1146],{"class":301}," TimerRequest\n",[39,1148,1149],{"class":41,"line":333},[39,1150,316],{"emptyLinePlaceholder":315},[39,1152,1153,1156,1158,1161,1164],{"class":41,"line":346},[39,1154,1155],{"class":301},"module ",[39,1157,357],{"class":297},[39,1159,1160],{"class":301}," importlib.import_module(",[39,1162,1163],{"class":49},"\"function_app\"",[39,1165,402],{"class":301},[39,1167,1168,1171,1173,1176,1178],{"class":41,"line":351},[39,1169,1170],{"class":301},"fn ",[39,1172,357],{"class":297},[39,1174,1175],{"class":301}," module.app.get_functions()[",[39,1177,119],{"class":381},[39,1179,435],{"class":301},[39,1181,1182,1184],{"class":41,"line":363},[39,1183,906],{"class":381},[39,1185,1186],{"class":301},"(fn.get_function_name())\n",[39,1188,1189,1191,1194,1197,1200,1203,1205,1207],{"class":41,"line":368},[39,1190,906],{"class":381},[39,1192,1193],{"class":301},"(json.dumps(json.loads(fn.get_function_json())[",[39,1195,1196],{"class":49},"\"bindings\"",[39,1198,1199],{"class":301},"], ",[39,1201,1202],{"class":458},"indent",[39,1204,357],{"class":297},[39,1206,688],{"class":381},[39,1208,1209],{"class":301},"))\n",[39,1211,1212,1214,1217,1219,1222,1224],{"class":41,"line":388},[39,1213,906],{"class":381},[39,1215,1216],{"class":301},"([s.get_dict_repr() ",[39,1218,885],{"class":297},[39,1220,1221],{"class":301}," s ",[39,1223,891],{"class":297},[39,1225,1226],{"class":301}," fn._settings])\n",[39,1228,1229],{"class":41,"line":405},[39,1230,316],{"emptyLinePlaceholder":315},[39,1232,1233,1236,1239],{"class":41,"line":438},[39,1234,1235],{"class":297},"class",[39,1237,1238],{"class":45}," CapturedOutput",[39,1240,385],{"class":301},[39,1242,1243,1246,1249],{"class":41,"line":449},[39,1244,1245],{"class":297},"    def",[39,1247,1248],{"class":381}," set",[39,1250,1251],{"class":301},"(self, value):\n",[39,1253,1254,1257,1260,1262],{"class":41,"line":475},[39,1255,1256],{"class":381},"        self",[39,1258,1259],{"class":301},".value ",[39,1261,357],{"class":297},[39,1263,1264],{"class":301}," value\n",[39,1266,1267],{"class":41,"line":506},[39,1268,316],{"emptyLinePlaceholder":315},[39,1270,1271,1274,1276],{"class":41,"line":515},[39,1272,1273],{"class":301},"out ",[39,1275,357],{"class":297},[39,1277,1278],{"class":301}," CapturedOutput()\n",[39,1280,1281,1284,1287,1289,1291],{"class":41,"line":520},[39,1282,1283],{"class":301},"fn.get_user_function()(TimerRequest(",[39,1285,1286],{"class":458},"past_due",[39,1288,357],{"class":297},[39,1290,615],{"class":381},[39,1292,1293],{"class":301},"), out)\n",[39,1295,1296,1298,1301,1304,1306,1308],{"class":41,"line":550},[39,1297,906],{"class":381},[39,1299,1300],{"class":301},"(pd.read_excel(io.BytesIO(out.value))[[",[39,1302,1303],{"class":49},"\"Region\"",[39,1305,75],{"class":301},[39,1307,411],{"class":49},[39,1309,1310],{"class":301},"]].values.tolist())\n",[10,1312,1313,1314,1318],{},"The printed bindings show the timer with its schedule and monitor flag and the blob output with its dated path; the retry policy appears as a function setting. Calling the handler builds the real workbook, and reading the captured bytes back shows the regions sorted by variance, West first. A test like this in CI catches the mistakes that otherwise only show up after deployment — a wrong binding name, a typo in the path expression, a schedule with five fields. ",[19,1315,1317],{"href":1316},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Ftest-excel-output-with-pytest\u002F","Test Excel Output with pytest"," shows how to turn it into a proper test.",[25,1320,1322],{"id":1321},"step-4-run-locally-and-deploy","Step 4 — Run locally and deploy",[10,1324,1325,1326,1329,1330,1332,1333,1336],{},"With Core Tools installed, ",[14,1327,1328],{},"func start"," runs the app on your machine against the storage in ",[14,1331,86],{}," — usually the Azurite emulator — and triggers timers on schedule; ",[14,1334,1335],{},"run_on_startup=True"," temporarily makes it run immediately for testing. Deploy with Core Tools or the Azure CLI:",[30,1338,1340],{"className":32,"code":1339,"language":34,"meta":35,"style":35},"func start                                           # local run\nfunc azure functionapp publish acme-reports-func     # deploy the project folder\n",[14,1341,1342,1353],{"__ignoreMap":35},[39,1343,1344,1347,1350],{"class":41,"line":42},[39,1345,1346],{"class":45},"func",[39,1348,1349],{"class":49}," start",[39,1351,1352],{"class":68},"                                           # local run\n",[39,1354,1355,1357,1360,1363,1366,1369],{"class":41,"line":65},[39,1356,1346],{"class":45},[39,1358,1359],{"class":49}," azure",[39,1361,1362],{"class":49}," functionapp",[39,1364,1365],{"class":49}," publish",[39,1367,1368],{"class":49}," acme-reports-func",[39,1370,1371],{"class":68},"     # deploy the project folder\n",[10,1373,1374,1375,1377,1378,1381,1382,1386],{},"On Azure, ",[14,1376,90],{}," is an app setting pointing at the Storage account; the report blobs land in its ",[14,1379,1380],{},"reports"," container. Configure the app with a managed identity and identity-based connections rather than connection strings with account keys where your organisation's policies call for it — ",[19,1383,1385],{"href":1384},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-azure-blob-storage\u002F","Upload an Excel Report to Azure Blob Storage"," covers the storage side.",[25,1388,1390],{"id":1389},"limits-that-decide-whether-a-report-fits","Limits that decide whether a report fits",[10,1392,1393,1394,1398,1399,1401],{},"Functions are meant for short tasks. On the classic Consumption plan an execution times out after five minutes by default and ten at most; Flex Consumption, Premium and Dedicated plans allow much longer runs. Memory is limited too. A report that queries a database, aggregates a few hundred thousand rows and writes a workbook fits comfortably; one that loads millions of rows into pandas does not, and belongs in a container job instead — see ",[19,1395,1397],{"href":1396},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-a-python-excel-report-in-docker\u002F","Run a Python Excel Report in Docker",". Cold starts add a few seconds to the first run after a quiet period, which does not matter for a weekly job. pandas and openpyxl themselves take a moment to import, so keep heavy optional libraries out of ",[14,1400,78],{}," unless the report needs them.",[97,1403,106,1408,106,1411,106,1414,106,1417,106,1422,106,1429,106,1435,106,1440,106,1446,106,1453,106,1457,106,1462,106,1466,106,1471,106,1475,106,1482,106,1485,106,1491,106,1496,106,1500,106,1504,106,1509,106,1512,106,1517,106,1520,106,1525,106,1528],{"viewBox":1404,"role":100,"ariaLabelledBy":1405,"xmlns":104,"style":105},"4 0 752 233",[1406,1407],"az-fit-t","az-fit-d",[108,1409,1410],{"id":1406},"Function or container job?",[112,1412,1413],{"id":1407},"Two panels. A timer function suits short reports that finish in minutes with modest memory. A container job suits long-running reports, large data volumes or heavy dependencies such as LibreOffice.",[116,1415],{"x":118,"y":119,"width":120,"height":1416,"fill":122},"233",[116,1418],{"x":125,"y":126,"width":1419,"height":1420,"rx":1421,"fill":229,"stroke":230,"style":132},"351.0","201.0","14",[134,1423,1425],{"transform":1424},"translate(182.5,29.0) scale(1.083)",[138,1426],{"d":1427,"fill":141,"stroke":237,"style":1428},"M13 2L4 14h7l-1 8 9-12h-7z","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[145,1430,1434],{"x":1431,"y":1432,"style":1433},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Timer-triggered function",[41,1436],{"x1":1437,"y1":1438,"x2":1439,"y2":1438,"stroke":230,"style":1066},"36.0","91.0","355.0",[145,1441,1445],{"x":1442,"y":1443,"style":1444},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","minutes, not hours",[134,1447,1449],{"transform":1448},"translate(38.5,99.5) scale(0.625)",[138,1450],{"d":1451,"fill":141,"stroke":230,"style":1452},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[145,1454,1456],{"x":1442,"y":1455,"style":1444},"137.0","modest data in memory",[134,1458,1460],{"transform":1459},"translate(38.5,125.5) scale(0.625)",[138,1461],{"d":1451,"fill":141,"stroke":230,"style":1452},[145,1463,1465],{"x":1442,"y":1464,"style":1444},"163.0","bindings write the output",[134,1467,1469],{"transform":1468},"translate(38.5,151.5) scale(0.625)",[138,1470],{"d":1451,"fill":141,"stroke":230,"style":1452},[145,1472,1474],{"x":1442,"y":1473,"style":1444},"189.0","Consumption: 10-minute cap",[134,1476,1478],{"transform":1477},"translate(38.5,177.5) scale(0.625)",[138,1479],{"d":1480,"fill":141,"stroke":1481,"style":1452},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[116,1483],{"x":1484,"y":126,"width":1419,"height":1420,"rx":1421,"fill":174,"stroke":165,"style":132},"389.0",[134,1486,1488],{"transform":1487},"translate(551.5,29.0) scale(1.083)",[138,1489],{"d":1490,"fill":141,"stroke":181,"style":1428},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6",[145,1492,1495],{"x":1493,"y":1432,"style":1494},"564.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Container job",[41,1497],{"x1":1498,"y1":1438,"x2":1499,"y2":1438,"stroke":165,"style":1066},"405.0","724.0",[145,1501,1503],{"x":1502,"y":1443,"style":1444},"429.0","long runs",[134,1505,1507],{"transform":1506},"translate(407.5,99.5) scale(0.625)",[138,1508],{"d":1451,"fill":141,"stroke":230,"style":1452},[145,1510,1511],{"x":1502,"y":1455,"style":1444},"large data volumes",[134,1513,1515],{"transform":1514},"translate(407.5,125.5) scale(0.625)",[138,1516],{"d":1451,"fill":141,"stroke":230,"style":1452},[145,1518,1519],{"x":1502,"y":1464,"style":1444},"heavy dependencies like LibreOffice",[134,1521,1523],{"transform":1522},"translate(407.5,151.5) scale(0.625)",[138,1524],{"d":1451,"fill":141,"stroke":230,"style":1452},[145,1526,1527],{"x":1502,"y":1473,"style":1444},"more to set up",[134,1529,1531],{"transform":1530},"translate(407.5,177.5) scale(0.625)",[138,1532],{"d":1533,"fill":141,"stroke":260,"style":1452},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[25,1535,1537],{"id":1536},"common-pitfalls","Common pitfalls",[1539,1540,1541,1555,1561,1571,1577],"ul",{},[1542,1543,1544,1547,1548,1550,1551,1554],"li",{},[785,1545,1546],{},"Five-field cron expressions."," Azure timers use six fields with seconds first; write ",[14,1549,155],{},", not the Linux-style ",[14,1552,1553],{},"0 7 * * 1",".",[1542,1556,1557,1560],{},[785,1558,1559],{},"Writing the workbook to disk."," Build it in memory and hand the bytes to the binding.",[1542,1562,1563,1566,1567,1570],{},[785,1564,1565],{},"Forgetting UTC."," Schedules and ",[14,1568,1569],{},"{DateTime}"," are UTC unless the time zone is configured.",[1542,1572,1573,1576],{},[785,1574,1575],{},"Long reports on Consumption."," Anything near the ten-minute cap needs a different plan or a container job.",[1542,1578,1579,1582,1583,1585],{},[785,1580,1581],{},"Heavy packages."," Every dependency in ",[14,1584,78],{}," is installed at deployment and loaded at cold start; keep the list short.",[25,1587,1589],{"id":1588},"conclusion","Conclusion",[10,1591,1592,1593,1595,1596,1598],{},"Put the report logic in a plain function that returns workbook bytes, and wrap it in a v2 handler with ",[14,1594,576],{}," on a six-field NCRONTAB schedule and ",[14,1597,623],{}," writing to a dated path. Add a retry policy, check the schedule with croniter, and test the registered bindings and the handler locally before deploying with Core Tools. Keep reports short enough for the plan's time limit, and move larger ones to a container job.",[25,1600,1602],{"id":1601},"frequently-asked-questions","Frequently asked questions",[10,1604,1605,1608],{},[785,1606,1607],{},"How do I schedule a Python script in Azure Functions?","\nCreate a function app with a timer trigger: in the Python v2 model, decorate a function with @app.timer_trigger(schedule=\"0 0 7 * * 1\", arg_name=\"timer\"). The schedule is an NCRONTAB expression with six fields, starting with seconds.",[10,1610,1611,1614],{},[785,1612,1613],{},"Why does my Azure timer trigger run at the wrong time?","\nUsually either the expression does not have the six fields Azure expects, with seconds first, or the schedule is being evaluated in UTC while you expected local time. Use six fields with seconds first and set WEBSITE_TIME_ZONE where supported if you need local time.",[10,1616,1617,1620],{},[785,1618,1619],{},"How do I save an Excel file from an Azure Function?","\nBuild the workbook in a BytesIO buffer and pass the bytes to a blob output binding with report.set(data), or upload them with the Azure Storage SDK. Avoid relying on the function's local file system.",[10,1622,1623,1626],{},[785,1624,1625],{},"How long can an Azure Function run?","\nOn the Consumption plan the default timeout is five minutes and the maximum ten. Flex Consumption, Premium and Dedicated plans allow longer executions.",[25,1628,1630],{"id":1629},"related","Related",[1539,1632,1633,1639,1646,1653],{},[1542,1634,1635,1636,1638],{},"Up one level: ",[19,1637,22],{"href":21}," — every way to run reports on a schedule.",[1542,1640,1641,1645],{},[19,1642,1644],{"href":1643},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda\u002F","Run an Excel Report in AWS Lambda"," — the AWS equivalent.",[1542,1647,1648,1652],{},[19,1649,1651],{"href":1650},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-microsoft-graph-api\u002F","Send an Excel Report with the Microsoft Graph API"," — emailing the result from Azure.",[1542,1654,1655,1659],{},[19,1656,1658],{"href":1657},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fchoose-a-scheduler-for-python-excel-reports\u002F","Choose a Scheduler for Python Excel Reports"," — when a function is the right choice.",[1661,1662,1663],"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 pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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-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":35,"searchDepth":65,"depth":65,"links":1665},[1666,1667,1668,1669,1670,1671,1672,1673,1674,1675],{"id":27,"depth":65,"text":28},{"id":281,"depth":65,"text":282},{"id":779,"depth":65,"text":780},{"id":1097,"depth":65,"text":1098},{"id":1321,"depth":65,"text":1322},{"id":1389,"depth":65,"text":1390},{"id":1536,"depth":65,"text":1537},{"id":1588,"depth":65,"text":1589},{"id":1601,"depth":65,"text":1602},{"id":1629,"depth":65,"text":1630},"2026-09-29","Schedule a Python Excel report as an Azure Function: a timer trigger with a six-field NCRONTAB schedule, the workbook built in memory and written to Blob Storage through an output binding, a retry policy, time zones and execution time limits, local testing of the handler and its bindings, and deployment with Core Tools.","md",[1680,1682,1684,1686],{"q":1607,"a":1681},"Create a function app with a timer trigger: in the Python v2 model, decorate a function with @app.timer_trigger(schedule=\"0 0 7 * * 1\", arg_name=\"timer\"). The schedule is an NCRONTAB expression with six fields, starting with seconds.",{"q":1613,"a":1683},"Usually either the expression does not have the six fields Azure expects, with seconds first, or the schedule is being evaluated in UTC while you expected local time. Use six fields with seconds first and set WEBSITE_TIME_ZONE where supported if you need local time.",{"q":1619,"a":1685},"Build the workbook in a BytesIO buffer and pass the bytes to a blob output binding with report.set(data), or upload them with the Azure Storage SDK. Avoid relying on the function's local file system.",{"q":1625,"a":1687},"On the Consumption plan the default timeout is five minutes and the maximum ten. Flex Consumption, Premium and Dedicated plans allow longer executions.","run an excel report in azure functions",null,{"breadcrumb":1691},[1692,1695,1698],{"name":1693,"item":1694},"Home","\u002F",{"name":1696,"item":1697},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions",{"title":1701,"description":1702},"Schedule a Python Excel Report with Azure Functions Timer Trigger","Run an Excel report on Azure Functions (Python v2 model): timer_trigger with NCRONTAB seconds field, blob_output binding with {DateTime} paths, BytesIO workbooks, retry policies, UTC schedules, Consumption plan time limits and testing bindings locally.","run-an-excel-report-in-azure-functions","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-azure-functions\u002Findex",[1706,1707],"scheduling","publishing","how-to","j3REeTjaoX3Qw5HngYDzb-bovxXg3J1LflJIJ--4kow",[1711,1715],{"title":1712,"path":1713,"stem":1714,"children":-1},"Run a Python Excel Report in AWS Lambda","\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-in-aws-lambda\u002Findex",{"title":1716,"path":1717,"stem":1718,"children":-1},"Run an Excel Report on Google Cloud Run Jobs","\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-on-google-cloud-run-jobs","automating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-an-excel-report-on-google-cloud-run-jobs\u002Findex",[1720,1722],{"path":1721,"title":1696},"\u002Fautomating-reporting-workflows",{"path":1723,"title":22},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron",[],[1726,1729,1732,1735,1738,1741],{"path":1727,"title":1728,"kind":1708},"\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":1730,"title":1731,"kind":1708},"\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":1733,"title":1734,"kind":1708},"\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":1736,"title":1737,"kind":1708},"\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":1739,"title":1740,"kind":1708},"\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":1742,"title":1743,"kind":1708},"\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",1790972138856]