Passed
Push — master ( 47312b...0d4b48 )
by Andrey
01:13
created

cookiecutter.zipfile.unzip()   F

Complexity

Conditions 17

Size

Total Lines 100
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 100
rs 1.8
c 0
b 0
f 0
cc 17
nop 5

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like cookiecutter.zipfile.unzip() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Utility functions for handling and fetching repo archives in zip format."""
2
3
from __future__ import absolute_import
4
5
import os
6
import tempfile
7
from zipfile import ZipFile
8
9
import requests
10
11
try:
12
    # BadZipfile was renamed to BadZipFile in Python 3.2.
13
    from zipfile import BadZipFile
14
except ImportError:
15
    from zipfile import BadZipfile as BadZipFile
16
17
from cookiecutter.exceptions import InvalidZipRepository
18
from cookiecutter.prompt import read_repo_password
19
from cookiecutter.utils import make_sure_path_exists, prompt_and_delete
20
21
22
def unzip(zip_uri, is_url, clone_to_dir='.', no_input=False, password=None):
23
    """Download and unpack a zipfile at a given URI.
24
25
    This will download the zipfile to the cookiecutter repository,
26
    and unpack into a temporary directory.
27
28
    :param zip_uri: The URI for the zipfile.
29
    :param is_url: Is the zip URI a URL or a file?
30
    :param clone_to_dir: The cookiecutter repository directory
31
        to put the archive into.
32
    :param no_input: Suppress any prompts
33
    :param password: The password to use when unpacking the repository.
34
    """
35
    # Ensure that clone_to_dir exists
36
    clone_to_dir = os.path.expanduser(clone_to_dir)
37
    make_sure_path_exists(clone_to_dir)
38
39
    if is_url:
40
        # Build the name of the cached zipfile,
41
        # and prompt to delete if it already exists.
42
        identifier = zip_uri.rsplit('/', 1)[1]
43
        zip_path = os.path.join(clone_to_dir, identifier)
44
45
        if os.path.exists(zip_path):
46
            download = prompt_and_delete(zip_path, no_input=no_input)
47
        else:
48
            download = True
49
50
        if download:
51
            # (Re) download the zipfile
52
            r = requests.get(zip_uri, stream=True)
53
            with open(zip_path, 'wb') as f:
54
                for chunk in r.iter_content(chunk_size=1024):
55
                    if chunk:  # filter out keep-alive new chunks
56
                        f.write(chunk)
57
    else:
58
        # Just use the local zipfile as-is.
59
        zip_path = os.path.abspath(zip_uri)
60
61
    # Now unpack the repository. The zipfile will be unpacked
62
    # into a temporary directory
63
    try:
64
        zip_file = ZipFile(zip_path)
65
66
        if len(zip_file.namelist()) == 0:
67
            raise InvalidZipRepository('Zip repository {} is empty'.format(zip_uri))
68
69
        # The first record in the zipfile should be the directory entry for
70
        # the archive. If it isn't a directory, there's a problem.
71
        first_filename = zip_file.namelist()[0]
72
        if not first_filename.endswith('/'):
73
            raise InvalidZipRepository(
74
                'Zip repository {} does not include '
75
                'a top-level directory'.format(zip_uri)
76
            )
77
78
        # Construct the final target directory
79
        project_name = first_filename[:-1]
80
        unzip_base = tempfile.mkdtemp()
81
        unzip_path = os.path.join(unzip_base, project_name)
82
83
        # Extract the zip file into the temporary directory
84
        try:
85
            zip_file.extractall(path=unzip_base)
86
        except RuntimeError:
87
            # File is password protected; try to get a password from the
88
            # environment; if that doesn't work, ask the user.
89
            if password is not None:
90
                try:
91
                    zip_file.extractall(path=unzip_base, pwd=password.encode('utf-8'))
92
                except RuntimeError:
93
                    raise InvalidZipRepository(
94
                        'Invalid password provided for protected repository'
95
                    )
96
            elif no_input:
97
                raise InvalidZipRepository(
98
                    'Unable to unlock password protected repository'
99
                )
100
            else:
101
                retry = 0
102
                while retry is not None:
103
                    try:
104
                        password = read_repo_password('Repo password')
105
                        zip_file.extractall(
106
                            path=unzip_base, pwd=password.encode('utf-8')
107
                        )
108
                        retry = None
109
                    except RuntimeError:
110
                        retry += 1
111
                        if retry == 3:
112
                            raise InvalidZipRepository(
113
                                'Invalid password provided ' 'for protected repository'
114
                            )
115
116
    except BadZipFile:
117
        raise InvalidZipRepository(
118
            'Zip repository {} is not a valid zip archive:'.format(zip_uri)
119
        )
120
121
    return unzip_path
122