Source code for aws_lambda_python_packager.util
from __future__ import annotations
import logging
import os
import re
import sys
from contextlib import contextmanager
from os import PathLike
from typing import Union
import requests
LOG = logging.getLogger(__name__)
_lambda_runtime_regex = re.compile(
r"^.*?\|\s*`(python3\.\d+)`\s*\|.*?\|\s*([armx864 \\_,]+)\s*\|.*$"
)
LAMBDA_RUNTIME_DOCS_URL = "https://raw.githubusercontent.com/awsdocs/aws-lambda-developer-guide/main/doc_source/lambda-runtimes.md"
GLUE_LIBRARIES_DOCS_URL = "https://raw.githubusercontent.com/awsdocs/aws-glue-developer-guide/master/doc_source/aws-glue-programming-python-libraries.md"
PACKAGE_URL = "https://raw.githubusercontent.com/mumblepins/aws-get-lambda-python-pkg-versions/main/{region}-{python_version}-{architecture}.json"
PathType = Union[str, PathLike]
class ArchitectureUnsupported(Exception):
"""Exception raised when the architecture is not supported"""
def get_lambda_runtimes():
"""Gets a list of supported Lambda runtimes from the AWS docs
Returns:
"""
r = requests.get(LAMBDA_RUNTIME_DOCS_URL, timeout=10)
r.raise_for_status()
runtimes = []
for line in r.text.splitlines():
m = _lambda_runtime_regex.match(line)
if m:
runtime = m.group(1)
archs = m.group(2).split(",")
archs = [a.strip().replace(r"\_", "_") for a in archs]
for arch in archs:
runtimes.append((runtime, arch))
return runtimes
[docs]def get_glue_libraries():
"""Gets libraries included in AWS Glue"""
r = requests.get(GLUE_LIBRARIES_DOCS_URL, timeout=10)
r.raise_for_status()
section = 0
glue_libraries = {}
for ln in r.text.splitlines():
if section == 0 or section % 1 == 0.5:
mch = re.match(r"AWS Glue version (\d).*out of the box", ln)
if mch:
section = int(mch.group(1))
glue_libraries[section] = {}
continue
if section in (2, 3):
if ln.strip() == "":
section += 0.5
continue
grp = re.search(r"\+\s+([^=]+)==([\\.\d]+)", ln)
glue_libraries[section][grp.group(1).replace("\\", "")] = grp.group(2).replace("\\", "")
return glue_libraries
def check_architecture(architecture):
"""Checks if the given architecture is supported
Args:
architecture: Architecture to check
Returns:
True if the architecture is supported, False otherwise
"""
architecture = architecture.lower().strip()
if architecture == "aarch64":
architecture = "arm64"
for _, arch in PLATFORMS:
if arch == architecture:
return architecture
raise ArchitectureUnsupported(f"{architecture} not supported") # pragma: no cover
[docs]def get_python_runtime(architecture="x86_64", target_version=None):
"""Gets an allowed python runtime for the given architecture
Args:
architecture: Architecture to target (x86_64 or arm64)
target_version: Python version to target
Returns:
A tuple of (python_version, architecture)
"""
architecture = check_architecture(architecture)
if target_version is None:
py_version = sys.version_info[0:2]
else:
if isinstance(target_version, str):
target_version = target_version.lower().strip("python").strip().split(".")
py_version = tuple(int(v) for v in target_version)
filtered_platforms = filter(
lambda x: x[1] == architecture,
[(tuple(int(v) for v in pv.strip("python").split(".")), ar) for pv, ar in PLATFORMS],
)
allowed_py_version = [a for a, _ in filtered_platforms]
py_version_constrained = max(min(py_version, max(allowed_py_version)), min(allowed_py_version))
return py_version_constrained, architecture
[docs]@contextmanager
def chdir_cm(path: PathType):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)
[docs]@contextmanager
def chgenv_cm(**kwargs):
old_env = os.environ.copy()
os.environ.update({k: v for k, v in kwargs.items() if v is not None})
for k, v in kwargs.items():
if v is None:
if k in os.environ:
del os.environ[k]
try:
yield
finally:
os.environ.clear()
os.environ.update(old_env)
PLATFORMS = get_lambda_runtimes()
__all__ = [
"PathType",
"PLATFORMS",
"chdir_cm",
"chgenv_cm",
"get_glue_libraries",
"get_python_runtime",
]