{"id":2212,"date":"2017-11-14T10:55:30","date_gmt":"2017-11-14T14:55:30","guid":{"rendered":"https:\/\/www.danielpradilla.info\/blog\/?p=2212"},"modified":"2017-11-10T18:26:40","modified_gmt":"2017-11-10T22:26:40","slug":"linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn","status":"publish","type":"post","link":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/","title":{"rendered":"Linear Optimization with or-tools \u00e2\u20ac\u201d building a web front-end with falcon and gunicorn"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2216\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/groceries\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries.jpg\" data-orig-size=\"1024,683\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"groceries\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries-1024x683.jpg\" class=\"aligncenter size-full wp-image-2216\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries.jpg\" alt=\"\" width=\"1024\" height=\"683\" srcset=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries.jpg 1024w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries-300x200.jpg 300w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceries-768x512.jpg 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>In a <a href=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools\/\">previous post<\/a>, I put together a script for solving a linear optimisation problem using <a href=\"https:\/\/developers.google.com\/optimization\/\" target=\"_blank\" rel=\"noopener\">Google&#8217;s OR-tools<\/a>. This python script is callable from the command line and you kinda need to know what you are doing and how to organize the parameters.<\/p>\n<p>So, in order to address this difficulty, I wanted to build a web front-end for this script, so that regular humans can interact with it.<\/p>\n<p>We can generalize this problem as <strong>how to build a web interface for a python script<\/strong>.<\/p>\n<p>First of all, we can split the concerns. What we need is a web page that serves as a user interface, and a web API connecting the webpage to the python backend.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2213\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/gunicorn1\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1.jpg\" data-orig-size=\"1597,238\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}\" data-image-title=\"gunicorn1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1-1024x153.jpg\" class=\"aligncenter wp-image-2213 size-full\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1.jpg\" alt=\"\" width=\"1597\" height=\"238\" srcset=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1.jpg 1597w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1-300x45.jpg 300w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1-768x114.jpg 768w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn1-1024x153.jpg 1024w\" sizes=\"auto, (max-width: 1597px) 100vw, 1597px\" \/><\/p>\n<p>Ideally, this API should be as light as possible. After all, the heavy lifting is going to be performed by the backend. Using Django would be easy but also overkill. I was looking for one of these microframeworks, you know, like Flask. And that&#8217;s how I got to <a href=\"https:\/\/falconframework.org\/\" target=\"_blank\" rel=\"noopener\">Falcon<\/a>.<\/p>\n<p>Falcon is like 10 times faster than Flask, it is as bare bones as you can get insomuch as you need to bring your own <a href=\"https:\/\/en.wikipedia.org\/wiki\/Web_Server_Gateway_Interface\" target=\"_blank\" rel=\"noopener\">WSGI server<\/a>, like <a href=\"http:\/\/gunicorn.org\/\">gunicorn<\/a> (but you can use <a href=\"https:\/\/docs.pylonsproject.org\/projects\/waitress\/en\/latest\/\">Waitress<\/a>\u00a0in Windows, or <a href=\"http:\/\/uwsgi-docs.readthedocs.io\/en\/latest\/\">uWSGI<\/a>).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2214\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/gunicorn2\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2.jpg\" data-orig-size=\"1614,261\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}\" data-image-title=\"gunicorn2\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2-1024x166.jpg\" class=\"aligncenter size-full wp-image-2214\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2.jpg\" alt=\"\" width=\"1614\" height=\"261\" srcset=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2.jpg 1614w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2-300x49.jpg 300w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2-768x124.jpg 768w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/gunicorn2-1024x166.jpg 1024w\" sizes=\"auto, (max-width: 1614px) 100vw, 1614px\" \/><\/p>\n<p>&nbsp;<\/p>\n<h2>TL;DR<\/h2>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/tree\/master\/www\/interview_grocery_startup\">Get the code here<\/a><\/p>\n<p>&nbsp;<\/p>\n<h2>1 Installing the dependencies<\/h2>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\npip install falcon cython gunicorn\r\n<\/pre>\n<h2>2 Creating a JSON output for the script<\/h2>\n<p>My plan was to use JSON to exchange data between the API and the webpage. So I needed a JSON response builder. I could add this functionality to the previously created python script, but I prefer to have it in a separate file which basically calls the main method of the solver script and returns a JSON payload.<\/p>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/blob\/master\/www\/interview_grocery_startup\/interview_grocery_startup_json.py\">Source code<\/a><\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport json\r\nimport interview_grocery_startup as igs\r\n\r\n\r\ndef get_json_response(cfg, what):\r\n    results = igs.main(cfg, what)\r\n\r\n    solver = results&#x5B;'solver']\r\n\r\n\r\n    if results&#x5B;'result_status'] == solver.OPTIMAL:\r\n        response = {'result_status': 'optimal answer'}\r\n\r\n        variable_list = results&#x5B;'variable_list']\r\n\r\n        print solver.wall_time()\r\n        print solver.Objective().Value()\r\n\r\n        response&#x5B;'wall_time'] = solver.wall_time()\r\n        response&#x5B;'objective_value']= solver.Objective().Value()\r\n\r\n        response&#x5B;'variables'] = dict()\r\n\r\n        response&#x5B;'variables_sum']=0\r\n        for variable in variable_list:\r\n            response&#x5B;'variables']&#x5B;variable.name()]= variable.solution_value()\r\n            response&#x5B;'variables_sum']+=variable.solution_value()\r\n\r\n    elif results&#x5B;'result_status'] == solver.INFEASIBLE:\r\n           response = {'result_status': 'infeasible'}\r\n      elif results&#x5B;'result_status'] == solver.POSSIBLE_OVERFLOW:\r\n        response = {'result_status': 'overflow'}\r\n\r\n    json_response = json.dumps(response, sort_keys=True)\r\n\r\n    return json_response\r\n\r\ndef main(cfg, what):\r\n    json_response = get_json_response(cfg, what)\r\n    print(json_response)\r\n    return json_response\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h2>3 Coding the API<\/h2>\n<p>The principle for creating an API in Falcon is very easy to understand: you define a class and then instantiate it and link it to a route. This ends up being very convenient and clear. You can define the routes and the methods as you please.<\/p>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/blob\/master\/www\/interview_grocery_startup\/interview_grocery_startup_api.py\">Source code<\/a><\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport falcon\r\nimport json\r\nimport interview_grocery_startup_json as igsj \r\n\r\nclass InterviewGroceryStartupResource(object):\r\n    def on_get(self, req, resp):\r\n        #Handles GET requests\r\n        \r\n        resp.status = falcon.HTTP_200  # This is the default status\r\n\r\n        resp.body =  igsj.main(cfg,cfg&#x5B;'what'])\r\n\r\n    def on_post(self, req, resp):\r\n        try:\r\n            body = req.stream.read()\r\n            body_json = json.loads(body.decode('utf-8'))\r\n            cfg = body_json&#x5B;&quot;cfg&quot;]\r\n        except KeyError:\r\n            raise falcon.HTTPBadRequest(\r\n            'Missing Config',\r\n            'A config (cfg) must be submitted in the request body.')\r\n\r\n        resp.status = falcon.HTTP_200\r\n        resp.body = igsj.main(cfg,cfg&#x5B;'what'])\r\n\r\n# falcon.API instances are callable WSGI apps\r\napp = application = falcon.API()\r\n\r\n# Resources are represented by long-lived class instances\r\nigsapi = InterviewGroceryStartupResource()\r\n\r\n# ApiTestResource will handle all requests to the '\/apitest' URL path\r\napp.add_route('\/igsapi', igsapi)\r\n<\/pre>\n<p>As you may see in the class, I added two methods. on_get is not doing much, the interesting one is on_post. On each post to the specified route, the scripts decodes de body, extracts a JSON object, looks for the property cfg (config) and sends that to the JSON response builder.<\/p>\n<p>(Yes, this means that whenever you POST, you need to send a JSON object in the body with a &#8220;cfg&#8221; attribute that looks more or less like this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n{\r\n                    &quot;cfg&quot;: {&quot;what&quot;: &quot;cost&quot;,\r\n                            &quot;maxWeight&quot;: 10,\r\n                            &quot;maxCost&quot;: 100,\r\n                            &quot;minCals&quot;: 14000,\r\n                            &quot;minShop&quot;: 0.25,\r\n                            &quot;total_cost&quot;: 0,\r\n                            &quot;food&quot;:  &#x5B;&#x5B;&quot;ham&quot;,650, 4],\r\n                                        &#x5B;&quot;lettuce&quot;,70,1.5],\r\n                                        &#x5B;&quot;cheese&quot;,1670,5],\r\n                                        &#x5B;&quot;tuna&quot;,830,20],\r\n                                        &#x5B;&quot;bread&quot;,1300,1.20]]\r\n                        }\r\n}\r\n<\/pre>\n<p>If you are running the API in a different machine than the one serving the webpage, you may have trouble with the same origin policy. In order to address this, you can enable cross-origin resource sharing, CORS.<\/p>\n<p>Add the following class to the code above<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nALLOWED_ORIGINS = &#x5B;'http:\/\/localhost';]\r\n\r\nclass CorsMiddleware(object):\r\n    def process_request(self, request, response):\r\n        origin = request.get_header('Origin')\r\n        if origin is not None and origin in ALLOWED_ORIGINS:\r\n            response.set_header('Access-Control-Allow-Origin', origin)\r\n        response.set_header('Access-Control-Allow-Origin', '*')\r\n<\/pre>\n<p>And call this class during the API instantiation:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\napp = application = falcon.API(middleware=&#x5B;CorsMiddleware()])\r\n<\/pre>\n<p>Pluggable magic!<\/p>\n<p>You may run this API in port 18000 (or the one you please) by calling gunicorn:<\/p>\n<pre>gunicorn interview_grocery_startup_api -b :18000 --reload<\/pre>\n<p><a href=\"http:\/\/docs.gunicorn.org\/en\/stable\/\">Check the gunicorn docs for more options<\/a><\/p>\n<p><a href=\"https:\/\/falcon.readthedocs.io\/en\/stable\/user\/quickstart.html\">The Falcon documentation is here<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<h2>4 Testing the API<\/h2>\n<p>I use the wonderful <a href=\"https:\/\/www.getpostman.com\/\">Postman<\/a> for testing all the APIs that I make<br \/>\n<a href=\"https:\/\/www.getpostman.com\/\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2215\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/postman\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman.jpg\" data-orig-size=\"857,499\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"postman\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman.jpg\" class=\"aligncenter size-full wp-image-2215\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman.jpg\" alt=\"\" width=\"857\" height=\"499\" srcset=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman.jpg 857w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman-300x175.jpg 300w, https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/postman-768x447.jpg 768w\" sizes=\"auto, (max-width: 857px) 100vw, 857px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<h2>5 Coding the interface<\/h2>\n<p>The web interface can easily be an HTML+JS+CSS thingie. I tried to keep it simple creating a single page with 3 parts: a formulation, a data table and a fat green button to get the results.<\/p>\n<p>From the functional perspective, the only thing you need to do is to perform POSTs to the \/gunicorn\/igsapi endpoint defined in the API script, and then process the response.<\/p>\n<p><a href=\"https:\/\/github.com\/danielpradilla\/or-tools-playground\/blob\/master\/www\/interview_grocery_startup\/js\/interview_grocery_startup.js\">Here you can see the javascript file that does everything<\/a>.<\/p>\n<p>I keep a variable (igs.payload) constantly updated with the JSON payload I&#8217;m going to send. And then I just POST whenever I please:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    jQuery.ajax({\r\n      type: &quot;POST&quot;,\r\n      url: &quot;\/gunicorn\/igsapi&quot;,\r\n      dataType: &quot;json&quot;,\r\n      data: JSON.stringify(igs.payload),\r\n\r\n      error:function (xhr, ajaxOptions, thrownError){\r\n          console.log(thrownError);\r\n      },\r\n\r\n      success:function(data, textStatus, jqXHR){\r\n        console.log(data);\r\n        igs.fillAnswerTable(data);\r\n      }\r\n    })\r\n<\/pre>\n<p>The result is sent to a very dirty fillAnswerTable function which builds and re-builds the HTML code for the solution table.<\/p>\n<p>For the UI look and feel I used <a href=\"https:\/\/semantic-ui.com\/\">Semantic UI<\/a>, my current favorite Bootstrap alternative.<\/p>\n<p>I&#8217;m also using Bret Victor&#8217;s <a href=\"http:\/\/worrydream.com\/Tangle\/\">Tangle<\/a> library to provide direct manipulation of the variables. Each manipulation fires an event that updates the igs.payload variable.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2217\" data-permalink=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/groceryshopping\/\" data-orig-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif\" data-orig-size=\"829,1046\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"groceryshopping\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping-812x1024.gif\" class=\"aligncenter size-full wp-image-2217\" src=\"https:\/\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/11\/groceryshopping.gif\" alt=\"\" width=\"829\" height=\"1046\" \/><\/p>\n<p>&nbsp;<\/p>\n<h2>Next steps<\/h2>\n<p>We got our little web app, but it has so many moving pieces. Wouldn&#8217;t it be nice to package it in a way that it always works? This will be the subject of a future post.<\/p>\n<p>Containerizing the solution with docker<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a previous post, I put together a script for solving a linear optimisation problem using Google&#8217;s OR-tools. This python script is callable from the command line and you kinda need to know what you are doing and how to organize the parameters. So, in order to address this difficulty, I wanted to build a&hellip; <a class=\"more-link\" href=\"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-building-a-web-front-end-with-falcon-and-gunicorn\/\">Continue reading <span class=\"screen-reader-text\">Linear Optimization with or-tools \u00e2\u20ac\u201d building a web front-end with falcon and gunicorn<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[331],"tags":[344,342,340,343,337,338,339,341],"class_list":["post-2212","post","type-post","status-publish","format-standard","hentry","category-software-development-en-en","tag-api","tag-falcon","tag-google","tag-gunicorn","tag-linear-programming","tag-optimization","tag-or-tools","tag-python","entry"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1tlzy-zG","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2229,"url":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools-containerizing-a-gunicorn-web-application\/","url_meta":{"origin":2212,"position":0},"title":"Linear optimization with or-tools: containerizing a gunicorn web application","author":"Daniel Pradilla","date":"15\/05\/2018","format":false,"excerpt":"Previously, we left our app working with our local python+gunicorn+nginx installation. In order to get there we had to do quite a bit of configuration and if we wanted to deploy this in a server or send it to a friend, we would have to go through a very error-prone\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/05\/docker.jpg?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":2205,"url":"https:\/\/www.danielpradilla.info\/blog\/linear-optimization-with-or-tools\/","url_meta":{"origin":2212,"position":1},"title":"Linear Optimization with or-tools","author":"Daniel Pradilla","date":"07\/06\/2017","format":false,"excerpt":"\u00a0 Getting started Over the last couple of months I've been getting my feet wet with linear programming and mathematical optimisation. I got a sense of how it all worked from this Discrete Optimisation course in Coursera and googling around I discovered that there are a ton of tools out\u2026","rel":"","context":"In &quot;Best of&quot;","block_context":{"text":"Best of","link":"https:\/\/www.danielpradilla.info\/blog\/category\/bestof\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2017\/06\/grocery_bag_brown_bag.jpg?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":2245,"url":"https:\/\/www.danielpradilla.info\/blog\/recommender-system-for-finding-subject-matter-experts-using-the-enron-email-corpus\/","url_meta":{"origin":2212,"position":2},"title":"Recommender system for finding subject matter experts using the Enron email corpus","author":"Daniel Pradilla","date":"20\/11\/2018","format":false,"excerpt":"This is a little project to create a recommender system to find mentors inside an organization, using Natural Language Processing. It started as an excuse to build a data visualization I had in mind: an interactive word cloud that did something. When I started, I didn't know anything about Topic\u2026","rel":"","context":"In &quot;Best of&quot;","block_context":{"text":"Best of","link":"https:\/\/www.danielpradilla.info\/blog\/category\/bestof\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/11\/bubbles2.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/11\/bubbles2.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2018\/11\/bubbles2.jpg?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":2416,"url":"https:\/\/www.danielpradilla.info\/blog\/echotree\/","url_meta":{"origin":2212,"position":3},"title":"EchoTree","author":"Daniel Pradilla","date":"03\/02\/2026","format":false,"excerpt":"For years now I kept some of my social accounts alive with a Python script. It read from a collection of RSS feeds and shared at random times. It worked, my feeds stayed active, and people asked me what was I doing reading at 3am. The script had a bit\u2026","rel":"","context":"In &quot;Best of&quot;","block_context":{"text":"Best of","link":"https:\/\/www.danielpradilla.info\/blog\/category\/bestof\/"},"img":{"alt_text":"EchoTree","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2026\/02\/echotree.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2026\/02\/echotree.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2026\/02\/echotree.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2026\/02\/echotree.jpg?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":2302,"url":"https:\/\/www.danielpradilla.info\/blog\/aws-lambda-python\/","url_meta":{"origin":2212,"position":4},"title":"10 things I learned while deploying my first python function to AWS Lambda","author":"Daniel Pradilla","date":"04\/01\/2019","format":false,"excerpt":"I spent a few days on and off trying to deploy a Flask REST service to AWS Lambda, just to experience what the cool kids were talking about. These are some of the things I learned along the way: \u00a0 Zappa is the easiest packager\/deployer for python (as of December\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2019\/01\/AWS-Lambda-290x300.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2399,"url":"https:\/\/www.danielpradilla.info\/blog\/my-python-logging-setup\/","url_meta":{"origin":2212,"position":5},"title":"My Python logging setup","author":"Daniel Pradilla","date":"24\/08\/2023","format":false,"excerpt":"Working on various Python projects has taught me the importance of consistent logging, especially when dealing with distributed computing frameworks like Spark. Logging is not just about keeping track of errors or information; it's about having a detailed and systematic record of the operations to understand the flow of your\u2026","rel":"","context":"In &quot;Software Dev.&quot;","block_context":{"text":"Software Dev.","link":"https:\/\/www.danielpradilla.info\/blog\/category\/software-development-en-en\/"},"img":{"alt_text":"My Python logging setup","src":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2023\/08\/5aadc010-b913-4eb0-9388-cd8d6c743271.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2023\/08\/5aadc010-b913-4eb0-9388-cd8d6c743271.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2023\/08\/5aadc010-b913-4eb0-9388-cd8d6c743271.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/www.danielpradilla.info\/blog\/wp-content\/uploads\/2023\/08\/5aadc010-b913-4eb0-9388-cd8d6c743271.jpg?resize=700%2C400&ssl=1 2x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts\/2212","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/comments?post=2212"}],"version-history":[{"count":0,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/posts\/2212\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/media?parent=2212"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/categories?post=2212"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.danielpradilla.info\/blog\/wp-json\/wp\/v2\/tags?post=2212"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}