diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index d9cb340..d1a282c 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -24,7 +24,7 @@ jobs: echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Run 'pr' target - run: make pr + run: poetry run poe pr alpine: runs-on: ubuntu-latest @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run alpine integration tests - run: DISTRO=alpine make test-integ + run: DISTRO=alpine poetry run poe test-integ amazonlinux: runs-on: ubuntu-latest @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run amazonlinux integration tests - run: DISTRO=amazonlinux make test-integ + run: DISTRO=amazonlinux poetry run poe test-integ debian: runs-on: ubuntu-latest @@ -48,7 +48,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run debian integration tests - run: DISTRO=debian make test-integ + run: DISTRO=debian poetry run poe test-integ ubuntu: runs-on: ubuntu-latest @@ -56,4 +56,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run ubuntu integration tests - run: DISTRO=ubuntu make test-integ + run: DISTRO=ubuntu poetry run poe test-integ diff --git a/.gitignore b/.gitignore index ea5d560..d0a548e 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,6 @@ deps/curl-*/ # local build artifacts build-artifacts + +# native wheels used for testing the separation of the native build +native-wheels/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index ab968cc..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,13 +0,0 @@ -repos: - - repo: local - hooks: - - id: ruff-check - name: ruff - entry: poetry run ruff check --fix - language: system - types: [python] - - id: ruff-format - name: ruff format - entry: poetry run ruff format - language: system - types: [python] diff --git a/Dockerfile.build b/Dockerfile.build index a26c832..1036be4 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -1,18 +1,11 @@ FROM python:3.9-alpine AS build-image -# Install build dependencies -RUN apk add --no-cache \ - build-base \ - libtool \ - autoconf \ - automake \ - elfutils-dev \ - make \ - cmake \ - libcurl \ - curl \ - libstdc++ \ - binutils + +RUN apk add --no-cache curl make + +# Install native wheel dependency +COPY native-wheels/*.whl /tmp/native-wheels/ +RUN pip install /tmp/native-wheels/*.whl # Build awslambdaric ARG RIC_BUILD_DIR="/home/build/" @@ -22,7 +15,7 @@ COPY . . RUN pip install setuptools RUN curl -sSL https://install.python-poetry.org | python3 - && \ ln -s /root/.local/bin/poetry /usr/local/bin/poetry -RUN make init build +RUN poetry install && poetry run poe build # Keep the built wheel accessible CMD ["sh", "-c", "echo 'Build complete. Wheel available in /home/build/dist/'"] \ No newline at end of file diff --git a/Dockerfile.rie b/Dockerfile.rie index 1caa8d8..60a7d0e 100644 --- a/Dockerfile.rie +++ b/Dockerfile.rie @@ -6,6 +6,10 @@ RUN apk add --no-cache libstdc++ curl RUN curl -Lo /usr/local/bin/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \ chmod +x /usr/local/bin/aws-lambda-rie +# Install native wheel dependency first +ADD native-wheels/*.whl /tmp/native/ +RUN pip install /tmp/native/*.whl + # Add the pre-built wheel ADD build-artifacts/*.whl /tmp/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 694b1c3..0000000 --- a/Makefile +++ /dev/null @@ -1,91 +0,0 @@ -.PHONY: target -target: - $(info ${HELP_MESSAGE}) - @exit 0 - -.PHONY: init -init: - python3 scripts/dev.py init - -.PHONY: test -test: - python3 scripts/dev.py test - -.PHONY: lint -lint: - python3 scripts/dev.py lint - -.PHONY: clean -clean: - python3 scripts/dev.py clean - -.PHONY: build -build: clean - python3 scripts/dev.py build - -.PHONY: setup-codebuild-agent -setup-codebuild-agent: - docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent - -.PHONY: test-smoke -test-smoke: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 3.9 - -.PHONY: test-integ -test-integ: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/. - -.PHONY: check-security -check-security: - poetry run bandit -r awslambdaric - -.PHONY: format -format: - poetry run ruff format awslambdaric/ tests/ - -.PHONY: check-format -check-format: - poetry run ruff format --check awslambdaric/ tests/ - -.PHONY: check-docstr -check-docstr: - python3 scripts/dev.py check-docstr - -.PHONY: dev -dev: init test - -.PHONY: pr -pr: init check-format check-annotations check-types check-type-usage check-security dev - -.PHONY: codebuild -codebuild: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild - -.PHONY: build-container -build-container: - ./scripts/build-container.sh - -.PHONY: test-rie -test-rie: - ./scripts/test-rie.sh - -define HELP_MESSAGE - -Usage: $ make [TARGETS] - -TARGETS - check-security Run bandit to find security issues. - check-docstr Check docstrings in project using ruff format check. - format Run ruff to automatically format your code. - build Build the package using scripts/dev.py. - clean Cleans the working directory using scripts/dev.py. - dev Run all development tests using scripts/dev.py. - init Install dependencies via scripts/dev.py. - build-container Build awslambdaric wheel in isolated container. - test-rie Test with RIE using pre-built wheel (run build-container first). - pr Perform all checks before submitting a Pull Request. - test Run unit tests using scripts/dev.py. - lint Run all linters via scripts/dev.py. - test-smoke Run smoke tests inside Docker. - test-integ Run all integration tests. -endef \ No newline at end of file diff --git a/awslambdaric/__init__.py b/awslambdaric/__init__.py index 0f94db0..e1e5905 100644 --- a/awslambdaric/__init__.py +++ b/awslambdaric/__init__.py @@ -1,3 +1,3 @@ """Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.""" -__version__ = "3.1.1" +__version__ = "3.0.2" diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py index 9bf1382..1847103 100644 --- a/awslambdaric/bootstrap.py +++ b/awslambdaric/bootstrap.py @@ -7,10 +7,12 @@ import sys import time import traceback +from typing import Any, Optional, List from .lambda_context import LambdaContext from .lambda_runtime_client import LambdaRuntimeClient from .lambda_runtime_exception import FaultException +from .interfaces import RuntimeClientProtocol, LogSinkProtocol from .lambda_runtime_log_utils import ( _DATETIME_FORMAT, _DEFAULT_FRAME_TYPE, @@ -152,18 +154,18 @@ def log_error(error_result, log_sink): def handle_event_request( - lambda_runtime_client, - request_handler, - invoke_id, - event_body, - content_type, - client_context_json, - cognito_identity_json, - invoked_function_arn, - epoch_deadline_time_in_ms, - tenant_id, - log_sink, -): + lambda_runtime_client: RuntimeClientProtocol, + request_handler: Any, + invoke_id: Optional[str], + event_body: Any, + content_type: Optional[str], + client_context_json: Optional[str], + cognito_identity_json: Optional[str], + invoked_function_arn: Optional[str], + epoch_deadline_time_in_ms: Optional[str], + tenant_id: Optional[str], + log_sink: LogSinkProtocol, +) -> None: """Handle Lambda event request.""" error_result = None try: @@ -228,12 +230,12 @@ def parse_json_header(header, name): def create_lambda_context( - client_context_json, - cognito_identity_json, - epoch_deadline_time_in_ms, - invoke_id, - invoked_function_arn, - tenant_id, + client_context_json: Optional[str], + cognito_identity_json: Optional[str], + epoch_deadline_time_in_ms: Optional[str], + invoke_id: Optional[str], + invoked_function_arn: Optional[str], + tenant_id: Optional[str], ): """Create Lambda context object.""" client_context = None @@ -390,7 +392,7 @@ def writelines(self, msgs): self.stream.flush() -class StandardLogSink(object): +class StandardLogSink: """Standard log sink.""" def __init__(self): @@ -403,11 +405,11 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, exc_tb): pass - def log(self, msg, frame_type=None): + def log(self, msg: str, frame_type: Optional[bytes] = None) -> None: """Log message to stdout.""" sys.stdout.write(msg) - def log_error(self, message_lines): + def log_error(self, message_lines: List[str]) -> None: """Log error message to stdout.""" error_message = ERROR_LOG_LINE_TERMINATE.join(message_lines) + "\n" sys.stdout.write(error_message) @@ -432,6 +434,7 @@ class FramedTelemetryLogSink(object): def __init__(self, fd): """Initialize framed telemetry log sink.""" self.fd = int(fd) + self.file: Any = None def __enter__(self): self.file = os.fdopen(self.fd, "wb", 0) @@ -440,7 +443,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, exc_tb): self.file.close() - def log(self, msg, frame_type=None): + def log(self, msg: str, frame_type: Optional[bytes] = None) -> None: """Log message with frame type.""" encoded_msg = msg.encode("utf8") @@ -453,7 +456,7 @@ def log(self, msg, frame_type=None): ) self.file.write(log_msg) - def log_error(self, message_lines): + def log_error(self, message_lines: List[str]) -> None: """Log error message.""" error_message = "\n".join(message_lines) self.log( diff --git a/awslambdaric/interfaces.py b/awslambdaric/interfaces.py new file mode 100644 index 0000000..aaecdc6 --- /dev/null +++ b/awslambdaric/interfaces.py @@ -0,0 +1,33 @@ +"""Protocol interfaces for AWS Lambda Runtime Interface Client.""" + +from typing import Protocol, Any, Dict, Tuple, Optional, List + + +class RuntimeClientProtocol(Protocol): + """Protocol for Lambda runtime client operations.""" + + marshaller: "MarshallerProtocol" + + def wait_next_invocation(self) -> Any: ... + + def post_invocation_result(self, invoke_id: Optional[str], result_data: Any, content_type: str = "application/json") -> None: ... + + def post_invocation_error(self, invoke_id: Optional[str], error_response_data: str, xray_fault: str) -> None: ... + + def post_init_error(self, error_response_data: Dict[str, Any], error_type_override: Optional[str] = None) -> None: ... + + +class MarshallerProtocol(Protocol): + """Protocol for request/response marshalling.""" + + def unmarshal_request(self, request: Any, content_type: Optional[str] = "application/json") -> Any: ... + + def marshal_response(self, response: Any) -> Tuple[Any, str]: ... + + +class LogSinkProtocol(Protocol): + """Protocol for logging operations.""" + + def log(self, msg: str, frame_type: Optional[bytes] = None) -> None: ... + + def log_error(self, message_lines: List[str]) -> None: ... \ No newline at end of file diff --git a/awslambdaric/lambda_context.py b/awslambdaric/lambda_context.py index e827993..ac701df 100644 --- a/awslambdaric/lambda_context.py +++ b/awslambdaric/lambda_context.py @@ -4,6 +4,7 @@ import os import sys import time +from typing import Optional, Any class LambdaContext(object): @@ -35,7 +36,7 @@ def __init__( ) self.identity = make_obj_from_dict(CognitoIdentity, {}) - if cognito_identity is not None: + if cognito_identity is not None and self.identity is not None: self.identity.cognito_identity_id = cognito_identity.get( "cognitoIdentityId" ) @@ -80,6 +81,10 @@ class CognitoIdentity(object): """Cognito identity information.""" __slots__ = ["cognito_identity_id", "cognito_identity_pool_id"] + + def __init__(self) -> None: + self.cognito_identity_id: Optional[str] = None + self.cognito_identity_pool_id: Optional[str] = None def __repr__(self): return ( @@ -100,6 +105,13 @@ class Client(object): "app_version_code", "app_package_name", ] + + def __init__(self) -> None: + self.installation_id: Optional[str] = None + self.app_title: Optional[str] = None + self.app_version_name: Optional[str] = None + self.app_version_code: Optional[str] = None + self.app_package_name: Optional[str] = None def __repr__(self): return ( @@ -117,6 +129,11 @@ class ClientContext(object): """Client context information.""" __slots__ = ["custom", "env", "client"] + + def __init__(self) -> None: + self.custom: Optional[Any] = None + self.env: Optional[Any] = None + self.client: Optional[Client] = None def __repr__(self): return ( diff --git a/awslambdaric/lambda_runtime_client.py b/awslambdaric/lambda_runtime_client.py index fd5fdf1..3d5cf09 100644 --- a/awslambdaric/lambda_runtime_client.py +++ b/awslambdaric/lambda_runtime_client.py @@ -1,9 +1,11 @@ """Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.""" import sys +from typing import Any, Dict, Optional from awslambdaric import __version__ from .lambda_runtime_exception import FaultException from .lambda_runtime_marshaller import to_json +from .interfaces import MarshallerProtocol, RuntimeClientProtocol ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type" @@ -16,25 +18,33 @@ def _user_agent(): return f"aws-lambda-python/{py_version}-{pkg_version}" -try: - import runtime_client - - runtime_client.initialize_client(_user_agent()) -except ImportError: - runtime_client = None +# Import native extension +import awslambdaric_native as runtime_client +runtime_client.initialize_client(_user_agent()) from .lambda_runtime_marshaller import LambdaMarshaller -class InvocationRequest(object): +class InvocationRequest: """Lambda invocation request.""" - def __init__(self, **kwds): + def __init__(self, **kwds: Any) -> None: """Initialize invocation request.""" + self.invoke_id: Optional[str] = kwds.get('invoke_id') + self.x_amzn_trace_id: Optional[str] = kwds.get('x_amzn_trace_id') + self.invoked_function_arn: Optional[str] = kwds.get('invoked_function_arn') + self.deadline_time_in_ms: Optional[str] = kwds.get('deadline_time_in_ms') + self.client_context: Optional[str] = kwds.get('client_context') + self.cognito_identity: Optional[str] = kwds.get('cognito_identity') + self.tenant_id: Optional[str] = kwds.get('tenant_id') + self.content_type: Optional[str] = kwds.get('content_type') + self.event_body: Any = kwds.get('event_body') self.__dict__.update(kwds) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """Check equality.""" + if not isinstance(other, InvocationRequest): + return False return self.__dict__ == other.__dict__ @@ -51,10 +61,10 @@ def __init__(self, endpoint, response_code, response_body): ) -class LambdaRuntimeClient(object): +class LambdaRuntimeClient(RuntimeClientProtocol): """Lambda runtime client.""" - marshaller = LambdaMarshaller() + marshaller: MarshallerProtocol = LambdaMarshaller() """marshaller is a class attribute that determines the unmarshalling and marshalling logic of a function's event and response. It allows for function authors to override the the default implementation, LambdaMarshaller which unmarshals and marshals JSON, to an instance of a class that implements the same interface.""" @@ -92,7 +102,7 @@ def call_rapid( if response.code != expected_http_code: raise LambdaRuntimeClientError(endpoint, response.code, response_body) - def post_init_error(self, error_response_data, error_type_override=None): + def post_init_error(self, error_response_data: Dict[str, Any], error_type_override: Optional[str] = None) -> None: """Post initialization error.""" import http @@ -143,6 +153,7 @@ def wait_next_invocation(self): ) else: response_body, headers = runtime_client.next() + return InvocationRequest( invoke_id=headers.get("Lambda-Runtime-Aws-Request-Id"), x_amzn_trace_id=headers.get("Lambda-Runtime-Trace-Id"), @@ -156,8 +167,8 @@ def wait_next_invocation(self): ) def post_invocation_result( - self, invoke_id, result_data, content_type="application/json" - ): + self, invoke_id: Optional[str], result_data: Any, content_type: str = "application/json" + ) -> None: """Post invocation result.""" runtime_client.post_invocation_result( invoke_id, @@ -169,7 +180,7 @@ def post_invocation_result( content_type, ) - def post_invocation_error(self, invoke_id, error_response_data, xray_fault): + def post_invocation_error(self, invoke_id: Optional[str], error_response_data: str, xray_fault: str) -> None: """Post invocation error.""" max_header_size = 1024 * 1024 # 1MiB xray_fault = xray_fault if len(xray_fault.encode()) < max_header_size else "" diff --git a/awslambdaric/lambda_runtime_marshaller.py b/awslambdaric/lambda_runtime_marshaller.py index fe0dd8f..1a0cbdd 100644 --- a/awslambdaric/lambda_runtime_marshaller.py +++ b/awslambdaric/lambda_runtime_marshaller.py @@ -3,9 +3,11 @@ import decimal import math import os +from typing import Any, Tuple, Optional import simplejson as json from .lambda_runtime_exception import FaultException +from .interfaces import MarshallerProtocol # simplejson's Decimal encoding allows '-NaN' as an output, which is a parse error for json.loads @@ -24,7 +26,7 @@ def __init__(self): else: super().__init__(use_decimal=False, allow_nan=True) - def default(self, obj): + def default(self, obj: Any) -> Any: """Handle special object types during encoding.""" if isinstance(obj, decimal.Decimal): if obj.is_nan(): @@ -38,14 +40,14 @@ def to_json(obj): return Encoder().encode(obj) -class LambdaMarshaller: +class LambdaMarshaller(MarshallerProtocol): """Marshaller for Lambda requests and responses.""" def __init__(self): """Initialize the marshaller.""" self.jsonEncoder = Encoder() - def unmarshal_request(self, request, content_type="application/json"): + def unmarshal_request(self, request: Any, content_type: Optional[str] = "application/json") -> Any: """Unmarshal incoming request.""" if content_type != "application/json": return request @@ -58,7 +60,7 @@ def unmarshal_request(self, request, content_type="application/json"): None, ) - def marshal_response(self, response): + def marshal_response(self, response: Any) -> Tuple[Any, str]: """Marshal response for Lambda.""" if isinstance(response, bytes): return response, "application/unknown" diff --git a/awslambdaric/runtime_client.cpp b/awslambdaric/runtime_client.cpp deleted file mode 100644 index 7fb2e95..0000000 --- a/awslambdaric/runtime_client.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ - -#include -#include -#include -#include - -#define NULL_IF_EMPTY(v) (((v) == NULL || (v)[0] == 0) ? NULL : (v)) - -static const std::string ENDPOINT(getenv("AWS_LAMBDA_RUNTIME_API") ? getenv("AWS_LAMBDA_RUNTIME_API") : "127.0.0.1:9001"); -static aws::lambda_runtime::runtime *CLIENT; - -static PyObject *method_initialize_client(PyObject *self, PyObject *args) { - char *user_agent_arg; - if (!PyArg_ParseTuple(args, "s", &user_agent_arg)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - const std::string user_agent = std::string(user_agent_arg); - - CLIENT = new aws::lambda_runtime::runtime(ENDPOINT, user_agent); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject *method_next(PyObject *self) { - aws::lambda_runtime::invocation_request response; - - // Release GIL and save thread state - // ref: https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock - PyThreadState *_save; - _save = PyEval_SaveThread(); - - auto outcome = CLIENT->get_next(); - if (!outcome.is_success()) { - // Reacquire GIL before exiting - PyEval_RestoreThread(_save); - PyErr_SetString(PyExc_RuntimeError, "Failed to get next"); - return NULL; - } - - response = outcome.get_result(); - // Reacquire GIL before constructing return object - PyEval_RestoreThread(_save); - - auto payload = response.payload; - auto request_id = response.request_id.c_str(); - auto trace_id = response.xray_trace_id.c_str(); - auto function_arn = response.function_arn.c_str(); - auto deadline = std::chrono::duration_cast(response.deadline.time_since_epoch()).count(); - auto client_context = response.client_context.c_str(); - auto content_type = response.content_type.c_str(); - auto cognito_id = response.cognito_identity.c_str(); - auto tenant_id = response.tenant_id.c_str(); - - PyObject *payload_bytes = PyBytes_FromStringAndSize(payload.c_str(), payload.length()); - PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s,s:s})", - payload_bytes, //Py_BuildValue() increments reference counter - "Lambda-Runtime-Aws-Request-Id", request_id, - "Lambda-Runtime-Trace-Id", NULL_IF_EMPTY(trace_id), - "Lambda-Runtime-Invoked-Function-Arn", function_arn, - "Lambda-Runtime-Deadline-Ms", deadline, - "Lambda-Runtime-Client-Context", NULL_IF_EMPTY(client_context), - "Content-Type", NULL_IF_EMPTY(content_type), - "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id), - "Lambda-Runtime-Aws-Tenant-Id", NULL_IF_EMPTY(tenant_id) - ); - - Py_XDECREF(payload_bytes); - return result; -} - -static PyObject *method_post_invocation_result(PyObject *self, PyObject *args) { - if (CLIENT == nullptr) { - PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); - return NULL; - } - - PyObject *invocation_response; - Py_ssize_t length; - char *request_id, *content_type, *response_as_c_string; - - if (!PyArg_ParseTuple(args, "sSs", &request_id, &invocation_response, &content_type)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - length = PyBytes_Size(invocation_response); - response_as_c_string = PyBytes_AsString(invocation_response); - std::string response_string(response_as_c_string, response_as_c_string + length); - - auto response = aws::lambda_runtime::invocation_response::success(response_string, content_type); - auto outcome = CLIENT->post_success(request_id, response); - if (!outcome.is_success()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation response"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject *method_post_error(PyObject *self, PyObject *args) { - if (CLIENT == nullptr) { - PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); - return NULL; - } - - char *request_id, *response_string, *xray_fault; - - if (!PyArg_ParseTuple(args, "sss", &request_id, &response_string, &xray_fault)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - auto response = aws::lambda_runtime::invocation_response(response_string, "application/json", false, xray_fault); - auto outcome = CLIENT->post_failure(request_id, response); - if (!outcome.is_success()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation error"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef Runtime_Methods[] = { - {"initialize_client", method_initialize_client, METH_VARARGS, NULL}, - {"next", (PyCFunction) method_next, METH_NOARGS, NULL}, - {"post_invocation_result", method_post_invocation_result, METH_VARARGS, NULL}, - {"post_error", method_post_error, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static struct PyModuleDef runtime_client = { - PyModuleDef_HEAD_INIT, - "runtime", - NULL, - -1, - Runtime_Methods, - NULL, - NULL, - NULL, - NULL -}; - -PyMODINIT_FUNC PyInit_runtime_client(void) { - return PyModule_Create(&runtime_client); -} diff --git a/poetry.lock b/poetry.lock index 491f844..0758dfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,42 +1,5 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. -[[package]] -name = "bandit" -version = "1.8.6" -description = "Security oriented static analyser for python code." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"}, - {file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -PyYAML = ">=5.3.1" -rich = "*" -stevedore = ">=1.20.0" - -[package.extras] -baseline = ["GitPython (>=3.1.30)"] -sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] -yaml = ["PyYAML"] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - [[package]] name = "colorama" version = "0.4.6" @@ -44,7 +7,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -151,18 +114,6 @@ files = [ [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -182,38 +133,6 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "filelock" -version = "3.18.0" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] - -[[package]] -name = "identify" -version = "2.6.12" -description = "File identification library for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, - {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, -] - -[package.extras] -license = ["ukkonen"] - [[package]] name = "iniconfig" version = "2.1.0" @@ -226,43 +145,6 @@ files = [ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mock" version = "5.2.0" @@ -280,18 +162,6 @@ build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest", "pytest-cov"] -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - [[package]] name = "packaging" version = "25.0" @@ -320,37 +190,17 @@ files = [ dev = ["jinja2"] [[package]] -name = "pbr" -version = "6.1.1" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -groups = ["dev"] -files = [ - {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, - {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "platformdirs" -version = "4.3.8" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." optional = false -python-versions = ">=3.9" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["dev"] files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - [[package]] name = "pluggy" version = "1.6.0" @@ -368,23 +218,23 @@ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "poethepoet" +version = "0.24.4" +description = "A task runner that works well with poetry." optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, + {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, + {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, ] [package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] [[package]] name = "pygments" @@ -401,6 +251,25 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyrefly" +version = "0.26.1" +description = "A fast Python type checker written in Rust" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyrefly-0.26.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d81e737125d7a5f64bc378dc699f28b33182e803d25ec74f1b59007baacb6b70"}, + {file = "pyrefly-0.26.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c0e0956065e4b45d026175adcb992472368a864ece889752b9397906d4d3ec11"}, + {file = "pyrefly-0.26.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be24b8550a4890beea7e8843c9e4b469f70d204a73c0f38652f61d0f4edc6f23"}, + {file = "pyrefly-0.26.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab3ed0cadd2de0f986925f72c7ca422b5a418d9416d614012565feb56e74d3a"}, + {file = "pyrefly-0.26.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4885ec86b7d500a808adbd9f8541971e13fa3d3babe8bb48d9d20a5ece71a3f9"}, + {file = "pyrefly-0.26.1-py3-none-win32.whl", hash = "sha256:1b997e4c201c2104008fc214256a3fe0de741fbf13d54c41fe5de6dd4f62f5bd"}, + {file = "pyrefly-0.26.1-py3-none-win_amd64.whl", hash = "sha256:3856d5f2a37a2c0f59ddab2f0ab175d338f361ee1003c357cf8c2e409cd59b22"}, + {file = "pyrefly-0.26.1-py3-none-win_arm64.whl", hash = "sha256:8a1a85e6d04649d1f0ade12e4424a0f08b27be963aba99958ee377e565b80714"}, + {file = "pyrefly-0.26.1.tar.gz", hash = "sha256:f598b8fa29200e9cfa2c6d53d66bc4c7278b254192429d36bd0b49b32a553d82"}, +] + [[package]] name = "pytest" version = "8.4.1" @@ -425,88 +294,6 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "rich" -version = "14.1.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, - {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - [[package]] name = "ruff" version = "0.1.15" @@ -534,27 +321,6 @@ files = [ {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - [[package]] name = "simplejson" version = "3.20.1" @@ -687,21 +453,6 @@ files = [ {file = "snapshot_restore_py-1.0.0-py3-none-any.whl", hash = "sha256:38f99e696793790f54658e71c68c7a8a40cea877c81232b5052383b1301aceba"}, ] -[[package]] -name = "stevedore" -version = "5.4.1" -description = "Manage dynamic plugins for Python applications" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, - {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, -] - -[package.dependencies] -pbr = ">=2.0.0" - [[package]] name = "tomli" version = "2.2.1" @@ -709,7 +460,6 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -758,28 +508,7 @@ files = [ {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] -[[package]] -name = "virtualenv" -version = "20.32.0" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56"}, - {file = "virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "36256ebce25e8e66039694f47ffe6303b754c88e69a9241f614a28f5ae0735e7" +content-hash = "fb2951082007675c7b94eaf035984e23a6651bf6028874b515bdd8baa0ef98c1" diff --git a/pyproject.toml b/pyproject.toml index 0007d8f..531f16d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,19 +18,29 @@ pytest = ">=3.0.7" mock = ">=2.0.0" parameterized = ">=0.9.0" ruff = "^0.1.0" -bandit = "^1.7.5" -pre-commit = "^3.0.0" - -# Development scripts -[tool.poetry.scripts] -init = "scripts.dev:init" -test = "scripts.dev:test" -lint = "scripts.dev:lint" -format = "scripts.dev:format_code" -clean = "scripts.dev:clean" -build = "scripts.dev:build" -build-container = "scripts.dev:build_container" -test-rie = "scripts.dev:test_rie" +poethepoet = "^0.24.0" +pyrefly = "^0.26.0" + +[tool.poe.tasks] +init = "poetry install" +test = "pytest tests" +lint = "ruff check awslambdaric/ tests/" +format = "ruff format awslambdaric/ tests/" +clean = { shell = "rm -rf build dist *.egg-info" } +build = ["clean", { cmd = "python setup.py sdist bdist_wheel" }] +build-container = { shell = "./scripts/build-container.sh" } +test-rie = { shell = "./scripts/test-rie.sh" } +check-security = "ruff check --select S awslambdaric/" +check-format = "ruff format --check awslambdaric/ tests/" +check-docstr = "ruff check --select D --ignore D105 awslambdaric/" +type-check = "pyrefly check" +test-smoke = { shell = "CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 3.9" } +test-integ = { shell = "CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO=\"$DISTRO\" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/." } +setup-codebuild-agent = { shell = "docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent" } + +# Composite tasks +dev = ["init", "test"] +pr = ["init", "check-format", "check-security", "dev"] [build-system] requires = ["poetry-core>=2.0.0,<3.0.0", "setuptools>=68", "wheel"] @@ -49,7 +59,12 @@ select = [ "I", # isort "UP", # pyupgrade "C90", # mccabe complexity + "S", # flake8-bandit security checks ] +[tool.pyrefly] +project-includes = ["**/*"] +project-excludes = ["**/*venv/**/*"] + [tool.ruff.format] quote-style = "double" diff --git a/scripts/dev.py b/scripts/dev.py deleted file mode 100644 index e5e91b6..0000000 --- a/scripts/dev.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import subprocess -import shutil -import sys -import os -from pathlib import Path - -ROOT = Path(__file__).resolve().parent.parent - -def run(cmd, check=True, env=None): - print("\n$ {}".format(' '.join(cmd) if isinstance(cmd, list) else cmd)) - result = subprocess.run(cmd, shell=isinstance(cmd, str), check=check, env=env) - if result.returncode != 0 and check: - sys.exit(result.returncode) - - -def init(): - print("Initializing environment") - run(["poetry", "install"]) - - -def test(): - print("Running tests") - run(["poetry", "run", "pytest", "tests"]) - - -def lint(): - print("Running linters") - run(["poetry", "run", "ruff", "check", "awslambdaric/", "tests/"]) - - -def format_code(): - print("Formatting code") - run(["poetry", "run", "ruff", "format", "awslambdaric/", "tests/"]) - - -def clean(): - print("Cleaning build artifacts") - dirs_to_remove = ["build", "dist", "*.egg-info"] - for pattern in dirs_to_remove: - for path in ROOT.glob(pattern): - if path.is_dir(): - shutil.rmtree(path) - print("Removed directory: {}".format(path)) - elif path.is_file(): - path.unlink() - print("Removed file: {}".format(path)) - - -def build(): - print("Building package") - env = os.environ.copy() - - # Set BUILD=true on Linux for native compilation - import platform - if platform.system() == "Linux": - env["BUILD"] = "true" - elif os.getenv("BUILD") == "true": - env["BUILD"] = "true" - - run([sys.executable, "setup.py", "sdist", "bdist_wheel"], env=env) - - -def build_container(): - print("Building awslambdaric wheel in container") - run(["./scripts/build-container.sh"]) - - -def test_rie(): - print("Testing with RIE using pre-built wheel") - run(["./scripts/test-rie.sh"]) - -def check_docstr(): - print("Checking docstrings") - run(["poetry", "run", "ruff", "check", "--select", "D", "--ignore", "D105", "awslambdaric/"]) - - -def main(): - parser = argparse.ArgumentParser(description="Development scripts") - parser.add_argument("command", choices=[ - "init", "test", "lint", "format", "clean", "build", "build-container", "test-rie", - "check-docstr" - ]) - - args = parser.parse_args() - - command_map = { - "init": init, - "test": test, - "lint": lint, - "format": format_code, - "clean": clean, - "build": build, - "build-container": build_container, - "test-rie": test_rie, - "check-docstr": check_docstr, - } - - command_map[args.command]() - - -if __name__ == "__main__": - main() - diff --git a/scripts/test-rie.sh b/scripts/test-rie.sh index 195d5cc..4544870 100755 --- a/scripts/test-rie.sh +++ b/scripts/test-rie.sh @@ -7,7 +7,7 @@ echo "Starting RIE test setup..." # Check if build artifacts exist if [ ! -d "build-artifacts" ] || [ -z "$(ls -A build-artifacts/*.whl 2>/dev/null)" ]; then - echo "No build artifacts found. Please run 'make build-container' first." + echo "No build artifacts found. Please run 'poetry run poe build-container' first." exit 1 fi @@ -22,5 +22,5 @@ echo "Test with:" echo "curl -XPOST \"http://localhost:9000/2015-03-31/functions/function/invocations\" -d '{\"message\":\"test\"}'" echo "" -docker run -it -p 9000:8080 \ +docker run -p 9000:8080 \ --rm awslambdaric-rie-test \ No newline at end of file diff --git a/setup.py b/setup.py index 2db51f2..3ce5c62 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,5 @@ -import os -import platform import sys -from subprocess import check_call, check_output -from setuptools import Extension, setup, find_packages +from setuptools import setup, find_packages if sys.version_info >= (3, 11): import tomllib @@ -27,34 +24,12 @@ def get_metadata(): "license": poetry_config["license"], "python_requires": poetry_config["dependencies"]["python"], "install_requires": [ - f"{pkg}{version}" if not version.startswith("^") and not version.startswith("~") else f"{pkg}>={version[1:]}" + f"{pkg}{version}" if isinstance(version, str) and not version.startswith("^") and not version.startswith("~") else f"{pkg}>={version[1:] if isinstance(version, str) else version}" for pkg, version in poetry_config["dependencies"].items() - if pkg != "python" + if pkg != "python" and not isinstance(version, dict) ] } -def get_curl_extra_linker_flags(): - if platform.system() != "Linux" or os.getenv("BUILD") != "true": - return [] - check_call(["./scripts/preinstall.sh"]) - cmd = ["./deps/artifacts/bin/curl-config", "--static-libs"] - curl_config = check_output(cmd).decode("utf-8").strip() - return curl_config.split(" ")[1:] - -def get_runtime_client_extension(): - if platform.system() != "Linux" and os.getenv("BUILD") != "true": - print("Native extension build skipped on non-Linux.") - return [] - return [Extension( - "runtime_client", - ["awslambdaric/runtime_client.cpp"], - extra_compile_args=["--std=c++11"], - library_dirs=["deps/artifacts/lib", "deps/artifacts/lib64"], - libraries=["aws-lambda-runtime", "curl"], - extra_link_args=get_curl_extra_linker_flags(), - include_dirs=["deps/artifacts/include"], - )] - metadata = get_metadata() setup( @@ -66,5 +41,4 @@ def get_runtime_client_extension(): packages=find_packages(), python_requires=metadata["python_requires"], install_requires=metadata["install_requires"], - ext_modules=get_runtime_client_extension(), ) diff --git a/tests/integration/docker/Dockerfile.echo.alpine b/tests/integration/docker/Dockerfile.echo.alpine index 7eaf9f0..e4d6010 100644 --- a/tests/integration/docker/Dockerfile.echo.alpine +++ b/tests/integration/docker/Dockerfile.echo.alpine @@ -37,7 +37,7 @@ RUN pip3 install setuptools RUN curl -sSL https://install.python-poetry.org | python3 - && \ ln -s /root/.local/bin/poetry /usr/local/bin/poetry -RUN make init build +RUN poetry install && poetry run poe build RUN ls -la ./dist/ diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux2 b/tests/integration/docker/Dockerfile.echo.amazonlinux2 index 883067a..1c80890 100644 --- a/tests/integration/docker/Dockerfile.echo.amazonlinux2 +++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2 @@ -91,7 +91,7 @@ RUN /usr/local/bin/python${RUNTIME_VERSION} -m pip install wheel poetry # Configure poetry to use the correct python version RUN poetry config virtualenvs.create false -RUN make init build +RUN poetry install && poetry run poe build RUN ls -la ./dist/ diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux2023 b/tests/integration/docker/Dockerfile.echo.amazonlinux2023 index 14ebc60..283f8fa 100644 --- a/tests/integration/docker/Dockerfile.echo.amazonlinux2023 +++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2023 @@ -94,7 +94,7 @@ RUN /usr/local/bin/python${RUNTIME_VERSION} -m pip install setuptools wheel poet # Configure poetry to use the correct python version RUN poetry config virtualenvs.create false -RUN make init build +RUN poetry install && poetry run poe build RUN ls -la ./dist/ diff --git a/tests/integration/docker/Dockerfile.echo.debian b/tests/integration/docker/Dockerfile.echo.debian index 42edc6e..2749d9b 100644 --- a/tests/integration/docker/Dockerfile.echo.debian +++ b/tests/integration/docker/Dockerfile.echo.debian @@ -25,7 +25,7 @@ RUN pip3 install setuptools RUN curl -sSL https://install.python-poetry.org | python3 - && \ ln -s /root/.local/bin/poetry /usr/local/bin/poetry -RUN make init build +RUN poetry install && poetry run poe build RUN ls -la ./dist/ diff --git a/tests/integration/docker/Dockerfile.echo.ubuntu b/tests/integration/docker/Dockerfile.echo.ubuntu index dc19642..c9d2848 100644 --- a/tests/integration/docker/Dockerfile.echo.ubuntu +++ b/tests/integration/docker/Dockerfile.echo.ubuntu @@ -56,7 +56,7 @@ RUN . /home/venv/bin/activate && \ pip install setuptools && \ curl -sSL https://install.python-poetry.org | python3 - && \ ln -s /root/.local/bin/poetry /usr/local/bin/poetry && \ - make init build + poetry install && poetry run poe build RUN ls -la ./dist/