Skip to content

Commit 0d9ec80

Browse files
committed
Quote exclusion regexes properly
* Regex-quote arbitrary files added to exclusion list * Add tests around this behaviour demonstrating regex nature * Update examples and docs to demonstrate this * Use immutable keyword parameters defaults too
1 parent 53cf396 commit 0d9ec80

File tree

7 files changed

+95
-23
lines changed

7 files changed

+95
-23
lines changed

Diff for: README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@ python setup.py install
1717

1818
### Configuration File
1919
The lambda uploader expects a directory with, at a minimum, your lambda function
20-
and a lambda.json file. It is not necessary to set requirements in your config
20+
and a `lambda.json` file. It is not necessary to set requirements in your config
2121
file since the lambda uploader will also check for and use a requirements.txt file.
2222

23-
Please note that you can leave the "vpc" object out of your config if you want your
23+
Please note that you can leave the `vpc` object out of your config if you want your
2424
lambda function to use your default VPC and subnets. If you wish to use your lambda
2525
function inside a specific VPC, make sure you set up the role correctly to allow this.
2626

27-
Example lambda.json file:
27+
Note also the `ignore` entry is an array of regular expression strings
28+
used to match against the relative paths - be careful to quote accordingly.
29+
For example, a traditional `*.txt` "glob" is matched by the JSON string:
30+
`".*\\.txt$"` (or just `"\\.txt$"`).
31+
32+
Example `lambda.json` file:
2833
```json
2934
{
3035
"name": "myFunction",
@@ -35,9 +40,9 @@ Example lambda.json file:
3540
"role": "arn:aws:iam::00000000000:role/lambda_basic_execution",
3641
"requirements": ["pygithub"],
3742
"ignore": [
38-
"circle.yml",
39-
".git",
40-
"/*.pyc"
43+
"circle\\.yml$",
44+
"\\.git$",
45+
"/.*\\.pyc$"
4146
],
4247
"timeout": 30,
4348
"memory": 512,

Diff for: example/lambda.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"role": "arn:aws:iam::00000000000:role/lambda_basic_execution",
77
"requirements": ["Jinja2==2.8"],
88
"ignore": [
9-
"circle.yml",
10-
".git",
11-
"/*.pyc"
9+
"circle\\.yml$",
10+
"\\.git$",
11+
"/.*\\.pyc$"
1212
],
1313
"timeout": 30,
1414
"memory": 512

Diff for: lambda_uploader/package.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
import zipfile
1818
import logging
1919
import sys
20+
import re
2021

2122
from subprocess import Popen, PIPE
2223
from lambda_uploader import utils
2324
from distutils.spawn import find_executable
2425

25-
# Python 2/3 compatability
26+
# Python 2/3 compatibility
2627
try:
2728
basestring
2829
except NameError:
@@ -34,8 +35,8 @@
3435
ZIPFILE_NAME = 'lambda_function.zip'
3536

3637

37-
def build_package(path, requires, virtualenv=None, ignore=[],
38-
extra_files=[], zipfile_name=ZIPFILE_NAME):
38+
def build_package(path, requires, virtualenv=None, ignore=None,
39+
extra_files=None, zipfile_name=ZIPFILE_NAME):
3940
'''Builds the zip file and creates the package with it'''
4041
pkg = Package(path, zipfile_name)
4142

@@ -69,7 +70,7 @@ def __init__(self, path, zipfile_name=ZIPFILE_NAME):
6970
self._requirements_file = os.path.join(self._path, "requirements.txt")
7071
self._extra_files = []
7172

72-
def build(self, ignore=[]):
73+
def build(self, ignore=None):
7374
'''Calls all necessary methods to build the Lambda Package'''
7475
self._prepare_workspace()
7576
self.install_dependencies()
@@ -214,7 +215,7 @@ def _install_requirements(self):
214215
if prc.returncode is not 0:
215216
raise Exception('pip returned unsuccessfully')
216217

217-
def package(self, ignore=[]):
218+
def package(self, ignore=None):
218219
"""
219220
Create a zip file of the lambda script and its dependencies.
220221
@@ -223,6 +224,7 @@ def package(self, ignore=[]):
223224
those files when creating the zip file. The paths to be matched are
224225
local to the source root.
225226
"""
227+
ignore = ignore or []
226228
package = os.path.join(self._temp_workspace, 'lambda_package')
227229

228230
# Copy site packages into package base
@@ -243,13 +245,13 @@ def package(self, ignore=[]):
243245
utils.copy_tree(lib64_path, package)
244246

245247
# Append the temp workspace to the ignore list:
246-
ignore += ["^%s/*" % TEMP_WORKSPACE_NAME]
248+
ignore.append(r"^%s/.*" % re.escape(TEMP_WORKSPACE_NAME))
247249
utils.copy_tree(self._path, package, ignore)
248250

249251
# Add extra files
250252
for p in self._extra_files:
251253
LOG.info('Copying extra %s into package' % p)
252-
ignore += ["%s" % p]
254+
ignore.append(re.escape(p))
253255
if os.path.isdir(p):
254256
utils.copy_tree(p, package, ignore=ignore, include_parent=True)
255257
else:

Diff for: lambda_uploader/utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
LOG = logging.getLogger(__name__)
2222

2323

24-
def copy_tree(src, dest, ignore=[], include_parent=False):
24+
def copy_tree(src, dest, ignore=None, include_parent=False):
25+
ignore = ignore or []
2526
if os.path.isfile(src):
2627
raise Exception('Cannot use copy_tree with a file as the src')
2728

@@ -55,7 +56,8 @@ def copy_tree(src, dest, ignore=[], include_parent=False):
5556

5657
# Iterate through every item in ignore
5758
# and check for matches in the path
58-
def _ignore_file(path, ignore=[]):
59+
def _ignore_file(path, ignore=None):
60+
ignore = ignore or []
5961
if not ignore:
6062
return False
6163
for ign in ignore:

Diff for: tests/extra/.dotfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Nothing here

Diff for: tests/test_package.py

+40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import shutil
23
import sys
34
import pytest
45

@@ -9,6 +10,7 @@
910
TESTING_TEMP_DIR = '.testing_temp'
1011
WORKING_TEMP_DIR = path.join(TESTING_TEMP_DIR, '.lambda_uploader_temp')
1112
PACKAGE_TEMP_DIR = path.join(WORKING_TEMP_DIR, 'lambda_package')
13+
DOTFILE_REGEX = r'^\.[^.].*'
1214

1315

1416
def setup_module(module):
@@ -21,6 +23,10 @@ def teardown_module(module):
2123
rmtree(TESTING_TEMP_DIR)
2224

2325

26+
def setup():
27+
shutil.rmtree(PACKAGE_TEMP_DIR, True)
28+
29+
2430
def test_package_zip_location():
2531
pkg = package.Package(TESTING_TEMP_DIR)
2632
assert pkg.zip_file == '.testing_temp/lambda_function.zip'
@@ -139,6 +145,40 @@ def test_package_with_extras():
139145
expected_extra_file2 = path.join(PACKAGE_TEMP_DIR, 'extra/foo/__init__.py')
140146
assert path.isfile(expected_extra_file2)
141147

148+
# test a hidden file
149+
expected_dotfile = path.join(PACKAGE_TEMP_DIR, 'extra', '.dotfile')
150+
assert path.isfile(expected_dotfile)
151+
152+
153+
def test_package_with_ignores():
154+
pkg = package.Package(TESTING_TEMP_DIR)
155+
pkg.extra_file(path.join('tests', 'extra'))
156+
pkg.package(ignore=[DOTFILE_REGEX])
157+
158+
# test ignored file is *not* there
159+
dotfile = path.join(PACKAGE_TEMP_DIR, 'extra', '.dotfile')
160+
assert not path.exists(dotfile)
161+
162+
163+
def test_ignores_using_all_items_and_regex():
164+
pkg = package.Package(TESTING_TEMP_DIR)
165+
pyc_path = path.join(TESTING_TEMP_DIR, 'fake.pyc')
166+
py_path = path.join(TESTING_TEMP_DIR, 'real.py')
167+
open(py_path, 'w').close()
168+
open(pyc_path, 'w').close()
169+
170+
pkg.package([r"dummy.*", r'[a-z]+\.pyc'])
171+
172+
os.remove(py_path)
173+
os.remove(pyc_path)
174+
175+
# test the ignores has excluded the .pyc
176+
expected_extra_file = path.join(PACKAGE_TEMP_DIR, 'fake.pyc')
177+
assert not path.exists(expected_extra_file)
178+
179+
# ...but the path not affected by either ignore entry remains
180+
assert path.exists(path.join(PACKAGE_TEMP_DIR, 'real.py'))
181+
142182

143183
def test_package_name():
144184
pkg = package.Package(TESTING_TEMP_DIR, zipfile_name='test.zip')

Diff for: tests/test_utils.py

+27-5
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919
]
2020

2121
TEST_IGNORE = [
22-
'ignore/*',
22+
'ignore/.*',
2323
'ignore-me.py',
2424
# The one below would exclude all the files if files to be ignored were
2525
# matched based on the full path, rather than the path relative to the
2626
# source directory, since the source directory contains 'test' in its path.
2727
'tests',
2828
]
2929

30+
IGNORE_TEMP = [
31+
r'^\.[^.].*',
32+
r'.*\.pyc$'
33+
]
34+
3035

3136
def test_copy_tree():
3237
os.mkdir(TESTING_TEMP_DIR)
@@ -49,8 +54,25 @@ def test_copy_tree():
4954

5055

5156
def test_ignore_file():
52-
result = utils._ignore_file('ignore/foo.py', TEST_IGNORE)
53-
assert result
57+
ignored = utils._ignore_file('ignore/foo.py', TEST_IGNORE)
58+
assert ignored
59+
60+
ignored = utils._ignore_file('ignore', TEST_IGNORE)
61+
assert not ignored
62+
63+
ignored = utils._ignore_file('bar/foo.py', TEST_IGNORE)
64+
assert not ignored
65+
66+
67+
def test_ignore_file_dotfile():
68+
ignored = utils._ignore_file('.mydotfile', IGNORE_TEMP)
69+
assert ignored
70+
71+
72+
def test_ignore_pyc():
73+
ignored = utils._ignore_file('pycharm', IGNORE_TEMP)
74+
assert not ignored
75+
76+
ignored = utils._ignore_file('bar/foo.pyc', IGNORE_TEMP)
77+
assert ignored
5478

55-
res = utils._ignore_file('bar/foo.py', TEST_IGNORE)
56-
assert res is False

0 commit comments

Comments
 (0)