@@ -9,6 +9,36 @@
Loading
9 9
from cookiecutter.prompt import read_repo_password
10 10
from cookiecutter.utils import make_sure_path_exists, prompt_and_delete
11 11
12 +
try:
13 +
    import boto3
14 +
except ImportError:
15 +
    pass
16 +
17 +
18 +
def is_s3_uri(zip_uri):
19 +
    """Return True if the ZIP file's URI points to S3."""
20 +
    return zip_uri.startswith('s3://')
21 +
22 +
23 +
def s3_paths(s3_uri):
24 +
    """Split an S3 URI into a bucket name and object name."""
25 +
    return s3_uri.replace('s3://', '').split('/', 1)
26 +
27 +
28 +
def s3_identifier(s3_uri):
29 +
    """Return the basename of an S3 URL."""
30 +
    _, obj = s3_paths(s3_uri)
31 +
    return obj.rsplit('/', 1)[1]
32 +
33 +
34 +
def presign_s3_uri(s3_uri, expiration=600):
35 +
    """Return a presigned URI for an S3 object."""
36 +
    s3 = boto3.client('s3')
37 +
    bucket, obj = s3_paths(s3_uri)
38 +
    return s3.generate_presigned_url(
39 +
        'get_object', Params={'Bucket': bucket, 'Key': obj}, ExpiresIn=expiration,
40 +
    )
41 +
12 42
13 43
def unzip(zip_uri, is_url, clone_to_dir='.', no_input=False, password=None):
14 44
    """Download and unpack a zipfile at a given URI.
@@ -30,7 +60,11 @@
Loading
30 60
    if is_url:
31 61
        # Build the name of the cached zipfile,
32 62
        # and prompt to delete if it already exists.
33 -
        identifier = zip_uri.rsplit('/', 1)[1]
63 +
        if is_s3_uri(zip_uri):
64 +
            identifier = s3_identifier(zip_uri)
65 +
            zip_uri = presign_s3_uri(zip_uri)
66 +
        else:
67 +
            identifier = zip_uri.rsplit('/', 1)[1]
34 68
        zip_path = os.path.join(clone_to_dir, identifier)
35 69
36 70
        if os.path.exists(zip_path):

@@ -23,6 +23,11 @@
Loading
23 23
    return bool(REPO_REGEX.match(value))
24 24
25 25
26 +
def is_s3_url(value):
27 +
    """Return True if the value is a URL to an S3 resource."""
28 +
    return value.startswith('s3://')
29 +
30 +
26 31
def is_zip_file(value):
27 32
    """Return True if value is a zip file."""
28 33
    return value.lower().endswith('.zip')
@@ -95,7 +100,7 @@
Loading
95 100
    if is_zip_file(template):
96 101
        unzipped_dir = unzip(
97 102
            zip_uri=template,
98 -
            is_url=is_repo_url(template),
103 +
            is_url=is_repo_url(template) or is_s3_url(template),
99 104
            clone_to_dir=clone_to_dir,
100 105
            no_input=no_input,
101 106
            password=password,
Files Coverage
cookiecutter 99.76%
__main__.py 100.00%
Project Totals (19 files) 99.76%
2985.3
TRAVIS_PYTHON_VERSION=3.7
TRAVIS_OS_NAME=linux
TOXENV=py37
2985.4
TRAVIS_PYTHON_VERSION=3.8
TRAVIS_OS_NAME=linux
TOXENV=py38
2985.2
TRAVIS_PYTHON_VERSION=3.6
TRAVIS_OS_NAME=linux
TOXENV=py36
2985.7
TRAVIS_OS_NAME=windows
TOXENV=py38
2985.5
TRAVIS_OS_NAME=windows
TOXENV=py36
2985.6
TRAVIS_OS_NAME=windows
TOXENV=py37
1
# comment spam as user can always click the failed coverage check
2
comment: false
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading