1+ from pathlib import Path
2+ from tempfile import NamedTemporaryFile
3+ import zipfile
4+ import shutil
5+
6+ from .python_project import PythonProject
7+
8+
9+ class Packager :
10+ AWS_LAMBDA_MAX_UNZIP_SIZE = 262144000
11+
12+ def __init__ (self , venv_path : Path , project_path : Path , output_path : Path ):
13+ self .project = PythonProject (project_path )
14+ self .venv_path = venv_path
15+ self .output_path = output_path
16+ self ._uncompressed_bytes = 0
17+
18+ @property
19+ def output_file_path (self ) -> Path :
20+ if self .output_path .is_dir ():
21+ return self .output_path / f'{ self .project .name } .zip'
22+ return self .output_path
23+
24+ @property
25+ def input_path (self ) -> Path :
26+ python_paths = list ((self .venv_path / 'lib' ).glob ('python*' ))
27+ if not python_paths :
28+ raise Exception ("input_path" )
29+ return python_paths [0 ] / 'site-packages'
30+
31+ def package (self ) -> None :
32+ print ("Packaging:" , self .project .path )
33+ print ("Output:" , self .output_file_path )
34+ print ("Input:" , self .input_path )
35+ print ("Entrypoint Package name:" , self .project .entrypoint_package_name )
36+
37+ with NamedTemporaryFile () as dependencies_zip :
38+ self .zip_all_dependencies (Path (dependencies_zip .name ))
39+
40+ def zip_all_dependencies (self , target_path : Path ) -> None :
41+ print (f"Zipping to { target_path } ..." )
42+
43+ with zipfile .ZipFile (target_path , 'w' , zipfile .ZIP_DEFLATED ) as zip_file :
44+ def zip_dir (path : Path ) -> None :
45+ for item in path .iterdir ():
46+ if item .is_dir ():
47+ zip_dir (item )
48+ else :
49+ self ._uncompressed_bytes += item .stat ().st_size
50+ zip_file .write (item , item .relative_to (self .input_path ))
51+
52+ zip_dir (self .input_path )
53+
54+ compressed_bytes = target_path .stat ().st_size
55+
56+ print (f"Uncompressed size: { self ._uncompressed_bytes :,} bytes" )
57+ print (f"Compressed size: { compressed_bytes :,} bytes" )
58+
59+ if self ._uncompressed_bytes > self .AWS_LAMBDA_MAX_UNZIP_SIZE :
60+ print (f"The uncompressed size of the ZIP file is greater than the AWS Lambda limit of { self .AWS_LAMBDA_MAX_UNZIP_SIZE :,} bytes." )
61+ if (compressed_bytes < self .AWS_LAMBDA_MAX_UNZIP_SIZE ):
62+ print (f"The compressed size ({ compressed_bytes :,} ) is less than the AWS limit, so the nested-zip strategy will be used." )
63+ self .generate_nested_zip (target_path )
64+ else :
65+ print (f"TODO Error. The unzipped size it too large for AWS Lambda." )
66+ else :
67+ shutil .copy (str (target_path ), str (self .output_file_path ))
68+
69+ def generate_nested_zip (self , inner_zip_path : Path ) -> None :
70+ with zipfile .ZipFile (self .output_file_path , 'w' ) as outer_zip_file :
71+ entrypoint_dir = Path (self .project .entrypoint_package_name )
72+ outer_zip_file .write (
73+ inner_zip_path ,
74+ arcname = str (entrypoint_dir / ".dependencies.zip" ),
75+ compresslevel = zipfile .ZIP_STORED
76+ )
77+ outer_zip_file .writestr (
78+ str (entrypoint_dir / "__init__.py" ),
79+ Path (__file__ ).parent .joinpath ("nested_zip_loader.py" ).read_text (),
80+ compresslevel = zipfile .ZIP_DEFLATED
81+ )
0 commit comments