Download a zip archive and extract one file from it I wrote a function that downloads a file https://www.ohjelmointiputka.net/tiedostot/junar.zip if it not already downloaded, unzips it and returns the content of junar1.in found in this zip. I have PEP8 complaints about the length of lines that I would like to fix. Is there a way to make the code more readable?

My code :

import os.path
import urllib.request
import shutil
import zipfile


def download_and_return_content():
    if not os.path.isfile('/tmp/junar.zip'):
        url = 'https://www.ohjelmointiputka.net/tiedostot/junar.zip'
        with urllib.request.urlopen(url) as response, open('junar.zip', 'wb') as out:
            data = response.read() # a `bytes` object
            out.write(data)
        shutil.move('junar.zip','/tmp')
    with zipfile.ZipFile('/tmp/junar.zip', 'r') as zip_ref:
        zip_ref.extractall('/tmp/')
    with open('/tmp/junar1.in') as f:
        return f.read()
PEP-8 Line Length

For particularly lengthy lines, you can always use parentheses to wrap them instead of the usual \ sign:

x = (1 + 2 + 3 +
     4 + 5 + 6)


Function Refactor

I would skip the step in your if statement where you use shutil.move, just save the file in /tmp directly:

def download_and_return_content():
    if not os.path.isfile('/tmp/junar.zip'):
        url = 'https://www.ohjelmointiputka.net/tiedostot/junar.zip'
        with urllib.request.urlopen(url) as response, open('/tmp/junar.zip', 'wb') as out:
            data = response.read() # a `bytes` object
            out.write(data)


Furthermore, if you are just looking to extract a single file, you can open one of the archives directly using ZipFile.open

    with ZipFile('/tmp/junar.zip') as myzip:
        with myzip.open('junar1.in') as f:
            return f.read()


ZipFile can also take a file-like object, so you can use a BytesIO object to hold your zip-file bytes, since /tmp implies you might not need to hold onto this data:

from io import BytesIO

def download_and_return_content():
    # your temporary file-handle
    tmp_file = BytesIO()

    url = 'https://www.ohjelmointiputka.net/tiedostot/junar.zip'
    with urllib.request.urlopen(url) as response:
        tmp_file.write(response.read())

    tmp_file.seek(0)

    with ZipFile(tmp_file) as myzip:
        with myzip.open('junar1.in') as fh:
            return fh.read()


Lastly, the if check implies that maybe you want to cache the data somehow. You could in theory use BytesIO as a mutable default. You can use BytesIO.tell() as your check if it has content:

def get_content(tmp=BytesIO()):
    # buffer is at position 0, it has not been read or written to
    # therefore it is probably empty
    if not tmp.tell():
        tmp.truncate() # just in case
        url = 'https://www.ohjelmointiputka.net/tiedostot/junar.zip'
        with urllib.request.urlopen(url) as response:
            tmp.write(response.read())

    # set buffer to position 0 to read content
    tmp.seek(0)

    with ZipFile(tmp) as myzip:
        # this will move the buffer to a non-zero position
        # so, now tmp.tell() will be non-zero and will pass the
        # if check on the next function call
        with myzip.open('junar1.in') as fh:
            return fh.read()


As a caveat, there are caching libraries in python that can accomplish this as well, I'm just not familiar enough with them to suggest any in a meaningful way.

Before everybody grabs their torches and pitchforks, the non-mutable-default way (usually mutable defaults are seen as bad design) could look something like this:

# refactor into two functions, one that does the actual urllib call
# for you to retrieve your data
def get_data(tmp=None):
    tmp = tmp or BytesIO()

    with urllib.request.urlopen(url) as response:
        tmp.write(response.read())

    return tmp

# and one to actually extract the file
def extract_file(tmp=None):
    tmp = tmp or get_data()

    tmp.seek(0)

    with ZipFile(tmp) as myzip:
        with myzip.open('junar1.in') as fh:
            return fh.read()


# now you can hold that BytesIO object
tmp_file = get_data()

content = extract_file(tmp_file)
# returns b'10\r\n6\r\n1\r\n4\r\n10\r\n7\r\n2\r\n3\r\n9\r\n5\r\n8\r\n'

# and if you want to write that temp file somewhere
with open('/tmp/somefile.zip', 'wb') as fh:
    tmp_file.seek(0)
    fh.write(tmp_file.read())


Of course this all depends on what you need that zipfile for, but this cuts down on the amount of reads and writes you are doing under the hood.