[{"data":1,"prerenderedAt":1663},["ShallowReactive",2],{"page-data-\u002Fposts\u002Fchanging-code-almost-without-editing-it-the-magic-of-decorators\u002F":3},{"id":4,"title":5,"body":6,"created":1650,"description":12,"excerpt":1651,"extension":1655,"meta":1656,"navigation":134,"path":1657,"seo":1658,"stem":1659,"tags":1660,"updated":1661,"__hash__":1662},"posts\u002Fposts\u002Fchanging-code-almost-without-editing-it-the-magic-of-decorators.md","Changing code - almost - without editing it: the magic of decorators",{"type":7,"value":8,"toc":1641},"minimark",[9,13,18,29,32,35,38,42,56,65,786,797,810,814,817,824,1122,1125,1128,1133,1136,1139,1147,1150,1153,1157,1165,1171,1176,1179,1302,1305,1313,1316,1320,1323,1330,1521,1528,1535,1554,1560,1583,1586,1589,1605,1609,1612,1616,1637],[10,11,12],"p",{},"As a Data Engineer, one of the common tasks is to support the Data Science team.\nSometimes you are required to customize one of the products they use daily.\nHow to make these customizations the least invasive, so that you can easily update it?",[14,15,17],"h2",{"id":16},"problem","Problem",[10,19,20,21,28],{},"Let's say your data science team is using ",[22,23,27],"a",{"href":24,"rel":25},"http:\u002F\u002Fgethue.com\u002F",[26],"nofollow","Hue"," to perform data exploration activities on the datasets in your data lake.\nThey find the dataset they want, they understand how to extract the information they want from it, so they create a wonderful query and want to save it to a file, for example to schedule it.",[10,30,31],{},"Here comes the pain:",[10,33,34],{},"Queries are saved in the internal Hue database, there is no option to store queries as files somewhere else (e.g. S3 or the local filesystem).",[10,36,37],{},"It is true that one could simply copy-paste the query in a local sql file and version it using git or any other SVC, but that's not the best user experience, especially for someone that is not super technical.",[14,39,41],{"id":40},"exploring-the-hue-codebase","Exploring the Hue codebase",[10,43,44,45,49,50,55],{},"You put aside your ",[46,47,48],"em",{},"data engineer-y"," skills and start digging into the Hue codebase, trying to find the code that saves queries in the hue database. Eventually you find that it is in the ",[22,51,54],{"href":52,"rel":53},"https:\u002F\u002Fgithub.com\u002Fcloudera\u002Fhue\u002Ftree\u002Fmaster\u002Fdesktop\u002Flibs\u002Fnotebook\u002Fsrc\u002Fnotebook",[26],"notebook"," library.",[10,57,58,59,64],{},"After some further digging you discover how Hue ",[22,60,63],{"href":61,"rel":62},"https:\u002F\u002Fgithub.com\u002Fcloudera\u002Fhue\u002Fblob\u002Fbranch-4.4\u002Fdesktop\u002Flibs\u002Fnotebook\u002Fsrc\u002Fnotebook\u002Fapi.py#L361-L376",[26],"saves a notebook",":",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-python shiki shiki-themes github-light github-dark","def _save_notebook(notebook, user):\n  notebook_type = notebook.get('type', 'notebook')\n  save_as = False\n\n  if notebook.get('parentSavedQueryUuid'): # We save into the original saved query, not into the query history\n    notebook_doc = Document2.objects.get_by_uuid(user=user, uuid=notebook['parentSavedQueryUuid'])\n  elif notebook.get('id'):\n    notebook_doc = Document2.objects.get(id=notebook['id'])\n  else:\n    notebook_doc = Document2.objects.create(name=notebook['name'], uuid=notebook['uuid'], type=notebook_type, owner=user)\n    Document.objects.link(notebook_doc, owner=notebook_doc.owner, name=notebook_doc.name, description=notebook_doc.description, extra=notebook_type)\n    save_as = True\n\n    if notebook.get('directoryUuid'):\n      notebook_doc.parent_directory = Document2.objects.get_by_uuid(user=user, uuid=notebook.get('directoryUuid'), perm_type='write')\n    else:\n      notebook_doc.parent_directory = Document2.objects.get_home_directory(user)\n\n  notebook['isSaved'] = True\n  notebook['isHistory'] = False\n  notebook['id'] = notebook_doc.id\n  _clear_sessions(notebook)\n  notebook_doc1 = notebook_doc._get_doc1(doc2_type=notebook_type)\n  notebook_doc.update_data(notebook)\n  notebook_doc.search = _get_statement(notebook)\n  notebook_doc.name = notebook_doc1.name = notebook['name']\n  notebook_doc.description = notebook_doc1.description = notebook['description']\n  notebook_doc.save()\n  notebook_doc1.save()\n\n  return notebook_doc, save_as\n\n\n@api_error_handler\n@require_POST\n@check_document_modify_permission()\ndef save_notebook(request):\n  response = {'status': -1}\n\n  notebook = json.loads(request.POST.get('notebook', '{}'))\n\n  notebook_doc, save_as = _save_notebook(notebook, request.user)\n\n  response['status'] = 0\n  response['save_as'] = save_as\n  response.update(notebook_doc.to_dict())\n  response['message'] = request.POST.get('editorMode') == 'true' and _('Query saved successfully') or _('Notebook saved successfully')\n\n  return JsonResponse(response)\n","python","",[73,74,75,92,117,129,136,154,187,201,222,231,281,317,328,333,346,384,392,402,407,423,437,451,457,475,481,492,513,533,539,545,550,559,564,569,575,581,590,601,627,632,664,669,680,685,700,715,721,773,778],"code",{"__ignoreMap":71},[76,77,80,84,88],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"szBVR","def",[76,85,87],{"class":86},"sScJk"," _save_notebook",[76,89,91],{"class":90},"sVt8B","(notebook, user):\n",[76,93,95,98,101,104,108,111,114],{"class":78,"line":94},2,[76,96,97],{"class":90},"  notebook_type ",[76,99,100],{"class":82},"=",[76,102,103],{"class":90}," notebook.get(",[76,105,107],{"class":106},"sZZnC","'type'",[76,109,110],{"class":90},", ",[76,112,113],{"class":106},"'notebook'",[76,115,116],{"class":90},")\n",[76,118,120,123,125],{"class":78,"line":119},3,[76,121,122],{"class":90},"  save_as ",[76,124,100],{"class":82},[76,126,128],{"class":127},"sj4cs"," False\n",[76,130,132],{"class":78,"line":131},4,[76,133,135],{"emptyLinePlaceholder":134},true,"\n",[76,137,139,142,144,147,150],{"class":78,"line":138},5,[76,140,141],{"class":82},"  if",[76,143,103],{"class":90},[76,145,146],{"class":106},"'parentSavedQueryUuid'",[76,148,149],{"class":90},"): ",[76,151,153],{"class":152},"sJ8bj","# We save into the original saved query, not into the query history\n",[76,155,157,160,162,165,169,171,174,177,179,182,184],{"class":78,"line":156},6,[76,158,159],{"class":90},"    notebook_doc ",[76,161,100],{"class":82},[76,163,164],{"class":90}," Document2.objects.get_by_uuid(",[76,166,168],{"class":167},"s4XuR","user",[76,170,100],{"class":82},[76,172,173],{"class":90},"user, ",[76,175,176],{"class":167},"uuid",[76,178,100],{"class":82},[76,180,181],{"class":90},"notebook[",[76,183,146],{"class":106},[76,185,186],{"class":90},"])\n",[76,188,190,193,195,198],{"class":78,"line":189},7,[76,191,192],{"class":82},"  elif",[76,194,103],{"class":90},[76,196,197],{"class":106},"'id'",[76,199,200],{"class":90},"):\n",[76,202,204,206,208,211,214,216,218,220],{"class":78,"line":203},8,[76,205,159],{"class":90},[76,207,100],{"class":82},[76,209,210],{"class":90}," Document2.objects.get(",[76,212,213],{"class":167},"id",[76,215,100],{"class":82},[76,217,181],{"class":90},[76,219,197],{"class":106},[76,221,186],{"class":90},[76,223,225,228],{"class":78,"line":224},9,[76,226,227],{"class":82},"  else",[76,229,230],{"class":90},":\n",[76,232,234,236,238,241,244,246,248,251,254,256,258,260,263,265,268,270,273,276,278],{"class":78,"line":233},10,[76,235,159],{"class":90},[76,237,100],{"class":82},[76,239,240],{"class":90}," Document2.objects.create(",[76,242,243],{"class":167},"name",[76,245,100],{"class":82},[76,247,181],{"class":90},[76,249,250],{"class":106},"'name'",[76,252,253],{"class":90},"], ",[76,255,176],{"class":167},[76,257,100],{"class":82},[76,259,181],{"class":90},[76,261,262],{"class":106},"'uuid'",[76,264,253],{"class":90},[76,266,267],{"class":167},"type",[76,269,100],{"class":82},[76,271,272],{"class":90},"notebook_type, ",[76,274,275],{"class":167},"owner",[76,277,100],{"class":82},[76,279,280],{"class":90},"user)\n",[76,282,284,287,289,291,294,296,298,301,304,306,309,312,314],{"class":78,"line":283},11,[76,285,286],{"class":90},"    Document.objects.link(notebook_doc, ",[76,288,275],{"class":167},[76,290,100],{"class":82},[76,292,293],{"class":90},"notebook_doc.owner, ",[76,295,243],{"class":167},[76,297,100],{"class":82},[76,299,300],{"class":90},"notebook_doc.name, ",[76,302,303],{"class":167},"description",[76,305,100],{"class":82},[76,307,308],{"class":90},"notebook_doc.description, ",[76,310,311],{"class":167},"extra",[76,313,100],{"class":82},[76,315,316],{"class":90},"notebook_type)\n",[76,318,320,323,325],{"class":78,"line":319},12,[76,321,322],{"class":90},"    save_as ",[76,324,100],{"class":82},[76,326,327],{"class":127}," True\n",[76,329,331],{"class":78,"line":330},13,[76,332,135],{"emptyLinePlaceholder":134},[76,334,336,339,341,344],{"class":78,"line":335},14,[76,337,338],{"class":82},"    if",[76,340,103],{"class":90},[76,342,343],{"class":106},"'directoryUuid'",[76,345,200],{"class":90},[76,347,349,352,354,356,358,360,362,364,366,369,371,374,377,379,382],{"class":78,"line":348},15,[76,350,351],{"class":90},"      notebook_doc.parent_directory ",[76,353,100],{"class":82},[76,355,164],{"class":90},[76,357,168],{"class":167},[76,359,100],{"class":82},[76,361,173],{"class":90},[76,363,176],{"class":167},[76,365,100],{"class":82},[76,367,368],{"class":90},"notebook.get(",[76,370,343],{"class":106},[76,372,373],{"class":90},"), ",[76,375,376],{"class":167},"perm_type",[76,378,100],{"class":82},[76,380,381],{"class":106},"'write'",[76,383,116],{"class":90},[76,385,387,390],{"class":78,"line":386},16,[76,388,389],{"class":82},"    else",[76,391,230],{"class":90},[76,393,395,397,399],{"class":78,"line":394},17,[76,396,351],{"class":90},[76,398,100],{"class":82},[76,400,401],{"class":90}," Document2.objects.get_home_directory(user)\n",[76,403,405],{"class":78,"line":404},18,[76,406,135],{"emptyLinePlaceholder":134},[76,408,410,413,416,419,421],{"class":78,"line":409},19,[76,411,412],{"class":90},"  notebook[",[76,414,415],{"class":106},"'isSaved'",[76,417,418],{"class":90},"] ",[76,420,100],{"class":82},[76,422,327],{"class":127},[76,424,426,428,431,433,435],{"class":78,"line":425},20,[76,427,412],{"class":90},[76,429,430],{"class":106},"'isHistory'",[76,432,418],{"class":90},[76,434,100],{"class":82},[76,436,128],{"class":127},[76,438,440,442,444,446,448],{"class":78,"line":439},21,[76,441,412],{"class":90},[76,443,197],{"class":106},[76,445,418],{"class":90},[76,447,100],{"class":82},[76,449,450],{"class":90}," notebook_doc.id\n",[76,452,454],{"class":78,"line":453},22,[76,455,456],{"class":90},"  _clear_sessions(notebook)\n",[76,458,460,463,465,468,471,473],{"class":78,"line":459},23,[76,461,462],{"class":90},"  notebook_doc1 ",[76,464,100],{"class":82},[76,466,467],{"class":90}," notebook_doc._get_doc1(",[76,469,470],{"class":167},"doc2_type",[76,472,100],{"class":82},[76,474,316],{"class":90},[76,476,478],{"class":78,"line":477},24,[76,479,480],{"class":90},"  notebook_doc.update_data(notebook)\n",[76,482,484,487,489],{"class":78,"line":483},25,[76,485,486],{"class":90},"  notebook_doc.search ",[76,488,100],{"class":82},[76,490,491],{"class":90}," _get_statement(notebook)\n",[76,493,495,498,500,503,505,508,510],{"class":78,"line":494},26,[76,496,497],{"class":90},"  notebook_doc.name ",[76,499,100],{"class":82},[76,501,502],{"class":90}," notebook_doc1.name ",[76,504,100],{"class":82},[76,506,507],{"class":90}," notebook[",[76,509,250],{"class":106},[76,511,512],{"class":90},"]\n",[76,514,516,519,521,524,526,528,531],{"class":78,"line":515},27,[76,517,518],{"class":90},"  notebook_doc.description ",[76,520,100],{"class":82},[76,522,523],{"class":90}," notebook_doc1.description ",[76,525,100],{"class":82},[76,527,507],{"class":90},[76,529,530],{"class":106},"'description'",[76,532,512],{"class":90},[76,534,536],{"class":78,"line":535},28,[76,537,538],{"class":90},"  notebook_doc.save()\n",[76,540,542],{"class":78,"line":541},29,[76,543,544],{"class":90},"  notebook_doc1.save()\n",[76,546,548],{"class":78,"line":547},30,[76,549,135],{"emptyLinePlaceholder":134},[76,551,553,556],{"class":78,"line":552},31,[76,554,555],{"class":82},"  return",[76,557,558],{"class":90}," notebook_doc, save_as\n",[76,560,562],{"class":78,"line":561},32,[76,563,135],{"emptyLinePlaceholder":134},[76,565,567],{"class":78,"line":566},33,[76,568,135],{"emptyLinePlaceholder":134},[76,570,572],{"class":78,"line":571},34,[76,573,574],{"class":86},"@api_error_handler\n",[76,576,578],{"class":78,"line":577},35,[76,579,580],{"class":86},"@require_POST\n",[76,582,584,587],{"class":78,"line":583},36,[76,585,586],{"class":86},"@check_document_modify_permission",[76,588,589],{"class":90},"()\n",[76,591,593,595,598],{"class":78,"line":592},37,[76,594,83],{"class":82},[76,596,597],{"class":86}," save_notebook",[76,599,600],{"class":90},"(request):\n",[76,602,604,607,609,612,615,618,621,624],{"class":78,"line":603},38,[76,605,606],{"class":90},"  response ",[76,608,100],{"class":82},[76,610,611],{"class":90}," {",[76,613,614],{"class":106},"'status'",[76,616,617],{"class":90},": ",[76,619,620],{"class":82},"-",[76,622,623],{"class":127},"1",[76,625,626],{"class":90},"}\n",[76,628,630],{"class":78,"line":629},39,[76,631,135],{"emptyLinePlaceholder":134},[76,633,635,638,640,643,646,649,651,653,656,659,661],{"class":78,"line":634},40,[76,636,637],{"class":90},"  notebook ",[76,639,100],{"class":82},[76,641,642],{"class":90}," json.loads(request.",[76,644,645],{"class":127},"POST",[76,647,648],{"class":90},".get(",[76,650,113],{"class":106},[76,652,110],{"class":90},[76,654,655],{"class":106},"'",[76,657,658],{"class":127},"{}",[76,660,655],{"class":106},[76,662,663],{"class":90},"))\n",[76,665,667],{"class":78,"line":666},41,[76,668,135],{"emptyLinePlaceholder":134},[76,670,672,675,677],{"class":78,"line":671},42,[76,673,674],{"class":90},"  notebook_doc, save_as ",[76,676,100],{"class":82},[76,678,679],{"class":90}," _save_notebook(notebook, request.user)\n",[76,681,683],{"class":78,"line":682},43,[76,684,135],{"emptyLinePlaceholder":134},[76,686,688,691,693,695,697],{"class":78,"line":687},44,[76,689,690],{"class":90},"  response[",[76,692,614],{"class":106},[76,694,418],{"class":90},[76,696,100],{"class":82},[76,698,699],{"class":127}," 0\n",[76,701,703,705,708,710,712],{"class":78,"line":702},45,[76,704,690],{"class":90},[76,706,707],{"class":106},"'save_as'",[76,709,418],{"class":90},[76,711,100],{"class":82},[76,713,714],{"class":90}," save_as\n",[76,716,718],{"class":78,"line":717},46,[76,719,720],{"class":90},"  response.update(notebook_doc.to_dict())\n",[76,722,724,726,729,731,733,736,738,740,743,746,749,752,755,758,761,763,766,768,771],{"class":78,"line":723},47,[76,725,690],{"class":90},[76,727,728],{"class":106},"'message'",[76,730,418],{"class":90},[76,732,100],{"class":82},[76,734,735],{"class":90}," request.",[76,737,645],{"class":127},[76,739,648],{"class":90},[76,741,742],{"class":106},"'editorMode'",[76,744,745],{"class":90},") ",[76,747,748],{"class":82},"==",[76,750,751],{"class":106}," 'true'",[76,753,754],{"class":82}," and",[76,756,757],{"class":90}," _(",[76,759,760],{"class":106},"'Query saved successfully'",[76,762,745],{"class":90},[76,764,765],{"class":82},"or",[76,767,757],{"class":90},[76,769,770],{"class":106},"'Notebook saved successfully'",[76,772,116],{"class":90},[76,774,776],{"class":78,"line":775},48,[76,777,135],{"emptyLinePlaceholder":134},[76,779,781,783],{"class":78,"line":780},49,[76,782,555],{"class":82},[76,784,785],{"class":90}," JsonResponse(response)\n",[10,787,788,789,792,793,796],{},"Interesting, you observe what Hue is passing in the ",[73,790,791],{},"request"," and discover that the ",[73,794,795],{},"_save_notebook"," method contains all the data you need to save the query to a file:",[798,799,800,804,807],"ul",{},[801,802,803],"li",{},"the name selected when saving the query",[801,805,806],{},"the query",[801,808,809],{},"the query type",[14,811,813],{"id":812},"time-to-customize-hue","Time to customize Hue",[10,815,816],{},"Now that you feel like a hacker because you discovered how Hue works under the hood, you are tempted to change the function code by adding some lines to save your query to a local file.",[10,818,819,820,64],{},"Something like this ",[821,822,823],"strong",{},"(This code is untested and doesn't validate the notebook object before using its properties, don't use it in production!!!)",[66,825,827],{"className":68,"code":826,"language":70,"meta":71,"style":71},"@api_error_handler\n@require_POST\n@check_document_modify_permission()\ndef save_notebook(request):\n  response = {'status': -1}\n\n  notebook = json.loads(request.POST.get('notebook', '{}'))\n\n  notebook_doc, save_as = _save_notebook(notebook, request.user)\n\n  # Save query to a local file\n  query_name = notebook['name'].replace(' ', '_')\n  query_type = notebook['type'] # e.g. 'hive-query'\n  destination = '\u002Fpath\u002Fto\u002Fqueries\u002F{}\u002F{}\u002F{}.sql'.format(request.user, query_type, query_name)\n  output_file = open(destination, 'w')\n  output_file.write(str(notebook['snippets'][0]['raw_statement']))\n  output_file.close()\n\n\n  response['status'] = 0\n  response['save_as'] = save_as\n  response.update(notebook_doc.to_dict())\n  response['message'] = request.POST.get('editorMode') == 'true' and _('Query saved successfully') or _('Notebook saved successfully')\n\n  return JsonResponse(response)\n",[73,828,829,833,837,843,851,869,873,897,901,909,913,918,942,958,985,1003,1031,1036,1040,1044,1056,1068,1072,1112,1116],{"__ignoreMap":71},[76,830,831],{"class":78,"line":79},[76,832,574],{"class":86},[76,834,835],{"class":78,"line":94},[76,836,580],{"class":86},[76,838,839,841],{"class":78,"line":119},[76,840,586],{"class":86},[76,842,589],{"class":90},[76,844,845,847,849],{"class":78,"line":131},[76,846,83],{"class":82},[76,848,597],{"class":86},[76,850,600],{"class":90},[76,852,853,855,857,859,861,863,865,867],{"class":78,"line":138},[76,854,606],{"class":90},[76,856,100],{"class":82},[76,858,611],{"class":90},[76,860,614],{"class":106},[76,862,617],{"class":90},[76,864,620],{"class":82},[76,866,623],{"class":127},[76,868,626],{"class":90},[76,870,871],{"class":78,"line":156},[76,872,135],{"emptyLinePlaceholder":134},[76,874,875,877,879,881,883,885,887,889,891,893,895],{"class":78,"line":189},[76,876,637],{"class":90},[76,878,100],{"class":82},[76,880,642],{"class":90},[76,882,645],{"class":127},[76,884,648],{"class":90},[76,886,113],{"class":106},[76,888,110],{"class":90},[76,890,655],{"class":106},[76,892,658],{"class":127},[76,894,655],{"class":106},[76,896,663],{"class":90},[76,898,899],{"class":78,"line":203},[76,900,135],{"emptyLinePlaceholder":134},[76,902,903,905,907],{"class":78,"line":224},[76,904,674],{"class":90},[76,906,100],{"class":82},[76,908,679],{"class":90},[76,910,911],{"class":78,"line":233},[76,912,135],{"emptyLinePlaceholder":134},[76,914,915],{"class":78,"line":283},[76,916,917],{"class":152},"  # Save query to a local file\n",[76,919,920,923,925,927,929,932,935,937,940],{"class":78,"line":319},[76,921,922],{"class":90},"  query_name ",[76,924,100],{"class":82},[76,926,507],{"class":90},[76,928,250],{"class":106},[76,930,931],{"class":90},"].replace(",[76,933,934],{"class":106},"' '",[76,936,110],{"class":90},[76,938,939],{"class":106},"'_'",[76,941,116],{"class":90},[76,943,944,947,949,951,953,955],{"class":78,"line":330},[76,945,946],{"class":90},"  query_type ",[76,948,100],{"class":82},[76,950,507],{"class":90},[76,952,107],{"class":106},[76,954,418],{"class":90},[76,956,957],{"class":152},"# e.g. 'hive-query'\n",[76,959,960,963,965,968,970,973,975,977,979,982],{"class":78,"line":335},[76,961,962],{"class":90},"  destination ",[76,964,100],{"class":82},[76,966,967],{"class":106}," '\u002Fpath\u002Fto\u002Fqueries\u002F",[76,969,658],{"class":127},[76,971,972],{"class":106},"\u002F",[76,974,658],{"class":127},[76,976,972],{"class":106},[76,978,658],{"class":127},[76,980,981],{"class":106},".sql'",[76,983,984],{"class":90},".format(request.user, query_type, query_name)\n",[76,986,987,990,992,995,998,1001],{"class":78,"line":348},[76,988,989],{"class":90},"  output_file ",[76,991,100],{"class":82},[76,993,994],{"class":127}," open",[76,996,997],{"class":90},"(destination, ",[76,999,1000],{"class":106},"'w'",[76,1002,116],{"class":90},[76,1004,1005,1008,1011,1014,1017,1020,1023,1025,1028],{"class":78,"line":386},[76,1006,1007],{"class":90},"  output_file.write(",[76,1009,1010],{"class":127},"str",[76,1012,1013],{"class":90},"(notebook[",[76,1015,1016],{"class":106},"'snippets'",[76,1018,1019],{"class":90},"][",[76,1021,1022],{"class":127},"0",[76,1024,1019],{"class":90},[76,1026,1027],{"class":106},"'raw_statement'",[76,1029,1030],{"class":90},"]))\n",[76,1032,1033],{"class":78,"line":394},[76,1034,1035],{"class":90},"  output_file.close()\n",[76,1037,1038],{"class":78,"line":404},[76,1039,135],{"emptyLinePlaceholder":134},[76,1041,1042],{"class":78,"line":409},[76,1043,135],{"emptyLinePlaceholder":134},[76,1045,1046,1048,1050,1052,1054],{"class":78,"line":425},[76,1047,690],{"class":90},[76,1049,614],{"class":106},[76,1051,418],{"class":90},[76,1053,100],{"class":82},[76,1055,699],{"class":127},[76,1057,1058,1060,1062,1064,1066],{"class":78,"line":439},[76,1059,690],{"class":90},[76,1061,707],{"class":106},[76,1063,418],{"class":90},[76,1065,100],{"class":82},[76,1067,714],{"class":90},[76,1069,1070],{"class":78,"line":453},[76,1071,720],{"class":90},[76,1073,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110],{"class":78,"line":459},[76,1075,690],{"class":90},[76,1077,728],{"class":106},[76,1079,418],{"class":90},[76,1081,100],{"class":82},[76,1083,735],{"class":90},[76,1085,645],{"class":127},[76,1087,648],{"class":90},[76,1089,742],{"class":106},[76,1091,745],{"class":90},[76,1093,748],{"class":82},[76,1095,751],{"class":106},[76,1097,754],{"class":82},[76,1099,757],{"class":90},[76,1101,760],{"class":106},[76,1103,745],{"class":90},[76,1105,765],{"class":82},[76,1107,757],{"class":90},[76,1109,770],{"class":106},[76,1111,116],{"class":90},[76,1113,1114],{"class":78,"line":477},[76,1115,135],{"emptyLinePlaceholder":134},[76,1117,1118,1120],{"class":78,"line":483},[76,1119,555],{"class":82},[76,1121,785],{"class":90},[10,1123,1124],{},"This code saves your query to a local file, so you can reference it from external tools.",[10,1126,1127],{},"Good job team, let's go grab some coffee.",[10,1129,1130],{},[821,1131,1132],{},"Not that fast!",[10,1134,1135],{},"The Product Owner of your team rushes into the office and tells you that you need to update to the latest Hue version that has just been released.",[10,1137,1138],{},"It's time to:",[798,1140,1141,1144],{},[801,1142,1143],{},"roll the update",[801,1145,1146],{},"re-apply your customization, plus eventually any other customizations that have been done to Hue.",[10,1148,1149],{},"For some reason you cannot make a Pull Request to the Hue project and you have to maintain this customization on your own.",[10,1151,1152],{},"Wouldn't it be great if there was a way to re-apply your customization to any latest version of Hue?",[14,1154,1156],{"id":1155},"enter-python-decorators","Enter Python Decorators",[10,1158,1159,1160],{},"According to ",[22,1161,1164],{"href":1162,"rel":1163},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPython_syntax_and_semantics#Decorators",[26],"Wikipedia",[1166,1167,1168],"blockquote",{},[10,1169,1170],{},"A decorator is any callable Python object that is used to modify a function, method or class definition.",[1166,1172,1173],{},[10,1174,1175],{},"A decorator is passed the original object being defined and returns a modified object, which is then bound to the name in the definition.",[10,1177,1178],{},"Decorators are an easy way to extend the behavior of a function by doing something before or after the function is called:",[66,1180,1182],{"className":68,"code":1181,"language":70,"meta":71,"style":71},"def my_decorator(function_to_decorate):\n    def decoration_function(*args, **kwargs):\n        print('Before function call')\n        function_to_decorate(*args, **kwargs)\n        print('After function call')\n    return decoration_function\n\n@my_decorator\ndef decorable_function():\n    print('Inside function call')\n\ndecorable_function()\n",[73,1183,1184,1194,1217,1229,1243,1254,1262,1266,1271,1281,1293,1297],{"__ignoreMap":71},[76,1185,1186,1188,1191],{"class":78,"line":79},[76,1187,83],{"class":82},[76,1189,1190],{"class":86}," my_decorator",[76,1192,1193],{"class":90},"(function_to_decorate):\n",[76,1195,1196,1199,1202,1205,1208,1211,1214],{"class":78,"line":94},[76,1197,1198],{"class":82},"    def",[76,1200,1201],{"class":86}," decoration_function",[76,1203,1204],{"class":90},"(",[76,1206,1207],{"class":82},"*",[76,1209,1210],{"class":90},"args, ",[76,1212,1213],{"class":82},"**",[76,1215,1216],{"class":90},"kwargs):\n",[76,1218,1219,1222,1224,1227],{"class":78,"line":119},[76,1220,1221],{"class":127},"        print",[76,1223,1204],{"class":90},[76,1225,1226],{"class":106},"'Before function call'",[76,1228,116],{"class":90},[76,1230,1231,1234,1236,1238,1240],{"class":78,"line":131},[76,1232,1233],{"class":90},"        function_to_decorate(",[76,1235,1207],{"class":82},[76,1237,1210],{"class":90},[76,1239,1213],{"class":82},[76,1241,1242],{"class":90},"kwargs)\n",[76,1244,1245,1247,1249,1252],{"class":78,"line":138},[76,1246,1221],{"class":127},[76,1248,1204],{"class":90},[76,1250,1251],{"class":106},"'After function call'",[76,1253,116],{"class":90},[76,1255,1256,1259],{"class":78,"line":156},[76,1257,1258],{"class":82},"    return",[76,1260,1261],{"class":90}," decoration_function\n",[76,1263,1264],{"class":78,"line":189},[76,1265,135],{"emptyLinePlaceholder":134},[76,1267,1268],{"class":78,"line":203},[76,1269,1270],{"class":86},"@my_decorator\n",[76,1272,1273,1275,1278],{"class":78,"line":224},[76,1274,83],{"class":82},[76,1276,1277],{"class":86}," decorable_function",[76,1279,1280],{"class":90},"():\n",[76,1282,1283,1286,1288,1291],{"class":78,"line":233},[76,1284,1285],{"class":127},"    print",[76,1287,1204],{"class":90},[76,1289,1290],{"class":106},"'Inside function call'",[76,1292,116],{"class":90},[76,1294,1295],{"class":78,"line":283},[76,1296,135],{"emptyLinePlaceholder":134},[76,1298,1299],{"class":78,"line":319},[76,1300,1301],{"class":90},"decorable_function()\n",[10,1303,1304],{},"Executing the following code will produce the output:",[66,1306,1311],{"className":1307,"code":1309,"language":1310,"meta":71},[1308],"language-text","Before function call\nInside function call\nAfter function call\n","text",[73,1312,1309],{"__ignoreMap":71},[10,1314,1315],{},"With this powerful Python feature, we could save our query locally after it has been stored in the Hue database without raising any Exception.",[14,1317,1319],{"id":1318},"time-to-decorate","Time to decorate!",[10,1321,1322],{},"Knowing all these informations, we can write a decorator that takes our notebook object and saves the query to a local file.",[10,1324,1325,1326,1329],{},"Therefore we create a ",[821,1327,1328],{},"query_decorators.py"," file:",[66,1331,1333],{"className":68,"code":1332,"language":70,"meta":71,"style":71},"def save_query_locally(function_to_decorate):\n    def decoration_function(*args, **kwargs):\n        # Before function call\n        function_to_decorate(*args, **kwargs)\n        # After function call\n        # Save query to a local file\n        notebook = args[0]\n        user = args[1]\n        query_name = notebook['name'].replace(' ', '_')\n        query_type = notebook['type'] # e.g. 'hive-query'\n        destination = '\u002Fpath\u002Fto\u002Fqueries\u002F{}\u002F{}\u002F{}.sql'.format(user, query_type, query_name)\n        output_file = open(destination, 'w')\n        output_file.write(str(notebook['snippets'][0]['raw_statement']))\n        output_file.close()\n    return decoration_function\n",[73,1334,1335,1344,1360,1365,1377,1382,1387,1401,1414,1435,1450,1474,1489,1510,1515],{"__ignoreMap":71},[76,1336,1337,1339,1342],{"class":78,"line":79},[76,1338,83],{"class":82},[76,1340,1341],{"class":86}," save_query_locally",[76,1343,1193],{"class":90},[76,1345,1346,1348,1350,1352,1354,1356,1358],{"class":78,"line":94},[76,1347,1198],{"class":82},[76,1349,1201],{"class":86},[76,1351,1204],{"class":90},[76,1353,1207],{"class":82},[76,1355,1210],{"class":90},[76,1357,1213],{"class":82},[76,1359,1216],{"class":90},[76,1361,1362],{"class":78,"line":119},[76,1363,1364],{"class":152},"        # Before function call\n",[76,1366,1367,1369,1371,1373,1375],{"class":78,"line":131},[76,1368,1233],{"class":90},[76,1370,1207],{"class":82},[76,1372,1210],{"class":90},[76,1374,1213],{"class":82},[76,1376,1242],{"class":90},[76,1378,1379],{"class":78,"line":138},[76,1380,1381],{"class":152},"        # After function call\n",[76,1383,1384],{"class":78,"line":156},[76,1385,1386],{"class":152},"        # Save query to a local file\n",[76,1388,1389,1392,1394,1397,1399],{"class":78,"line":189},[76,1390,1391],{"class":90},"        notebook ",[76,1393,100],{"class":82},[76,1395,1396],{"class":90}," args[",[76,1398,1022],{"class":127},[76,1400,512],{"class":90},[76,1402,1403,1406,1408,1410,1412],{"class":78,"line":203},[76,1404,1405],{"class":90},"        user ",[76,1407,100],{"class":82},[76,1409,1396],{"class":90},[76,1411,623],{"class":127},[76,1413,512],{"class":90},[76,1415,1416,1419,1421,1423,1425,1427,1429,1431,1433],{"class":78,"line":224},[76,1417,1418],{"class":90},"        query_name ",[76,1420,100],{"class":82},[76,1422,507],{"class":90},[76,1424,250],{"class":106},[76,1426,931],{"class":90},[76,1428,934],{"class":106},[76,1430,110],{"class":90},[76,1432,939],{"class":106},[76,1434,116],{"class":90},[76,1436,1437,1440,1442,1444,1446,1448],{"class":78,"line":233},[76,1438,1439],{"class":90},"        query_type ",[76,1441,100],{"class":82},[76,1443,507],{"class":90},[76,1445,107],{"class":106},[76,1447,418],{"class":90},[76,1449,957],{"class":152},[76,1451,1452,1455,1457,1459,1461,1463,1465,1467,1469,1471],{"class":78,"line":283},[76,1453,1454],{"class":90},"        destination ",[76,1456,100],{"class":82},[76,1458,967],{"class":106},[76,1460,658],{"class":127},[76,1462,972],{"class":106},[76,1464,658],{"class":127},[76,1466,972],{"class":106},[76,1468,658],{"class":127},[76,1470,981],{"class":106},[76,1472,1473],{"class":90},".format(user, query_type, query_name)\n",[76,1475,1476,1479,1481,1483,1485,1487],{"class":78,"line":319},[76,1477,1478],{"class":90},"        output_file ",[76,1480,100],{"class":82},[76,1482,994],{"class":127},[76,1484,997],{"class":90},[76,1486,1000],{"class":106},[76,1488,116],{"class":90},[76,1490,1491,1494,1496,1498,1500,1502,1504,1506,1508],{"class":78,"line":330},[76,1492,1493],{"class":90},"        output_file.write(",[76,1495,1010],{"class":127},[76,1497,1013],{"class":90},[76,1499,1016],{"class":106},[76,1501,1019],{"class":90},[76,1503,1022],{"class":127},[76,1505,1019],{"class":90},[76,1507,1027],{"class":106},[76,1509,1030],{"class":90},[76,1511,1512],{"class":78,"line":335},[76,1513,1514],{"class":90},"        output_file.close()\n",[76,1516,1517,1519],{"class":78,"line":348},[76,1518,1258],{"class":82},[76,1520,1261],{"class":90},[10,1522,1523,1524,1527],{},"We copy this file inside the ",[73,1525,1526],{},"desktop\u002Flibs\u002Fnotebook\u002Fsrc\u002Fnotebook\u002F"," directory of the Hue installation.",[10,1529,1530,1531,1534],{},"And in the ",[73,1532,1533],{},"api.py"," file, first we import the newly created decorator by adding a new import statement",[66,1536,1538],{"className":68,"code":1537,"language":70,"meta":71,"style":71},"from notebook.query_decorators import save_query_locally\n",[73,1539,1540],{"__ignoreMap":71},[76,1541,1542,1545,1548,1551],{"class":78,"line":79},[76,1543,1544],{"class":82},"from",[76,1546,1547],{"class":90}," notebook.query_decorators ",[76,1549,1550],{"class":82},"import",[76,1552,1553],{"class":90}," save_query_locally\n",[10,1555,1556,1557,1559],{},"we then decorate the ",[73,1558,795],{}," function:",[66,1561,1563],{"className":68,"code":1562,"language":70,"meta":71,"style":71},"@save_query_locally\ndef _save_notebook(notebook, user):\n    # ...\n",[73,1564,1565,1570,1578],{"__ignoreMap":71},[76,1566,1567],{"class":78,"line":79},[76,1568,1569],{"class":86},"@save_query_locally\n",[76,1571,1572,1574,1576],{"class":78,"line":94},[76,1573,83],{"class":82},[76,1575,87],{"class":86},[76,1577,91],{"class":90},[76,1579,1580],{"class":78,"line":119},[76,1581,1582],{"class":152},"    # ...\n",[10,1584,1585],{},"Everytime a user saves a query, the new decorator will also take care of saving the query to a local file.",[10,1587,1588],{},"If needed the decorator can even go further by pushing the query to automatically version it, imagination is the limit.",[1166,1590,1591],{},[10,1592,1593,1596,1597,1600,1601,1604],{},[821,1594,1595],{},"N.B."," All these steps can be automated, even by a simple bash script, for example by using ",[73,1598,1599],{},"cp"," or ",[73,1602,1603],{},"sed"," commands.",[14,1606,1608],{"id":1607},"conclusions","Conclusions",[10,1610,1611],{},"Decorators are quite a powerful Python feature that allows to achieve a lot of goals when writing your code, including extending product features without heavily modifying codebases that you cannot directly change or maintain.",[14,1613,1615],{"id":1614},"useful-resources","Useful Resources",[10,1617,1618,1619,1624,1625,1630,1631,1636],{},"If you want to learn more about decorators, you could see ",[22,1620,1623],{"href":1621,"rel":1622},"https:\u002F\u002Fwww.python.org\u002Fdev\u002Fpeps\u002Fpep-0318\u002F",[26],"PEP 318"," and the ",[22,1626,1629],{"href":1627,"rel":1628},"https:\u002F\u002Fwiki.python.org\u002Fmoin\u002FPythonDecorators",[26],"Python Decorator Wiki",", they both contain historical discussions and guidelines on how decorators should be implemented. You can find some examples of decorators in the ",[22,1632,1635],{"href":1633,"rel":1634},"https:\u002F\u002Fwiki.python.org\u002Fmoin\u002FPythonDecoratorLibrary",[26],"Python Decorator Library",".",[1638,1639,1640],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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);}",{"title":71,"searchDepth":94,"depth":94,"links":1642},[1643,1644,1645,1646,1647,1648,1649],{"id":16,"depth":94,"text":17},{"id":40,"depth":94,"text":41},{"id":812,"depth":94,"text":813},{"id":1155,"depth":94,"text":1156},{"id":1318,"depth":94,"text":1319},{"id":1607,"depth":94,"text":1608},{"id":1614,"depth":94,"text":1615},"2019-09-29",{"type":7,"value":1652},[1653],[10,1654,12],{},"md",{},"\u002Fposts\u002Fchanging-code-almost-without-editing-it-the-magic-of-decorators",{"title":5,"description":12},"posts\u002Fchanging-code-almost-without-editing-it-the-magic-of-decorators",[70],null,"aXyrhLIx7s9_ePLx13NALeJNQnUhsLBGcem9wtCI3gg",1788801177195]