<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Blog entirely.digital]]></title><description><![CDATA[Blog about frontend development, backend development and many other things]]></description><link>https://blog.entirely.digital/</link><image><url>https://blog.entirely.digital/favicon.png</url><title>Blog entirely.digital</title><link>https://blog.entirely.digital/</link></image><generator>Ghost 4.47</generator><lastBuildDate>Fri, 21 Nov 2025 13:36:09 GMT</lastBuildDate><atom:link href="https://blog.entirely.digital/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Easy way to test file uploads in Flask with PyTest]]></title><description><![CDATA[<p>Not that long ago I wanted to write a test for an endpoint that takes your uploaded files and sends it over to another service. I needed to test the initial upload and we already had some testing infrastructure in place, so I just needed to add new test cases.</p>]]></description><link>https://blog.entirely.digital/flask-pytest-testing-uploads/</link><guid isPermaLink="false">5f1c2e65e8605b00014334bd</guid><category><![CDATA[Python]]></category><category><![CDATA[Web development]]></category><category><![CDATA[PyTest]]></category><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Sun, 26 Jul 2020 21:15:31 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2020/07/download-1873539_1280.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2020/07/download-1873539_1280.png" alt="Easy way to test file uploads in Flask with PyTest"><p>Not that long ago I wanted to write a test for an endpoint that takes your uploaded files and sends it over to another service. I needed to test the initial upload and we already had some testing infrastructure in place, so I just needed to add new test cases. However, we were using PyTest which I haven&apos;t used before, as almost all of my testing experience was done in unittest. We were also working with the latest Flask 1.1.x. I had some issues coming up with code that would be suitable for our tests and the initial Google search did not yield satisfying results so I decided to write this short article about how to properly test file uploads in Flask.</p><p>All the code I will be describing is available <a href="https://github.com/mjurenka/example-flask-upload-testing">on my Github</a>, feel free to check it out, and run it yourself. I have started to work with Makefiles and I like it a lot, so I will be running tests and Flask app with &quot;make&quot; command.</p><h3 id="simple-upload-endpoint">Simple upload endpoint</h3><p>Let&apos;s consider some basic endpoint implementation which can receive images and stores them in local &quot;upload&quot; folder:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">@app.route(&apos;/upload&apos;, methods=[&apos;POST&apos;])
def upload():
    try:
        logging.info(request.files)
        uploaded_file = request.files[&apos;image&apos;]
    except:
        raise InvalidUsage(&quot;Please provide image to upload&quot;, status_code=400)

    if uploaded_file.content_type != &apos;image/jpeg&apos;:
        raise InvalidUsage(&quot;Only JPEG images are allowed&quot;, status_code=400)

    try:
        filename = secure_filename(uploaded_file.filename)
        destination_file = os.path.join(app.config[&apos;UPLOAD_FOLDER&apos;], filename)
        uploaded_file.save(destination_file)
        return {&quot;file&quot;: filename}, 201
    except:
        raise
        raise InvalidUsage(&apos;Failed to upload image&apos;, status_code=500)</code></pre><figcaption>Excerpt of app.py</figcaption></figure><p>I want to explain pieces of this function, just in case you are not familiar with all of the functionalities.</p><p>First of all, we access files from form-data upload and pick out the field named &quot;image&quot;:</p><pre><code class="language-python">uploaded_file = request.files[&apos;image&apos;]</code></pre><p>Then we can perform a MIME-type check on that file to see if it&apos;s an image or something else (if you are not sure what is a MIME-type, you can read about it <a href="https://en.wikipedia.org/wiki/Media_type">here</a>):</p><pre><code class="language-python">if uploaded_file.content_type != &apos;image/jpeg&apos;:
        raise InvalidUsage(&quot;Only JPEG images are allowed&quot;, status_code=400)</code></pre><p>Please note that this particular check is extremely naive as it decides Mime-type based only on the file extension. If you want to have true MIME-type detection, you can use <a href="https://github.com/ahupp/python-magic">Python-Magic</a>. But this naive approach will be good for now as we will exploit later on in testing examples.</p><p>Now after we can be somewhat sure that we have an image that is being uploaded, we can process it, but first, we might want to find out the name of the file, because I want to keep the filename the same in my upload folder:</p><pre><code class="language-python">filename = secure_filename(uploaded_file.filename)</code></pre><p>This <a href="https://werkzeug.palletsprojects.com/en/1.0.x/utils/#werkzeug.utils.secure_filename">secure_filename</a> function is a part of Werkzeug&apos;s utils package, it strips all unnecessary and dangerous characters from the uploaded file&apos;s name.</p><p>Then we want to construct a path where our uploaded file will be stored:</p><pre><code class="language-python">destination_file = os.path.join(app.config[&apos;UPLOAD_FOLDER&apos;], filename)</code></pre><p>The upload folder value is set at the top of app.py and we are using os.path.join function to construct a system-compatible path to the file.</p><p>You can also construct a file path manually by doing something like this:</p><pre><code class="language-python">destination_file = app.config[&apos;UPLOAD_FOLDER&apos;] + &apos;/&apos; + filename</code></pre><p>And I bet some of you reading this article are doing just that in your programs and apps. If you are developing on a Windows machine you have to use backslashes, but you are probably deploying your app on a Linux server, and that requires forwards slashes. Those system differences in paths can cause errors and bugs and this is why you should use <a href="https://docs.python.org/3/library/os.path.html#os.path.join">os.path.join(path, *paths)</a>, which will solve those problems for you automatically.</p><p>After that we can finally save this image to the disk:</p><pre><code class="language-python">uploaded_file.save(destination_file)</code></pre><p>And we want to return the clean filename along with the status code of 201 (Created). Here we can use new Flask&apos;s syntax where we return a tuple of dictionary and integer, the dictionary will get converted to JSON and integer will be our response status code:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">return {&quot;file&quot;: filename}, 201</code></pre><figcaption>I really like this shorthand return syntax</figcaption></figure><h3 id="the-test">The test</h3><p>Let&apos;s now set up our testing infrastructure. First of all, we want to create a folder named &quot;tests&quot; in our project and convert it to a package by creating an __init__.py file inside.</p><p>As we will be using Flask&apos;s test client to test our endpoint, we want to configure as a fixture so it can be easily accessed in our tests. We can do that if we create a file named conftest.py:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">import pytest
import app

@pytest.fixture(scope=&apos;module&apos;)
def test_client():
    flask_app = app.app
    testing_client = flask_app.test_client()
    ctx = flask_app.app_context()
    ctx.push()
    yield testing_client
    ctx.pop()
</code></pre><figcaption>conftest.py</figcaption></figure><p>Now we can start writing some tests! Here where I have stumbled from the beginning, it seemed like everyone on the face of the Earth was writing tests with dummy streams of data, something like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def test_upload_text_stream(test_client):
    file_name = &quot;fake-text-stream.txt&quot;
    data = {
        &apos;image&apos;: (io.BytesIO(b&quot;some initial text data&quot;), file_name)
    }
    response = test_client.post(&apos;/upload&apos;, data=data)
    assert response.status_code == 400</code></pre><figcaption>Uploading dummy stream of data as text file</figcaption></figure><p>In this test, we define a file_name with .txt extension, because we want to make sure our naive MIME-type checked does not allow text files to be uploaded, we only want images.</p><p>Then we create a data dictionary payload with the field &apos;image&apos; in which we will store a tuple with 2 values:</p><ul><li>a byte stream of data</li><li>name of the file we are uploading</li></ul><p>Note that we can name the file with anything we want, we don&apos;t have to send the real name of the file.</p><p>After we have everything ready, we POST the data to our test client and we check for a status code of 400 to check that server has rejected the payload.</p><p>If we want to do the same dummy data stream upload, but make sure we succeed, we can do this:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def test_upload_image_stream(test_client):
    image_name = &quot;fake-image-stream.jpg&quot;
    data = {
        &apos;image&apos;: (io.BytesIO(b&quot;some random data&quot;), image_name)
    }
    response = test_client.post(&apos;/upload&apos;, data=data)
    assert response.status_code == 201
    assert response.json[&apos;file&apos;] == image_name</code></pre><figcaption>Uploading dummy stream of data as JPEG</figcaption></figure><p>This test is sending virtually the same data as the one above, but we have a different filename with a different extension (jpg instead of txt), so we &quot;fool&quot; our MIME-type detector, and the server will save the file, respond with status code 201 (Created) and return the name of the file.</p><p>But I wanted to run these tests with files I already had in my repository. I did not want to use any dummy data streams, because I was also testing integration with other services and dummy data would interfere with test results.</p><p>To use real files inside your project, you will create tests that do this:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def test_upload_textfile(test_client):
    file = &quot;random-file.txt&quot;
    data = {
        &apos;image&apos;: (open(file, &apos;rb&apos;), file)
    }
    response = test_client.post(&apos;/upload&apos;, data=data)
    assert response.status_code == 400</code></pre><figcaption>Testing upload with real text file</figcaption></figure><p>What you have to do, is provide a byte stream of a real file, you have to use the open() function and set the mode to &apos;rb&apos; (read+binary). This will read the file as a binary and provide the correct data for the upload.</p><p>To check our upload with a nice picture, I picked this picture:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.entirely.digital/content/images/2020/07/pizza-cat.jpg" class="kg-image" alt="Easy way to test file uploads in Flask with PyTest" loading="lazy" width="640" height="400" srcset="https://blog.entirely.digital/content/images/size/w600/2020/07/pizza-cat.jpg 600w, https://blog.entirely.digital/content/images/2020/07/pizza-cat.jpg 640w"><figcaption>Pizza-cat or Cat-pizza?</figcaption></figure><p>I want to upload this cute picture of a Pizza-Cat to my endpoint and make sure it works correctly:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def test_upload_image_file(test_client):
    image = &quot;pizza-cat.jpg&quot;
    data = {
        &apos;image&apos;: (open(image, &apos;rb&apos;), image)
    }
    response = test_client.post(&apos;/upload&apos;, data=data)
    assert response.status_code == 201
    assert response.json[&apos;file&apos;] == image</code></pre><figcaption>Testing upload with real JPEG</figcaption></figure><p>You can run all the tests in the project by running &quot;make test&quot;.</p><p>After I have found out how easy it was to create tests that use real files for upload I immediately wanted to write an article about it. I think we all should write tests for the code we write because tested code is more reliable than untested code and if you know more about how to create proper tests for something harder, like file uploads, you will get faster in a habit of writing tests along with your code.</p><p>Good luck with your tests!</p><p>M.</p>]]></content:encoded></item><item><title><![CDATA[Pipenv: Python Dev Workflow for Humans]]></title><description><![CDATA[<p>I took the title of this article directly from <a href="https://pipenv-fork.readthedocs.io/en/latest/">the documentation of Pipenv</a> because it is amazingly accurate. If you have ever used package managers available for other languages, such as NPM or CocoaPods, and came to Python, you might have noticed that our Pip is not so sophisticated. It</p>]]></description><link>https://blog.entirely.digital/pipenv-better-dev-workflow/</link><guid isPermaLink="false">5f1c2c18e8605b000143347c</guid><category><![CDATA[Python]]></category><category><![CDATA[General]]></category><category><![CDATA[Virtual environment]]></category><category><![CDATA[Pipenv]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Sat, 25 Jul 2020 15:11:42 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2020/07/hand-4661763_1280.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2020/07/hand-4661763_1280.jpg" alt="Pipenv: Python Dev Workflow for Humans"><p>I took the title of this article directly from <a href="https://pipenv-fork.readthedocs.io/en/latest/">the documentation of Pipenv</a> because it is amazingly accurate. If you have ever used package managers available for other languages, such as NPM or CocoaPods, and came to Python, you might have noticed that our Pip is not so sophisticated. It can also be a good thing since the barrier for entry is lowered, it&apos;s easier, but it also encourages filling our requirements.txt file with a bunch of dependencies from other libraries. Pipenv is here to help us manage our dependencies, it makes working with virtual environments more seamless and ensures that we are always using correct packages when developing our code. Want to know more? Read on!</p><h3 id="example-app">Example App</h3><p>Let&apos;s say we want to make an app that uploads a file to an S3 bucket. I picked this example because Amazon&apos;s Boto3 library has quite a few dependencies, and I think something like this is a prime example of how to optimize our requirements file. This app should be pretty straightforward and I didn&apos;t want to just pick a lot of arbitrary dependencies just to show you how full can your requirements file get. I will be doing everything in a virtual environment so we can get an accurate picture of our project dependencies. This example code was taken from <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html">https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html</a>.</p><figure class="kg-card kg-code-card"><pre><code class="language-python">import logging
import boto3
from botocore.exceptions import ClientError

def upload_file(file_name, bucket, object_name=None):
    &quot;&quot;&quot;Upload a file to an S3 bucket

    :param file_name: File to upload
    :param bucket: Bucket to upload to
    :param object_name: S3 object name. If not specified then file_name is used
    :return: True if file was uploaded, else False
    &quot;&quot;&quot;

    # If S3 object_name was not specified, use file_name
    if object_name is None:
        object_name = file_name

    # Upload the file
    s3_client = boto3.client(&apos;s3&apos;)
    try:
        response = s3_client.upload_file(file_name, bucket, object_name)
    except ClientError as e:
        logging.error(e)
        return False
    return True

upload_file(&quot;requirements.txt&quot;, &quot;test-bucket&quot;)</code></pre><figcaption>Boto3 example code for S3 upload</figcaption></figure><p>Everything you are about to see was executed <a href="https://github.com/mjurenka/example-pipenv">inside this repository</a>. You can clone it and follow along, I highly recommend you see it for yourself. If you want to make your life easier, also use Pyenv (read about it here) and Python 3.8.2 which was used to create examples for this article.</p><h3 id="basic-pip-usage">Basic Pip usage</h3><p>Let&apos;s see how we would use Pip to build this app. First, we would create a virtual environment and activate it:</p><pre><code class="language-shell">cd pip-example
python -m venv venv
. venv/bin/activate
</code></pre><p>Let&apos;s now check our Pip freeze command to make sure we don&apos;t have any packages installed:</p><pre><code class="language-shell">pip freeze</code></pre><p>Let&apos;s now install boto3 library and check our &quot;pip freeze&quot;:</p><pre><code class="language-shell">pip install boto3
pip freeze</code></pre><pre><code>boto3==1.14.28
botocore==1.17.28
docutils==0.15.2
jmespath==0.10.0
python-dateutil==2.8.1
s3transfer==0.3.3
six==1.15.0
urllib3==1.25.10</code></pre><p>The reason why we are checking &quot;pip freeze&quot; is that a lot of people, when working on a project, install packages first and add them to requirements later, and they often use the command &quot;pip freeze&quot; and forward that output to requirements.txt. But this list of dependencies is not something we want to save and version in our repository as the only real dependency from this list is boto3.</p><p>Right now it&apos;s quite easy to pick out our dependency and copy it to the requirements file, but how about when you introduce a couple more libraries? Once your list of dependencies gets longer, your willingness to pick out correct dependencies from a list gets lower and you start flooding your requirements with unnecessary libraries, which will inevitably produce clashes among their dependencies.</p><p>Let&apos;s now deactivate our virtual environment to check out Pipenv:</p><pre><code class="language-shell">deactivate</code></pre><h3 id="basic-pipenv-usage">Basic Pipenv usage</h3><p>First, we need to install Pipenv, which is pretty easy on a macOS with Homebrew installed:</p><pre><code class="language-shell">brew install pipenv</code></pre><p>Otherwise you should follow <a href="https://pipenv-fork.readthedocs.io/en/latest/install.html#installing-pipenv">installation steps from Pipenv documentation</a>.</p><p>Let&apos;s now do the same thing, but with Pipenv:</p><pre><code class="language-shell">cd pipenv-example
pipenv --three
pipenv shell</code></pre><p>If you are using pyenv (you should be) and you have specified a local version of Python, you can just use Pipenv shell and everything will be set up for you. <a href="https://blog.entirely.digital/pyenv-multiple-python-versions/">You can read about pyenv in my previous article.</a></p><p>Once we are inside our virtual environment, we can install our dependency:</p><pre><code class="language-shell">pipenv install boto3</code></pre><p>You will see some output from that command, something similar to this:</p><pre><code>Installing boto3&#x2026;
Adding boto3 to Pipfile&apos;s [packages]&#x2026;
&#x2714; Installation Succeeded 
Pipfile.lock not found, creating&#x2026;
Locking [dev-packages] dependencies&#x2026;
Locking [packages] dependencies&#x2026;
Building requirements...
Resolving dependencies...
&#x2714; Success! 
Updated Pipfile.lock (dfb424)!
Installing dependencies from Pipfile.lock (dfb424)&#x2026;
  &#x1F40D;   &#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589;&#x2589; 0/0 &#x2014; 00:00:00</code></pre><p>&quot;That&apos;s a lot of new things, it mentions Pipfile, Pipfile.lock, what are these things?&quot; you might ask. Let&apos;s look at the contents of Pipfile first:</p><pre><code>[[source]]
name = &quot;pypi&quot;
url = &quot;https://pypi.org/simple&quot;
verify_ssl = true

[dev-packages]

[packages]
boto3 = &quot;*&quot;

[requires]
python_version = &quot;3.8&quot;
</code></pre><p>Pipfile is the basis of our dependency list, it holds all of our main dependencies that we have installed and nothing more. It only has boto3 in the packages section, the version is specified as *, which means it should install the latest version (we should probably change that to fixed version). To install the same version as in Pip example (1.14.28) we can simply run:</p><pre><code class="language-shell">pipenv install boto3==1.14.28</code></pre><p>And that will update our Pipfile to this:</p><pre><code>[[source]]
name = &quot;pypi&quot;
url = &quot;https://pypi.org/simple&quot;
verify_ssl = true

[dev-packages]

[packages]
boto3 = &quot;==1.14.28&quot;

[requires]
python_version = &quot;3.8&quot;
</code></pre><p>This file is the reason we want to use Pipenv, it provides a source of truth for dependencies for our project, and we can be certain that the packages it lists are the true dependencies.</p><p><a href="https://github.com/mjurenka/example-pipenv/blob/master/pipenv-example/Pipfile.lock">You can look at the contents of Pipfile.lock here</a> (file is pure JSON and it&apos;s quite large and hard to read inside this article). You can see all the dependencies that the boto3 library requires, it contains versions, hashes, markers, all the goodies that package managers require.</p><p>To deactivate our virtual environment when using Pipenv it&apos;s as simple as this:</p><pre><code class="language-shell">exit</code></pre><p>I must say I prefer the activation and deactivation of virtual environments in Pipenv over regular venvs, as it closer tracks the behavior of regular shells.</p><h3 id="pip-freeze-problem">&quot;Pip freeze&quot; problem</h3><p>Consider this scenario (which happened to me not that long ago): You will start working on a new project, you clone the repo to your machine, you create your virtual environment, you run &quot;pip install -r requirements.txt&quot;, and you&apos;ll get an error message like this:</p><pre><code>ERROR: zappa 0.51.0 has requirement python-dateutil&lt;2.7.0,
but you&apos;ll have python-dateutil 2.8.1 which is incompatible.</code></pre><p>This is caused by storing the output of &quot;pip freeze&quot; directly, instead of creating it carefully line by line, dependency by dependency. It&apos;s just a pile of libraries, their dependencies, and dependencies of dependencies.</p><p>How do you resolve that kind of mess now? The easiest thing is to go to the requirements file and just remove a specific row for python-dateutil, run it again, and then it works with no errors. But how can we know that we don&apos;t need that 2.8.1 version? After all, it was specified as a dependency, so it should stay at 2.8.1, right?</p><p>You can never be 100% certain unless you go over the entire codebase and make sure. Some pieces of code somewhere might be reliant on that specific version and it might cause a crash later on. In my case, I went over the codebase as it was relatively small, and after I made sure it&apos;s not used anywhere I removed it and reinstalled the packages.</p><h3 id="pipenv-does-not-have-the-freeze-issue">Pipenv does not have the &quot;freeze issue&quot;</h3><p>Since Pipenv updates it&apos;s Pipfile during the installation of a new package, it does not have the same above-mentioned problem. It keeps its list of dependencies clean and tidy, however, there can always be issues with clashing sub-dependencies, but at least you will get notified about it when you install a new package that is causing the clash, and you have a chance to correct it yourself, and not push it on the next person who downloads and installs dependencies for your code.</p><h3 id="migrating-to-pipenv">Migrating to Pipenv</h3><p>If you want to start using Pipenv today, you don&apos;t have to do anything special, but you might encounter &quot;pip freeze&quot; issues on your projects. When you activate the virtual environment from Pipenv and it detects a requirements.txt file, it will try to install all the dependencies from it, however, it does not know which are the actual dependencies, so it just adds all of them to Pipfile as packages. You can try it out yourself:</p><pre><code class="language-shell">cd migrate-example
pipenv shell
---
&#x2714; Successfully created virtual environment! 
Virtualenv location: /Users/martin/.local/share/virtualenvs/migrate-example-aEf2PuZd
requirements.txt found, instead of Pipfile! Converting&#x2026;
&#x2714; Success! </code></pre><p>Our new Pipfile after migration:</p><pre><code class="language-pipfile">[[source]]
name = &quot;pypi&quot;
url = &quot;https://pypi.org/simple&quot;
verify_ssl = true

[dev-packages]

[packages]
boto3 = &quot;==1.14.28&quot;
botocore = &quot;==1.17.28&quot;
docutils = &quot;==0.15.2&quot;
jmespath = &quot;==0.10.0&quot;
python-dateutil = &quot;==2.8.1&quot;
s3transfer = &quot;==0.3.3&quot;
six = &quot;==1.15.0&quot;
urllib3 = &quot;==1.25.10&quot;

[requires]
python_version = &quot;3.8&quot;</code></pre><p>You can clean packages that you know are not essential for your code and are just sub-dependencies. After that simply create a lock file and you can commit both Pipfile and Pipfile.lock to your repo:</p><pre><code>pipenv lock
---
Locking [dev-packages] dependencies&#x2026;
Locking [packages] dependencies&#x2026;
Building requirements...
Resolving dependencies...
&#x2714; Success! 
Updated Pipfile.lock (dc9747)!</code></pre><h3 id="specific-version-of-library-in-pipfile">Specific version of library in Pipfile</h3><p>When I started working with Pipenv, I had one question for which it was a bit difficult to find an answer, at least at that time: &quot;How can I specify the exact version of a library to use in my Pipfile?&quot;.</p><p>I was trying many combinations in Pipfile, all of them incorrect:</p><pre><code>boto3 = 1.14.28
boto3 = &quot;1.14.28&quot;
boto3==1.14.28
boto3==&quot;1.14.28&quot;</code></pre><p>As you can see from example Pipfile above, the correct usage is:</p><pre><code>boto3 = &quot;==1.14.28&quot;</code></pre><p>I hope this article showed you how useful it can be to use Pipenv as a package manager for your project. It does not matter if your team or project is small, medium, or large, all of them could benefit from a properly defined dependency list that is well maintained and Pipenv helps you with that immensely. It helps to prevent errors and bugs, and I am really glad that we already have a tool like this at our disposal in Python. It&apos;s time to start using it!</p>]]></content:encoded></item><item><title><![CDATA[How to manage multiple Python versions?]]></title><description><![CDATA[<p>If you work with Python long enough, you will find yourself in a pickle. You might want to have multiple versions of Python installed on your computer at the same time, because your OS requires one version, different version is required for your project, and another one is a requirement</p>]]></description><link>https://blog.entirely.digital/pyenv-multiple-python-versions/</link><guid isPermaLink="false">5f13344de8605b0001433335</guid><category><![CDATA[Python]]></category><category><![CDATA[macOS]]></category><category><![CDATA[pyenv]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Sat, 18 Jul 2020 18:52:00 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2020/07/chris-ried-ieic5Tq8YMk-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2020/07/chris-ried-ieic5Tq8YMk-unsplash-1.jpg" alt="How to manage multiple Python versions?"><p>If you work with Python long enough, you will find yourself in a pickle. You might want to have multiple versions of Python installed on your computer at the same time, because your OS requires one version, different version is required for your project, and another one is a requirement for that one weird library you need for whatever reason and there is no compatibility with newer version of Python.</p><p>What you can do is install all the versions you need and reference them by their version, let&apos;s say you have python 2.7, 3.6 and 3.8:</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">python2.7 hello.py
python3.6 hello.py
python3.8 hello.py</code></pre><figcaption>multiple python versions on single machine</figcaption></figure><p>I would say that this kind of usage is pretty common. But there is a way to make our lives easier and simpler. No more remembering which version is required for which project, making sure you and your team use the same one, having issues while running your code because you have selected the wrong version to run.</p><h3 id="welcome-to-pyenv">Welcome to pyenv</h3><p>This interesting library allows us to have multiple Python versions installed on the system, allows us to configure Python versions per folder and also set single global version. Sounds too good to be true? Let&apos;s take a look.</p><h3 id="installation">Installation</h3><p>This article was be written for MacOS, since it is the system I&apos;m using (and I think it&apos;s really good for developers). You should install tools (like pyenv) and libraries using Homebrew. Homebrew is a package manager, something similar to Ubuntu&apos;s apt-get, which makes it easy to install almost everything on your system even if it requires configuration and compilation.</p><p>So we will install pyenv with Homebrew:</p><pre><code class="language-shell">brew install pyenv</code></pre><p>Yes, it is that easy. Everything is being installed and being taken care of, just sit back, relax and let brew do its thing.</p><p>Once we have pyenv installed, let&apos;s install multiple Python versions! In this example I will be showing how to install version 3.6 and 3.8, but you can list all available versions for installation with following command:</p><pre><code class="language-shell">pyenv install --list</code></pre><p>We will install version &#xA0;3.8 first by running the install command:</p><pre><code class="language-shell">pyenv install 3.8</code></pre><p>And we should see something like this:</p><pre><code class="language-shell">python-build: definition not found: 3.8

The following versions contain `3.8&apos; in the name:
  3.8.0
  3.8-dev
  3.8.1
  3.8.2
  miniconda-3.8.3
  miniconda3-3.8.3

See all available versions with `pyenv install --list&apos;.
</code></pre><p>It appears that we &#xA0;have to also specify patch version number we want to install. I would argue that this is a good thing because you have to pick the exact version you want to install and use, you are in total control of your Python, however that also means no automatic update to the latest version. </p><p>Ok then, install version 3.8.2 and check if it&apos;s registered in pyenv:</p><pre><code class="language-shell">pyenv install 3.8.2</code></pre><p>You should see a list similar to this one (note I have used pyenv before and my output might differ from yours as it&apos;s fresh installation of pyenv):</p><pre><code>pyenv versions
* system (set by /Users/martin/.pyenv/version)
  3.8.2</code></pre><p>Let&apos;s install also version 3.6.9 and check versions again:</p><pre><code>pyenv install 3.6.9
pyenv versions
* system (set by /Users/martin/.pyenv/version)
  3.6.9
  3.8.2
</code></pre><p>Now we can set different Python versions for different folders, and we can also switch global version (default version when there is no specific local version in a folder). Let&apos;s do that now, let&apos;s make 2 new folders and set different python version for each one of them:</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">cd ~
mkdir version1 &amp;&amp; cd $_
pyenv local 3.6.9
python

Python 3.6.9 (default, Jun 10 2020, 01:11:21) 
[GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.32.62)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; </code></pre><figcaption>folder with Python 3.6.9</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">cd ~
mkdir version2 &amp;&amp; cd $_
pyenv local 3.8.2
python

Python 3.8.2 (default, Jul 18 2020, 20:10:28) 
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; </code></pre><figcaption>folder with Python 3.8.2</figcaption></figure><p>You can see that with command &quot;<em>pyenv local VERSION</em>&quot; you can set any arbitrary installed Python version in a folder. It actually creates invisible file named &quot;.python-version&quot; in that folder, so if you want you can include it in your repository and your entire team will now be using the same python version when executing code in that folder (they have to have pyenv installed as well of course).</p><p>I want to quickly mention that you can also set your default global Python interpreter version with this command:</p><pre><code class="language-shell">pyenv global 3.6.9</code></pre><p>Also you can set version for your current shell instance:</p><pre><code class="language-shell">pyenv shell 3.6.9</code></pre><p>As you can see pyenv is really good at managing Python versions. If you want to know how it does it in the background, you can read more about it here. It is very powerful tool for Python development and really useful one if you need to have multiple versions installed at once and switch seamlessly between them. I personally use it everywhere, but it is kind of <em>configure-once-and-forget-about-it</em> type of tool, which is the best kind I think. Simple, powerful and yet unobtrusive. It does its thing and let&apos;s me do my work effectively.</p><p>How do you like pyenv? How do you manage your Python version installations? Let me know in the comment section below!</p><p>M.</p>]]></content:encoded></item><item><title><![CDATA[Docker, Gunicorn and Flask]]></title><description><![CDATA[Do you want deploy your Flask app quickly and easily? Do you want it to be stable, robust and able to handle lots of requests? Then this article is for you.]]></description><link>https://blog.entirely.digital/docker-gunicorn-and-flask/</link><guid isPermaLink="false">5ee8d82da9744600018367a6</guid><category><![CDATA[Docker]]></category><category><![CDATA[Flask]]></category><category><![CDATA[Gunicorn]]></category><category><![CDATA[Python]]></category><category><![CDATA[Backend development]]></category><category><![CDATA[Frontend development]]></category><category><![CDATA[Web development]]></category><category><![CDATA[Unix]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Wed, 15 Jul 2020 13:02:39 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2020/07/New-Project-4.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2020/07/New-Project-4.png" alt="Docker, Gunicorn and Flask"><p>In this day and age it is nice to deploy your services in containers. It takes most of the headache of configuring servers and environments away. But sometimes things just don&apos;t work the way you expect them to and sometimes it&apos;s hard to find answers to the questions you have.</p><p>That&apos;s what happened to me when I was trying to deploy one small web service built on Flask to AWS ECS service. It just would not work in a way I wanted it to. So, let&apos;s talk about it!</p><p><a href="https://github.com/mjurenka/example-docker-gunicorn-flask"><em>All the code I&apos;m showing is available on GitHub here.</em></a></p><p>Let&apos;s assume some basic Hello World Flask app like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">from flask import Flask
app = Flask(__name__)

app.config[&apos;DEBUG&apos;] = True

@app.route(&quot;/&quot;)
def hello_world():
    return &quot;Hello, World!&quot;

if __name__ == &quot;__main__&quot;:
    # use 0.0.0.0 to use it in container
    app.run(host=&apos;0.0.0.0&apos;)</code></pre><figcaption>app/app.py</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>flask==1.0.2</code></pre><figcaption>requirements.txt</figcaption></figure><p>It&apos;s simple enough. For fast and easy development we can use built-in development server from Flask, however it is not production-ready server. Let&apos;s see how we can make a Dockerfile that would use this built-in server:</p><figure class="kg-card kg-code-card"><pre><code class="language-docker">FROM python:3.8-slim
RUN mkdir /app
WORKDIR /app
ADD requirements.txt /app
RUN pip3 install -r requirements.txt
ADD . /app
EXPOSE 5000
ENTRYPOINT [&quot;python&quot;, &quot;app/app.py&quot;]</code></pre><figcaption>Dockerfile</figcaption></figure><p>You can see the code for this <a href="https://github.com/mjurenka/example-docker-gunicorn-flask">here</a>. If we run the container and send requests to it, we can see it is working:</p><pre><code class="language-shell">docker build --tag flask-app .
docker run -p 5000:5000 flask-app
curl http://localhost:5000</code></pre><p>This is good enough for development purposes, but let&apos;s say you need to take this app to production. This server simply cannot handle the workload, it works on just one thread and multiple users using application that is a bit more complicated, for example accesses a database, will be extremely slow, will hang and eventually might crash.</p><p>What you want to use in this case is WSGI server. You can read more about WSGI servers <a href="https://www.fullstackpython.com/wsgi-servers.html">here</a>. We will be using Gunicorn as our WSGI server for this example. Let&apos;s rework out application now to use it.</p><p>First suggestion I have is to make a separate file where we will load our application and prepare it for running in production. That way it&apos;s detached from your app and it avoids name space confusion, since almost everything is named &quot;app&quot; &#x1F604;. Let&apos;s make new file named &quot;wsgi.py&quot;:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">from .app import app

# do some production specific things to the app
app.config[&apos;DEBUG&apos;] = False</code></pre><figcaption>app/wsgi.py</figcaption></figure><p>And we should also add Gunicorn to our requirements.txt, create Gunicorn config file and update Dockerfile to &#xA0;run the app on Gunicorn.</p><figure class="kg-card kg-code-card"><pre><code>flask==1.0.2
gunicorn==20.0.4</code></pre><figcaption>requirements.txt</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>bind = &quot;0.0.0.0:5000&quot;
workers = 4
threads = 4
timeout = 120</code></pre><figcaption>gunicorn_config.py</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-docker">FROM python:3.8-slim
RUN mkdir /app
WORKDIR /app
ADD requirements.txt /app
RUN pip3 install -r requirements.txt
ADD . /app
EXPOSE 5000
ENTRYPOINT [&quot;gunicorn&quot;, &quot;--config&quot;, &quot;gunicorn_config.py&quot;, &quot;app.wsgi:app&quot;]</code></pre><figcaption>Dockerfile</figcaption></figure><p>You can get the code for this right <a href="https://github.com/mjurenka/example-docker-gunicorn-flask/tree/gunicorn">here</a>. We will build it and will run it the same way as before. Now we have python application that is much sturdier in production environment, it&apos;s much faster &#xA0;serving responses when there are more users using your app since it is using more threads and has multiple workers to serve those requests.</p><p>However this is not perfect solution and it has some issues that will have to be solved:</p><h3 id="no-ssl-support">No SSL support</h3><p>This is a big issue in day of modern web development. Everything should be encrypted. Everything. It is no one&apos;s business to see unencrypted data traversing The Net. Gunicorn supports SSL, but what I like to do is to use <a href="https://www.nginx.com">NGINX</a> as a reverse proxy in front of Gunicorn. This way Nginx can handle web server tasks and Gunicorn can handle application tasks. You will have to configure Nginx to use SSL, but Nginx gives you so much more freedom and much more configuration options to configure everything the way you want it. Keep an eye on this blog, I have an article in the works about how to configure Nginx with Let&apos;s Encrypt certificates in Docker to make it easy to deploy.</p><h3 id="weird-issues-with-running-gunicorn-in-docker">Weird issues with running Gunicorn in Docker</h3><p>When I was deploying my last app, I found some inconsistent behavior in my Docker container. That was what has prompted this article. I just could not for the life of me get Gunicorn working in the container. It just would not start. After hours of trying almost everything and googling around, I found a way to fix it.</p><p>We need to put the command of starting Gunicorn to separate script file and then just invoke that script. It seems to solve all the issues. Let&apos;s make entrypoint.sh:</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">#!/bin/bash
exec gunicorn --config /app/gunicorn_config.py app.wsgi:app</code></pre><figcaption>entrypoint.sh</figcaption></figure><p>And update our Dockerfile:</p><pre><code class="language-docker">FROM python:3.8-slim
RUN mkdir /app
WORKDIR /app
ADD requirements.txt /app
RUN pip3 install -r requirements.txt
ADD . /app
EXPOSE 5000
RUN chmod +x ./entrypoint.sh
ENTRYPOINT [&quot;sh&quot;, &quot;entrypoint.sh&quot;]</code></pre><p>You can see the code for this <a href="https://github.com/mjurenka/example-docker-gunicorn-flask/tree/gunicorn-issue-fix">here</a>. After this change, Gunicorn now works properly and without any issues.</p><p>If you know why this is helping with running Gunicorn in Docker container, feel free to leave comment down below!</p><p>M.</p><p><em>Update: Redditor skiutoss pointed out some awesome ready-mage images from tiangolo on GitHub, specifically image <a href="https://github.com/tiangolo/meinheld-gunicorn-flask-docker">meinheld-gunicorn-flask-docker</a> could be a great starting point for porting your Flask app into Docker container.</em></p>]]></content:encoded></item><item><title><![CDATA[SSH Config for AWS]]></title><description><![CDATA[<p>How many times did you forget where is your key to login to AWS EC2 instance? How many times did you have to search in Slack or in your email history to get the IP address you needed to access client&apos;s remote server?</p><p>Tired of using ssh like</p>]]></description><link>https://blog.entirely.digital/ssh-config-for-aws/</link><guid isPermaLink="false">5ee8b1abc6add60001016346</guid><category><![CDATA[AWS]]></category><category><![CDATA[EC2]]></category><category><![CDATA[Linux]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Unix]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Tue, 16 Jun 2020 12:00:00 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2020/06/system-2660914_1920.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2020/06/system-2660914_1920.jpg" alt="SSH Config for AWS"><p>How many times did you forget where is your key to login to AWS EC2 instance? How many times did you have to search in Slack or in your email history to get the IP address you needed to access client&apos;s remote server?</p><p>Tired of using ssh like this?</p><pre><code class="language-shell">ssh -i ~/Downloads/example-dev-key.pem ubuntu@example.com</code></pre><p></p><p><strong>No more!</strong></p><p>You need to learn how to properly use config file for SSH. You can specify all the necessary details once and then just forget it. And let&apos;s be honest, we will forget it.</p><p>Only one thing is required: you need to have your private key, as the config does not support password authentication.</p><p>First we need to create our config file and set correct permissions.</p><pre><code class="language-shell">touch ~/.ssh/config
chmod 600 ~/.ssh/config</code></pre><p>We will be editing this file from now on.</p><h3 id="how-to-configure-access-to-aws-ec2-instance">How to configure access to AWS EC2 instance</h3><p>First of all, you should copy your key somewhere safe, I have decided to copy it to my .ssh folder so I can keep everything together as it makes it easier for me.</p><p>Example config entry:</p><pre><code>host ec2-example-dev
 Hostname example.com
 Port 22
 IdentityFile ~/.ssh/example-dev.pem
 User ubuntu</code></pre><p>Lets break it down:</p><ul><li>host: this is the name you choose for your server. You will use it to ssh to it.</li><li>Hostname: this is the hostname or IP address of the server</li><li>Port: port where the SSH server is running, defaults to 22</li><li>IdentityFile: this is why we are doing this, it allows us to specify key for our ssh connection</li><li>User: name of the user which will be connection to the server</li></ul><p>To connect to our server, we simply call ssh like this:</p><pre><code class="language-shell">ssh ec2-example-dev</code></pre><p>Isn&apos;t it easier? Easy to remember. Now I just have to make sure I don&apos;t forget how I named my servers &#x1F605;.</p><h3 id="how-to-configure-access-to-servers-with-your-key">How to configure access to servers with your key</h3><p>If you are using your regular ssh key (id_rsa) to access servers, you can make use of this configuration as well. You can set up custom names for different servers, set up ports and usernames for them.</p><p>How I ssh&apos;d before:</p><pre><code class="language-shell">ssh martin@example.com</code></pre><p>or if you are using custom port (which you should be using):</p><pre><code class="language-shell">ssh martin@example.com -p 2233</code></pre><p>We can simplify it with our config like this:</p><pre><code>host example
 Hostname example.com
 Port 2233
 User martin</code></pre><p>And we can ssh</p><pre><code class="language-shell">ssh example</code></pre><p></p><p>This shows the power of config file for SSH. It really helped me to manage all the servers I need to access on regular basis and honestly, EC2 instances with custom keys were the biggest pain to access, but now with well defined config file, it is a breeze to connect to them to manage and monitor them.</p><p>Learn more how to use ssh config files <a href="https://www.ssh.com/ssh/config/">here</a>.</p><p>M.</p>]]></content:encoded></item><item><title><![CDATA[Backticks in `Swift`]]></title><description><![CDATA[Have a nicer codebase by using backticks in Swift.]]></description><link>https://blog.entirely.digital/swift-backticks/</link><guid isPermaLink="false">5d44221fc4c0680001412b57</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Sun, 04 Aug 2019 20:00:00 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2019/08/backtick.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2019/08/backtick.png" alt="Backticks in `Swift`"><p>The backtick ` in Swift can be used in variety of ways. It allows you to create identifiers from language keywords.</p><p>One of the most important uses is the interaction with other languages that have different reserved keywords.</p><p>From Swift you can call C and Objective-C functions directly, but imagine you want to call C function that is named <em>guard</em>. You cannot call it without a backtick:</p><pre><code class="language-swift">`guard`()</code></pre><p>Perhaps better showcase would be an enum. You might want to create an enum with following values:</p><pre><code class="language-swift">enum Foo {
    case `var`
    case `let`
    case `class`
    case `func`
}

let items = [Foo.class, Foo.let, .var]</code></pre><p>Using backticks enables you to use reserved language keywords as you see fit.</p><p>If you want to replicate Apple&apos;s API design in your codebase and use the <em>default</em> keyword as a property name, you&apos;ll have to use a backtick. For example NotificationCenter uses singleton called <em>default</em>, but <em>default</em> is also a keyword used in switch statements. To implement your own property with name <em>default</em>, you can use something like this:</p><pre><code class="language-swift">class MyOwnNotificationCenter {
    static let `default` = MyOwnNotificationCenter()
    ...
}</code></pre><p>Have you ever used <em>[weak self]</em> in a closure? If not, you should! But once you make weak reference to <em>self</em>, <em>self</em> becomes optional and sometimes it can be a pain to work with it. Before Swift 4.2 you had to do something like this to keep strong reference to <em>self</em> and keep it&apos;s name (or change the name and use <em>strongSelf</em> variable name):</p><pre><code class="language-swift">self.provider.fetchData { [weak self] (fetchedData) in
    guard let `self` = self else { return }
    let processedData = self.processData(fetchedData)
    self.updateUI(with: processedData)
}</code></pre><p><a href="https://github.com/apple/swift-evolution/blob/master/proposals/0079-upgrade-self-from-weak-to-strong.md">But as of Swift 4.2 you can now do something like this</a>:</p><pre><code class="language-swift">self.provider.fetchData { [weak self] (fetchedData) in
    guard let self = self else { return }
    let processedData = self.processData(fetchedData)
    self.updateUI(with: processedData)
}</code></pre><p>You can now upgrade reference from weak to strong using<em> </em>optional binding and it is now consistent with other optional variables and properties used in your codebase.</p><p>Those are just few examples for using backticks, but I think you get the idea. It is really neat that we have a way to name our identifiers how we want, even if they are same as language-reserved keywords. </p><p>We all know that naming variables is one of the hardest things in programming &#x1F609;.</p><p>M.</p>]]></content:encoded></item><item><title><![CDATA[Welcome to my new Blog!]]></title><description><![CDATA[It feels good to finally have my own blog. I should have done it long time ago.]]></description><link>https://blog.entirely.digital/new-blog/</link><guid isPermaLink="false">5d441312c4c0680001412b24</guid><category><![CDATA[General]]></category><dc:creator><![CDATA[Martin]]></dc:creator><pubDate>Fri, 02 Aug 2019 10:00:00 GMT</pubDate><media:content url="https://blog.entirely.digital/content/images/2019/08/beverage-3157395_1920.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.entirely.digital/content/images/2019/08/beverage-3157395_1920.jpg" alt="Welcome to my new Blog!"><p>It feels good to finally have my own blog. I should have done it long time ago.</p><p>This blog is <a href="https://ghost.org/">Ghost blogging platform</a> running on <a href="https://www.docker.com/">Docker</a>, with <a href="https://www.nginx.com/">Nginx</a> web server, <a href="https://letsencrypt.org/">Let&apos;s Encrypt</a> certificates, all hosted on <a href="https://digitalocean.com">Digital Ocean</a>.</p><p>I look forward to writing some (perhaps) interesting articles about new things I have learned in iOS development and share some (maybe basic) knowledge of Docker. If you have better knowledge than me in topics I write about, don&apos;t hesitate to share your insights! We all are here to learn.</p><p>See you in next article!</p><p>M.</p>]]></content:encoded></item></channel></rss>