{"id":749,"date":"2017-07-10T00:16:55","date_gmt":"2017-07-09T16:16:55","guid":{"rendered":"https:\/\/www.techcoil.com\/blog\/?p=749"},"modified":"2018-09-05T00:34:52","modified_gmt":"2018-09-04T16:34:52","slug":"how-to-host-your-python-3-flask-mvp-with-supervisor-on-ubuntu-server-16-04","status":"publish","type":"post","link":"https:\/\/www.techcoil.com\/blog\/how-to-host-your-python-3-flask-mvp-with-supervisor-on-ubuntu-server-16-04\/","title":{"rendered":"How to host your Python 3 Flask MVP with Supervisor on Ubuntu Server 16.04"},"content":{"rendered":"<p>Due to its minimalistic design, the <a href=\"http:\/\/flask.pocoo.org\/\" title=\"Python Flask home page\" target=\"_blank\">Python Flask<\/a> framework is ideal for building the web server layer of minimal viable products (MVP) to validate customers' needs. However, development work is just one part of the user validation efforts. To ensure that our customer can access our Flask MVP and provide feedback as and when they are available, we will need to get it running with as a server daemon. <\/p>\n<p><a href=\"http:\/\/supervisord.org\/\" target=\"_blank\">Supervisor<\/a> is a convenient tool for running applications as a server daemon. <\/p>\n<p>This post documents the steps that I took to host a Python 3 Flask MVP with Supervisor on an Ubuntu Server 16.04 instance.<\/p>\n<h2>Creating a simple Python 3 Flask application as a proof of concept<\/h2>\n<p>To proof that it is possible to host a Python 3 Flask application with Supervisor on a Ubuntu Server 16.04 instance, I first created a \"Hello World\" application with minimal Python 3 dependencies.<\/p>\n<h3>Project structure<\/h3>\n<p>This \"Hello World\" application consists of a folder and three files within the folder:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmvpapp\r\n|--requirements.txt\r\n|--start.sh\r\n|--app.py\r\n<\/pre>\n<h3>The requirements.txt<\/h3>\n<p>The requirements.txt is for specifying the Python 3 dependencies that our Python 3 Flask application is going to need. Since this is a minimalistic application, the requirements.txt contains only the following line:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nFlask==0.12.2\r\n<\/pre>\n<h3>Shell script to start the Python 3 Flask Application: start.sh<\/h3>\n<p>The shell script is intended for the Supervisor daemon to start the \"Hello World\" Python 3 Flask application. The shell script contains the following content:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsource mvpappenv\/bin\/activate\r\npython app.py\r\ndeactivate\r\n<\/pre>\n<p>The <code>mvpappenv<\/code> folder contains a virtual environment that we will created later with <a href=\"https:\/\/virtualenv.pypa.io\/en\/stable\/\" target=\"_blank\">virtualenv<\/a>. In this shell script, we first get into the virtual environment by sourcing the activate script that resides in <code>mvpappenv\/bin<\/code> folder. We then use the python binary to run the Python 3 Flask Application. The deactivate command will be run when the Python 3 Flask application got terminated by whatever reason. In such a case, the executor of the shell script will get out of the virtual environment.<\/p>\n<h3>The Python 3 Flask source code: app.py<\/h3>\n<p>To test drive the approach to hosting a Python 3 Flask MVP with Supervisor on a Ubuntu Server 16.04 instance, we can simply create a Python 3 Flask application that will run a development server with a simple endpoint that returns a simple string as a response to HTTP GET requests:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom flask import Flask\r\napp = Flask(__name__)\r\n\r\n@app.route('\/')\r\ndef mvp_hello():\r\n    return 'Hello there, I am a MVP!'\r\n\r\napp.run(host='0.0.0.0', port=80)\r\n<\/pre>\n<h2>Copying the project files over to the server instance<\/h2>\n<p>Once we had provisioned an instance of Ubuntu Server 16.04 for our minimal viable product, we can then copy over the project folder from our machine to the Ubuntu Server 16.04 instance. Assuming that we got an instance of Ubuntu Server 16.04 running at <strong>55.12.32.45<\/strong> and we are at the folder that contains the <code>mvpapp<\/code> folder, we will run the following shell command to perform the copy:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nscp -r mvpapp root@55.12.32.45:\/var\/flaskapp\/\r\n<\/pre>\n<h2>Logging into the Ubuntu Server 16.04 instance through ssh<\/h2>\n<p>After we get the mvpapp folder into the Ubuntu Server 16.04 instance, we can use ssh to get into the Ubuntu Server 16.04 instance to perform the necessary configurations:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nssh root@55.12.32.45\r\n<\/pre>\n<h2>Installing Python 3 Package Manager (pip3)<\/h2>\n<p>If you do not have the Python 3 Package Manager in your Ubuntu Server 16.04 instance, you can install it via the following command:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo apt-get install python3-pip\r\n<\/pre>\n<h2>Installing virtualenv<\/h2>\n<p>If you do not have virtualenv in your Ubuntu Server 16.04 instance, you can install it via the following command: <\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo apt-get install virtualenv\r\n<\/pre>\n<h2>Installing Supervisor<\/h2>\n<p>To install Supervisor, we can run the following command:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo apt-get install supervisor -y &amp;&amp; sudo supervisord\r\n<\/pre>\n<p>After the command completes, Supervisor will be installed and started on the Ubuntu Server 16.04 instance.<\/p>\n<h2>Preparing the virtual environment for running our Python 3 Flask MVP<\/h2>\n<p>After installing Supervisor, we can then proceed to prepare the virtual environment that our Python 3 Flask MVP will need. To do so, we run the following commands:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\ncd \/var\/flaskapp\/mvpapp\r\nvirtualenv -p python3 mvpappenv\r\nsource mvpappenv\/bin\/activate\r\npip install -r requirements.txt\r\ndeactivate\r\n<\/pre>\n<p>The above commands does the following:<\/p>\n<ul>\n<li>Get into the project folder of our MVP app.<\/li>\n<li>Create a virtual environment, <code>mvpappenv<\/code> with python3 as the python executable.<\/li>\n<li>Get into the virtual environment specified by <code>mvpappenv<\/code>.<\/li>\n<li>Install the Python dependencies that are described in <code>requirements.txt<\/code>.<\/li>\n<li>Get out of the virtual environment specified by <code>mvpappenv<\/code>.<\/li>\n<\/ul>\n<h2>Making start.sh executable<\/h2>\n<p>In order for <code>start.sh<\/code> to be executable by the Supervisor daemon, we run the following command:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo chmod +x \/var\/flaskapp\/mvpapp\/start.sh\r\n<\/pre>\n<h2>Creating a Supervisor configuration file to run an instance of our Python 3 Flask application<\/h2>\n<p>By default, the Supervisor daemon will look inside the <code>\/etc\/supervisor\/conf.d<\/code> folder to look for instructions to get applications running. To get the Supervisor daemon to run our Python 3 Flask MVP as its subprocess, we first point nano to a configuration file in <code>\/etc\/supervisor\/conf.d<\/code>:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo nano \/etc\/supervisor\/conf.d\/mvpapp.conf\r\n<\/pre>\n<p>And paste in the following contents:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;program:mvp-flask-app]\r\ndirectory=\/var\/flaskapp\/mvpapp\r\ncommand=\/bin\/bash -E -c .\/start.sh\r\nautostart=true\r\nautorestart=true\r\nstopsignal=INT\r\nstopasgroup=true\r\nkillasgroup=true\r\n<\/pre>\n<p>This set of configurations will <a href=\"https:\/\/www.techcoil.com\/blog\/supervisor-configurations-to-ensure-that-my-python-flask-application-releases-binded-ports-during-a-supervisor-restart\/\" target=\"_blank\">ensure that our Python 3 Flask application releases binded port(s) during a supervisor restart<\/a>. <\/p>\n<p>After we had saved the configuration file, we can then run the following command to get the Supervisor daemon to take our program configurations:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo systemctl restart supervisor.service\r\n<\/pre>\n<p>With that, the Supervisor daemon will run our Python 3 Flask MVP as a daemon. We can use <code>supervisorctl<\/code> to check whether our Python 3 Flask MVP is running successfully. Running the following command gets us into the supervisor control panel:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo supervisorctl\r\n<\/pre>\n<p>And in the supervisor control panel, we should see something similar to the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nmvp-flask-app                    RUNNING    pid 15146, uptime 0:18:29\r\nsupervisor&gt; \r\n<\/pre>\n<h2>Inspecting the logs generated by our Python 3 Flask MVP<\/h2>\n<p>We can check the logs generated by our Python 3 Flask MVP inside of the supervisor control panel. Since the Flask development server writes to standard error, we can check the logs by typing the following command inside of the supervisor control panel:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\ntail mvp-flask-app stderr\r\n<\/pre>\n<p>This command will produce the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n * Running on http:\/\/0.0.0.0:80\/ (Press CTRL+C to quit)\r\n<\/pre>\n<p>Alternatively, we can inspect the logs at the default location where Supervisor daemon writes to. Since we did not specify the location where the logs for our Python 3 Flask MVP will write to inside <code>mvpapp.conf<\/code>, the Supervisor daemon will place the logs at the <code>\/var\/log\/supervisor<\/code> folder. The naming of the logs will be similar to the following:<\/p>\n<ul>\n<li><code>mvp-flask-app-stderr---supervisor-NXcYnI.log<\/code> for output to standard error.<\/li>\n<li><code>mvp-flask-app-stdout---supervisor-U6uIKG.log<\/code> for output to standard out.<\/li>\n<\/ul>\n<p>Whenever we restart the Supervisor daemon, the log files will be deleted and new ones with different names will be generated. <\/p>\n<h2>Securing Python 3 Flask endpoints with https<\/h2>\n<p>If our Python 3 Flask MVP will be dealing with user input, it would be wise to secure the endpoints with https. To do so, we can follow the steps in this section.<\/p>\n<h3>Mapping a domain name to the public IP address of the Ubuntu Server instance running our Python 3 Flask MVP<\/h3>\n<p><a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\">Let's Encrypt<\/a> is an amazing certificate authority who wants to give free encryption for everyone. With Let's Encrypt, we can secure our minimal viable products with SSL\/TLS certificates that are certified by a certificate authority. In order to use Let's Encrypt to create free SSL\/TLS certificates, we would need to map a domain name to the IP address of our Python3 Flask MVP. For this example, let's assume that I had created an <strong>A record<\/strong> to map <strong>mvp1.techcoil.com<\/strong> to <strong>55.12.32.45<\/strong>.<\/p>\n<h3>Installing CertBot<\/h3>\n<p>To get our free SSL\/TLS certificate from Let's Encrypt, we will need to use a <a href=\"https:\/\/github.com\/ietf-wg-acme\/acme\/\" target=\"_blank\">ACME<\/a> client. Since Let's Encrypt recommended the use of CertBot at this point of writing, we shall install CertBot on our Ubuntu Server 16.04 instance.<\/p>\n<p>Following the <a href=\"https:\/\/certbot.eff.org\/#ubuntuxenial-other\" target=\"_blank\">instructions from CertBot<\/a>, we will run the following commands to install CertBot on our Ubuntu Server 16.04 instance: <\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo apt-get update\r\nsudo apt-get install software-properties-common -y\r\nsudo add-apt-repository ppa:certbot\/certbot \r\nsudo apt-get update\r\nsudo apt-get install certbot -y\r\n<\/pre>\n<h3>Modifications needed at our Python 3 Flask MVP to demonstrate control over our domain<\/h3>\n<p>After we got CertBot installed on our Ubuntu Server 16.04 instance, we will proceed to modify our Python 3 Flask MVP to demonstrate control over our domain. <\/p>\n<p>The Let's Encrypt server will look for some random file written by the ACME client at the <strong>\/.well-known<\/strong> uri path of our domain in order to be convinced that we owned the domain that we wish to secure with https.<\/p>\n<p>Hence, we will need to <a href=\"https:\/\/www.techcoil.com\/blog\/serve-static-files-python-3-flask\/\" target=\"_blank\">create an endpoint that serves static files<\/a> over the \/.well-known uri. The location where it reads the static files will be where CertBot writes the random file while participating in the ACME protocol. <\/p>\n<p>Referencing <a href=\"\/blog\/serve-static-files-python-3-flask\/\">the article on how to serve static files with Python 3 + Flask<\/a>, we will modify our <code>app.py<\/code> file to contain the following codes:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport os\r\n\r\nfrom flask import Flask\r\nfrom flask import send_from_directory\r\napp = Flask(__name__)\r\n\r\nwell_known_dir =  os.path.join(os.path.dirname(os.path.realpath(__file__)), '.well-known')\r\n\r\n@app.route('\/')\r\ndef mvp_hello():\r\n    return 'Hello there, I am an MVP!'\r\n\r\n@app.route('\/.well-known\/&lt;path:path&gt;')\r\ndef serve_file_for_acme(path):\r\n    print(path)\r\n    return send_from_directory(well_known_dir, path)\r\n\r\napp.run(host='0.0.0.0', port=80)\r\n<\/pre>\n<p>After we had saved the changes that we made to <code>app.py<\/code>, we then restart Supervisor to make Python 3 run the our new codes:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo systemctl restart supervisor.service\r\n<\/pre>\n<h3>Creating an SSL Certificate for our Python 3 Flask MVP<\/h3>\n<p>To create the SSL certificate for our Python 3 Flask MVP, we then run the following command:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo certbot certonly --webroot -w \/var\/flaskapp\/mvpapp -d mvp1.techcoil.com\r\n<\/pre>\n<p>This will cause the CertBot client to create the create the ACME challenges inside the <code>\/var\/flaskapp\/mvpapp<\/code> folder in the process of creating a SSL certificate for the <code>mvp1.techcoil.com<\/code> domain.<\/p>\n<p>At the first run, the CertBot client will ask for an email address for urgent renewal and security notices, agreement to terms of services and whether we are willing to share your email address with the Electronic Frontier Foundation. <\/p>\n<p>Once we had provided answers to those questions, CertBot will create the SSL artefacts inside the <code>\/etc\/letsencrypt\/live\/mvp1.techcoil.com<\/code> folder. Inside <code>\/etc\/letsencrypt\/live\/mvp1.techcoil.com<\/code>, we will also find <code>cert.pem<\/code>, <code>chain.pem<\/code>, <code>fullchain.pem<\/code> and <code>privkey.pem<\/code>. The files inside <code>\/etc\/letsencrypt\/live\/mvp1.techcoil.com<\/code> folder are symbolic links for facilitating certificate renewal without changing our server side configurations.<\/p>\n<h2>Creating another Python 3 Flask application for serving https traffic<\/h2>\n<p>After the CertBot client had created the files needed for serving https traffic, we can then proceed to create another Python 3 Flask application that speaks https. To do so, we first add the following files in the <code>mvpapp<\/code> folder:<\/p>\n<ul>\n<li>https-app.py<\/li>\n<li>start-https-app.sh<\/li>\n<\/ul>\n<h3>The Python 3 Flask source code for serving https: https-app.py<\/h3>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport os\r\n\r\nfrom flask import Flask\r\n\r\napp = Flask(__name__)\r\n\r\n@app.route('\/')\r\ndef mvp_hello():\r\n    return 'Hello there, I am an MVP served through https!'\r\n\r\n\r\napp.run(host='0.0.0.0',\r\n        port=443,\r\n        ssl_context = ('\/etc\/letsencrypt\/live\/mvp1.techcoil.com\/cert.pem',\r\n                       '\/etc\/letsencrypt\/live\/mvp1.techcoil.com\/privkey.pem'))\r\n<\/pre>\n<p>Similar to app.py, we create a route that returns a simple string as a response to HTTP GET requests. The difference is that we include a tuple that contains the path to the certificate and private key that CertBot had created for us earlier via the <code>ssl_context<\/code> parameter of the <code><a href=\"http:\/\/flask.pocoo.org\/docs\/0.12\/api\/#flask.Flask.run\" target=\"_blank\">app.run<\/a><\/code> function.<\/p>\n<h3>The shell script to start the Python 3 Flask application that serves https traffic: start-https-app.sh<\/h3>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nsource mvpappenv\/bin\/activate\r\npython https-app.py\r\ndeactivate\r\n<\/pre>\n<p>This shell script is similar to start.sh, except that it runs <code>https-app.py<\/code>. <\/p>\n<p>In order for <code>start-ssl.sh<\/code> to be executable by the Supervisor daemon. We make it executable by running the following command:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo chmod +x \/var\/flaskapp\/mvpapp\/start-https-app.sh\r\n<\/pre>\n<h3>Creating a new Supervisor configuration file to run an instance of our Python 3 Flask application that speaks https<\/h3>\n<p>Finally we create a new Supervisor configuration file to run an instance of our Python 3 Flask application that speaks https:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;program:mvp-flask-app-with-https]\r\ndirectory=\/var\/flaskapp\/mvpapp\r\ncommand=\/bin\/bash -E -c .\/start-https-app.sh\r\nautostart=true\r\nautorestart=true\r\nstopsignal=INT\r\nstopasgroup=true\r\nkillasgroup=true\r\n<\/pre>\n<p>As with the case of the first Python 3 Flask application that we had created earlier, we will save the this new Supervisor configurations file at the <code>\/etc\/supervisor\/conf.d<\/code> folder. Let's assume that we name this file as <code>mvpapp-with-https.conf<\/code>.<\/p>\n<h3>Getting Supervisor daemon to start the Python 3 Flask application that serves https traffic<\/h3>\n<p>With the files in place, we will then run the following command to get the Supervisor daemon to start the Python 3 Flask application that serves https traffic:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo systemctl restart supervisor.service\r\n<\/pre>\n<p>Once the Supervisor daemon had restarted successfully, we can then use the <code>supervisorctl<\/code> command to check the status of the Python 3 Flask application that serves https traffic:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nsudo supervisorctl\r\n<\/pre>\n<p>In the supervisor control panel, we will see something similar to the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nmvp-flask-app                    RUNNING    pid 15146, uptime 1:25:12\r\nmvp-flask-app-with-https         RUNNING    pid 16346, uptime 0:18:29\r\nsupervisor&gt; \r\n<\/pre>\n<p>We can then check whether mvp-flask-app-with-https was started successfully by issuing the following command in the supervisor control panel:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\ntail mvp-flask-app-with-https stderr\r\n<\/pre>\n<p>Which will produce the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n* Running on http:\/\/0.0.0.0:443\/ (Press CTRL+C to quit)\r\n<\/pre>\n\n      <ul id=\"social-sharing-buttons-list\">\n        <li class=\"facebook\">\n          <a href=\"https:\/\/www.facebook.com\/sharer\/sharer.php?u=https%3A%2F%2Fwp.me%2Fp245TQ-c5\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n            <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Facebook.png\" alt=\"Facebook icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"twitter\">\n          <a href=\"https:\/\/twitter.com\/intent\/tweet?text=&url=https%3A%2F%2Fwp.me%2Fp245TQ-c5&via=Techcoil_com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Twitter.png\" alt=\"Twitter icon\"> Tweet\n          <\/a>\n        <\/li>\n        <li class=\"linkedin\">\n          <a href=\"https:\/\/www.linkedin.com\/shareArticle?mini=1&title=&url=https%3A%2F%2Fwp.me%2Fp245TQ-c5&source=https:\/\/www.techcoil.com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/linkedin.png\" alt=\"Linkedin icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"pinterest\">\n          <a href=\"https:\/\/pinterest.com\/pin\/create\/button\/?url=https%3A%2F%2Fwww.techcoil.com%2Fblog%2Fwp-json%2Fwp%2Fv2%2Fposts%2F749&description=\" class=\"pin-it-button\" target=\"_blank\" role=\"button\" rel=\"nofollow\" count-layout=\"horizontal\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Pinterest.png\" alt=\"Pinterest icon\"> Save\n          <\/a>\n        <\/li>\n      <\/ul>\n    ","protected":false},"excerpt":{"rendered":"<p>Due to its minimalistic design, the <a href=\"http:\/\/flask.pocoo.org\/\" title=\"Python Flask home page\" target=\"_blank\">Python Flask<\/a> framework is ideal for building the web server layer of minimal viable products (MVP) to validate customers&#8217; needs. However, development work is just one part of the user validation efforts. To ensure that our customer can access our Flask MVP and provide feedback as and when they are available, we will need to get it running with as a server daemon. <\/p>\n<p><a href=\"http:\/\/supervisord.org\/\" target=\"_blank\">Supervisor<\/a> is a convenient tool for running applications as a server daemon. <\/p>\n<p>This post documents the steps that I took to host a Python 3 Flask MVP with Supervisor on an Ubuntu Server 16.04 instance.<\/p>\n","protected":false},"author":1,"featured_media":1244,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"footnotes":""},"categories":[375,4],"tags":[431,266,226,233,254,195,256,427,253,349,426],"jetpack_featured_media_url":"https:\/\/www.techcoil.com\/blog\/wp-content\/uploads\/Python-Logo.gif","jetpack_shortlink":"https:\/\/wp.me\/p245TQ-c5","jetpack-related-posts":[],"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/749"}],"collection":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/comments?post=749"}],"version-history":[{"count":0,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/749\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media\/1244"}],"wp:attachment":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media?parent=749"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/categories?post=749"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/tags?post=749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}