Skip to content

Commit 01eca55

Browse files
Use standard distribution file naming convention (#8)
* Improve POC script logging * Change default output filename to align with de facto standards for underscores * Use PEP-427 distribution file naming
1 parent 53cbb16 commit 01eca55

File tree

10 files changed

+70
-19
lines changed

10 files changed

+70
-19
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ poetry bundle venv .build/.venv --without dev
2020
package-python-function .build/.venv --output-dir .build/lambda
2121
```
2222

23-
The output will be a .zip file with the same name as your project from your pyproject.toml file.
23+
The output will be a .zip file with the same name as your project from your pyproject.toml file (with dashes replaced
24+
with underscores).
2425

2526
## Installation
2627
Use [pipx](https://github.com/pypa/pipx) to install:
@@ -40,7 +41,7 @@ One of the following must be specified:
4041
- `--output`: The full output path of the final zip file.
4142

4243
- `--output-dir`: The output directory for the final zip file. The name of the zip file will be based on the project's
43-
name in the pyproject.toml file.
44+
name in the pyproject.toml file (with dashes replaced with underscores).
4445

4546

4647

package_python_function/packager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, venv_path: Path, project_path: Path, output_dir: Path, output
1818
self.venv_path = venv_path
1919

2020
self.output_dir = output_file.parent if output_file else output_dir
21-
self.output_file = output_file if output_file else output_dir / f'{self.project.name}.zip'
21+
self.output_file = output_file if output_file else output_dir / f'{self.project.distribution_name}.zip'
2222

2323
self._uncompressed_bytes = 0
2424

package_python_function/python_project.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pathlib import Path
33
from typing import Optional
44
import tomllib
5+
import re
56

67

78
class PythonProject:
@@ -16,14 +17,24 @@ def name(self) -> str:
1617
('tool', 'poetry', 'name'),
1718
))
1819

20+
"""
21+
Get the normalized name of the distribution, according to the Python Packaging Authority (PyPa) guidelines.
22+
This is used to create the name of the zip file.
23+
The name is normalized by replacing any non-alphanumeric characters with underscores.
24+
https://peps.python.org/pep-0427/#escaping-and-unicode
25+
"""
26+
@cached_property
27+
def distribution_name(self) -> str:
28+
return re.sub("[^\w\d.]+", "_", self.name, re.UNICODE)
29+
1930
@cached_property
2031
def entrypoint_package_name(self) -> str:
2132
"""
2233
The subdirectory name in the source virtual environment's site-packages that contains the function's entrypoint
2334
code.
2435
"""
2536
# TODO : Parse out the project's package dir(s) if defined. Use the first one if there are multiple.
26-
return self.name.replace('-', '_')
37+
return self.distribution_name
2738

2839
def find_value(self, paths: tuple[tuple[str]]) -> str:
2940
for path in paths:
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1+
import logging
2+
3+
logger = logging.getLogger(__name__)
4+
5+
logger.info("Load")
6+
17
def other_package_module():
2-
print("other_package_module")
8+
logger.info("Hello from other_package_module")

scripts/poc/inner_package/zip_in_zip_test/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# This file represents the original module's __init__.py file that gets renamed when creating the innner ZIP.
22

3-
print("__init__ original")
3+
import logging
4+
5+
logger = logging.getLogger(__name__ + "(__init__.py ORIGINAL)")
6+
7+
logger.info("Hello, I am the original pacakge __init__.py")
48

59
GLOBAL_VALUE_IN_INIT_ORIGINAL = "This global is defined in the original __init__.py"
610

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
print("main.py: Load")
1+
import logging
2+
3+
logger = logging.getLogger(__name__)
4+
5+
logger.info("main.py: Load")
26

37
from zip_in_zip_test import GLOBAL_VALUE_IN_INIT_ORIGINAL, other_module_function
48
from other_package.other_package_module import other_package_module
59

610
def main():
7-
print("Hello from main!")
8-
print(GLOBAL_VALUE_IN_INIT_ORIGINAL)
11+
logger.info("Hello from main!")
12+
logger.info(GLOBAL_VALUE_IN_INIT_ORIGINAL)
913
other_module_function()
1014
other_package_module()
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1+
import logging
2+
3+
logger = logging.getLogger(__name__)
4+
5+
logger.info("Load")
6+
17
def other_module_function():
2-
print("I'm in other_module_function")
8+
logger.info("I'm in other_module_function")

scripts/poc/lambda-runner.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
# This is my best attempt at simulating what AWS Lambda does
22
# Instead of messing with zipping and unzipping in this experiment, I just copy the files to the .test directory.
33

4+
import logging
45
from pathlib import Path
56
import shutil
67
import sys
78

8-
print('[lambda-runner]')
9-
print('sys.path:', sys.path)
9+
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
10+
logger = logging.getLogger("lambda-runner.py")
11+
12+
logger.info('BEGIN')
1013

1114
module_path = Path(__file__).parent
1215
TEST_DIR = module_path / ".test"
1316
PACKAGE_NAME = "zip_in_zip_test"
1417
TEST_PACKAGE_DIR = TEST_DIR / PACKAGE_NAME
1518

1619
shutil.rmtree(TEST_DIR, ignore_errors=True)
20+
21+
# Copy the stub `zip_in_zip_test` package to the .test directory. This simulates the outer ZIP extraction
22+
# that lambda will do. This is the module that Lambda will import, and where we will do the inner ZIP extraction.
1723
shutil.copytree(str(module_path / PACKAGE_NAME), str(TEST_PACKAGE_DIR))
24+
25+
# Copy the inner package to the .test directory. This simulates the inner ZIP file, but without actually dealing
26+
# with zip/unzip in this experiemen.
1827
shutil.copytree(str(module_path / "inner_package"), str(TEST_PACKAGE_DIR / ".inner_package"))
1928

2029
sys.path.insert(0, str(TEST_DIR))
2130

31+
logger.info('--- Importing entrypoint module ---')
2232
import importlib
2333
module = importlib.import_module('zip_in_zip_test.main')
34+
logger.info('--- Calling entryoint function ---')
2435
module.__dict__['main']()

scripts/poc/zip_in_zip_test/__init__.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
# This works perfectly!
2-
3-
print('zip_in_zip_test.__init__: BEGIN. This is the loader.')
4-
print("module_path:", __file__)
5-
1+
"""
2+
Demonstrate how we can swap out the content an entire package during the __init__.py load process of that package.
3+
"""
64
from pathlib import Path
75
import importlib
86
import sys
7+
import logging
8+
9+
logger = logging.getLogger(__name__ + "(__init__.py loader)")
10+
11+
logger.info(f'BEGIN. This is the loader. {__file__}')
912

1013
module_path = Path(__file__).parent
1114

15+
# This is where we would unzip the inner ZIP file. For this experiment, we can skip actually doing that and pretend
16+
# that it was extracted to .inner_package/
17+
1218
# This works if I insert at zero.
1319
# Why does the serverless-python-requirements insist on inserting at 1?
1420
# From https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-searchpath:
@@ -17,6 +23,7 @@
1723

1824
# This also works. I am thinking this is the best way, because we need to unmount the original decompressed directory
1925
# since it contains the load __init__.py.
26+
previous_sys_path_root = sys.path[0]
2027
sys.path[0] = str(module_path / ".inner_package")
2128

2229

@@ -30,6 +37,7 @@
3037
# importlib.import_module(__name__)
3138

3239
# This also works. I think this is the best way.
40+
logger.info(f'Reloading {__name__} after switching {previous_sys_path_root} to {sys.path[0]}.')
3341
importlib.reload(sys.modules[__name__])
3442

35-
print('zip_in_zip_test.__init__: END')
43+
logger.info('END')

tests/test_package_python_function.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ def test_package_python_function(tmp_path: Path) -> None:
3434
]
3535
main()
3636

37-
assert (output_dir_path / 'project-1.zip').exists()
37+
assert (output_dir_path / 'project_1.zip').exists()

0 commit comments

Comments
 (0)