239
239
# the Lambda.
240
240
# See: https://github.com/Miserlou/Zappa/pull/1730
241
241
ALB_LAMBDA_ALIAS = "current-alb-version"
242
-
243
- ##
244
- # Classes
245
- ##
242
+ X86_ARCHITECTURE = "x86_64"
243
+ ARM_ARCHITECTURE = "arm64"
244
+ VALID_ARCHITECTURES = (X86_ARCHITECTURE , ARM_ARCHITECTURE )
245
+
246
+
247
+ def build_manylinux_wheel_file_match_pattern (runtime : str , architecture : str ) -> re .Pattern :
248
+ # Support PEP600 (https://peps.python.org/pep-0600/)
249
+ # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
250
+ runtime_major_version , runtime_minor_version = runtime [6 :].split ("." )
251
+ python_tag = f"cp{ runtime_major_version } { runtime_minor_version } " # python3.13 -> cp313
252
+ manylinux_legacy_tags = ("manylinux2014" , "manylinux2010" , "manylinux1" )
253
+ if architecture == X86_ARCHITECTURE :
254
+ valid_platform_tags = [X86_ARCHITECTURE ]
255
+ elif architecture == ARM_ARCHITECTURE :
256
+ valid_platform_tags = [ARM_ARCHITECTURE , "aarch64" ]
257
+ else :
258
+ raise ValueError (f"Invalid 'architecture', must be one of { VALID_ARCHITECTURES } , got: { architecture } " )
259
+
260
+ manylinux_wheel_file_match = (
261
+ rf'^.*{ python_tag } -(manylinux_\d+_\d+_({ "|" .join (valid_platform_tags )} )[.])?'
262
+ rf'({ "|" .join (manylinux_legacy_tags )} )_({ "|" .join (valid_platform_tags )} )[.]whl$'
263
+ )
264
+
265
+ # The 'abi3' tag is a compiled distribution format designed for compatibility across multiple Python 3 versions.
266
+ # An abi3 wheel is built against the stable ABI (Application Binary Interface) of a minimum supported Python version.
267
+ # -- make sure cp3XX version is <= to the runtime version (runtime_minor_version)
268
+ minimum_minor_version = 5
269
+ abi_valid_python_minor_versions = [str (i ) for i in range (minimum_minor_version , int (runtime_minor_version ) + 1 )]
270
+ manylinux_suffixes = [r"_\d+_\d+" , r"manylinux_\d+_\d+" ]
271
+ manylinux_suffixes .extend (manylinux_legacy_tags )
272
+ manylinux_wheel_abi3_file_match = (
273
+ # rf'^.*cp3.-abi3-manylinux({"|".join(manylinux_suffixes)})_({"|".join(valid_platform_tags)}).whl$'
274
+ rf'^.*cp3({ "|" .join (abi_valid_python_minor_versions )} )-abi3-'
275
+ rf'manylinux(({ "|" .join (manylinux_suffixes )} )_({ "" .join (valid_platform_tags )} )(\.|))+.whl$'
276
+ )
277
+ combined_match_pattern = rf"({ manylinux_wheel_file_match } )|({ manylinux_wheel_abi3_file_match } )"
278
+ manylinux_wheel_file_match_pattern = re .compile (combined_match_pattern )
279
+ return manylinux_wheel_file_match_pattern
246
280
247
281
248
282
class Zappa :
@@ -263,7 +297,7 @@ class Zappa:
263
297
apigateway_policy = None
264
298
cloudwatch_log_levels = ["OFF" , "ERROR" , "INFO" ]
265
299
xray_tracing = False
266
-
300
+ architecture = None
267
301
##
268
302
# Credentials
269
303
##
@@ -283,6 +317,7 @@ def __init__(
283
317
tags = (),
284
318
endpoint_urls = {},
285
319
xray_tracing = False ,
320
+ architecture = None ,
286
321
):
287
322
"""
288
323
Instantiate this new Zappa instance, loading any custom credentials if necessary.
@@ -304,14 +339,14 @@ def __init__(
304
339
305
340
self .runtime = runtime
306
341
307
- # TODO: Support PEP600 properly (https://peps.python.org/pep-0600/)
308
- self . manylinux_suffix_start = f"cp { self . runtime [ 6 :]. replace ( '.' , '' ) } "
309
- self . manylinux_suffixes = ( "_2_24" , "2014" , "2010" , "1" )
310
- # TODO: Support aarch64 architecture
311
- self . manylinux_wheel_file_match = re . compile (
312
- rf'^.* { self .manylinux_suffix_start } -(manylinux_\d+_\d+_x86_64[.])?manylinux( { "|" . join ( self . manylinux_suffixes ) } )_x86_64[.]whl$' # noqa: E501
313
- )
314
- self .manylinux_wheel_abi3_file_match = re . compile ( r"^.*cp3.-abi3-manylinux.*_x86_64[.]whl$" )
342
+ if not architecture :
343
+ architecture = X86_ARCHITECTURE
344
+ if architecture not in VALID_ARCHITECTURES :
345
+ raise ValueError ( f"Invalid architecture ' { architecture } '. Must be one of: { VALID_ARCHITECTURES } " )
346
+
347
+ self .architecture = architecture
348
+
349
+ self .manylinux_wheel_file_match = build_manylinux_wheel_file_match_pattern ( runtime , architecture )
315
350
316
351
self .endpoint_urls = endpoint_urls
317
352
self .xray_tracing = xray_tracing
@@ -754,7 +789,7 @@ def splitpath(path):
754
789
# use the compiled bytecode anyway..
755
790
if filename [- 3 :] == ".py" and root [- 10 :] != "migrations" :
756
791
abs_filename = os .path .join (root , filename )
757
- abs_pyc_filename = abs_filename + "c"
792
+ abs_pyc_filename = f" { abs_filename } c" # XXX.pyc
758
793
if os .path .isfile (abs_pyc_filename ):
759
794
# but only if the pyc is older than the py,
760
795
# otherwise we'll deploy outdated code!
@@ -870,34 +905,33 @@ def get_cached_manylinux_wheel(self, package_name, package_version, disable_prog
870
905
"""
871
906
Gets the locally stored version of a manylinux wheel. If one does not exist, the function downloads it.
872
907
"""
873
- cached_wheels_dir = os . path . join (tempfile .gettempdir (), "cached_wheels" )
908
+ cached_wheels_dir = Path (tempfile .gettempdir ()) / "cached_wheels"
874
909
875
- if not os . path . isdir ( cached_wheels_dir ):
876
- os . makedirs ( cached_wheels_dir )
910
+ if not cached_wheels_dir . is_dir () or not cached_wheels_dir . exists ( ):
911
+ cached_wheels_dir . mkdir ( parents = True , exist_ok = True )
877
912
else :
878
913
# Check if we already have a cached copy
914
+ # - get package name from prefix of the wheel file
879
915
wheel_name = re .sub (r"[^\w\d.]+" , "_" , package_name , flags = re .UNICODE )
880
- wheel_file = f"{ wheel_name } -{ package_version } -*_x86_64.whl"
881
- wheel_path = os .path .join (cached_wheels_dir , wheel_file )
882
-
883
- for pathname in glob .iglob (wheel_path ):
884
- if re .match (self .manylinux_wheel_file_match , pathname ):
885
- logger .info (f" - { package_name } =={ package_version } : Using locally cached manylinux wheel" )
886
- return pathname
887
- elif re .match (self .manylinux_wheel_abi3_file_match , pathname ):
888
- for manylinux_suffix in self .manylinux_suffixes :
889
- if f"manylinux{ manylinux_suffix } _x86_64" in pathname :
890
- logger .info (f" - { package_name } =={ package_version } : Using locally cached manylinux wheel" )
891
- return pathname
916
+ valid_architectures = (X86_ARCHITECTURE ,)
917
+ if self .architecture == ARM_ARCHITECTURE :
918
+ valid_architectures = (ARM_ARCHITECTURE , "aarch64" )
919
+ for arch in valid_architectures :
920
+ wheel_file_pattern = f"{ wheel_name } -{ package_version } -*_{ arch } .whl"
921
+ for pathname in cached_wheels_dir .glob (wheel_file_pattern ):
922
+ if self .manylinux_wheel_file_match .match (str (pathname )):
923
+ logger .info (f" - { package_name } =={ package_version } : Using locally cached manylinux wheel" )
924
+ return pathname
892
925
893
926
# The file is not cached, download it.
894
927
wheel_url , filename = self .get_manylinux_wheel_url (package_name , package_version )
895
928
if not wheel_url :
929
+ logger .warning (f" - { package_name } =={ package_version } : No manylinux wheel found for this package" )
896
930
return None
897
931
898
- wheel_path = os . path . join ( cached_wheels_dir , filename )
932
+ wheel_path = cached_wheels_dir / filename
899
933
logger .info (f" - { package_name } =={ package_version } : Downloading" )
900
- with open (wheel_path , "wb" ) as f :
934
+ with wheel_path . open ("wb" ) as f :
901
935
self .download_url_with_progress (wheel_url , f , disable_progress )
902
936
903
937
if not zipfile .is_zipfile (wheel_path ):
@@ -917,23 +951,23 @@ def get_manylinux_wheel_url(self, package_name, package_version, ignore_cache: b
917
951
every time.
918
952
"""
919
953
cached_pypi_info_dir = Path (tempfile .gettempdir ()) / "cached_pypi_info"
920
- if not cached_pypi_info_dir .is_dir ():
921
- os . makedirs ( cached_pypi_info_dir )
954
+ if not cached_pypi_info_dir .exists ():
955
+ cached_pypi_info_dir . mkdir ( parents = True , exist_ok = True )
922
956
923
957
# Even though the metadata is for the package, we save it in a
924
958
# filename that includes the package's version. This helps in
925
959
# invalidating the cached file if the user moves to a different
926
960
# version of the package.
927
961
# Related: https://github.com/Miserlou/Zappa/issues/899
928
962
data = None
929
- json_file_name = "{0 !s}-{1 !s}.json". format ( package_name , package_version )
963
+ json_file_name = f" { package_name !s} -{ package_version !s} .json"
930
964
json_file_path = cached_pypi_info_dir / json_file_name
931
965
if json_file_path .exists ():
932
966
with json_file_path .open ("rb" ) as metafile :
933
967
data = json .load (metafile )
934
968
935
969
if not data or ignore_cache :
936
- url = "https://pypi.python.org/pypi/{}/json" . format ( package_name )
970
+ url = f "https://pypi.python.org/pypi/{ package_name } /json"
937
971
try :
938
972
res = requests .get (url , timeout = float (os .environ .get ("PIP_TIMEOUT" , 1.5 )))
939
973
data = res .json ()
@@ -949,14 +983,10 @@ def get_manylinux_wheel_url(self, package_name, package_version, ignore_cache: b
949
983
return None , None
950
984
951
985
for f in data ["releases" ][package_version ]:
952
- if re . match ( self .manylinux_wheel_file_match , f ["filename" ]):
986
+ if self .manylinux_wheel_file_match . match ( f ["filename" ]):
953
987
# Since we have already lowered package names in get_installed_packages
954
988
# manylinux caching is not working for packages with capital case in names like MarkupSafe
955
989
return f ["url" ], f ["filename" ].lower ()
956
- elif re .match (self .manylinux_wheel_abi3_file_match , f ["filename" ]):
957
- for manylinux_suffix in self .manylinux_suffixes :
958
- if f"manylinux{ manylinux_suffix } _x86_64" in f ["filename" ]:
959
- return f ["url" ], f ["filename" ].lower ()
960
990
return None , None
961
991
962
992
##
@@ -1126,6 +1156,8 @@ def create_lambda_function(
1126
1156
TracingConfig = {"Mode" : "Active" if self .xray_tracing else "PassThrough" },
1127
1157
SnapStart = {"ApplyOn" : snap_start if snap_start else "None" },
1128
1158
Layers = layers ,
1159
+ # zappa currently only supports a single architecture, and uses a str value internally
1160
+ Architectures = [self .architecture ],
1129
1161
)
1130
1162
if not docker_image_uri :
1131
1163
kwargs ["Runtime" ] = runtime
0 commit comments