├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGES.md ├── Dockerfile ├── LICENSE ├── MIGRATING.md ├── README.md ├── RELEASE.md ├── TODO ├── examples ├── event_based │ ├── example.sh │ ├── example_function.py │ └── requirements.txt └── gcp_http │ ├── example.sh │ ├── example_http_function.py │ └── requirements.txt ├── lambdex ├── __init__.py ├── __main__.py ├── bin │ ├── __init__.py │ └── lambdex.py ├── resources │ ├── __init__.py │ └── lambdex_handler.py └── version.py ├── pyproject.toml ├── scripts └── build-lambdex-pex.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | checks: 5 | name: TOXENV=${{ matrix.tox-env }} 6 | runs-on: ubuntu-22.04 7 | strategy: 8 | matrix: 9 | include: 10 | - check-name: Format 11 | python-version: "3.10" 12 | tox-env: fmt-check 13 | - check-name: Packaging 14 | python-version: "3.10" 15 | tox-env: package 16 | steps: 17 | - name: Checkout Lambdex 18 | uses: actions/checkout@v4 19 | - name: Setup Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "${{ matrix.python-version }}" 23 | - name: Check ${{ matrix.check-name }} 24 | uses: pantsbuild/actions/run-tox@e63d2d0e3c339bdffbe5e51e7c39550e3bc527bb 25 | with: 26 | tox-env: ${{ matrix.tox-env }} 27 | integration-tests-27-36: 28 | name: (${{ matrix.os }}) TOXENV=py${{ matrix.python-version[0] }}${{ matrix.python-version[1] }}-int-${{ matrix.it-selector }}-pex1.6 29 | runs-on: ${{ matrix.os }} 30 | strategy: 31 | matrix: 32 | include: 33 | - python-version: [2, 7, 18] 34 | os: macos-12 35 | it-selector: "{pre,post}" 36 | - python-version: [2, 7, 18] 37 | os: ubuntu-22.04 38 | it-selector: "{pre,post}" 39 | - python-version: [3, 6, 15] 40 | os: ubuntu-22.04 41 | it-selector: "{pre,post}" 42 | steps: 43 | - name: Checkout Lambdex 44 | uses: actions/checkout@v4 45 | - name: Setup Python ${{ matrix.python-version[0] }}${{ matrix.python-version[1] }} 46 | # Upgrade node16 -> node20: Out for review here: 47 | # https://github.com/gabrielfalcao/pyenv-action/pull/444 48 | uses: pex-tool/pyenv-action@baec18679698d2f80064cc04eb9ad0c8dc5ca8f8 49 | env: 50 | ENSUREPIP: no 51 | with: 52 | default: "${{ join(matrix.python-version, '.') }}" 53 | - name: Run Integration Tests 54 | run: | 55 | which python 56 | python -V 57 | python <(curl -fSL https://bootstrap.pypa.io/pip/${{ matrix.python-version[0] }}.${{ matrix.python-version[1] }}/get-pip.py) 58 | python -m pip install -U "tox<4" 59 | python -m tox -e py${{ matrix.python-version[0] }}${{ matrix.python-version[1] }}-int-${{ matrix.it-selector }}-pex1.6 60 | integration-tests-37-312: 61 | name: (${{ matrix.os }}) TOXENV=py${{ matrix.python-version[0] }}${{ matrix.python-version[1] }}-int-${{ matrix.it-selector }}-pex1.6 62 | runs-on: ${{ matrix.os }} 63 | strategy: 64 | matrix: 65 | include: 66 | - python-version: [3, 7] 67 | os: ubuntu-22.04 68 | it-selector: "{pre,post}" 69 | - python-version: [3, 8] 70 | os: ubuntu-22.04 71 | it-selector: "{pre,post}" 72 | - python-version: [3, 9] 73 | os: ubuntu-22.04 74 | it-selector: "{pre,post}" 75 | - python-version: [3, 10] 76 | os: macos-12 77 | it-selector: "post" 78 | - python-version: [3, 10] 79 | os: ubuntu-22.04 80 | it-selector: "post" 81 | - python-version: [3, 11] 82 | os: ubuntu-22.04 83 | it-selector: "post" 84 | - python-version: [3, 12] 85 | os: ubuntu-22.04 86 | it-selector: "post" 87 | steps: 88 | - name: Checkout Lambdex 89 | uses: actions/checkout@v4 90 | - name: Setup Python ${{ join(matrix.python-version, '.') }} 91 | uses: actions/setup-python@v5 92 | with: 93 | python-version: "${{ join(matrix.python-version, '.') }}" 94 | - name: Run Integration Tests 95 | uses: pantsbuild/actions/run-tox@e63d2d0e3c339bdffbe5e51e7c39550e3bc527bb 96 | with: 97 | tox-env: py${{ matrix.python-version[0] }}${{ matrix.python-version[1] }}-int-${{ matrix.it-selector }}-pex1.6 98 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+.[0-9]+.[0-9]+ 6 | workflow_dispatch: 7 | inputs: 8 | tag: 9 | description: The tag to manually run a deploy for. 10 | required: true 11 | jobs: 12 | determine-tag: 13 | name: Determine the release tag to operate against. 14 | runs-on: ubuntu-22.04 15 | outputs: 16 | release-tag: ${{ steps.determine-tag.outputs.release-tag }} 17 | release-version: ${{ steps.determine-tag.outputs.release-version }} 18 | steps: 19 | - name: Determine Tag 20 | id: determine-tag 21 | run: | 22 | if [[ -n "${{ github.event.inputs.tag }}" ]]; then 23 | RELEASE_TAG=${{ github.event.inputs.tag }} 24 | else 25 | RELEASE_TAG=${GITHUB_REF#refs/tags/} 26 | fi 27 | if [[ "${RELEASE_TAG}" =~ ^v[0-9]+.[0-9]+.[0-9]+$ ]]; then 28 | echo "release-tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT 29 | echo "release-version=${RELEASE_TAG#v}" >> $GITHUB_OUTPUT 30 | else 31 | echo "::error::Release tag '${RELEASE_TAG}' must match 'v\d+.\d+.\d+'." 32 | exit 1 33 | fi 34 | pypi: 35 | name: Publish sdist and wheel to PyPI 36 | runs-on: ubuntu-22.04 37 | environment: Release 38 | needs: determine-tag 39 | steps: 40 | - name: Checkout ${{ needs.determine-tag.outputs.release-tag }} 41 | uses: actions/checkout@v4 42 | with: 43 | ref: ${{ needs.determine-tag.outputs.release-tag }} 44 | - name: Setup Python 3.9 45 | uses: actions/setup-python@v5 46 | with: 47 | python-version: 3.9 48 | - name: Publish ${{ needs.determine-tag.outputs.release-tag }} 49 | uses: pantsbuild/actions/run-tox@e63d2d0e3c339bdffbe5e51e7c39550e3bc527bb 50 | env: 51 | FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }} 52 | FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 53 | with: 54 | tox-env: publish 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | /.tox 4 | *.egg-info 5 | /build 6 | /dist 7 | .cache 8 | /htmlcov 9 | .coverage -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.2.0 4 | 5 | This release brings official support for Python 3.12 and is also the last release of Lambdex. 6 | See [the migration guide](MIGRATING.md) for how modern PEXes can be used directly with no need for 7 | Lambdex. 8 | 9 | ## 0.1.9 10 | 11 | This release fixes a bug wherein, when using the -o/--output option Lambdex would fail to write the 12 | output file if the original input file was not writeable. 13 | 14 | ## 0.1.8 15 | 16 | This release adds an -o/--output option, for when the input file can't be modified. 17 | 18 | * An option to write the result to a new file. (#28) 19 | 20 | ## 0.1.7 21 | 22 | This release brings official support for Python 3.10 and 3.11. 23 | 24 | ## 0.1.6 25 | 26 | This release brings support for creating a lambdex that works on GCP. The feature should work for 27 | ancient Pex but is only tested against modern Pex (>=1.6). 28 | 29 | * Allow arbitrary handlers to support more runtimes. (#22) 30 | * Create arg that can specify module name. (#21) 31 | 32 | ## 0.1.5 33 | 34 | This release adds support for customizing the entry point at runtime using `LAMBDEX_ENTRY_POINT` 35 | 36 | * Use `LAMBDEX_ENTRY_POINT` env var for entry point if available (#19) 37 | 38 | ## 0.1.4 39 | 40 | This release fixes the implicit Lambdex dependency on setuptools and uses the vendored version 41 | supplied by modern versions of Pex. 42 | 43 | * Grab setuptools from pex >= 1.6.0. (#8) 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is a minimum Amazon Linux environment that contains 'pex' 2 | # It can be tailored to a certain extent in order to build Amazon Linux pex files. 3 | # 4 | # See http://docs.aws.amazon.com/AmazonECR/latest/userguide/amazon_linux_container_image.html 5 | # for more info about the parent image. 6 | 7 | FROM 137112412989.dkr.ecr.us-west-2.amazonaws.com/amazonlinux 8 | 9 | RUN yum upgrade -y && yum install -y python27-pip.noarch 10 | RUN pip install pex requests wheel 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /MIGRATING.md: -------------------------------------------------------------------------------- 1 | # Migrating to modern Pex 2 | 3 | Lambdex used to be needed to produce zip files useable in lambda functions, but with modern Pex, 4 | it no longer is. Starting with Pex version 2.1.98 you only need to include `import __pex__` at the 5 | top of your lambda handler entrypoint module and build the PEX with 6 | `--inherit-path {fallback,prefer}`. 7 | 8 | For example, with the following `my_lambda_module.py`: 9 | ```python 10 | import __pex__ 11 | 12 | import hashlib 13 | 14 | import requests 15 | 16 | 17 | def handler(event, context): 18 | url = event["url"] 19 | return { 20 | url: hashlib.sha256(requests.get(url).content).hexdigest(), 21 | "requests.__file__": requests.__file__, 22 | } 23 | ``` 24 | 25 | You can create a zip that will work[^1] in the Python 3.12 AWS lambda runtime with: 26 | ``` 27 | pex \ 28 | --python python3.12 \ 29 | requests \ 30 | --module my_lambda_module \ 31 | --output-file pex_lambda_function.zip \ 32 | --inherit-path=fallback 33 | ``` 34 | 35 | With the zip uploaded and the lambda runtime configured to use the `my_lambda_module.handler` 36 | handler, you can post an event with `{"url": "https://example.org"}` to the handler endpoint 37 | and see a response similar to: 38 | ```json 39 | { 40 | "https://example.org": "ea8fac7c65fb589b0d53560f5251f74f9e9b243478dcb6b3ea79b5e36449c8d9", 41 | "requests.__file__": "/var/task/.deps/requests-2.32.3-py3-none-any.whl/requests/__init__.py" 42 | } 43 | ``` 44 | 45 | [^1]: In general, you need to either build the PEX in an environment compatible with the Lambda 46 | deployment environment or else use the the Pex [`--complete-platform`]( 47 | https://docs.pex-tool.org/buildingpex.html#complete-platform) option to properly cross-resolve 48 | for the deployement environment. This is no different a requirement than existed when using 49 | Lambdex to transform a PEX into a lambda-compatible zip previously. 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambdex 2 | 3 | > [!WARNING] 4 | > Lambdex is no longer necessary and the 0.2.0 release of Lambdex is the last. 5 | > Modern PEXes can be used directly as Lambda zips. See the [the migration guide](MIGRATING.md) for 6 | > details. 7 | 8 | lambdex turns pex files into aws lambda functions. 9 | 10 | [pex](https://github.com/pex-tool/pex) is a tool that simplifies packaging python environments and is ideally suited 11 | for aws lambda. lambdex takes pex files and turns them into aws lambda functions, allowing 12 | you to more easily run complex applications in the cloud. 13 | 14 | aws lambda documentation and concepts can be found [here](https://aws.amazon.com/lambda/getting-started/). 15 | 16 | ## using the lambdex cli 17 | 18 | The lambdex cli has two subcommands: `build` and `test`. `build` further has two possible modes of operation: by specifying 19 | an entry point that already exists within the pex file (`-e`) or by specifying an external script and handler to embed within 20 | the pex file (`-s/-H`). 21 | 22 | ### step 1: package a pex file 23 | 24 | First you must package a pex file. Assuming you already have the `pex` tool 25 | and a requirements.txt, you can simply run 26 | 27 | pex -r requirements.txt -o lambda_function.pex 28 | 29 | to produce a pex file containing the requirements. If you must build a pex 30 | file with platform-specific extensions, see the tips section below for more 31 | information about building Amazon Linux-specific extensions. 32 | 33 | ### step 2: add lambdex handler 34 | 35 | This can be done one of two ways, depending on where your code lives. 36 | 37 | If you have a handler function named 'handler' in package 38 | 'mymodule.myapp' that is already contained within lambda_function.pex, 39 | then you can simply run 40 | 41 | lambdex build -e mymodule.myapp:handler lambda_function.pex 42 | 43 | If you have a script function.py with a lambda handler named `my_handler`, you would instead run 44 | 45 | lambdex build -s function.py -H my_handler lambda_function.pex 46 | 47 | This bundles function.py within the pex environment and instructs lambdex to 48 | call the python function `my_handler` when being invoked by AWS. 49 | 50 | If you would like to build a GCP Cloud Function, you will need to specify the name of the entrypoint module 51 | to be `main.py`. 52 | 53 | lambdex build -s example_http_function.py -M main.py lambda_function.zip 54 | 55 | ### step 3 (optional): test your lambdex function 56 | 57 | Once you have created a lambdex file, you can test it as if it were being invoked by Amazon using `lambdex test`. 58 | Given a lambdex package `lambda_function.pex`, you can either send it an empty json event using 59 | 60 | lambdex test --empty lambda_function.pex 61 | 62 | You can alternately supply a list of files containing json structs e.g. 63 | 64 | lambdex test lambda_function.pex event1.json event2.json ... 65 | 66 | Testing a GCP HTTP Cloud Function requires specifying the type. 67 | 68 | lambdex test --type gcp-http lambda_function.zip 69 | 70 | > Note: In order to test GCP HTTP Cloud Functions, you must be using pex v1.6 or greater. 71 | 72 | ### step 4: upload lambda function 73 | 74 | You can create/update lambda functions via the AWS Console, or you can do it 75 | via the CLI using `aws lambda create-function` or `aws lambda update-function-code` respectively. 76 | 77 | *NOTE*: When creating the function, you must specify the AWS Lambda handler as 78 | `lambdex_handler.handler`. Via the CLI, this is the `--handler` flag. This 79 | is the wrapper injected by lambdex that manages invocation of your code. 80 | 81 | Do not confuse this with the `-H` option to `lambdex build`. 82 | 83 | ## tips 84 | 85 | ### building amazon linux pex files 86 | 87 | Most simple dependencies have no platform-specific extensions and thus can be built anywhere. However there are a number of 88 | popular packages (e.g. numpy, scipy, matplotlib, PIL, ...) that require building C extensions that can prove tricky 89 | to get packaged correctly. 90 | 91 | Amazon provides an amazonlinux docker image which can be useful for building platform-specific extensions to run 92 | on AWS Lambda. See [documentation](http://docs.aws.amazon.com/AmazonECR/latest/userguide/amazon_linux_container_image.html) 93 | for information about that image. 94 | 95 | The minimum Dockerfile to produce can environment that can build Amazon Linux-specific pex files can be found [here](https://github.com/pex-tool/lambdex/blob/main/Dockerfile) 96 | 97 | ### controlling runtime execution 98 | 99 | To override the entry point that was specified at build time, you can use the `LAMBDEX_ENTRY_POINT` env var: 100 | 101 | LAMBDEX_ENTRY_POINT=mymodule.myapp:other_handler ... 102 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | ## Preparation 4 | 5 | ### Version Bump and Changelog 6 | 7 | Bump the version in [`lambdex/version.py`](lambdex/version.py) and update 8 | [`CHANGES.md`](CHANGES.md) with any changes that are likely to be useful to consumers and then open 9 | a PR with these changes and land it on https://github.com/pex-tool/lambdex main. 10 | 11 | ## Release 12 | 13 | ### Push Release Tag 14 | 15 | Sync a local branch with https://github.com/pex-tool/lambdex main and confirm it has the version 16 | bump and changelog update as the tip commit: 17 | 18 | ``` 19 | $ git log --stat -1 HEAD 20 | commit 1ce9fa95893b05bc0bc3a9a7d9c415deeb14d447 (HEAD -> main, origin/main, origin/HEAD) 21 | Author: John Sirois 22 | Date: Mon Apr 26 12:36:53 2021 -0800 23 | 24 | Prepare the 0.1.4 release. (#21) 25 | 26 | lambdex/version.py | 2 +- 27 | CHANGES.md | 41 +++++++ 28 | 2 files changed, 42 insertions(+), 1 deletions(-) 29 | ``` 30 | 31 | Tag the release as `v` and push the tag to https://github.com/pex-tool/lambdex main: 32 | 33 | ``` 34 | $ git tag --sign -am 'Release 0.1.4' v0.1.4 35 | $ git push --tags https://github.com/pex-tool/lambdex HEAD:main 36 | ``` 37 | 38 | The release to PyPI is automated but requires approval from at least one Core or Maintainers team 39 | member. These folks will all get an email with a link to the GitHub release workflow to do this. 40 | Alternatively, they can open the Release workflow 41 | [here](https://github.com/pex-tool/lambdex/actions?query=workflow%3ARelease) and navigate to the 42 | release approval widget. 43 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1) lambdex test should use something other than 'None' for context 2 | 2) pex should be fixed so that we can build pex files from w/in lambdex (pex cli composability broke somewhere in 1.1.x) 3 | 3) tests? 4 | -------------------------------------------------------------------------------- /examples/event_based/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pex -r requirements.txt -o lambda_function.zip 4 | lambdex build -s example_function.py lambda_function.zip 5 | lambdex test lambda_function.zip <(echo '{"url": "https://github.com/pex-tool"}') 6 | -------------------------------------------------------------------------------- /examples/event_based/example_function.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | import requests 4 | 5 | 6 | def handler(event, context): 7 | url = event["url"] 8 | print("%s sha256:%s" % (url, hashlib.sha256(requests.get(url).content).hexdigest())) 9 | 10 | 11 | def other_handler(event, context): 12 | print("Other handler invoked") 13 | -------------------------------------------------------------------------------- /examples/event_based/requirements.txt: -------------------------------------------------------------------------------- 1 | requests<=2.27.1 2 | 3 | # Pin low to work around Python 3 syntax creeping in. 4 | certifi<=2021.10.8 5 | -------------------------------------------------------------------------------- /examples/gcp_http/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pex -r requirements.txt -o lambda_function.zip 4 | dist/lambdex build -s example_http_function.py -M main.py lambda_function.zip 5 | dist/lambdex test --type gcp-http lambda_function.zip <(echo '{"url": "https://github.com/pex-tool"}') 6 | -------------------------------------------------------------------------------- /examples/gcp_http/example_http_function.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | import flask 4 | import requests 5 | 6 | 7 | def handler(request): 8 | request_json = request.get_json() 9 | url = request_json.get("url") 10 | if not url: 11 | print("No URL provided!") 12 | return {} 13 | 14 | print("%s sha256:%s" % (url, hashlib.sha256(requests.get(url).content).hexdigest())) 15 | return { 16 | "url": flask.escape(url), 17 | "sha256": hashlib.sha256(requests.get(url).content).hexdigest(), 18 | } 19 | -------------------------------------------------------------------------------- /examples/gcp_http/requirements.txt: -------------------------------------------------------------------------------- 1 | flask==1.1.4 2 | requests<=2.27.1 3 | 4 | # Pin low to work around Python 3 syntax creeping in. 5 | certifi<=2021.10.8 6 | 7 | # Work around 'cannot import name 'soft_unicode' from 'markupsafe' by pinning it low. 8 | MarkupSafe<=2.0.1 9 | -------------------------------------------------------------------------------- /lambdex/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | # N.B.: Flit uses this as our distribution description. 5 | """Lambdex turns pex files into aws lambda python functions.""" 6 | 7 | from __future__ import absolute_import 8 | 9 | from .version import __version__ as __lambdex_version__ 10 | 11 | __version__ = __lambdex_version__ # N.B.: Flit uses this as out distribution version. 12 | -------------------------------------------------------------------------------- /lambdex/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import absolute_import 5 | 6 | from lambdex.bin import lambdex 7 | 8 | if __name__ == "__main__": 9 | lambdex.main() 10 | -------------------------------------------------------------------------------- /lambdex/bin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | -------------------------------------------------------------------------------- /lambdex/bin/lambdex.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | from __future__ import absolute_import, print_function 5 | 6 | import argparse 7 | import contextlib 8 | import hashlib 9 | import json 10 | import os 11 | import pkgutil 12 | import shutil 13 | import stat 14 | import sys 15 | import zipfile 16 | 17 | from pex.pex_bootstrapper import bootstrap_pex_env 18 | 19 | from lambdex.version import __version__ 20 | 21 | EVENT_FUNCTION_SIGNATURE = "event" 22 | GCP_HTTP_FUNCTION_SIGNATURE = "gcp-http" 23 | 24 | 25 | def die(msg): 26 | print(msg, file=sys.stderr) 27 | sys.exit(1) 28 | 29 | 30 | class LambdexInfo(object): 31 | @classmethod 32 | def from_string(cls, blob): 33 | return LambdexInfo(**json.loads(blob)) 34 | 35 | def __init__(self, entry_point): 36 | self.entry_point = entry_point 37 | 38 | def to_json(self): 39 | return json.dumps({"entry_point": self.entry_point}) 40 | 41 | 42 | def _write_zip_content(zf, filename, content): 43 | info = zipfile.ZipInfo(filename) 44 | info.external_attr = 0o755 << 16 45 | zf.writestr(info, content) 46 | 47 | 48 | def write_lambdex_handler(pex_zip, options): 49 | if (options.script is not None and options.entry_point is not None) or ( 50 | options.script is None and options.entry_point is None 51 | ): 52 | die("Must specify one of -s/--script or -e/--entry-point but not both.") 53 | 54 | if options.output: 55 | output_zip = options.output 56 | shutil.copy(pex_zip, output_zip) 57 | os.chmod(output_zip, os.stat(output_zip).st_mode | stat.S_IWRITE) 58 | else: 59 | output_zip = pex_zip 60 | 61 | script = None 62 | if options.script is not None: 63 | method = options.handler 64 | script = os.path.basename(options.script) 65 | filename_prefix, ext = os.path.splitext(script) 66 | if ext != ".py": 67 | die('--script must be a python file that ends with ".py"') 68 | # TODO(wickman) Validate that there is a symbol w/in the file that 69 | # matches the method using ast.parse 70 | entry_point = "%s:%s" % (filename_prefix, method) 71 | else: 72 | entry_point = options.entry_point 73 | 74 | lambdex_info = LambdexInfo(entry_point) 75 | 76 | with contextlib.closing(zipfile.ZipFile(output_zip, "a")) as zf: 77 | if script is not None: 78 | with open(os.path.realpath(options.script), "rb") as fp: 79 | _write_zip_content(zf, script, fp.read()) 80 | _write_zip_content(zf, "LAMBDEX-INFO", lambdex_info.to_json()) 81 | _write_zip_content( 82 | zf, options.module, pkgutil.get_data("lambdex.resources", "lambdex_handler.py") 83 | ) 84 | 85 | 86 | # lambdex build foo.pex 87 | # [-H handler] 88 | # [-M module.py] 89 | # [-s script.py] 90 | # [-e pkg:symbol] 91 | # [-o output_file.zip] 92 | def build_lambdex(args): 93 | write_lambdex_handler(args.pex, args) 94 | 95 | 96 | def configure_build_command(parser): 97 | parser = parser.add_parser( 98 | "build", 99 | help="build a lambdex package", 100 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 101 | ) 102 | parser.set_defaults(func=build_lambdex) 103 | 104 | parser.add_argument( 105 | "pex", metavar="PEXFILE", help="The pex file to turn into a lambdex package." 106 | ) 107 | 108 | parser.add_argument( 109 | "-s", 110 | "--script", 111 | dest="script", 112 | metavar="FILENAME", 113 | default=None, 114 | help="The script to include, if any.", 115 | ) 116 | 117 | parser.add_argument( 118 | "-e", 119 | "--entry-point", 120 | dest="entry_point", 121 | metavar="PACKAGE:NAME", 122 | default=None, 123 | help="Set the entry point of the lambda function to this package:name tuple.", 124 | ) 125 | 126 | parser.add_argument( 127 | "-H", 128 | "--script-handler", 129 | dest="handler", 130 | default="handler", 131 | metavar="FUNCTION", 132 | help="Invoke this function within the script.", 133 | ) 134 | 135 | parser.add_argument( 136 | "-M", 137 | "--script-module", 138 | dest="module", 139 | default="lambdex_handler.py", 140 | metavar="FILENAME", 141 | help="Root module of the lambda.", 142 | ) 143 | 144 | parser.add_argument( 145 | "-o", 146 | "--output", 147 | dest="output", 148 | default=None, 149 | metavar="FILENAME", 150 | help="Write output to this path. Otherwise, modifies the input file in-place.", 151 | ) 152 | 153 | 154 | def load_json_blob(filename): 155 | if filename == "-": 156 | return json.loads(sys.stdin.read()) 157 | else: 158 | with open(filename, "rb") as fp: 159 | return json.load(fp) 160 | 161 | 162 | CHUNK_SIZE = 64 * 1024 163 | 164 | 165 | def hash_file(filename, hasher=hashlib.sha256): 166 | hash = hasher() 167 | with open(filename, "rb") as fp: 168 | for chunk in iter(lambda: fp.read(CHUNK_SIZE), b""): 169 | hash.update(chunk) 170 | return hash.hexdigest() 171 | 172 | 173 | def unzip(filename, path): 174 | os.makedirs(path + "~") 175 | with contextlib.closing(zipfile.ZipFile(filename, "r")) as zfp: 176 | zfp.extractall(path=path + "~") 177 | os.rename(path + "~", path) 178 | 179 | 180 | @contextlib.contextmanager 181 | def cached_environment(root, pex_file): 182 | if os.path.isdir(pex_file): 183 | yield pex_file 184 | return 185 | 186 | sha = hash_file(pex_file) 187 | target = os.path.join(os.path.normpath(os.path.expanduser(root)), sha) 188 | 189 | if not os.path.exists(target): 190 | unzip(pex_file, target) 191 | 192 | yield target 193 | 194 | 195 | @contextlib.contextmanager 196 | def chdir(dirname): 197 | cwd = os.getcwd() 198 | os.chdir(dirname) 199 | yield 200 | os.chdir(cwd) 201 | 202 | 203 | def load_entry_point(entry_point): 204 | if sys.version_info[:2] >= (3, 8): 205 | from importlib.metadata import EntryPoint 206 | 207 | return EntryPoint(name=None, value=entry_point, group=None).load() 208 | else: 209 | try: 210 | # PEX >= 1.6.0 211 | from pex.third_party.pkg_resources import EntryPoint 212 | except ImportError: 213 | # PEX < 1.6.0 has an install requirement of setuptools which we leverage knowledge of. 214 | from pkg_resources import EntryPoint 215 | return EntryPoint.parse("run = {ep}".format(ep=entry_point)).resolve() 216 | 217 | 218 | # lambdex test [context configuration options] foo.pex = 1.6.0 27 | from pex.pex_bootstrapper import bootstrap_pex_env 28 | except ImportError: 29 | # PEX < 1.6.0 has an install requirement of setuptools which we leverage knowledge of. 30 | from _pex.pex_bootstrapper import bootstrap_pex_env 31 | 32 | bootstrap_pex_env(__entry_point__) 33 | 34 | __lambdex_entry_point = os.environ.get("LAMBDEX_ENTRY_POINT") 35 | 36 | if not __lambdex_entry_point: 37 | import json as __json 38 | import zipfile 39 | 40 | if zipfile.is_zipfile(__entry_point__): 41 | import contextlib 42 | 43 | with contextlib.closing(zipfile.ZipFile(__entry_point__)) as zf: 44 | __lambdex_info_blob = zf.read("LAMBDEX-INFO") 45 | else: 46 | with open(os.path.join(__entry_point__, "LAMBDEX-INFO"), "rb") as fp: 47 | __lambdex_info_blob = fp.read() 48 | 49 | __lambdex_info = __json.loads(__lambdex_info_blob) 50 | __lambdex_entry_point = __lambdex_info["entry_point"] 51 | 52 | 53 | def load_entry_point(entry_point): 54 | if sys.version_info[:2] >= (3, 8): 55 | from importlib.metadata import EntryPoint 56 | 57 | return EntryPoint(name=None, value=entry_point, group=None).load() 58 | else: 59 | try: 60 | # PEX >= 1.6.0 61 | from pex.third_party.pkg_resources import EntryPoint 62 | except ImportError: 63 | # PEX < 1.6.0 has an install requirement of setuptools which we leverage knowledge of. 64 | from pkg_resources import EntryPoint 65 | return EntryPoint.parse("run = {ep}".format(ep=entry_point)).resolve() 66 | 67 | 68 | __RUNNER = load_entry_point(__lambdex_entry_point) 69 | del load_entry_point 70 | 71 | 72 | def handler(*args, **kwargs): 73 | return __RUNNER(*args, **kwargs) 74 | -------------------------------------------------------------------------------- /lambdex/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). 2 | # Licensed under the Apache License, Version 2.0 (see LICENSE). 3 | 4 | __version__ = "0.2.0" 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<3"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [tool.flit.metadata] 6 | module = "lambdex" 7 | author = "The Lambdex developers" 8 | author-email = "developers@pex-tool.org" 9 | home-page = "https://github.com/pex-tool/lambdex" 10 | description-file = "README.md" 11 | classifiers = [ 12 | "Development Status :: 4 - Beta", 13 | "Intended Audience :: Developers", 14 | "License :: OSI Approved :: Apache Software License", 15 | "Operating System :: Unix", 16 | "Operating System :: POSIX :: Linux", 17 | "Operating System :: MacOS :: MacOS X", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 2", 20 | "Programming Language :: Python :: 2.7", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.6", 23 | "Programming Language :: Python :: 3.7", 24 | "Programming Language :: Python :: 3.8", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | "Topic :: Software Development :: Build Tools", 30 | "Topic :: System :: Archiving :: Packaging", 31 | "Topic :: System :: Software Distribution", 32 | "Topic :: Utilities", 33 | ] 34 | requires = ["pex>=1.1.15"] 35 | requires-python = ">=2.7,<3.13,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" 36 | 37 | [tool.flit.metadata.requires-extra] 38 | test-gcp-http = [ 39 | "flask==1.1.4; python_version < '3.6'", 40 | "flask==2.0.3; python_version >= '3.6' and python_version < '3.7'", 41 | "flask==2.2.2; python_version >= '3.7'", 42 | ] 43 | 44 | [tool.flit.scripts] 45 | lambdex = "lambdex.bin.lambdex:main" 46 | 47 | [tool.flit.sdist] 48 | include = ["CHANGES.md"] 49 | 50 | [tool.flit.metadata.urls] 51 | Changelog = "https://github.com/pex-tool/lambdex/blob/main/CHANGES.md" 52 | 53 | [tool.black] 54 | line-length = 100 55 | target-version = ["py27"] 56 | exclude = ''' 57 | /( 58 | | \.git 59 | )/ 60 | ''' 61 | 62 | [tool.isort] 63 | profile = "black" 64 | line_length = 100 65 | known_first_party = "lambdex" 66 | 67 | -------------------------------------------------------------------------------- /scripts/build-lambdex-pex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import os 6 | import subprocess 7 | import sys 8 | from argparse import ArgumentParser 9 | 10 | 11 | def main(dest): 12 | # N.B.: Pex outputs --version to STDERR under Python 2.7 argparse; so we capture both streams. 13 | output = subprocess.check_output(args=["pex", "--version"], stderr=subprocess.STDOUT) 14 | 15 | # iN.B.: Older versions of Pex respond to --version with 'pex ' whereas newer versions 16 | # of Pex just respond with ''. 17 | pex_version = output.decode("utf-8").strip().split(" ", 1)[-1] 18 | 19 | pex_requirement = "pex=={version}".format(version=pex_version) 20 | print( 21 | "Using {pex_requirement} to build a Lambdex PEX.".format(pex_requirement=pex_requirement), 22 | file=sys.stderr, 23 | ) 24 | subprocess.check_call( 25 | args=["pex", "--python", sys.executable, ".", pex_requirement, "-c", "lambdex", "-o", dest] 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | parser = ArgumentParser() 31 | parser.add_argument("dest", nargs=1) 32 | options = parser.parse_args() 33 | main(options.dest[0]) 34 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = True 3 | skip_missing_interpreters = True 4 | minversion = 3.23.0 5 | envlist = 6 | fmt-check 7 | py39-int-pre-pex1.6 8 | py39-int-post-pex1.6 9 | 10 | [tox:.package] 11 | # N.B.: tox will use the same python version as under what tox is installed to package, so unless 12 | # this is python 3 you can require a given python version for the packaging environment via the 13 | # basepython key. We need this for flit which only runs under python3 but works for python2 14 | # projects. 15 | basepython = python3 16 | 17 | [_integration] 18 | deps = 19 | tox 20 | commands = 21 | tox -e pex 22 | 23 | [_event_integration] 24 | deps = 25 | {[_integration]deps} 26 | allowlist_externals = 27 | chmod 28 | cp 29 | {toxinidir}/dist/lambdex 30 | {toxinidir}/dist/lambda_function_copy.pex 31 | {toxinidir}/dist/lambda_function.pex 32 | commands = 33 | {[_integration]commands} 34 | pex --version 35 | pex -r {toxinidir}/examples/event_based/requirements.txt -o {toxinidir}/dist/lambda_function.pex 36 | cp {toxinidir}/dist/lambda_function.pex {toxinidir}/dist/lambda_function_ro.pex 37 | chmod 0544 {toxinidir}/dist/lambda_function_ro.pex 38 | {toxinidir}/dist/lambdex build -o {toxinidir}/dist/lambda_function_copy.pex -s examples/event_based/example_function.py -H handler -M lambdex_handler.py {toxinidir}/dist/lambda_function_ro.pex 39 | {toxinidir}/dist/lambda_function_copy.pex -c 'from lambdex_handler import handler; handler(\{"url":"https://github.com/pex-tool/lambdex"\}, None)' 40 | {toxinidir}/dist/lambdex build -s examples/event_based/example_function.py -H handler -M lambdex_handler.py {toxinidir}/dist/lambda_function.pex 41 | {toxinidir}/dist/lambda_function.pex -c 'from lambdex_handler import handler; handler(\{"url":"https://github.com/pex-tool/lambdex"\}, None)' 42 | tox -e entry-point-env-var 43 | 44 | [_gcp_http_integration] 45 | deps = 46 | {[_integration]deps} 47 | .[test-gcp-http] 48 | allowlist_externals = 49 | {toxinidir}/dist/lambdex 50 | commands = 51 | {[_integration]commands} 52 | pex --version 53 | pex -r {toxinidir}/examples/gcp_http/requirements.txt -o {toxinidir}/dist/gcp_http_function.pex 54 | {toxinidir}/dist/lambdex build -s examples/gcp_http/example_http_function.py -H handler -M main.py {toxinidir}/dist/gcp_http_function.pex 55 | {toxinidir}/dist/lambdex test --type gcp-http --empty {toxinidir}/dist/gcp_http_function.pex 56 | 57 | [testenv:py{27,36,37,38,39}-int-pre-pex1.6] 58 | # NB: 1.4.8 is the first pre-1.6.0 version to support -c and Python 3.9 is the last version to 59 | # support collections.Iterable which 1.4.8 uses. 60 | deps = 61 | {[_event_integration]deps} 62 | pex==1.4.8 63 | allowlist_externals = 64 | {[_event_integration]allowlist_externals} 65 | commands = 66 | {[_event_integration]commands} 67 | 68 | [testenv:py{27,36,37,38,39,310,311,312}-int-post-pex1.6] 69 | deps = 70 | {[_event_integration]deps} 71 | {[_gcp_http_integration]deps} 72 | pex>=1.6.0 73 | allowlist_externals = 74 | {[_event_integration]allowlist_externals} 75 | {[_gcp_http_integration]allowlist_externals} 76 | commands = 77 | {[_event_integration]commands} 78 | {[_gcp_http_integration]commands} 79 | 80 | [testenv:entry-point-env-var] 81 | setenv = 82 | LAMBDEX_ENTRY_POINT = example_function:other_handler 83 | commands = 84 | {toxinidir}/dist/lambdex test --empty {toxinidir}/dist/lambda_function.pex 85 | allowlist_externals = 86 | {toxinidir}/dist/lambdex 87 | {toxinidir}/dist/lambda_function.pex 88 | chmod 89 | 90 | [testenv:pex] 91 | deps = 92 | pex==2.1.43; python_version < "3.10" 93 | # N.B.: This is the lowest version of Pex to support up through Python 3.12. 94 | pex==2.1.139; python_version >= "3.10" 95 | commands = 96 | python scripts/build-lambdex-pex.py {toxinidir}/dist/lambdex 97 | 98 | [testenv:lambdex] 99 | commands = lambdex {posargs} 100 | 101 | [testenv:fmt] 102 | basepython = python3 103 | skip_install = true 104 | deps = 105 | black==21.4b1 106 | # The 8.1.0 release of click breaks black; so we pin. 107 | click==8.0.1 108 | isort==5.8.0 109 | commands = 110 | black . 111 | isort . 112 | 113 | [testenv:fmt-check] 114 | basepython = {[testenv:fmt]basepython} 115 | skip_install = true 116 | deps = 117 | {[testenv:fmt]deps} 118 | commands = 119 | black --check . 120 | isort --check-only . 121 | 122 | [_flit] 123 | basepython = python3 124 | deps = 125 | flit 126 | pygments 127 | 128 | [testenv:package] 129 | basepython = {[_flit]basepython} 130 | deps = 131 | {[_flit]deps} 132 | commands = 133 | flit build 134 | 135 | [testenv:publish] 136 | skip_install = true 137 | basepython = {[_flit]basepython} 138 | passenv = 139 | # These are used in CI. 140 | FLIT_USERNAME 141 | FLIT_PASSWORD 142 | deps = 143 | {[_flit]deps} 144 | commands = 145 | flit publish 146 | --------------------------------------------------------------------------------