diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md new file mode 100755 index 00000000000..beb340ad645 --- /dev/null +++ b/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,67 @@ + + + +{{ if .Versions -}} + +# Unreleased + +{{ if .Unreleased.CommitGroups -}} +{{ range .Unreleased.CommitGroups -}} +## {{ .Title }} + +{{ range .Commits -}} +{{ if and (not (hasPrefix .Subject "changelog rebuild")) (not (hasPrefix .Subject "layer docs update")) (not (hasPrefix .Subject "bump version to")) -}} +* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end -}} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} +{{ range .CommitGroups -}} + +## {{ .Title }} + +{{ range .Commits -}} +{{ if and (not (hasPrefix .Subject "changelog rebuild")) (not (hasPrefix .Subject "layer docs update")) (not (hasPrefix .Subject "bump version to")) -}} +* {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end -}} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +## Reverts +{{ range .RevertCommits -}} +* {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .MergeCommits -}} +## Pull Requests + +{{ range .MergeCommits -}} +* {{ .Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +## {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{- if .Versions }} +[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD +{{ range .Versions -}} +{{ if .Tag.Previous -}} +[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} +{{ end -}} diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100755 index 00000000000..9ec1c8ef31e --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,37 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/aws-powertools/powertools-lambda-python +options: + commits: + filters: + Type: + - feat + - fix + - perf + - refactor + - docs + - chore + - revert + commit_groups: + title_maps: + feat: Features + fix: Bug Fixes + perf: Performance Improvements + refactor: Code Refactoring + docs: Documentation + chore: Maintenance + revert: Regression + header: + pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" + pattern_maps: + - Type + - Scope + - Subject + notes: + keywords: + - BREAKING CHANGE + # issues: + # prefix: + # - # diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..1db8406d9e4 --- /dev/null +++ b/.flake8 @@ -0,0 +1,17 @@ +[flake8] +exclude = docs, .eggs, setup.py, example, .aws-sam, .git, dist, *.md, *.yaml, example/samconfig.toml, *.txt, *.ini +ignore = E203, E266, W503, BLK100, W291, I004 +max-line-length = 120 +max-complexity = 15 +; flake8-builtins isn't honouring inline ignore (A003) +per-file-ignores = + tests/e2e/utils/data_builder/__init__.py:F401 + tests/e2e/utils/data_fetcher/__init__.py:F401 + aws_lambda_powertools/utilities/data_classes/s3_event.py:A003 + +[isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 120 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..1062320f5b1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +* @aws-powertools/lambda-python-core diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000..c670ea38274 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,92 @@ +name: Bug report +description: Report a reproducible bug to help us improve +title: "Bug: TITLE" +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a bug report. Please add as much information as possible to help us reproduce, and remove any potential sensitive data. + + Please become familiar with [our definition of bug](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/MAINTAINERS.md#is-that-a-bug). + - type: textarea + id: expected_behaviour + attributes: + label: Expected Behaviour + description: Please share details on the behaviour you expected + validations: + required: true + - type: textarea + id: current_behaviour + attributes: + label: Current Behaviour + description: Please share details on the current issue + validations: + required: true + - type: textarea + id: code_snippet + attributes: + label: Code snippet + description: Please share a code snippet to help us reproduce the issue + render: python + validations: + required: true + - type: textarea + id: solution + attributes: + label: Possible Solution + description: If known, please suggest a potential resolution + validations: + required: false + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Please share how we might be able to reproduce this issue + validations: + required: true + - type: input + id: version + attributes: + label: Powertools for AWS Lambda (Python) version + placeholder: "latest, 1.25.6" + value: latest + validations: + required: true + - type: dropdown + id: runtime + attributes: + label: AWS Lambda function runtime + options: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + validations: + required: true + - type: dropdown + id: packaging + attributes: + label: Packaging format used + options: + - Lambda Layers + - Serverless Application Repository (SAR) App + - PyPi + multiple: true + validations: + required: true + - type: textarea + id: logs + attributes: + label: Debugging logs + description: If available, please share [debugging logs](https://docs.powertools.aws.dev/lambda/python/#debug-mode) + render: python + validations: + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..8acf5081f3b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/aws-powertools/powertools-lambda-python/discussions/new + about: Ask a general question about Lambda Powertools diff --git a/.github/ISSUE_TEMPLATE/documentation_improvements.yml b/.github/ISSUE_TEMPLATE/documentation_improvements.yml new file mode 100644 index 00000000000..e750d51923f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_improvements.yml @@ -0,0 +1,50 @@ +name: Documentation improvements +description: Suggest a documentation update to improve everyone's experience +title: "Docs: TITLE" +labels: ["documentation", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for helping us improve everyone's experience. We review documentation updates on a case by case basis. + - type: textarea + id: search_area + attributes: + label: What were you searching in the docs? + description: Please help us understand how you looked for information that was either unclear or not available + validations: + required: true + - type: input + id: area + attributes: + label: Is this related to an existing documentation section? + description: Please share a link, if applicable + validations: + required: false + - type: textarea + id: idea + attributes: + label: How can we improve? + description: Please share your thoughts on how we can improve this experience + validations: + required: true + - type: textarea + id: suggestion + attributes: + label: Got a suggestion in mind? + description: Please suggest a proposed update + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: I understand the final update might be different from my proposed suggestion, or refused. + required: true + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000000..a39fb211ac1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,48 @@ +name: Feature request +description: Suggest an idea for Powertools for AWS Lambda (Python) +title: "Feature request: TITLE" +labels: ["feature-request", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to suggest an idea to the Powertools for AWS Lambda (Python) project. + + *Future readers*: Please react with 👍 and your use case to help us understand customer demand. + - type: textarea + id: problem + attributes: + label: Use case + description: Please help us understand your use case or problem you're facing + validations: + required: true + - type: textarea + id: suggestion + attributes: + label: Solution/User Experience + description: Please share what a good solution would look like to this use case + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternative solutions + description: Please describe what alternative solutions to this use case, if any + render: markdown + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This feature request meets [Powertools for AWS Lambda (Python) Tenets](https://docs.powertools.aws.dev/lambda/python/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Java](https://github.com/aws-powertools/powertools-lambda-java/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml new file mode 100644 index 00000000000..bbc3ff3b3d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintenance.yml @@ -0,0 +1,65 @@ +name: Maintenance +description: Suggest an activity to help address governance and anything internal +title: "Maintenance: TITLE" +labels: ["internal", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to help us improve operational excellence. + + *Future readers*: Please react with 👍 and your use case to help us understand customer demand. + - type: textarea + id: importance + attributes: + label: Why is this needed? + description: Please help us understand the value so we can prioritize it accordingly + validations: + required: true + - type: dropdown + id: area + attributes: + label: Which area does this relate to? + multiple: true + options: + - Tests + - Static typing + - Tracer + - Logger + - Metrics + - Event Handler - REST API + - Event Handler - GraphQL API + - Middleware factory + - Parameters + - Batch processing + - Validation + - Event Source Data Classes + - Parser + - Idempotency + - Feature flags + - JMESPath functions + - Streaming + - Automation + - Other + - type: textarea + id: suggestion + attributes: + label: Solution + description: If available, please share what a good solution would look like + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This request meets [Powertools for AWS Lambda (Python) Tenets](https://docs.powertools.aws.dev/lambda/python/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Java](https://github.com/aws-powertools/powertools-lambda-java/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/rfc.yml b/.github/ISSUE_TEMPLATE/rfc.yml new file mode 100644 index 00000000000..31d8d7fe0c6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rfc.yml @@ -0,0 +1,109 @@ +name: Request for Comments (RFC) +description: Feature design and detailed proposals +title: "RFC: TITLE" +labels: ["RFC", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a RFC. Please add as many details as possible to help further enrich this design. + - type: input + id: relation + attributes: + label: Is this related to an existing feature request or issue? + description: Please share a link, if applicable + - type: dropdown + id: area + attributes: + label: Which Powertools for AWS Lambda (Python) utility does this relate to? + options: + - Tracer + - Logger + - Metrics + - Event Handler - REST API + - Event Handler - GraphQL API + - Middleware factory + - Parameters + - Batch processing + - Typing + - Validation + - Event Source Data Classes + - Parser + - Idempotency + - Feature flags + - JMESPath functions + - Other + validations: + required: true + - type: textarea + id: summary + attributes: + label: Summary + description: Please provide an overview in one or two paragraphs + validations: + required: true + - type: textarea + id: problem + attributes: + label: Use case + description: Please share the use case and motivation behind this proposal + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: Please explain the design in detail, so anyone familiar with the project could implement it + placeholder: What the user experience looks like before and after this design? + validations: + required: true + - type: textarea + id: scope + attributes: + label: Out of scope + description: Please explain what should be considered out of scope in your proposal + validations: + required: true + - type: textarea + id: challenges + attributes: + label: Potential challenges + description: Nothing is perfect. Please share what common challenges, edge cases, unresolved areas, and suggestions on how to mitigate them + validations: + required: true + - type: textarea + id: integrations + attributes: + label: Dependencies and Integrations + description: If applicable, please share whether this feature has additional dependencies, and how it might integrate with other utilities available + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternative solutions + description: Please describe what alternative solutions to this use case, if any + render: markdown + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This feature request meets [Powertools for AWS Lambda (Python) Tenets](https://docs.powertools.aws.dev/lambda/python/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Java](https://github.com/aws-powertools/powertools-lambda-java/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. + + Metadata information for admin purposes, please leave them empty. + + * RFC PR: + * Approved by: '' + * Reviewed by: '' diff --git a/.github/ISSUE_TEMPLATE/share_your_work.yml b/.github/ISSUE_TEMPLATE/share_your_work.yml new file mode 100644 index 00000000000..e4e4ed601be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/share_your_work.yml @@ -0,0 +1,56 @@ +name: I Made This (showcase your work) +description: Share what you did with Powertools for AWS Lambda (Python) 💞💞. Blog post, workshops, presentation, sample apps, etc. +title: "[I Made This]: " +labels: ["community-content"] +body: + - type: markdown + attributes: + value: Thank you for helping spread the word out on Powertools, truly! + - type: input + id: content + attributes: + label: Link to your material + description: | + Please share the original link to your material. + + *Note: Short links will be expanded when added to Powertools for AWS Lambda (Python) documentation* + validations: + required: true + - type: textarea + id: description + attributes: + label: Description + description: Describe in one paragraph what's in it for them (readers) + validations: + required: true + - type: input + id: author + attributes: + label: Preferred contact + description: What's your preferred contact? We'll list it next to this content + validations: + required: true + - type: input + id: author-social + attributes: + label: (Optional) Social Network + description: If different from preferred contact, what's your preferred contact for social interactions? + validations: + required: false + - type: textarea + id: notes + attributes: + label: (Optional) Additional notes + description: | + Any notes you might want to share with us related to this material. + + *Note: These notes are explicitly to Powertools for AWS Lambda (Python) maintainers. It will not be added to the community resources page.* + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: I understand this content may be removed from Powertools for AWS Lambda (Python) documentation if it doesn't conform with the [Code of Conduct](https://aws.github.io/code-of-conduct) + required: true diff --git a/.github/ISSUE_TEMPLATE/static_typing.yml b/.github/ISSUE_TEMPLATE/static_typing.yml new file mode 100644 index 00000000000..eb8c7a77387 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/static_typing.yml @@ -0,0 +1,70 @@ +name: Static typing mismatch report +description: Report a static type mismatch caught by a static type checker +title: "Static typing: TITLE" +labels: ["typing", "triage"] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a static typing report. Please add as much information as possible to help us reproduce. + + Our preferred static type checker is [Mypy](https://mypy.readthedocs.io/en/stable/) using the following [configuration](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/mypy.ini). + - type: dropdown + id: tool + attributes: + label: Static type checker used + options: + - mypy (project's standard) + - pyright/pylance + - pyre + - pytype + validations: + required: true + - type: dropdown + id: runtime + attributes: + label: AWS Lambda function runtime + options: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + validations: + required: true + - type: input + id: version + attributes: + label: Powertools for AWS Lambda (Python) version + placeholder: "latest, 1.25.6" + value: latest + validations: + required: true + - type: textarea + id: output + attributes: + label: Static type checker info + description: Please share your static type checker's output, its configuration, and how you typically run it + validations: + required: true + - type: textarea + id: code_snippet + attributes: + label: Code snippet + description: Please share a code snippet to help us reproduce the issue + render: python + validations: + required: true + - type: textarea + id: solution + attributes: + label: Possible Solution + description: If known, please suggest a potential resolution + validations: + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/ISSUE_TEMPLATE/support_powertools.yml b/.github/ISSUE_TEMPLATE/support_powertools.yml new file mode 100644 index 00000000000..c2c5f33676c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_powertools.yml @@ -0,0 +1,64 @@ +name: Support Powertools for AWS Lambda (Python) (become a reference) +description: Add your organization's name or logo to the Powertools for AWS Lambda (Python) documentation +title: "[Support Powertools for AWS Lambda (Python)]: <your organization name>" +labels: ["customer-reference"] +body: + - type: markdown + attributes: + value: | + Thank you for becoming a reference customer. Your support means a lot to us. It also helps new customers to know who's using it. + + If you would like us to also display your organization's logo, please share a link in the `Company logo` field. + - type: input + id: organization + attributes: + label: Organization Name + description: Please share the name of your organization + placeholder: ACME + validations: + required: true + - type: input + id: name + attributes: + label: Your Name + description: Please share your name + validations: + required: true + - type: input + id: job + attributes: + label: Your current position + description: Please share your current position at your company + validations: + required: true + - type: input + id: logo + attributes: + label: (Optional) Company logo + description: Company logo you want us to display. You also allow us to resize for optimal placement in the documentation. + validations: + required: false + - type: textarea + id: use_case + attributes: + label: (Optional) Use case + description: How are you using Powertools for AWS Lambda (Python) today? *features, etc.* + validations: + required: false + - type: checkboxes + id: other_languages + attributes: + label: Also using other Powertools for AWS Lambda languages? + options: + - label: Java + required: false + - label: TypeScript + required: false + - label: .NET + required: false + - type: markdown + attributes: + value: | + *By raising a Support Powertools for AWS Lambda (Python) issue, you are granting AWS permission to use your company's name (and/or logo) for the limited purpose described here. You are also confirming that you have authority to grant such permission.* + + *You can opt-out at any time by commenting or reopening this issue.* diff --git a/.github/ISSUE_TEMPLATE/tech_debt.yml b/.github/ISSUE_TEMPLATE/tech_debt.yml new file mode 100644 index 00000000000..064c118ba20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tech_debt.yml @@ -0,0 +1,62 @@ +name: Technical debt +description: Suggest an activity to help address technical debt. +title: "Tech debt: TITLE" +labels: ["tech-debt", "triage"] +body: + - type: markdown + attributes: + value: Thank you for taking the time to help us proactively improve delivery velocity, safely. + - type: textarea + id: importance + attributes: + label: Why is this needed? + description: Please help us understand the value so we can prioritize it accordingly + validations: + required: true + - type: dropdown + id: area + attributes: + label: Which area does this relate to? + multiple: true + options: + - Tests + - Static typing + - Tracer + - Logger + - Metrics + - Event Handler - REST API + - Event Handler - GraphQL API + - Middleware factory + - Parameters + - Batch processing + - Validation + - Event Source Data Classes + - Parser + - Idempotency + - Feature flags + - JMESPath functions + - Streaming + - Automation + - Other + - type: textarea + id: suggestion + attributes: + label: Suggestion + description: If available, please share what a good solution would look like + validations: + required: false + - type: checkboxes + id: acknowledgment + attributes: + label: Acknowledgment + options: + - label: This request meets [Powertools for AWS Lambda (Python) Tenets](https://docs.powertools.aws.dev/lambda/python/latest/#tenets) + required: true + - label: Should this be considered in other Powertools for AWS Lambda languages? i.e. [Java](https://github.com/aws-powertools/powertools-lambda-java/), [TypeScript](https://github.com/aws-powertools/powertools-lambda-typescript/), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet/) + required: false + - type: markdown + attributes: + value: | + --- + + **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..5265d390063 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,40 @@ +<!-- markdownlint-disable MD041 MD043 --> +**Issue number:** + +## Summary + +### Changes + +> Please provide a summary of what's being changed + +### User experience + +> Please share what the user experience looks like before and after this change + +## Checklist + +If your change doesn't seem to apply, please leave them unchecked. + +* [ ] [Meet tenets criteria](https://docs.powertools.aws.dev/lambda/python/#tenets) +* [ ] I have performed a self-review of this change +* [ ] Changes have been tested +* [ ] Changes are documented +* [ ] PR title follows [conventional commit semantics](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/.github/semantic.yml) + +<details> +<summary>Is this a breaking change?</summary> + +**RFC issue number**: + +Checklist: + +* [ ] Migration process documented +* [ ] Implement warnings (if it can live side by side) + +</details> + +## Acknowledgment + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. + +**Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000000..c9597418e3f --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,3 @@ +self-hosted-runner: + labels: + - aws-powertools_ubuntu-latest_8-core diff --git a/.github/actions/create-pr/action.yml b/.github/actions/create-pr/action.yml new file mode 100644 index 00000000000..39ba6f60b1f --- /dev/null +++ b/.github/actions/create-pr/action.yml @@ -0,0 +1,88 @@ +name: "Create PR custom action" +description: "Create a PR and a temporary branch, close duplicates" + +# PROCESS +# +# 1. Setup git client using Powertools for AWS Lambda (Python) bot username +# 2. Pushes staged files to a temporary branch +# 3. Creates a PR from temporary branch against a target branch (typically trunk: develop, main, etc.) +# 4. Searches for duplicate PRs with the same title +# 5. If duplicates are found, link to the most recent one, close and delete their branches so we keep a single PR +# 6. In the event of failure, we delete the now orphaned branch (if any), and propagate the failure + +# USAGE +# +# - name: Create PR +# id: create-pr +# uses: ./.github/actions/create-pr +# with: +# files: "CHANGELOG.md" +# temp_branch_prefix: "ci-changelog" +# pull_request_title: "chore(ci): changelog rebuild" +# github_token: ${{ secrets.GITHUB_TOKEN }} +# - name: Step to demonstrate how to access outputs (no need for this) +# run: | +# echo "PR number: ${PR_ID}" +# echo "Branch: ${BRANCH}" +# env: +# PR_ID: ${{ steps.create-pr.outputs.pull_request_id}} +# BRANCH: ${{ steps.create-pr.outputs.temp_branch}} + +inputs: + files: + description: "Files to add separated by space" + required: true + temp_branch_prefix: + description: "Prefix for temporary git branch to be created, e.g, ci-docs" + required: true + pull_request_title: + description: "Pull Request title to use" + required: true + github_token: + description: "GitHub token for GitHub CLI" + required: true + target_branch: + description: "Branch to target when creating a PR against (develop, by default)" + required: false + default: develop + +outputs: + pull_request_id: + description: "Pull request ID created" + value: ${{ steps.create-pr.outputs.pull_request_id }} + temp_branch: + description: "Temporary branch created with staged changed" + value: ${{ steps.create-pr.outputs.temp_branch }} + +runs: + using: "composite" + steps: + - id: adjust-path + run: echo "${{ github.action_path }}" >> $GITHUB_PATH + shell: bash + - id: setup-git + name: Git client setup and refresh tip + run: | + git config user.name "Powertools for AWS Lambda (Python) bot" + git config user.email "151832416+aws-powertools-bot@users.noreply.github.com" + git config pull.rebase true + git config remote.origin.url >&- + shell: bash + - id: create-pr + working-directory: ${{ env.GITHUB_WORKSPACE }} + run: create_pr_for_staged_changes.sh "${FILES}" + env: + FILES: ${{ inputs.files }} + TEMP_BRANCH_PREFIX: ${{ inputs.temp_branch_prefix }} + PR_TITLE: ${{ inputs.pull_request_title }} + BASE_BRANCH: ${{ inputs.target_branch }} + GH_TOKEN: ${{ inputs.github_token }} + shell: bash + - id: cleanup + name: Cleanup orphaned branch + if: failure() + run: git push origin --delete "${TEMP_BRANCH_PREFIX}-${GITHUB_RUN_ID}" || echo "Must have failed before creating temporary branch; no cleanup needed." + env: + TEMP_BRANCH_PREFIX: ${{ inputs.temp_branch_prefix }} + GITHUB_RUN_ID: ${{ github.run_id }} + shell: bash diff --git a/.github/actions/create-pr/create_pr_for_staged_changes.sh b/.github/actions/create-pr/create_pr_for_staged_changes.sh new file mode 100755 index 00000000000..f31217467ee --- /dev/null +++ b/.github/actions/create-pr/create_pr_for_staged_changes.sh @@ -0,0 +1,148 @@ +#!/bin/bash +set -uo pipefail # prevent accessing unset env vars, prevent masking pipeline errors to the next command + +#docs +#title :create_pr_for_staged_changes.sh +#description :This script will create a PR for staged changes, detect and close duplicate PRs. All PRs will be omitted from Release Notes and Changelogs +#author :@heitorlessa +#date :May 8th 2023 +#version :0.1 +#usage :bash create_pr_for_staged_changes.sh {git_staged_files_or_directories_separated_by_space} +#notes :Meant to use in GitHub Actions only. Temporary branch will be named $TEMP_BRANCH_PREFIX-$GITHUB_RUN_ID +#os_version :Ubuntu 22.04.2 LTS +#required_env_vars :PR_TITLE, TEMP_BRANCH_PREFIX, GH_TOKEN +#============================================================================== + +# Sets GitHub Action with error message to ease troubleshooting +function error() { + echo "::error file=${FILENAME}::$1" + exit 1 +} + +function debug() { + TIMESTAMP=$(date -u "+%FT%TZ") # 2023-05-10T07:53:59Z + echo ""${TIMESTAMP}" - $1" +} + +function notice() { + echo "::notice file=${FILENAME}::$1" +} + +function start_span() { + echo "::group::$1" +} + +function end_span() { + echo "::endgroup::" +} + +function has_required_config() { + start_span "Validating required config" + test -z "${TEMP_BRANCH_PREFIX}" && error "TEMP_BRANCH_PREFIX env must be set to create a PR" + test -z "${PR_TITLE}" && error "PR_TITLE env must be set" + test -z "${GH_TOKEN}" && error "GH_TOKEN env must be set for GitHub CLI" + + # Default GitHub Actions Env Vars: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + debug "Are we running in GitHub Action environment?" + test -z "${GITHUB_RUN_ID}" && error "GITHUB_RUN_ID env must be set to trace Workflow Run ID back to PR" + test -z "${GITHUB_SERVER_URL}" && error "GITHUB_SERVER_URL env must be set to trace Workflow Run ID back to PR" + test -z "${GITHUB_REPOSITORY}" && error "GITHUB_REPOSITORY env must be set to trace Workflow Run ID back to PR" + + debug "Config validated successfully!" + set_environment_variables + end_span +} + +function set_environment_variables() { + start_span "Setting environment variables" + export readonly WORKFLOW_URL="${GITHUB_SERVER_URL}"/"${GITHUB_REPOSITORY}"/actions/runs/"${GITHUB_RUN_ID}" # e.g., heitorlessa/aws-lambda-powertools-test/actions/runs/4913570678 + export readonly TEMP_BRANCH="${TEMP_BRANCH_PREFIX}"-"${GITHUB_RUN_ID}" # e.g., ci-changelog-4894658712 + export readonly BASE_BRANCH="${BASE_BRANCH:-develop}" # e.g., main, defaults to develop if missing + export readonly PR_BODY="This is an automated PR created from the following workflow" + export readonly FILENAME=".github/scripts/$(basename "$0")" + export readonly NO_DUPLICATES_MESSAGE="No duplicated PRs found" + export readonly SKIP_LABEL="skip-changelog" + + end_span +} + +function has_anything_changed() { + start_span "Validating git staged files" + HAS_ANY_SOURCE_CODE_CHANGED="$(git status --porcelain)" + + test -z "${HAS_ANY_SOURCE_CODE_CHANGED}" && debug "Nothing to update; exitting early" && exit 0 + end_span +} + +function create_temporary_branch_with_changes() { + start_span "Creating temporary branch: "${TEMP_BRANCH}"" + git checkout -b "${TEMP_BRANCH}" + + debug "Committing staged files: $*" + echo "$@" | xargs -n1 git add || error "Failed to add staged changes: "$@"" + git commit -m "${PR_TITLE}" + + git push origin "${TEMP_BRANCH}" || error "Failed to create new temporary branch" + end_span +} + +function create_pr() { + start_span "Creating PR against ${TEMP_BRANCH} branch" + # TODO: create label + NEW_PR_URL=$(gh pr create --title "${PR_TITLE}" --body "${PR_BODY}: ${WORKFLOW_URL}" --base "${BASE_BRANCH}" --label "${SKIP_LABEL}" || error "Failed to create PR") # e.g, https://github.com/aws-powertools/powertools-lambda-python/pull/13 + + # greedy remove any string until the last URL path, including the last '/'. https://opensource.com/article/17/6/bash-parameter-expansion + debug "Extracing PR Number from PR URL: "${NEW_PR_URL}"" + NEW_PR_ID="${NEW_PR_URL##*/}" # 13 + export NEW_PR_URL + export NEW_PR_ID + end_span +} + +function close_duplicate_prs() { + start_span "Searching for duplicate PRs" + DUPLICATE_PRS=$(gh pr list --search "${PR_TITLE}" --json number --jq ".[] | select(.number != ${NEW_PR_ID}) | .number") # e.g, 13\n14 + + if [ -z "${DUPLICATE_PRS}" ]; then + debug "No duplicate PRs found" + DUPLICATE_PRS="${NO_DUPLICATES_MESSAGE}" + else + debug "Closing duplicated PRs: "${DUPLICATE_PRS}"" + echo "${DUPLICATE_PRS}" | xargs -L1 gh pr close --delete-branch --comment "Superseded by #${NEW_PR_ID}" + fi + + export readonly DUPLICATE_PRS + end_span +} + +function report_job_output() { + start_span "Updating job outputs" + echo pull_request_id="${NEW_PR_ID}" >>"$GITHUB_OUTPUT" + echo temp_branch="${TEMP_BRANCH}" >>"$GITHUB_OUTPUT" + end_span +} + +function report_summary() { + start_span "Creating job summary" + echo "### Pull request created successfully :rocket: ${NEW_PR_URL} <br/><br/> Closed duplicated PRs: ${DUPLICATE_PRS}" >>"$GITHUB_STEP_SUMMARY" + + notice "PR_URL is: ${NEW_PR_URL}" + notice "PR_BRANCH is: ${TEMP_BRANCH}" + notice "PR_DUPLICATES are: ${DUPLICATE_PRS}" + end_span +} + +function main() { + # Sanity check + has_anything_changed + has_required_config + + create_temporary_branch_with_changes "$@" + create_pr + close_duplicate_prs + + report_job_output + report_summary +} + +main "$@" diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml new file mode 100644 index 00000000000..1f1347e4220 --- /dev/null +++ b/.github/actions/download-artifact/action.yml @@ -0,0 +1,58 @@ +name: Download artifact +description: Wrapper around GitHub's official action, with additional extraction before download + +# PROCESS +# +# 1. Downloads artifact using actions/download-artifact action +# 2. Extracts and overwrites tarball previously uploaded +# 3. Remove archive after extraction + +# NOTES +# +# Upload-artifact and download-artifact takes ~2m40s to upload 8MB +# so this is custom action cuts down the entire operation to 1s +# by uploading/extracting a tarball while relying on the official upload-artifact/download-artifact actions +# + +# USAGE +# +# NOTE: Meant to be used with ./.github/actions/upload-artifact +# +# - name: Restore sealed source code +# uses: ./.github/actions/download-artifact +# with: +# name: ${{ needs.seal.outputs.INTEGRITY_HASH }} +# path: . + +# https://github.com/actions/download-artifact/blob/main/action.yml +inputs: + name: + description: Artifact name + required: true + path: + description: Destination path. By default, it will download to the current working directory. + required: false + default: . + +runs: + using: composite + steps: + - name: Download artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + + - name: Extract artifacts + run: tar -xvf "${ARCHIVE}" + env: + ARCHIVE: ${{ inputs.name }}.tar + shell: bash + working-directory: ${{ inputs.path }} + + - name: Remove archive + run: rm -f "${ARCHIVE}" + env: + ARCHIVE: ${{ inputs.name }}.tar + shell: bash + working-directory: ${{ inputs.path }} diff --git a/.github/actions/seal-restore/action.yml b/.github/actions/seal-restore/action.yml new file mode 100644 index 00000000000..1107414b640 --- /dev/null +++ b/.github/actions/seal-restore/action.yml @@ -0,0 +1,82 @@ +name: "Restore sealed source code" +description: "Restore sealed source code and confirm integrity hash" + +# PROCESS +# +# 1. Exports artifact name using Prefix + GitHub Run ID (unique for each release trigger) +# 2. Compress entire source code as tarball OR given files +# 3. Create and export integrity hash for tarball +# 4. Upload artifact +# 5. Remove archive + +# USAGE +# +# - name: Seal and upload +# id: seal_source_code +# uses: ./.github/actions/seal +# with: +# artifact_name_prefix: "source" +# +# - name: Restore sealed source code +# uses: ./.github/actions/seal-restore +# with: +# integrity_hash: ${{ needs.seal_source_code.outputs.integrity_hash }} +# artifact_name: ${{ needs.seal_source_code.outputs.artifact_name }} + +# NOTES +# +# To be used together with .github/actions/seal + +inputs: + integrity_hash: + description: "Integrity hash to verify" + required: true + artifact_name: + description: "Sealed artifact name to restore" + required: true + +runs: + using: "composite" + steps: + - id: adjust-path + run: echo "${{ github.action_path }}" >> $GITHUB_PATH + shell: bash + + - name: Download artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{ inputs.artifact_name }} + path: . + + - id: integrity_hash + name: Create integrity hash for downloaded artifact + run: | + HASH=$(sha256sum "${ARTIFACT_NAME}.tar" | awk '{print $1}') + + echo "current_hash=${HASH}" >> "$GITHUB_OUTPUT" + env: + ARTIFACT_NAME: ${{ inputs.artifact_name }} + shell: bash + + - id: verify_hash + name: Verify sealed artifact integrity hash + run: test "${CURRENT_HASH}" = "${PROVIDED_HASH}" || exit 1 + env: + ARTIFACT_NAME: ${{ inputs.artifact_name }} + PROVIDED_HASH: ${{ inputs.integrity_hash }} + CURRENT_HASH: ${{ steps.integrity_hash.outputs.current_hash }} + shell: bash + + # Restore and overwrite tarball in current directory + - id: overwrite + name: Extract tarball + run: tar -xvf "${ARTIFACT_NAME}".tar + env: + ARTIFACT_NAME: ${{ inputs.artifact_name }} + shell: bash + + - name: Remove archive + run: rm -f "${ARTIFACT_NAME}.tar" + env: + ARTIFACT_NAME: ${{ inputs.artifact_name }} + shell: bash diff --git a/.github/actions/seal/action.yml b/.github/actions/seal/action.yml new file mode 100644 index 00000000000..c3e75a13e92 --- /dev/null +++ b/.github/actions/seal/action.yml @@ -0,0 +1,93 @@ +name: "Seal and hash source code" +description: "Seal and export source code as a tarball artifact along with its integrity hash" + +# PROCESS +# +# 1. Exports artifact name using Prefix + GitHub Run ID (unique for each release trigger) +# 2. Compress entire source code as tarball OR given files +# 3. Create and export integrity hash for tarball +# 4. Upload artifact +# 5. Remove archive + +# USAGE +# +# - name: Seal and upload +# id: seal_source_code +# uses: ./.github/actions/seal +# with: +# artifact_name_prefix: "source" + +inputs: + files: + description: "Files to seal separated by space" + required: false + artifact_name_prefix: + description: "Prefix to use when exporting artifact" + required: true + +outputs: + integrity_hash: + description: "Source code integrity hash" + value: ${{ steps.integrity_hash.outputs.integrity_hash }} + artifact_name: + description: "Artifact name containTemporary branch created with staged changed" + value: ${{ steps.export_artifact_name.outputs.artifact_name }} + +runs: + using: "composite" + steps: + - id: adjust-path + run: echo "${{ github.action_path }}" >> $GITHUB_PATH + shell: bash + + - id: export_artifact_name + name: Export final artifact name + run: echo "artifact_name=${ARTIFACT_PREFIX}-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + env: + GITHUB_RUN_ID: ${{ github.run_id }} + ARTIFACT_PREFIX: ${{ inputs.artifact_name_prefix }} + shell: bash + + # By default, create a tarball of the current directory minus .git + # otherwise it breaks GH Actions when restoring it + - id: compress_all + if: ${{ !inputs.files }} + name: Create tarball for entire source + run: tar --exclude-vcs -cvf "${ARTIFACT_NAME}".tar * + env: + ARTIFACT_NAME: ${{ steps.export_artifact_name.outputs.artifact_name }} + shell: bash + + # If a list of files are given, then create a tarball for those only + - id: compress_selected_files + if: ${{ inputs.files }} + name: Create tarball for selected files + run: tar --exclude-vcs -cvf "${ARTIFACT_NAME}".tar "${FILES}" + env: + FILES: ${{ inputs.files }} + ARTIFACT_NAME: ${{ steps.export_artifact_name.outputs.artifact_name }} + shell: bash + + - id: integrity_hash + name: Create and export integrity hash for tarball + run: | + HASH=$(sha256sum "${ARTIFACT_NAME}.tar" | awk '{print $1}') + + echo "integrity_hash=${HASH}" >> "$GITHUB_OUTPUT" + env: + ARTIFACT_NAME: ${{ steps.export_artifact_name.outputs.artifact_name }} + shell: bash + + - name: Upload artifacts + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + if-no-files-found: error + name: ${{ steps.export_artifact_name.outputs.artifact_name }} + path: ${{ steps.export_artifact_name.outputs.artifact_name }}.tar + retention-days: 1 + + - name: Remove archive + run: rm -f "${ARTEFACT_NAME}.tar" + env: + ARTIFACT_NAME: ${{ steps.export_artifact_name.outputs.artifact_name }} + shell: bash diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml new file mode 100644 index 00000000000..f88ea2475b9 --- /dev/null +++ b/.github/actions/upload-artifact/action.yml @@ -0,0 +1,82 @@ +name: Upload artifact +description: Wrapper around GitHub's official action, with additional archiving before upload + +# PROCESS +# +# 1. Creates tarball excluding .git files +# 2. Uploads tarball using actions/upload-artifact action, fail CI job if no file is found +# 3. Remove archive after uploading it. + +# NOTES +# +# Upload-artifact and download-artifact takes ~2m40s to upload 8MB +# so this is custom action cuts down the entire operation to 1s +# by uploading/extracting a tarball while relying on the official upload-artifact/download-artifact actions +# + +# USAGE +# +# NOTE: Meant to be used with ./.github/actions/download-artifact +# +# - name: Upload sealed source code +# uses: ./.github/actions/upload-artifact +# with: +# name: ${{ steps.integrity.outputs.INTEGRITY_HASH }} +# path: . + +# https://github.com/actions/upload-artifact/blob/main/action.yml +inputs: + name: + description: Artifact name + required: true + path: + description: > + A file, directory or wildcard pattern that describes what to upload. + + You can pass multiple paths separated by space (e.g., dir1 dir2 file.txt). + + Paths and wildcard patterns must be tar command compatible. + required: true + retention-days: + description: > + Artifact retention in days. By default 1 day, max of 90 days, and 0 honours default repo retention. + + You can change max days in the repository settings. + required: false + default: "1" + if-no-files-found: + description: > + Action to perform if no files are found: warn, error, ignore. By default, it fails fast with 'error'. + + Options: + warn: Output a warning but do not fail the action + error: Fail the action with an error message + ignore: Do not output any warnings or errors, the action does not fail + required: false + default: error + +runs: + using: composite + steps: + - name: Archive artifacts + run: | + tar --exclude-vcs \ + -cvf "${ARCHIVE}" "${PATH_TO_ARCHIVE}" + env: + ARCHIVE: ${{ inputs.name }}.tar + PATH_TO_ARCHIVE: ${{ inputs.path }} + shell: bash + + - name: Upload artifacts + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + if-no-files-found: ${{ inputs.if-no-files-found }} + name: ${{ inputs.name }} + path: ${{ inputs.name }}.tar + retention-days: ${{ inputs.retention-days }} + + - name: Remove archive + run: rm -f "${ARCHIVE}" + env: + ARCHIVE: ${{ inputs.name }}.tar + shell: bash diff --git a/.github/actions/upload-release-provenance/action.yml b/.github/actions/upload-release-provenance/action.yml new file mode 100644 index 00000000000..d0829efd4f4 --- /dev/null +++ b/.github/actions/upload-release-provenance/action.yml @@ -0,0 +1,67 @@ +name: "Upload provenance attestation to release" +description: "Download and upload newly generated provenance attestation to latest release." + +# PROCESS +# +# 1. Downloads provenance attestation artifact generated earlier in the release pipeline +# 2. Updates latest GitHub draft release pointing to newly git release tag +# 3. Uploads provenance attestation file to latest GitHub draft release + +# USAGE +# +# - name: Upload provenance +# id: upload-provenance +# uses: ./.github/actions/upload-release-provenance +# with: +# release_version: ${{ needs.seal.outputs.RELEASE_VERSION }} +# provenance_name: ${{needs.provenance.outputs.provenance-name}} +# github_token: ${{ secrets.GITHUB_TOKEN }} + +# NOTES +# +# There are no outputs. +# + +inputs: + provenance_name: + description: "Provenance artifact name to download" + required: true + release_version: + description: "Release version (e.g., 2.20.0)" + required: true + github_token: + description: "GitHub token for GitHub CLI" + required: true + +runs: + using: "composite" + steps: + - id: adjust-path + run: echo "${{ github.action_path }}" >> $GITHUB_PATH + shell: bash + + - id: download-provenance + name: Download newly generated provenance + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{ inputs.provenance_name }} + + - id: sync-release-tag + name: Update draft release tag to release commit tag + run: | + CURRENT_DRAFT_RELEASE=$(gh release list | awk '{ if ($2 == "Draft") print $1}') + gh release edit "${CURRENT_DRAFT_RELEASE}" --tag v"${RELEASE_VERSION}" + env: + RELEASE_VERSION: ${{ inputs.release_version }} + GH_TOKEN: ${{ inputs.github_token }} + shell: bash + + - id: upload-provenance + name: Upload provenance to release tag + # clobber flag means overwrite release asset if available (eventual consistency, retried failed steps) + run: gh release upload --clobber v"${RELEASE_VERSION}" "${PROVENANCE_FILE}" + env: + RELEASE_VERSION: ${{ inputs.release_version }} + PROVENANCE_FILE: ${{ inputs.provenance_name }} + GH_TOKEN: ${{ inputs.github_token }} + shell: bash diff --git a/.github/actions/verify-provenance/verify_provenance.sh b/.github/actions/verify-provenance/verify_provenance.sh new file mode 100755 index 00000000000..dbfe280473d --- /dev/null +++ b/.github/actions/verify-provenance/verify_provenance.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -uo pipefail # prevent accessing unset env vars, prevent masking pipeline errors to the next command + +#docs +#title :verify_provenance.sh +#description :This script will download and verify a signed Powertools for AWS Lambda (Python) release build with SLSA Verifier +#author :@heitorlessa +#date :July 1st 2023 +#version :0.1 +#usage :bash verify_provenance.sh {release version} +#notes :Meant to use in GitHub Actions or locally (MacOS, Linux, WSL). +#os_version :Ubuntu 22.04.2 LTS +#============================================================================== + +# Check if RELEASE_VERSION is provided as a command line argument +if [[ $# -eq 1 ]]; then + export readonly RELEASE_VERSION="$1" +else + echo "ERROR: Please provider Powertools release version as a command line argument." + echo "Example: bash verify_provenance.sh 2.20.0" + exit 1 +fi + +export readonly ARCHITECTURE=$(uname -m | sed 's/x86_64/amd64/g') # arm64, x86_64 ->amd64 +export readonly OS_NAME=$(uname -s | tr '[:upper:]' '[:lower:]') # darwin, linux +export readonly SLSA_VERIFIER_VERSION="2.3.0" +export readonly SLSA_VERIFIER_CHECKSUM_FILE="SHA256SUM.md" +export readonly SLSA_VERIFIER_BINARY="./slsa-verifier-${OS_NAME}-${ARCHITECTURE}" + +export readonly RELEASE_BINARY="aws_lambda_powertools-${RELEASE_VERSION}-py3-none-any.whl" +export readonly ORG="aws-powertools" +export readonly REPO="powertools-lambda-python" +export readonly PROVENANCE_FILE="multiple.intoto.jsonl" + +export readonly FILES=("${SLSA_VERIFIER_BINARY}" "${SLSA_VERIFIER_CHECKSUM_FILE}" "${PROVENANCE_FILE}" "${RELEASE_BINARY}") + +function debug() { + TIMESTAMP=$(date -u "+%FT%TZ") # 2023-05-10T07:53:59Z + echo ""${TIMESTAMP}" DEBUG - [*] $1" +} + +function error() { + cleanup + TIMESTAMP=$(date -u "+%FT%TZ") # 2023-05-10T07:53:59Z + echo ""${TIMESTAMP}" ERROR - [!] $1" + echo ""${TIMESTAMP}" ERROR - [!] exiting" + exit 1 +} + +function download_slsa_verifier() { + readonly SLSA_URL="https://github.com/slsa-framework/slsa-verifier/releases/download/v${SLSA_VERIFIER_VERSION}/slsa-verifier-${OS_NAME}-${ARCHITECTURE}" + # debug "Downloading SLSA Verifier for - Binary: slsa-verifier-${OS_NAME}-${ARCHITECTURE}" + debug "Downloading SLSA Verifier binary: ${SLSA_URL}" + curl \ + --location \ + --fail \ + --silent \ + -O "${SLSA_URL}" || error "Failed to download SLSA Verifier binary" + + readonly SLSA_CHECKSUM_URL="https://raw.githubusercontent.com/slsa-framework/slsa-verifier/f59b55ef2190581d40fc1a5f3b7a51cab2f4a652/${SLSA_VERIFIER_CHECKSUM_FILE}" + debug "Downloading SLSA Verifier checksums" + curl \ + --location \ + --fail \ + --silent \ + -O "${SLSA_CHECKSUM_URL}" || error "Failed to download SLSA Verifier binary checksum file" + + debug "Verifying SLSA Verifier binary integrity" + CURRENT_HASH=$(sha256sum "${SLSA_VERIFIER_BINARY}" | awk '{print $1}') + if [[ $(grep "${CURRENT_HASH}" "${SLSA_VERIFIER_CHECKSUM_FILE}") ]]; then + debug "SLSA Verifier binary integrity confirmed" + chmod +x "${SLSA_VERIFIER_BINARY}" + else + error "Failed integrity check for SLSA Verifier binary: ${SLSA_VERIFIER_BINARY}" + fi +} + +function download_provenance() { + readonly PROVENANCE_URL="https://github.com/${ORG}/${REPO}/releases/download/v${RELEASE_VERSION}/${PROVENANCE_FILE}" + debug "Downloading attestation: ${PROVENANCE_URL}" + + curl \ + --location \ + --fail \ + --silent \ + -O ${PROVENANCE_URL} || error "Failed to download provenance. Does the release already exist?" +} + +function download_release_artifact() { + debug "Downloading ${RELEASE_VERSION} release from PyPi" + python -m pip download \ + --only-binary=:all: \ + --no-deps \ + --quiet \ + aws-lambda-powertools=="${RELEASE_VERSION}" +} + +function verify_provenance() { + debug "Verifying attestation with slsa-verifier" + "${SLSA_VERIFIER_BINARY}" verify-artifact \ + --provenance-path "${PROVENANCE_FILE}" \ + --source-uri github.com/${ORG}/${REPO} \ + ${RELEASE_BINARY} +} + +function cleanup() { + debug "Cleaning up previously downloaded files" + rm -f "${SLSA_VERIFIER_BINARY}" + rm -f "${SLSA_VERIFIER_CHECKSUM_FILE}" + rm -f "${PROVENANCE_FILE}" + rm -f "${RELEASE_BINARY}" + echo "${FILES[@]}" | xargs -n1 echo "Removed file: " +} + +function main() { + download_slsa_verifier + download_provenance + download_release_artifact + verify_provenance + cleanup +} + +main + +# Lessons learned +# +# 1. If source doesn't match provenance +# +# FAILED: SLSA verification failed: source used to generate the binary does not match provenance: expected source 'awslabs/aws-lambda-powertools-python', got 'heitorlessa/aws-lambda-powertools-test' +# +# 2. Avoid building deps during download in Test registry endpoints +# +# FAILED: Could not find a version that satisfies the requirement poetry-core>=1.3.2 (from versions: 1.2.0) +# diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml new file mode 100644 index 00000000000..006db19c585 --- /dev/null +++ b/.github/boring-cyborg.yml @@ -0,0 +1,165 @@ +##### Labeler ########################################################################################################## +labelPRBasedOnFilePath: + logger: + - aws_lambda_powertools/logging/* + - aws_lambda_powertools/logging/**/* + - aws_lambda_powertools/package_logger.py + tracer: + - aws_lambda_powertools/tracing/* + - aws_lambda_powertools/tracing/**/* + metrics: + - aws_lambda_powertools/metrics/* + - aws_lambda_powertools/metrics/**/* + event_handlers: + - aws_lambda_powertools/event_handler/* + - aws_lambda_powertools/event_handler/**/* + middleware_factory: + - aws_lambda_powertools/middleware_factory/* + - aws_lambda_powertools/middleware_factory/**/* + parameters: + - aws_lambda_powertools/parameters/* + - aws_lambda_powertools/parameters/**/* + batch: + - aws_lambda_powertools/batch/* + - aws_lambda_powertools/batch/**/* + validator: + - aws_lambda_powertools/validation/* + - aws_lambda_powertools/validation/**/* + event_sources: + - aws_lambda_powertools/data_classes/* + - aws_lambda_powertools/data_classes/**/* + parser: + - aws_lambda_powertools/parser/* + - aws_lambda_powertools/parser/**/* + idempotency: + - aws_lambda_powertools/idempotency/* + - aws_lambda_powertools/idempotency/**/* + feature_flags: + - aws_lambda_powertools/feature_flags/* + - aws_lambda_powertools/feature_flags/**/* + jmespath: + - aws_lambda_powertools/utilities/jmespath_utils/* + typing: + - aws_lambda_powertools/utilities/typing/* + - mypy.ini + streaming: + - aws_lambda_powertools/utilities/streaming/* + commons: + - aws_lambda_powertools/shared/* + + documentation: + - docs/* + - docs/**/* + - mkdocs.yml + + github-actions: + - .github/workflows/* + - .github/workflows/**/* + - .github/dependabot.yml + - .github/boring-cyborg.yml + - .github/release-drafter.yml + - .github/semantic.yml + - .github/stale.yml + - .github/mergify.yml + + github-templates: + - .github/ISSUE_TEMPLATE/* + - .github/PULL_REQUEST_TEMPLATE.md + - .github/.chglog/* + - .github/.chglog/**/* + + internal: + - .flake8 + - .bandit.baseline + - .gitignore + - .pre-commit-config.yaml + - MANIFEST.in + - Makefile + - CONTRIBUTING.md + - MAINTAINERS.md + - CODE_OF_CONDUCT.md + - LICENSE + - THIRD-PARTY-LICENSES + - aws_lambda_powertools_python/shared/* + - aws_lambda_powertools_python/shared/** + + dependencies: + - pyproject.toml + - poetry.lock + + tests: + - tests/* + - tests/**/* + - benchmark/* + - benchmark/**/* + +##### Greetings ######################################################################################################## +firstPRWelcomeComment: > + Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need. + + In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET) + +# Comment to be posted to congratulate user on their first merged PR +firstPRMergeComment: > + Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience! + +# Comment to be posted to on first time issues +firstIssueWelcomeComment: > + Thanks for opening your first issue here! We'll come back to you as soon as we can. + + In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET) + +###### IssueLink Adder ################################################################################################# +# Insert Issue (Jira/Github etc) link in PR description based on the Issue ID in PR title. +#insertIssueLinkInPrDescription: +# # specify the placeholder for the issue link that should be present in the description +# descriptionIssuePlaceholderRegexp: "^Issue link: (.*)$" +# matchers: +# # you can have several matches - for different types of issues +# # only the first matching entry is replaced +# jiraIssueMatch: +# # specify the regexp of issue id that you can find in the title of the PR +# # the match groups can be used to build the issue id (${1}, ${2}, etc.). +# titleIssueIdRegexp: \[(AIRFLOW-[0-9]{4})\] +# # the issue link to be added. ${1}, ${2} ... are replaced with the match groups from the +# # title match (remember to use quotes) +# descriptionIssueLink: "[${1}](https://issues.apache.org/jira/browse/${1}/)" +# docOnlyIssueMatch: +# titleIssueIdRegexp: \[(AIRFLOW-X{4})\] +# descriptionIssueLink: "`Document only change, no JIRA issue`" + +###### Title Validator ################################################################################################# +# Verifies if commit/PR titles match the regexp specified +#verifyTitles: +# # Regular expression that should be matched by titles of commits or PR +# titleRegexp: ^\[AIRFLOW-[0-9]{4}\].*$|^\[AIRFLOW-XXXX\].*$ +# # If set to true, it will always check the PR title (as opposed to the individual commits). +# alwaysUsePrTitle: true +# # If set to true, it will only check the commit in case there is a single commit. +# # In case of multiple commits it will check PR title. +# # This reflects the standard behaviour of Github that for `Squash & Merge` GitHub +# # uses the PR title rather than commit messages for the squashed commit ¯\_(ツ)_/¯ +# # For single-commit PRs it takes the squashed commit message from the commit as expected. +# # +# # If set to false it will check all commit messages. This is useful when you do not squash commits at merge. +# validateEitherPrOrSingleCommitTitle: true +# # The title the GitHub status should appear from. +# statusTitle: "Title Validator" +# # A custom message to be displayed when the title passes validation. +# successMessage: "Validation successful!" +# # A custom message to be displayed when the title fails validation. +# # Allows insertion of ${type} (commit/PR), ${title} (the title validated) and ${regex} (the titleRegexp above). +# failureMessage: "Wrong ${type} title: ${title}" + +###### PR/Branch Up-To-Date Checker #################################################################################### +# Check if the branch is up to date with develop when certain files are modified +#checkUpToDate: +# # The default branch is "develop", change the branch if you want to check against a different target branch +# targetBranch: develop +# files: +# # File paths that you want to check for +# # In this example, it checks if the branch is up to date when alembic migrations are modified in the PR. +# # It helps avoid multiple heads in alembic migrations in a collaborative development project. +# - airflow/migrations/* +# - airflow/migrations/**/* +# - airflow/alembic.ini diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..fed13e3e577 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,89 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: chore + include: scope + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + commit-message: + prefix: chore + include: scope + ignore: + # 2022-04-23: Ignoring boto3 changes until we need to care about them. + - dependency-name: "boto3" + groups: + boto-typing: + patterns: + - "mypy-boto3-*" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + commit-message: + prefix: chore + include: scope + allow: + # Allow updates for AWS CDK + - dependency-name: "aws-cdk" + + - package-ecosystem: pip + directory: /benchmark/src/instrumented + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + + - package-ecosystem: pip + directory: /benchmark/src/reference + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + + - package-ecosystem: docker + directory: /docs + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + + - package-ecosystem: pip + directory: /docs + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + + - package-ecosystem: pip + directory: /examples/event_handler_graphql/src + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + + - package-ecosystem: gomod + directory: /layer/scripts/layer-balancer + commit-message: + prefix: chore + include: scope + schedule: + interval: daily + groups: + layer-balancer: + patterns: + - "*" diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 00000000000..dc3f1953586 --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,30 @@ +## Temporarily disabled after Mergify breaking changes +## might move to custom GitHub Actions altogether +# queue_rules: +# - name: default +# conditions: +# # Conditions to get out of the queue (= merged) +# - check-success=Semantic Pull Request +# - "#approved-reviews-by>=1" +# - -title~=(WIP|wip) +# - -label~="do-not-merge" +# - "#changes-requested-reviews-by=0" + +# pull_request_rules: +# - name: automatic merge for Dependabot pull requests +# conditions: +# - author~=^dependabot(|-preview)\[bot\]$ +# actions: +# queue: +# name: default +# method: squash +# commit_message: title+body + +# - name: Automatic merge ⬇️ on approval ✔ +# conditions: +# - "#approved-reviews-by>=2" +# actions: +# queue: +# name: default +# method: squash +# commit_message: title+body diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..b590898b517 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,37 @@ +change-template: "* $TITLE (#$NUMBER) by @$AUTHOR" +categories: + - title: "⚡ Breaking Changes" + labels: + - "breaking-change" + - title: "🌟New features and non-breaking changes" + labels: + - "feature" + - title: "📜 Documentation updates" + labels: + - "documentation" + - title: "🐛 Bug and hot fixes" + labels: + - "bug" + - "fix" + - title: "🚒 Deprecations" + labels: + - "deprecated" + - title: "🔧 Maintenance" + labels: + - "internal" + - "dependencies" +exclude-labels: + - "skip-changelog" +tag-template: "v$NEXT_PATCH_VERSION" +template: | + ## Summary + + **[Human readable summary of changes]** + + ## Changes + + $CHANGES + + ## This release was made possible by the following contributors: + + $CONTRIBUTORS diff --git a/.github/scripts/comment_on_large_pr.js b/.github/scripts/comment_on_large_pr.js new file mode 100644 index 00000000000..c17199faf76 --- /dev/null +++ b/.github/scripts/comment_on_large_pr.js @@ -0,0 +1,73 @@ +const { + PR_NUMBER, + PR_ACTION, + PR_AUTHOR, + IGNORE_AUTHORS, +} = require("./constants") + + +/** + * Notify PR author to split XXL PR in smaller chunks + * + * @param {object} core - core functions instance from @actions/core + * @param {object} gh_client - Pre-authenticated REST client (Octokit) + * @param {string} owner - GitHub Organization + * @param {string} repository - GitHub repository + */ +const notifyAuthor = async ({ + core, + gh_client, + owner, + repository, +}) => { + core.info(`Commenting on PR ${PR_NUMBER}`) + + let msg = `### ⚠️Large PR detected⚠️ + +Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews. + `; + + try { + await gh_client.rest.issues.createComment({ + owner: owner, + repo: repository, + body: msg, + issue_number: PR_NUMBER, + }); + } catch (error) { + core.setFailed("Failed to notify PR author to split large PR"); + console.error(err); + } +} + +module.exports = async ({github, context, core}) => { + if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { + return core.notice("Author in IGNORE_AUTHORS list; skipping...") + } + + if (PR_ACTION != "labeled") { + return core.notice("Only run on PRs labeling actions; skipping") + } + + + /** @type {string[]} */ + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: PR_NUMBER, + }) + + // Schema: https://docs.github.com/en/rest/issues/labels#list-labels-for-an-issue + for (const label of labels) { + core.info(`Label: ${label}`) + if (label.name == "size/XXL") { + await notifyAuthor({ + core: core, + gh_client: github, + owner: context.repo.owner, + repository: context.repo.repo, + }) + break; + } + } +} diff --git a/.github/scripts/constants.js b/.github/scripts/constants.js new file mode 100644 index 00000000000..8bfe5571974 --- /dev/null +++ b/.github/scripts/constants.js @@ -0,0 +1,42 @@ +module.exports = Object.freeze({ + /** @type {string} */ + // Values: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request + "PR_ACTION": process.env.PR_ACTION?.replace(/"/g, '') || "", + + /** @type {string} */ + "PR_AUTHOR": process.env.PR_AUTHOR?.replace(/"/g, '') || "", + + /** @type {string} */ + "PR_BODY": process.env.PR_BODY || "", + + /** @type {string} */ + "PR_TITLE": process.env.PR_TITLE || "", + + /** @type {number} */ + "PR_NUMBER": process.env.PR_NUMBER || 0, + + /** @type {string} */ + "PR_IS_MERGED": process.env.PR_IS_MERGED || "false", + + /** @type {string} */ + "PR_LABELS": process.env.PR_LABELS || "", + + /** @type {string} */ + "LABEL_BLOCK": "do-not-merge", + + /** @type {string} */ + "LABEL_BLOCK_REASON": "need-issue", + + /** @type {string} */ + "LABEL_BLOCK_MISSING_LICENSE_AGREEMENT": "need-license-agreement-acknowledge", + + /** @type {string} */ + "LABEL_PENDING_RELEASE": "pending-release", + + /** @type {string} */ + "HANDLE_MAINTAINERS_TEAM": "@aws-powertools/powertools-lambda-python", + + /** @type {string[]} */ + "IGNORE_AUTHORS": ["dependabot[bot]", "markdownify[bot]"], + +}); diff --git a/.github/scripts/download_pr_artifact.js b/.github/scripts/download_pr_artifact.js new file mode 100644 index 00000000000..274467c1f1c --- /dev/null +++ b/.github/scripts/download_pr_artifact.js @@ -0,0 +1,26 @@ +module.exports = async ({github, context, core}) => { + const fs = require('fs'); + + const workflowRunId = process.env.WORKFLOW_ID; + core.info(`Listing artifacts for workflow run ${workflowRunId}`); + + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: workflowRunId, + }); + + const matchArtifact = artifacts.data.artifacts.filter(artifact => artifact.name == "pr")[0]; + + core.info(`Downloading artifacts for workflow run ${workflowRunId}`); + const artifact = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + + core.info("Saving artifact found", artifact); + + fs.writeFileSync('pr.zip', Buffer.from(artifact.data)); +} diff --git a/.github/scripts/enforce_acknowledgment.js b/.github/scripts/enforce_acknowledgment.js new file mode 100644 index 00000000000..3e3be636ede --- /dev/null +++ b/.github/scripts/enforce_acknowledgment.js @@ -0,0 +1,40 @@ +const { +PR_ACTION, +PR_AUTHOR, +PR_BODY, +PR_NUMBER, +IGNORE_AUTHORS, +LABEL_BLOCK, +LABEL_BLOCK_REASON +} = require("./constants") + +module.exports = async ({github, context, core}) => { + if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { + return core.notice("Author in IGNORE_AUTHORS list; skipping...") + } + + if (PR_ACTION != "opened") { + return core.notice("Only newly open PRs are labelled to avoid spam; skipping") + } + + const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?<issue>\d+)/; + const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); + if (isMatch == null) { + core.info(`No related issue found, maybe the author didn't use the template but there is one.`) + + let msg = "No related issues found. Please ensure there is an open issue related to this change to avoid significant delays or closure."; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + body: msg, + issue_number: PR_NUMBER, + }); + + return await github.rest.issues.addLabels({ + issue_number: PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [LABEL_BLOCK, LABEL_BLOCK_REASON] + }) + } +} diff --git a/.github/scripts/label_missing_acknowledgement_section.js b/.github/scripts/label_missing_acknowledgement_section.js new file mode 100644 index 00000000000..12b85241d1d --- /dev/null +++ b/.github/scripts/label_missing_acknowledgement_section.js @@ -0,0 +1,41 @@ +const { + PR_ACTION, + PR_AUTHOR, + PR_BODY, + PR_NUMBER, + IGNORE_AUTHORS, + LABEL_BLOCK, + LABEL_BLOCK_MISSING_LICENSE_AGREEMENT +} = require("./constants") + +module.exports = async ({github, context, core}) => { + if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { + return core.notice("Author in IGNORE_AUTHORS list; skipping...") + } + + if (PR_ACTION != "opened") { + return core.notice("Only newly open PRs are labelled to avoid spam; skipping") + } + + const RELATED_ACK_SECTION_REGEX = /By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice./; + + const isMatch = RELATED_ACK_SECTION_REGEX.exec(PR_BODY); + if (isMatch == null) { + core.info(`No acknowledgement section found, maybe the author didn't use the template but there is one.`) + + let msg = "No acknowledgement section found. Please make sure you used the template to open a PR and didn't remove the acknowledgment section. Check the template here: https://github.com/aws-powertools/powertools-lambda-python/blob/develop/.github/PULL_REQUEST_TEMPLATE.md#acknowledgment"; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + body: msg, + issue_number: PR_NUMBER, + }); + + return await github.rest.issues.addLabels({ + issue_number: PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [LABEL_BLOCK, LABEL_BLOCK_MISSING_LICENSE_AGREEMENT] + }) + } +} diff --git a/.github/scripts/label_missing_related_issue.js b/.github/scripts/label_missing_related_issue.js new file mode 100644 index 00000000000..705e414c47f --- /dev/null +++ b/.github/scripts/label_missing_related_issue.js @@ -0,0 +1,40 @@ +const { + PR_ACTION, + PR_AUTHOR, + PR_BODY, + PR_NUMBER, + IGNORE_AUTHORS, + LABEL_BLOCK, + LABEL_BLOCK_REASON +} = require("./constants") + +module.exports = async ({github, context, core}) => { + if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { + return core.notice("Author in IGNORE_AUTHORS list; skipping...") + } + + if (PR_ACTION != "opened") { + return core.notice("Only newly open PRs are labelled to avoid spam; skipping") + } + + const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?<issue>\d+)/; + const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); + if (isMatch == null) { + core.info(`No related issue found, maybe the author didn't use the template but there is one.`) + + let msg = "No related issues found. Please ensure there is an open issue related to this change to avoid significant delays or closure."; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + body: msg, + issue_number: PR_NUMBER, + }); + + return await github.rest.issues.addLabels({ + issue_number: PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [LABEL_BLOCK, LABEL_BLOCK_REASON] + }) + } +} diff --git a/.github/scripts/label_pr_based_on_title.js b/.github/scripts/label_pr_based_on_title.js new file mode 100644 index 00000000000..02f77f448b8 --- /dev/null +++ b/.github/scripts/label_pr_based_on_title.js @@ -0,0 +1,62 @@ +const { PR_NUMBER, PR_TITLE, PR_LABELS } = require("./constants") + +module.exports = async ({github, context, core}) => { + const FEAT_REGEX = /feat(\((.+)\))?(:.+)/ + const BUG_REGEX = /(fix|bug)(\((.+)\))?(:.+)/ + const DOCS_REGEX = /(docs|doc)(\((.+)\))?(:.+)/ + const CHORE_REGEX = /(chore)(\((.+)\))?(:.+)/ + const DEPRECATED_REGEX = /(deprecated)(\((.+)\))?(:.+)/ + const REFACTOR_REGEX = /(refactor)(\((.+)\))?(:.+)/ + + const labels = { + "feature": FEAT_REGEX, + "bug": BUG_REGEX, + "documentation": DOCS_REGEX, + "internal": CHORE_REGEX, + "enhancement": REFACTOR_REGEX, + "deprecated": DEPRECATED_REGEX, + } + + // get PR labels from env + const prLabels = PR_LABELS.replaceAll("\"", "").split(","); + const labelKeys = Object.keys(labels); + + let miss = 0; + try { + for (const label in labels) { + const matcher = new RegExp(labels[label]) + const matches = matcher.exec(PR_TITLE) + if (matches != null) { + core.info(`Auto-labeling PR ${PR_NUMBER} with ${label}`) + + for (const prLabel of prLabels) { + if (labelKeys.includes(prLabel) && prLabel !== label) { + core.info(`PR previously tagged with: ${prLabel}, removing.`); + await github.rest.issues.removeLabel({ + issue_number: PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + name: prLabel + }) + } + } + + await github.rest.issues.addLabels({ + issue_number: PR_NUMBER, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [label] + }) + + return; + } else { + core.debug(`'${PR_TITLE}' didn't match '${label}' semantic.`) + miss += 1 + } + } + } finally { + if (miss == Object.keys(labels).length) { + core.notice(`PR ${PR_NUMBER} title '${PR_TITLE}' doesn't follow semantic titles; skipping...`) + } + } +} diff --git a/.github/scripts/label_related_issue.js b/.github/scripts/label_related_issue.js new file mode 100644 index 00000000000..790aac1ced5 --- /dev/null +++ b/.github/scripts/label_related_issue.js @@ -0,0 +1,53 @@ +const { + PR_AUTHOR, + PR_BODY, + PR_NUMBER, + IGNORE_AUTHORS, + LABEL_PENDING_RELEASE, + HANDLE_MAINTAINERS_TEAM, + PR_IS_MERGED, +} = require("./constants") + +module.exports = async ({github, context, core}) => { + if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { + return core.notice("Author in IGNORE_AUTHORS list; skipping...") + } + + if (PR_IS_MERGED == "false") { + return core.notice("Only merged PRs to avoid spam; skipping") + } + + const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?<issue>\d+)/; + + const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); + + try { + if (!isMatch) { + core.setFailed(`Unable to find related issue for PR number ${PR_NUMBER}.\n\n Body details: ${PR_BODY}`); + return await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + body: `${HANDLE_MAINTAINERS_TEAM} No related issues found. Please ensure '${LABEL_PENDING_RELEASE}' label is applied before releasing.`, + issue_number: PR_NUMBER, + }); + } + } catch (error) { + core.setFailed(`Unable to create comment on PR number ${PR_NUMBER}.\n\n Error details: ${error}`); + throw new Error(error); + } + + const { groups: {issue} } = isMatch + + try { + core.info(`Auto-labeling related issue ${issue} for release`) + return await github.rest.issues.addLabels({ + issue_number: issue, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [LABEL_PENDING_RELEASE] + }) + } catch (error) { + core.setFailed(`Is this issue number (${issue}) valid? Perhaps a discussion?`); + throw new Error(error); + } +} diff --git a/.github/scripts/post_release.js b/.github/scripts/post_release.js new file mode 100644 index 00000000000..d6a598f1960 --- /dev/null +++ b/.github/scripts/post_release.js @@ -0,0 +1,112 @@ +const STAGED_LABEL = "pending-release"; + +/** + * Fetch issues using GitHub REST API + * + * @param {object} gh_client - Pre-authenticated REST client (Octokit) + * @param {string} org - GitHub Organization + * @param {string} repository - GitHub repository + * @param {string} state - GitHub issue state (open, closed) + * @param {string} label - Comma-separated issue labels to fetch + * @return {Object[]} issues - Array of issues matching params + * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} + */ +const fetchIssues = async ({ + gh_client, + org, + repository, + state = "all", + label = STAGED_LABEL, +}) => { + + try { + const { data: issues } = await gh_client.rest.issues.listForRepo({ + owner: org, + repo: repository, + state: state, + labels: label, + }); + + return issues; + + } catch (error) { + console.error(error); + throw new Error("Failed to fetch issues") + } + +}; + +/** + * Notify new release and close staged GitHub issue + * + * @param {object} gh_client - Pre-authenticated REST client (Octokit) + * @param {string} owner - GitHub Organization + * @param {string} repository - GitHub repository + * @param {string} release_version - GitHub Release version + * @see {@link https://octokit.github.io/rest.js/v18#usage|Octokit client} + */ +const notifyRelease = async ({ + gh_client, + owner, + repository, + release_version, +}) => { + const release_url = `https://github.com/${owner}/${repository}/releases/tag/v${release_version}`; + + const issues = await fetchIssues({ + gh_client: gh_client, + org: owner, + repository: repository, + }); + + issues.forEach(async (issue) => { + console.info(`Updating issue number ${issue.number}`); + + const comment = `This is now released under [${release_version}](${release_url}) version!`; + try { + await gh_client.rest.issues.createComment({ + owner: owner, + repo: repository, + body: comment, + issue_number: issue.number, + }); + } catch (error) { + console.error(error); + throw new Error(`Failed to update issue ${issue.number} about ${release_version} release`) + } + + + // Close issue and remove staged label; keep existing ones + const labels = issue.labels + .filter((label) => label.name != STAGED_LABEL) + .map((label) => label.name); + + try { + await gh_client.rest.issues.update({ + repo: repository, + owner: owner, + issue_number: issue.number, + state: "closed", + labels: labels, + }); + } catch (error) { + console.error(error); + throw new Error("Failed to close issue") + } + + console.info(`Issue number ${issue.number} closed and updated`); + }); +}; + +// context: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts +module.exports = async ({ github, context }) => { + const { RELEASE_VERSION } = process.env; + console.log(`Running post-release script for ${RELEASE_VERSION} version`); + + await notifyRelease({ + gh_client: github, + owner: context.repo.owner, + repository: context.repo.repo, + release_version: RELEASE_VERSION, + }); +}; diff --git a/.github/scripts/save_pr_details.js b/.github/scripts/save_pr_details.js new file mode 100644 index 00000000000..ba2de975b3c --- /dev/null +++ b/.github/scripts/save_pr_details.js @@ -0,0 +1,23 @@ +module.exports = async ({github, context, core}) => { + const fs = require('fs'); + const filename = "pr.txt"; + + const labelsData = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: (context.payload.issue || context.payload.pull_request || context.payload).number, + }); + + const labels = labelsData.data.map((label) => { + return label['name']; + }); + + try { + fs.writeFileSync(`./${filename}`, JSON.stringify({...context.payload, ...{labels:labels.join(",")}})); + + return `PR successfully saved ${filename}` + } catch (err) { + core.setFailed("Failed to save PR details"); + console.error(err); + } +} diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 00000000000..39119da8d05 --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,19 @@ +# conventional commit types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json +types: + - feat + - fix + - docs + - style + - refactor + - perf + - test + - build + - ci + - chore + - revert + - improv + +# Always validate the PR title +# and ignore the commits to lower the entry bar for contribution +# while titles make up the Release notes to ease maintenance overhead +titleOnly: true diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000000..d0b1d54f4a3 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,14 @@ +only: issues +daysUntilStale: 14 +daysUntilClose: 7 +exemptLabels: + - bug + - feature-request +staleLabel: pending-close-response-required +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +closeComment: > + This issue has been automatically closed because of inactivity. + Please open a new issue if you are still encountering problems. diff --git a/.github/workflows/build_changelog.yml b/.github/workflows/build_changelog.yml new file mode 100644 index 00000000000..ffa6163ca03 --- /dev/null +++ b/.github/workflows/build_changelog.yml @@ -0,0 +1,31 @@ +# Standalone workflow to update changelog if necessary +name: Build changelog + +# PROCESS +# +# 1. Fetch latest changes compared to the latest tag +# 2. Rebuild CHANGELOG.md using Keep A Changelog format +# 3. Create a PR with the latest changelog (close and reference any it supersedes) + +# USAGE +# +# Always triggered on PR merge or manually from GitHub UI if we must. + +on: + workflow_dispatch: +# push: +# branches: +# - develop + schedule: + # Note: run daily at 10am UTC time until upstream git-chlog uses stable sorting + - cron: "0 10 * * *" + +permissions: + contents: read + +jobs: + changelog: + permissions: + contents: write # create temporary branch to store changelog changes + pull-requests: write # create PR with changelog changes + uses: ./.github/workflows/reusable_publish_changelog.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..d49fb8749eb --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,38 @@ +name: "CodeQL" + +# PROCESS +# +# 1. Static code analysis with CodeQL + +# USAGE +# +# NOTE: This is our slowest workflow hence it only runs on code merged. +# +# Always triggered on PR merge when source code changes. + +on: + push: + branches: [develop] + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + if: github.repository == 'aws-powertools/powertools-lambda-python' + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@f31a31c052207cc13b328d6295c5b728bb49568c # v2.13.1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@f31a31c052207cc13b328d6295c5b728bb49568c # v2.13.1 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000000..7d1ac0d64ae --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,22 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: 'Dependency Review' + uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3 diff --git a/.github/workflows/dispatch_analytics.yml b/.github/workflows/dispatch_analytics.yml new file mode 100644 index 00000000000..74a2ae789fa --- /dev/null +++ b/.github/workflows/dispatch_analytics.yml @@ -0,0 +1,66 @@ +name: Dispatch analytics + +# PROCESS +# +# 1. Trade GitHub JWT token with AWS credentials for the analytics account +# 2. Invoke a Lambda function dispatcher synchronously with the read-only scoped JWT token +# 3. The dispatcher function will call GitHub APIs to read data from the last hour and aggregate for operational analytics + +# USAGE +# +# NOTE: meant to use as a scheduled task only (or manually for debugging purposes). + +on: + workflow_dispatch: + + schedule: + - cron: "0 * * * *" + +permissions: + contents: read + + +jobs: + dispatch_token: + if: github.repository == 'aws-powertools/powertools-lambda-python' + concurrency: + group: analytics + runs-on: ubuntu-latest + environment: analytics + permissions: + id-token: write + actions: read + checks: read + contents: read # previously we needed `write` to use GH_TOKEN in our dispatcher (Lambda) + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: eu-central-1 + role-to-assume: ${{ secrets.AWS_ANALYTICS_ROLE_ARN }} + + - name: Invoke Lambda function + run: | + payload=$(echo -n '{"githubToken": "${{ secrets.GITHUB_TOKEN }}"}' | base64) + response=$(aws lambda invoke \ + --function-name "${{ secrets.AWS_ANALYTICS_DISPATCHER_ARN }}" \ + --payload "$payload" \ + response.json \ + --query 'FunctionError' \ + --output text) + + cat response.json ; echo # add newline at the end + + if [ "$response" != "None" ]; then + echo "Error invoking lambda function: $response. Aborting." + exit 1 + fi diff --git a/.github/workflows/label_pr_on_title.yml b/.github/workflows/label_pr_on_title.yml new file mode 100644 index 00000000000..c17e3740586 --- /dev/null +++ b/.github/workflows/label_pr_on_title.yml @@ -0,0 +1,67 @@ +name: Label PR based on title + +# PROCESS +# +# 1. Fetch PR details previously saved from untrusted location +# 2. Parse details for safety +# 3. Label PR based on semantic title (e.g., area, change type) + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/record_pr.yml +# +# Security Note: +# +# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. +# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. +# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. +# +# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, +# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. + + +on: + workflow_run: + workflows: ["Record PR details"] + types: + - completed + +permissions: + contents: read + +jobs: + get_pr_details: + permissions: + actions: read # download PR artifact + contents: read # checkout code + # Guardrails to only ever run if PR recording workflow was indeed + # run in a PR event and ran successfully + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/workflows/reusable_export_pr_details.yml + with: + record_pr_workflow_id: ${{ github.event.workflow_run.id }} + workflow_origin: ${{ github.event.repository.full_name }} + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + label_pr: + needs: get_pr_details + runs-on: ubuntu-latest + permissions: + pull-requests: write # label respective PR + steps: + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Label PR based on title" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} + PR_TITLE: ${{ needs.get_pr_details.outputs.prTitle }} + PR_LABELS: ${{ needs.get_pr_details.outputs.prLabels }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + # This safely runs in our base repo, not on fork + # thus allowing us to provide a write access token to label based on PR title + # and label PR based on semantic title accordingly + script: | + const script = require('.github/scripts/label_pr_based_on_title.js') + await script({github, context, core}) diff --git a/.github/workflows/on_closed_issues.yml b/.github/workflows/on_closed_issues.yml new file mode 100644 index 00000000000..61a14b028d4 --- /dev/null +++ b/.github/workflows/on_closed_issues.yml @@ -0,0 +1,33 @@ +name: Closed Issue Message + +# PROCESS +# +# 1. Comment on recently closed issues to warn future responses may not be looked after + +# USAGE +# +# Always triggered upon issue closure +# + +on: + issues: + types: [closed] +permissions: + contents: read + +jobs: + auto_comment: + runs-on: ubuntu-latest + permissions: + issues: write # comment on issues + steps: + - uses: aws-actions/closed-issue-message@8b6324312193476beecf11f8e8539d73a3553bf4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + message: | + ### ⚠️COMMENT VISIBILITY WARNING⚠️ + This issue is now closed. Please be mindful that future comments are hard for our team to see. + + If you need more assistance, please either tag a [team member](https://github.com/aws-powertools/powertools-lambda-python/blob/develop/MAINTAINERS.md#current-maintainers) or open a new issue that references this one. + + If you wish to keep having a conversation with other community members under this issue feel free to do so. diff --git a/.github/workflows/on_label_added.yml b/.github/workflows/on_label_added.yml new file mode 100644 index 00000000000..45bc470bf4e --- /dev/null +++ b/.github/workflows/on_label_added.yml @@ -0,0 +1,62 @@ +name: On Label added + +# PROCESS +# +# 1. Fetch PR details previously saved from untrusted location +# 2. Parse details for safety +# 3. Comment on PR labels `size/XXL` and suggest splitting into smaller PRs if possible + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/record_pr.yml +# +# Security Note: +# +# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. +# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. +# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. +# +# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, +# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. + +on: + workflow_run: + workflows: ["Record PR details"] + types: + - completed + +permissions: + contents: read + +jobs: + get_pr_details: + permissions: + actions: read # download PR artifact + contents: read # checkout code + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/workflows/reusable_export_pr_details.yml + with: + record_pr_workflow_id: ${{ github.event.workflow_run.id }} + workflow_origin: ${{ github.event.repository.full_name }} + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + + split_large_pr: + needs: get_pr_details + runs-on: ubuntu-latest + permissions: + pull-requests: write # comment on PR + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + # Maintenance: Persist state per PR as an artifact to avoid spam on label add + - name: "Suggest split large Pull Request" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} + PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} + PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const script = require('.github/scripts/comment_on_large_pr.js'); + await script({github, context, core}); diff --git a/.github/workflows/on_merged_pr.yml b/.github/workflows/on_merged_pr.yml new file mode 100644 index 00000000000..fa221b9a4bc --- /dev/null +++ b/.github/workflows/on_merged_pr.yml @@ -0,0 +1,64 @@ +name: On PR merge + +# PROCESS +# +# 1. Fetch PR details previously saved from untrusted location +# 2. Parse details for safety +# 3. Add `pending-release` label for related issue +# 4. Make a comment in PR if related issue is invalid or can't be labeled + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/record_pr.yml +# +# Security Note: +# +# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. +# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. +# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. +# +# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, +# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. + +on: + workflow_run: + workflows: ["Record PR details"] + types: + - completed + +permissions: + contents: read + +jobs: + get_pr_details: + permissions: + actions: read # download PR artifact + contents: read # checkout code + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + uses: ./.github/workflows/reusable_export_pr_details.yml + with: + record_pr_workflow_id: ${{ github.event.workflow_run.id }} + workflow_origin: ${{ github.event.repository.full_name }} + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + release_label_on_merge: + needs: get_pr_details + runs-on: ubuntu-latest + permissions: + pull-requests: write # make a comment in PR if unable to find related issue + issues: write # label issue with pending-release + if: needs.get_pr_details.outputs.prIsMerged == 'true' + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Label PR related issue for release" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} + PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} + PR_IS_MERGED: ${{ needs.get_pr_details.outputs.prIsMerged }} + PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const script = require('.github/scripts/label_related_issue.js') + await script({github, context, core}) diff --git a/.github/workflows/on_opened_pr.yml b/.github/workflows/on_opened_pr.yml new file mode 100644 index 00000000000..2175e167140 --- /dev/null +++ b/.github/workflows/on_opened_pr.yml @@ -0,0 +1,81 @@ +name: On new PR + +# PROCESS +# +# 1. Fetch PR details previously saved from untrusted location +# 2. Parse details for safety +# 3. Confirm there is a related issue for newly opened PR +# 4. Verify if PR template is used and legal acknowledgement hasn't been removed + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/record_pr.yml +# +# Security Note: +# +# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. +# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. +# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. +# +# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, +# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. + +on: + workflow_run: + workflows: ["Record PR details"] + types: + - completed + +permissions: + contents: read + +jobs: + get_pr_details: + permissions: + actions: read # download PR artifact + contents: read # checkout code + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/workflows/reusable_export_pr_details.yml + with: + record_pr_workflow_id: ${{ github.event.workflow_run.id }} + workflow_origin: ${{ github.event.repository.full_name }} + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + check_related_issue: + permissions: + pull-requests: write # label and comment on PR if missing related issue (requirement) + needs: get_pr_details + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Ensure related issue is present" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} + PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} + PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} + PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const script = require('.github/scripts/label_missing_related_issue.js') + await script({github, context, core}) + check_acknowledge_section: + needs: get_pr_details + runs-on: ubuntu-latest + permissions: + pull-requests: write # label and comment on PR if missing acknowledge section (requirement) + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Ensure acknowledgement section is present" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} + PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} + PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} + PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const script = require('.github/scripts/label_missing_acknowledgement_section.js') + await script({github, context, core}) diff --git a/.github/workflows/on_pr_updates.yml b/.github/workflows/on_pr_updates.yml new file mode 100644 index 00000000000..2663d707399 --- /dev/null +++ b/.github/workflows/on_pr_updates.yml @@ -0,0 +1,36 @@ +# Fail PR check if do-not-merge label is present +name: PR requirements + +# PROCESS +# +# 1. Verify whether 'do-not-merge' label is present +# 2. Fail PR to prevent merging until resolved +# 3. Pass PR if do-not-merge label is removed by a maintainer + +# USAGE +# +# Always triggered on PR labeling changes. + +# NOTES +# +# PR requirements are checked async in on_opened_pr.yml and enforced here synchronously +# due to limitations in GH API. + +on: + pull_request: + types: + - opened + - labeled + - unlabeled + +permissions: {} # no permission required + +jobs: + check-requirements: + runs-on: ubuntu-latest + steps: + - name: Block if it doesn't minimum requirements + if: contains(github.event.pull_request.labels.*.name, 'do-not-merge') + run: | + echo "This PR does not meet minimum requirements (check PR comments)." + exit 1 diff --git a/.github/workflows/on_push_docs.yml b/.github/workflows/on_push_docs.yml new file mode 100644 index 00000000000..fc1aa3786e6 --- /dev/null +++ b/.github/workflows/on_push_docs.yml @@ -0,0 +1,36 @@ +name: Docs + +# PROCESS +# +# 1. Build User Guide and API docs +# 2. Publish to GitHub Pages +# 3. Publish to S3 (new home) + +# USAGE +# +# Always triggered on PR merge when changes in documentation changes occur. + +on: + push: + branches: + - develop + paths: + - "docs/**" + - "mkdocs.yml" + - "examples/**" + - "CHANGELOG.md" + +permissions: + contents: read + +jobs: + release-docs: + permissions: + contents: write # push to gh-pages + pages: write # deploy gh-pages website + id-token: write # trade JWT token for AWS credentials in AWS Docs account + secrets: inherit + uses: ./.github/workflows/reusable_publish_docs.yml + with: + version: develop + alias: stage \ No newline at end of file diff --git a/.github/workflows/on_schedule_monthly_roadmap_reminder.yml b/.github/workflows/on_schedule_monthly_roadmap_reminder.yml new file mode 100644 index 00000000000..a274e2dea08 --- /dev/null +++ b/.github/workflows/on_schedule_monthly_roadmap_reminder.yml @@ -0,0 +1,22 @@ +name: Monthly roadmap reminder + +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 1 * *' # runs first day of the month + +permissions: + contents: read + + +jobs: + call-workflow-passing-data: + permissions: + contents: read + pull-requests: read + issues: write # create monthly roadmap report + + # setting to `@main` until we have releases and governance installed + uses: aws-powertools/actions/.github/workflows/monthly_roadmap_reminder.yml@main + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ossf_scorecard.yml b/.github/workflows/ossf_scorecard.yml new file mode 100644 index 00000000000..5c29c3717c4 --- /dev/null +++ b/.github/workflows/ossf_scorecard.yml @@ -0,0 +1,48 @@ +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + schedule: + - cron: "0 9 * * *" + push: + branches: [develop] + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + environment: scorecard + permissions: + security-events: write # update code-scanning dashboard + id-token: write # confirm org+repo identity before publish results + + steps: + - name: "Checkout code" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true # publish to OSSF Scorecard REST API + repo_token: ${{ secrets.SCORECARD_TOKEN }} # read-only fine-grained token to read branch protection settings + + - name: "Upload results" + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + with: + sarif_file: results.sarif diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 00000000000..9345becd993 --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,275 @@ +name: Pre-Release + +# PRE-RELEASE PROCESS +# +# === Automated activities === +# +# 1. [Seal] Bump to release version and export source code with integrity hash +# 2. [Quality check] Restore sealed source code, run tests, linting, security and complexity base line +# 3. [Build] Restore sealed source code, create and export hashed build artifact for PyPi release (wheel, tarball) +# 4. [Provenance] Generates provenance for build, signs attestation with GitHub OIDC claims to confirm it came from this release pipeline, commit, org, repo, branch, hash, etc. +# 5. [Release] Restore built artifact, and publish package to PyPi prod repository +# 6. [PR to bump version] Restore sealed source code, and create a PR to update trunk with latest released project metadata + +# NOTE +# +# See MAINTAINERS.md "Releasing a new version" for release mechanisms +# +# Every job is isolated and starts a new fresh container. + +env: + RELEASE_COMMIT: ${{ github.sha }} + +on: + workflow_dispatch: + inputs: + skip_code_quality: + description: "Skip tests, linting, and baseline. Only use if release fail for reasons beyond our control and you need a quick release." + default: false + type: boolean + required: false + skip_pypi: + description: "Skip publishing to PyPi. Used for testing release steps." + default: false + type: boolean + required: false + schedule: + # Note: run daily on weekdays at 8am UTC time + - cron: "0 8 * * 1-5" + +permissions: + contents: read + +jobs: + + # This job bumps the package version to the pre-release version + # creates an integrity hash from the source code + # uploads the artifact with the integrity hash as the key name + # so subsequent jobs can restore from a trusted point in time to prevent tampering + seal: + # ignore forks + if: github.repository == 'aws-powertools/powertools-lambda-python' + + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + integrity_hash: ${{ steps.seal_source_code.outputs.integrity_hash }} + artifact_name: ${{ steps.seal_source_code.outputs.artifact_name }} + RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION }} + steps: + # NOTE: Different from prod release, we need both poetry and source code available in earlier steps to bump and verify. + + # We use a pinned version of Poetry to be certain it won't modify source code before we create a hash + - name: Install poetry + run: | + pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + pipx inject poetry git+https://github.com/monim67/poetry-bumpversion@315fe3324a699fa12ec20e202eb7375d4327d1c4 # v0.3.1 + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Bump and export release version + id: release_version + run: | + RELEASE_VERSION="$(poetry version prerelease --short | head -n1 | tr -d '\n')" + + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + + - name: Verifies pre-release version semantics + # verify pre-release semantics before proceeding to avoid versioning pollution + # e.g., 2.40.0a1 and 2.40.0b2 are valid while 2.40.0 is not + # NOTE. we do it in a separate step to handle edge cases like + # `poetry` CLI uses immutable install, versioning behaviour could change even in a minor version (we had breaking changes before) + # a separate step allows us to pinpoint what happened (before/after) + run: | + if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+[a-b].*$ ]]; then + echo "Version $VERSION doesn't look like a pre-release version; aborting" + exit 1 + fi + env: + RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION}} + + - name: Seal and upload + id: seal_source_code + uses: ./.github/actions/seal + with: + artifact_name_prefix: "source" + + # This job runs our automated test suite, complexity and security baselines + # it ensures previously merged have been tested as part of the pull request process + # + # NOTE + # + # we don't upload the artifact after testing to prevent any tampering of our source code dependencies + quality_check: + needs: seal + runs-on: ubuntu-latest + permissions: + contents: read + steps: + # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Debug cache restore + run: cat pyproject.toml + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "poetry" + - name: Install dependencies + run: make dev + - name: Run all tests, linting and baselines + run: make pr + + # This job creates a release artifact (tar.gz, wheel) + # it checks out code from release commit for custom actions to work + # then restores the sealed source code (overwrites any potential tampering) + # it's done separately from release job to enforce least privilege. + # We export just the final build artifact for release + build: + runs-on: ubuntu-latest + needs: [quality_check, seal] + permissions: + contents: read + outputs: + integrity_hash: ${{ steps.seal_build.outputs.integrity_hash }} + artifact_name: ${{ steps.seal_build.outputs.artifact_name }} + attestation_hashes: ${{ steps.encoded_hash.outputs.attestation_hashes }} + steps: + # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "poetry" + + - name: Build python package and wheel + run: poetry build + + - name: Seal and upload + id: seal_build + uses: ./.github/actions/seal + with: + artifact_name_prefix: "build" + files: "dist/" + + # NOTE: SLSA retraces our build to its artifact to ensure it wasn't tampered + # coupled with GitHub OIDC, SLSA can then confidently sign it came from this release pipeline+commit+branch+org+repo+actor+integrity hash + - name: Create attestation encoded hash for provenance + id: encoded_hash + working-directory: dist + run: echo "attestation_hashes=$(sha256sum ./* | base64 -w0)" >> "$GITHUB_OUTPUT" + + # This job creates a provenance file that describes how our release was built (all steps) + # after it verifies our build is reproducible within the same pipeline + # it confirms that its own software and the CI build haven't been tampered with (Trust but verify) + # lastly, it creates and sign an attestation (multiple.intoto.jsonl) that confirms + # this build artifact came from this GitHub org, branch, actor, commit ID, inputs that triggered this pipeline, and matches its integrity hash + # NOTE: supply chain threats review (we protect against all of them now): https://slsa.dev/spec/v1.0/threats-overview + provenance: + needs: [seal, build] + permissions: + contents: write # nested job explicitly require despite upload assets being set to false + actions: read # To read the workflow path. + id-token: write # To sign the provenance. + # NOTE: provenance fails if we use action pinning... it's a Github limitation + # because SLSA needs to trace & attest it came from a given branch; pinning doesn't expose that information + # https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#referencing-the-slsa-generator + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.attestation_hashes }} + upload-assets: false # we upload its attestation in create_tag job, otherwise it creates a new release + + # This job uses release artifact to publish to PyPi + # it exchanges JWT tokens with GitHub to obtain PyPi credentials + # since it's already registered as a Trusted Publisher. + # It uses the sealed build artifact (.whl, .tar.gz) to release it + release: + needs: [build, seal, provenance] + environment: pre-release + runs-on: ubuntu-latest + permissions: + id-token: write # OIDC for PyPi Trusted Publisher feature + env: + RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }} + steps: + # NOTE: we need actions/checkout in order to use our local actions (e.g., ./.github/actions) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.build.outputs.integrity_hash }} + artifact_name: ${{ needs.build.outputs.artifact_name }} + + - name: Upload to PyPi prod + if: ${{ !inputs.skip_pypi }} + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + + # Creates a PR with the latest version we've just released + # since our trunk is protected against any direct pushes from automation + bump_version: + needs: [release, seal, provenance] + permissions: + contents: write # create-pr action creates a temporary branch + pull-requests: write # create-pr action creates a PR using the temporary branch + runs-on: ubuntu-latest + steps: + # NOTE: we need actions/checkout to authenticate and configure git first + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Download provenance + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{needs.provenance.outputs.provenance-name}} + + - name: Update provenance + run: mkdir -p "${PROVENANCE_DIR}" && mv "${PROVENANCE_FILE}" "${PROVENANCE_DIR}/" + env: + PROVENANCE_FILE: ${{ needs.provenance.outputs.provenance-name }} + PROVENANCE_DIR: provenance/${{ needs.seal.outputs.RELEASE_VERSION}} + + - name: Create PR + id: create-pr + uses: ./.github/actions/create-pr + with: + files: "pyproject.toml aws_lambda_powertools/shared/version.py provenance/" + temp_branch_prefix: "ci-bump" + pull_request_title: "chore(ci): new pre-release ${{ needs.seal.outputs.RELEASE_VERSION }}" + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish_v2_layer.yml b/.github/workflows/publish_v2_layer.yml new file mode 100644 index 00000000000..fc74e5956b1 --- /dev/null +++ b/.github/workflows/publish_v2_layer.yml @@ -0,0 +1,317 @@ +name: Deploy v2 layer to all regions + +# PROCESS +# +# 1. Compile Layer using cdk-aws-lambda-powertools-layer CDK construct for x86 and ARM (uses custom runner as it's CPU heavy) +# 2. Kick off pipeline for beta, prod, and canary releases +# 3. Create PR to update trunk so staged docs also point to the latest Layer ARN, when merged +# 4. Builds and publishes docs with latest Layer ARN using given version (generally coming from release) + + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/release.yml +# +# publish_layer: +# needs: [seal, release, create_tag] +# secrets: inherit +# permissions: +# id-token: write +# contents: write +# pages: write +# pull-requests: write +# uses: ./.github/workflows/publish_v2_layer.yml +# with: +# latest_published_version: ${{ needs.seal.outputs.RELEASE_VERSION }} +# pre_release: ${{ inputs.pre_release }} +# source_code_artifact_name: ${{ needs.seal.outputs.artifact_name }} +# source_code_integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + + +on: + workflow_dispatch: + inputs: + latest_published_version: + description: "Latest PyPi published version to rebuild latest docs for, e.g. 2.0.0, 2.0.0a1 (pre-release)" + required: true + source_code_artifact_name: + description: "Artifact name to restore sealed source code" + type: string + required: true + source_code_integrity_hash: + description: "Sealed source code integrity hash" + type: string + required: true + pre_release: + description: "Publishes documentation using a pre-release tag (2.0.0a1)." + default: false + type: boolean + required: false + workflow_call: + inputs: + latest_published_version: + type: string + description: "Latest PyPi published version to rebuild latest docs for, e.g. 2.0.0, 2.0.0a1 (pre-release)" + required: true + pre_release: + description: "Publishes documentation using a pre-release tag (2.0.0a1)." + default: false + type: boolean + required: false + source_code_artifact_name: + description: "Artifact name to restore sealed source code" + type: string + required: true + source_code_integrity_hash: + description: "Sealed source code integrity hash" + type: string + required: true + +permissions: + contents: read + + +env: + RELEASE_COMMIT: ${{ github.sha }} + +jobs: + build-layer: + permissions: + # lower privilege propagated from parent workflow (release.yml) + contents: read + id-token: write + pages: none + pull-requests: none + runs-on: aws-powertools_ubuntu-latest_8-core + defaults: + run: + working-directory: ./layer + steps: + - name: checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ inputs.source_code_integrity_hash }} + artifact_name: ${{ inputs.source_code_artifact_name }} + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: "16.12" + - name: Setup python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "pip" + - name: Resolve and install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install --require-hashes -r requirements.txt + + - name: Set up QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v2.0.0 + with: + platforms: arm64 + # NOTE: we need QEMU to build Layer against a different architecture (e.g., ARM) + + - name: Set up Docker Buildx + id: builder + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + with: + install: true + driver: docker + platforms: linux/amd64,linux/arm64 + + - name: Install CDK + working-directory: ./ + run: | + npm ci + npx cdk --version + + # Baking time for PyPi eventual consistency; 60s seemed more than enough + # https://github.com/aws-powertools/powertools-lambda-python/issues/2491 + - name: Baking time (PyPi) + run: sleep 60 + + - name: CDK build + run: npx cdk synth --verbose --context version="${{ inputs.latest_published_version }}" -o cdk.out + - name: zip output + run: zip -r cdk.out.zip cdk.out + - name: Archive CDK artifacts + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: cdk-layer-artefact + path: layer/cdk.out.zip + + beta: + needs: build-layer + # lower privilege propagated from parent workflow (release.yml) + permissions: + id-token: write + contents: read + pages: write # docs will be updated with latest Layer ARNs + pull-requests: write # creation-action will create a PR with Layer ARN updates + uses: ./.github/workflows/reusable_deploy_v2_layer_stack.yml + secrets: inherit + with: + stage: "BETA" + artefact-name: "cdk-layer-artefact" + environment: "layer-beta" + latest_published_version: ${{ inputs.latest_published_version }} + source_code_artifact_name: ${{ inputs.source_code_artifact_name }} + source_code_integrity_hash: ${{ inputs.source_code_integrity_hash }} + + prod: + needs: beta + # lower privilege propagated from parent workflow (release.yml) + permissions: + id-token: write + contents: read + pages: write # docs will be updated with latest Layer ARNs + pull-requests: write # creation-action will create a PR with Layer ARN updates + uses: ./.github/workflows/reusable_deploy_v2_layer_stack.yml + secrets: inherit + with: + stage: "PROD" + artefact-name: "cdk-layer-artefact" + environment: "layer-prod" + latest_published_version: ${{ inputs.latest_published_version }} + source_code_artifact_name: ${{ inputs.source_code_artifact_name }} + source_code_integrity_hash: ${{ inputs.source_code_integrity_hash }} + + sar-beta: + needs: beta # canaries run on Layer Beta env + permissions: + # lower privilege propagated from parent workflow (release.yml) + id-token: write + contents: read + pull-requests: none + pages: none + uses: ./.github/workflows/reusable_deploy_v2_sar.yml + secrets: inherit + with: + stage: "BETA" + artefact-name: "cdk-layer-artefact" + environment: "layer-beta" + package-version: ${{ inputs.latest_published_version }} + source_code_artifact_name: ${{ inputs.source_code_artifact_name }} + source_code_integrity_hash: ${{ inputs.source_code_integrity_hash }} + + + sar-prod: + needs: sar-beta + permissions: + # lower privilege propagated from parent workflow (release.yml) + id-token: write + contents: read + pull-requests: none + pages: none + uses: ./.github/workflows/reusable_deploy_v2_sar.yml + secrets: inherit + with: + stage: "PROD" + artefact-name: "cdk-layer-artefact" + environment: "layer-prod" + package-version: ${{ inputs.latest_published_version }} + source_code_artifact_name: ${{ inputs.source_code_artifact_name }} + source_code_integrity_hash: ${{ inputs.source_code_integrity_hash }} + + + # Updating the documentation with the latest Layer ARNs is a two-phase process + # + # 1. Update layer ARNs with latest deployed locally and create a PR with these changes + # 2. Pull from temporary branch with these changes and update the docs we're releasing + # + # This keeps our permissions tight and we don't run into a conflict, + # where a new release creates a new doc (2.16.0) while layers are still pointing to 2.15 + # because the PR has to be merged while release process is running + + update_v2_layer_arn_docs: + needs: prod + outputs: + temp_branch: ${{ steps.create-pr.outputs.temp_branch }} + runs-on: ubuntu-latest + permissions: + # lower privilege propagated from parent workflow (release.yml) + contents: write + pull-requests: write + id-token: none + pages: none + steps: + - name: Checkout repository # reusable workflows start clean, so we need to checkout again + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ inputs.source_code_integrity_hash }} + artifact_name: ${{ inputs.source_code_artifact_name }} + + - name: Download CDK layer artifacts + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + path: cdk-layer-stack + pattern: cdk-layer-stack-* # merge all Layer artifacts created per region earlier (reusable_deploy_v2_layer_stack.yml; step "Save Layer ARN artifact") + merge-multiple: true + - name: Replace layer versions in documentation + run: | + ls -la cdk-layer-stack/ + ./layer/scripts/update_layer_arn.sh cdk-layer-stack + # NOTE: It felt unnecessary creating yet another PR to update changelog w/ latest tag + # since this is the only step in the release where we update docs from a temp branch + - name: Update changelog with latest tag + run: make changelog + - name: Create PR + id: create-pr + uses: ./.github/actions/create-pr + with: + files: "docs/index.md examples CHANGELOG.md" + temp_branch_prefix: "ci-layer-docs" + pull_request_title: "chore(ci): layer docs update" + github_token: ${{ secrets.GITHUB_TOKEN }} + + + prepare_docs_alias: + runs-on: ubuntu-latest + permissions: + # lower privilege propagated from parent workflow (release.yml) + contents: read + pages: none + id-token: none + pull-requests: none + outputs: + DOCS_ALIAS: ${{ steps.set-alias.outputs.DOCS_ALIAS }} + steps: + - name: Set docs alias + id: set-alias + run: | + DOCS_ALIAS=latest + if [[ "${{ inputs.pre_release }}" == true ]] ; then + DOCS_ALIAS=alpha + fi + echo DOCS_ALIAS="$DOCS_ALIAS" >> "$GITHUB_OUTPUT" + + release_docs: + needs: [update_v2_layer_arn_docs, prepare_docs_alias] + permissions: + # lower privilege propagated from parent workflow (release.yml) + contents: write + pages: write + pull-requests: none + id-token: write + secrets: inherit + uses: ./.github/workflows/reusable_publish_docs.yml + with: + version: ${{ inputs.latest_published_version }} + alias: ${{ needs.prepare_docs_alias.outputs.DOCS_ALIAS }} + git_ref: ${{ needs.update_v2_layer_arn_docs.outputs.temp_branch }} diff --git a/.github/workflows/quality_check.yml b/.github/workflows/quality_check.yml new file mode 100644 index 00000000000..77e78201f70 --- /dev/null +++ b/.github/workflows/quality_check.yml @@ -0,0 +1,82 @@ +name: Code quality + +# PROCESS +# +# 1. Install all dependencies and spin off containers for all supported Python versions +# 2. Run code formatters and linters (various checks) for code standard +# 3. Run static typing checker for potential bugs +# 4. Run entire test suite for regressions except end-to-end (unit, functional, performance) +# 5. Run static analysis (in addition to CodeQL) for common insecure code practices +# 6. Run complexity baseline to avoid error-prone bugs and keep maintenance lower +# 7. Collect and report on test coverage + +# USAGE +# +# Always triggered on new PRs, PR changes and PR merge. + + +on: + pull_request: + paths: + - "aws_lambda_powertools/**" + - "tests/**" + - "pyproject.toml" + - "poetry.lock" + - "mypy.ini" + branches: + - develop + - v3 + push: + paths: + - "aws_lambda_powertools/**" + - "tests/**" + - "pyproject.toml" + - "poetry.lock" + - "mypy.ini" + branches: + - develop + - v3 + +permissions: + contents: read + +jobs: + quality_check: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + env: + PYTHON: "${{ matrix.python-version }}" + permissions: + contents: read # checkout code only + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Install poetry + run: pipx install poetry + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: ${{ matrix.python-version }} + cache: "poetry" + - name: Install dependencies + run: make dev + - name: Formatting and Linting + run: make lint + - name: Static type checking + run: make mypy + - name: Test with pytest + run: make test + - name: Test dependencies with Nox + run: make test-dependencies + - name: Security baseline + run: make security-baseline + - name: Complexity baseline + run: make complexity-baseline + - name: Upload coverage to Codecov + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # 4.5.0 + with: + file: ./coverage.xml + env_vars: PYTHON + name: aws-lambda-powertools-python-codecov diff --git a/.github/workflows/quality_check_pydanticv2.yml b/.github/workflows/quality_check_pydanticv2.yml new file mode 100644 index 00000000000..8ef6b05f87f --- /dev/null +++ b/.github/workflows/quality_check_pydanticv2.yml @@ -0,0 +1,67 @@ +name: Code quality - Pydanticv2 + +# PROCESS +# +# 1. Install all dependencies and spin off containers for all supported Python versions +# 2. Run code formatters and linters (various checks) for code standard +# 3. Run static typing checker for potential bugs +# 4. Run entire test suite for regressions except end-to-end (unit, functional, performance) +# 5. Run static analysis (in addition to CodeQL) for common insecure code practices +# 6. Run complexity baseline to avoid error-prone bugs and keep maintenance lower +# 7. Collect and report on test coverage + +# USAGE +# +# Always triggered on new PRs, PR changes and PR merge. + +on: + pull_request: + paths: + - "aws_lambda_powertools/**" + - "tests/**" + - "pyproject.toml" + - "poetry.lock" + - "mypy.ini" + branches: + - develop + push: + paths: + - "aws_lambda_powertools/**" + - "tests/**" + - "pyproject.toml" + - "poetry.lock" + - "mypy.ini" + branches: + - develop + +permissions: + contents: read + +jobs: + quality_check: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + env: + PYTHON: "${{ matrix.python-version }}" + permissions: + contents: read # checkout code only + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Install poetry + run: pipx install poetry + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: ${{ matrix.python-version }} + cache: "poetry" + - name: Replacing Pydantic v1 with v2 > 2.0.3 + run: | + rm -rf poetry.lock + poetry add "pydantic=^2.0.3" + - name: Install dependencies + run: make dev + - name: Test with pytest + run: make test-pydanticv2 diff --git a/.github/workflows/rebuild_latest_docs.yml b/.github/workflows/rebuild_latest_docs.yml new file mode 100644 index 00000000000..665cad81dd1 --- /dev/null +++ b/.github/workflows/rebuild_latest_docs.yml @@ -0,0 +1,39 @@ +name: Rebuild latest docs + +# PROCESS +# +# 1. Build User Guide and API docs +# 2. Publish to GitHub Pages +# 3. Publish to S3 (new home) + +# USAGE +# +# Only used for deploying a documentation hotfix to /latest and its associated version w/o a full release. +# +# Steps: +# +# 1. Trigger "Rebuild latest docs" workflow manually: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow +# 2. Use the latest version released under Releases e.g. 2.0.0 + +on: + workflow_dispatch: + inputs: + latest_published_version: + description: "Latest PyPi published version to rebuild latest docs for, e.g. 2.16.3" + default: "2.16.3" + required: true + +permissions: + contents: read + +jobs: + release-docs: + permissions: + contents: write # push to gh-pages + pages: write # deploy gh-pages website + id-token: write # trade JWT token for AWS credentials in AWS Docs account + secrets: inherit + uses: ./.github/workflows/reusable_publish_docs.yml + with: + version: ${{ inputs.latest_published_version }} + alias: latest diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml new file mode 100644 index 00000000000..e8ec70bcf60 --- /dev/null +++ b/.github/workflows/record_pr.yml @@ -0,0 +1,60 @@ +name: Record PR details + +# PROCESS +# +# 1. Runs in fork location upon PR creation or changes +# 2. Saves GitHub Pull Request Webhook payload +# 3. Uploads as a temporary GitHub Action Artifact with shortest retention + +# USAGE +# +# see .github/workflows/on_merged_pr.yml and related for full example. +# +# on: +# workflow_run: +# workflows: ["Record PR details"] +# types: +# - completed +# +# Security Note: +# +# For security, this is intended to be a 2-step process: (1) collect PR, (2) act on PR. +# Do not ever use `pull_request_target` to "simplify", as it sends a write-token to the fork. Our linter should catch it. +# +# The first step runs in untrusted location (fork), therefore we limit permissions to only check out code. +# +# The second step will be workflows that want to act on a given PR, this time with intended permissions, and +# it runs on its base location (this repo!). +# +# This enforces zero trust where this workflow always runs on fork with zero permissions on GH_TOKEN. +# When this workflow completes, X workflows run in our repository with the appropriate permissions and sanitize inputs. +# +# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, +# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. + + +on: + pull_request: + types: [opened, edited, closed, labeled] + +permissions: + contents: read + +jobs: + record_pr: + runs-on: ubuntu-latest + permissions: + contents: read # NOTE: treat as untrusted location + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Extract PR details" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const script = require('.github/scripts/save_pr_details.js') + await script({github, context, core}) + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: pr + path: pr.txt + retention-days: 1 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000000..473968803b0 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,32 @@ +name: Release Drafter + +# PROCESS +# +# 1. Enumerate all PRs in merged state +# 2. Filter out any PR labeled `skip-changelog` +# 3. Updates or creates a new release in Draft mode + +# USAGE +# +# Always run on merged PRs or manually via GitHub UI for debugging purposes. +# +# see .github/release-drafter.yml for configuration + +on: + push: + branches: + - develop + workflow_dispatch: + +permissions: + contents: read + +jobs: + update_release_draft: + runs-on: ubuntu-latest + permissions: + contents: write # create release in draft mode + steps: + - uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v5.20.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..5611b6bc180 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,376 @@ +name: Release + +# RELEASE PROCESS +# +# === Automated activities === +# +# 1. [Seal] Bump to release version and export source code with integrity hash +# 2. [Quality check] Restore sealed source code, run tests, linting, security and complexity base line +# 3. [Build] Restore sealed source code, create and export hashed build artifact for PyPi release (wheel, tarball) +# 4. [Provenance] Generates provenance for build, signs attestation with GitHub OIDC claims to confirm it came from this release pipeline, commit, org, repo, branch, hash, etc. +# 5. [Release] Restore built artifact, and publish package to PyPi prod repository +# 6. [Create Tag] Restore sealed source code, create a new git tag using released version, uploads provenance to latest draft release +# 7. [PR to bump version] Restore sealed source code, and create a PR to update trunk with latest released project metadata +# 8. [Publish Layer] Compile Layer and kick off pipeline for beta, prod, and canary releases +# 9. [Publish Layer] Update docs with latest Layer ARNs and Changelog +# 10. [Publish Layer] Create PR to update trunk so staged docs also point to the latest Layer ARN, when merged +# 12. [Post release] Close all issues labeled "pending-release" and notify customers about the release +# +# === Manual activities === +# +# 1. Kick off this workflow with the intended version +# 2. Update draft release notes after this workflow completes +# 3. If not already set, use `v<new version>` as a tag, e.g., v1.26.4, and select develop as target branch + +# NOTE +# +# See MAINTAINERS.md "Releasing a new version" for release mechanisms +# +# Every job is isolated and starts a new fresh container. + +env: + RELEASE_COMMIT: ${{ github.sha }} + RELEASE_TAG_VERSION: ${{ inputs.version_to_publish }} + +on: + workflow_dispatch: + inputs: + version_to_publish: + description: "Version to be released in PyPi, Docs, and Lambda Layer, e.g. v2.0.0, v2.0.0a0 (pre-release)" + default: v2.0.0 + required: true + skip_pypi: + description: "Skip publishing to PyPi as it can't publish more than once. Useful for semi-failed releases" + default: false + type: boolean + required: false + skip_code_quality: + description: "Skip tests, linting, and baseline. Only use if release fail for reasons beyond our control and you need a quick release." + default: false + type: boolean + required: false + pre_release: + description: "Publishes documentation using a pre-release tag (v2.0.0a0). You are still responsible for passing a pre-release version tag to the workflow." + default: false + type: boolean + required: false + +permissions: + contents: read + +jobs: + + # This job bumps the package version to the release version + # creates an integrity hash from the source code + # uploads the artifact with the integrity hash as the key name + # so subsequent jobs can restore from a trusted point in time to prevent tampering + seal: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + integrity_hash: ${{ steps.seal_source_code.outputs.integrity_hash }} + artifact_name: ${{ steps.seal_source_code.outputs.artifact_name }} + RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION }} + steps: + - name: Export release version + id: release_version + # transform tag format `v<version` to `<version>` + run: | + RELEASE_VERSION="${RELEASE_TAG_VERSION:1}" + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + # We use a pinned version of Poetry to be certain it won't modify source code before we create a hash + - name: Install poetry + run: | + pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + pipx inject poetry git+https://github.com/monim67/poetry-bumpversion@315fe3324a699fa12ec20e202eb7375d4327d1c4 # v0.3.1 + + - name: Bump package version + id: versioning + run: poetry version "${RELEASE_VERSION}" + env: + RELEASE_VERSION: ${{ steps.release_version.outputs.RELEASE_VERSION}} + + - name: Seal and upload + id: seal_source_code + uses: ./.github/actions/seal + with: + artifact_name_prefix: "source" + + # This job runs our automated test suite, complexity and security baselines + # it ensures previously merged have been tested as part of the pull request process + # + # NOTE + # + # we don't upload the artifact after testing to prevent any tampering of our source code dependencies + quality_check: + needs: seal + runs-on: ubuntu-latest + permissions: + contents: read + steps: + # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Debug cache restore + run: cat pyproject.toml + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "poetry" + - name: Install dependencies + run: make dev + - name: Run all tests, linting and baselines + run: make pr + + # This job creates a release artifact (tar.gz, wheel) + # it checks out code from release commit for custom actions to work + # then restores the sealed source code (overwrites any potential tampering) + # it's done separately from release job to enforce least privilege. + # We export just the final build artifact for release + build: + runs-on: ubuntu-latest + needs: [quality_check, seal] + permissions: + contents: read + outputs: + integrity_hash: ${{ steps.seal_build.outputs.integrity_hash }} + artifact_name: ${{ steps.seal_build.outputs.artifact_name }} + attestation_hashes: ${{ steps.encoded_hash.outputs.attestation_hashes }} + steps: + # NOTE: we need actions/checkout to configure git first (pre-commit hooks in make dev) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "poetry" + + - name: Build python package and wheel + run: poetry build + + - name: Seal and upload + id: seal_build + uses: ./.github/actions/seal + with: + artifact_name_prefix: "build" + files: "dist/" + + # NOTE: SLSA retraces our build to its artifact to ensure it wasn't tampered + # coupled with GitHub OIDC, SLSA can then confidently sign it came from this release pipeline+commit+branch+org+repo+actor+integrity hash + - name: Create attestation encoded hash for provenance + id: encoded_hash + working-directory: dist + run: echo "attestation_hashes=$(sha256sum ./* | base64 -w0)" >> "$GITHUB_OUTPUT" + + # This job creates a provenance file that describes how our release was built (all steps) + # after it verifies our build is reproducible within the same pipeline + # it confirms that its own software and the CI build haven't been tampered with (Trust but verify) + # lastly, it creates and sign an attestation (multiple.intoto.jsonl) that confirms + # this build artifact came from this GitHub org, branch, actor, commit ID, inputs that triggered this pipeline, and matches its integrity hash + # NOTE: supply chain threats review (we protect against all of them now): https://slsa.dev/spec/v1.0/threats-overview + provenance: + needs: [seal, build] + permissions: + contents: write # nested job explicitly require despite upload assets being set to false + actions: read # To read the workflow path. + id-token: write # To sign the provenance. + # NOTE: provenance fails if we use action pinning... it's a Github limitation + # because SLSA needs to trace & attest it came from a given branch; pinning doesn't expose that information + # https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#referencing-the-slsa-generator + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.attestation_hashes }} + upload-assets: false # we upload its attestation in create_tag job, otherwise it creates a new release + + # This job uses release artifact to publish to PyPi + # it exchanges JWT tokens with GitHub to obtain PyPi credentials + # since it's already registered as a Trusted Publisher. + # It uses the sealed build artifact (.whl, .tar.gz) to release it + release: + needs: [build, seal, provenance] + environment: release + runs-on: ubuntu-latest + permissions: + id-token: write # OIDC for PyPi Trusted Publisher feature + env: + RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }} + steps: + # NOTE: we need actions/checkout in order to use our local actions (e.g., ./.github/actions) + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.build.outputs.integrity_hash }} + artifact_name: ${{ needs.build.outputs.artifact_name }} + + - name: Upload to PyPi prod + if: ${{ !inputs.skip_pypi }} + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + + # PyPi test maintenance affected us numerous times, leaving for history purposes + # - name: Upload to PyPi test + # if: ${{ !inputs.skip_pypi }} + # uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + # with: + # repository-url: https://test.pypi.org/legacy/ + + # We create a Git Tag using our release version (e.g., v2.16.0) + # using our sealed source code we created earlier. + # Because we bumped version of our project as part of CI + # we need to add this into git before pushing the tag + # otherwise the release commit will be used as the basis for the tag. + # Later, we create a PR to update trunk with our newest release version (e.g., bump_version job) + create_tag: + needs: [release, seal, provenance] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + # NOTE: we need actions/checkout to authenticate and configure git first + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - id: setup-git + name: Git client setup and refresh tip + run: | + git config user.name "Powertools for AWS Lambda (Python) bot" + git config user.email "151832416+aws-powertools-bot@users.noreply.github.com" + git config remote.origin.url >&- + + - name: Create Git Tag + run: | + git add pyproject.toml aws_lambda_powertools/shared/version.py + git commit -m "chore: version bump" + git tag -a v"${RELEASE_VERSION}" -m "release_version: v${RELEASE_VERSION}" + git push origin v"${RELEASE_VERSION}" + env: + RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }} + + - name: Upload provenance + id: upload-provenance + uses: ./.github/actions/upload-release-provenance + with: + release_version: ${{ needs.seal.outputs.RELEASE_VERSION }} + provenance_name: ${{needs.provenance.outputs.provenance-name}} + github_token: ${{ secrets.GITHUB_TOKEN }} + + # Creates a PR with the latest version we've just released + # since our trunk is protected against any direct pushes from automation + bump_version: + needs: [release, seal] + permissions: + contents: write # create-pr action creates a temporary branch + pull-requests: write # create-pr action creates a PR using the temporary branch + runs-on: ubuntu-latest + steps: + # NOTE: we need actions/checkout to authenticate and configure git first + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Create PR + id: create-pr + uses: ./.github/actions/create-pr + with: + files: "pyproject.toml aws_lambda_powertools/shared/version.py" + temp_branch_prefix: "ci-bump" + pull_request_title: "chore(ci): bump version to ${{ needs.seal.outputs.RELEASE_VERSION }}" + github_token: ${{ secrets.GITHUB_TOKEN }} + + # This job compiles a Lambda Layer optimized for space and speed (e.g., Cython) + # It then deploys to Layer's Beta and Prod account, including SAR Beta and Prod account. + # It uses canaries to attest Layers can be used and imported between stages. + # Lastly, it updates our documentation with the latest Layer ARN for all regions + # + # NOTE + # + # Watch out for the depth limit of 4 nested workflow_calls. + # publish_layer -> publish_v2_layer -> reusable_deploy_v2_layer_stack + publish_layer: + needs: [seal, release, create_tag] + secrets: inherit + permissions: + id-token: write + contents: write + pages: write + pull-requests: write + uses: ./.github/workflows/publish_v2_layer.yml + with: + latest_published_version: ${{ needs.seal.outputs.RELEASE_VERSION }} + pre_release: ${{ inputs.pre_release }} + source_code_artifact_name: ${{ needs.seal.outputs.artifact_name }} + source_code_integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + + post_release: + needs: [seal, release, publish_layer] + permissions: + contents: read + issues: write + discussions: write + pull-requests: write + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ needs.seal.outputs.RELEASE_VERSION }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ needs.seal.outputs.integrity_hash }} + artifact_name: ${{ needs.seal.outputs.artifact_name }} + + - name: Close issues related to this release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const post_release = require('.github/scripts/post_release.js') + await post_release({github, context, core}) diff --git a/.github/workflows/reusable_deploy_v2_layer_stack.yml b/.github/workflows/reusable_deploy_v2_layer_stack.yml new file mode 100644 index 00000000000..5891c7e0b85 --- /dev/null +++ b/.github/workflows/reusable_deploy_v2_layer_stack.yml @@ -0,0 +1,207 @@ +name: Deploy CDK Layer v2 stack + +# PROCESS +# +# 1. Split what AWS regions support ARM vs regions that Lambda support ARM +# 2. Deploy previously built layer for each AWS commercial region +# 3. Export all published Layers as JSON +# 4. Deploy Canaries to every deployed region to test whether Powertools can be imported etc. + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/publish_v2_layer.yml +# +# beta: +# needs: build-layer +# # lower privilege propagated from parent workflow (release.yml) +# permissions: +# id-token: write +# contents: read +# pages: write # docs will be updated with latest Layer ARNs +# pull-requests: write # creation-action will create a PR with Layer ARN updates +# uses: ./.github/workflows/reusable_deploy_v2_layer_stack.yml +# secrets: inherit +# with: +# stage: "BETA" +# artefact-name: "cdk-layer-artefact" +# environment: "layer-beta" +# latest_published_version: ${{ inputs.latest_published_version }} + +on: + workflow_call: + inputs: + stage: + description: "Deployment stage (BETA, PROD)" + required: true + type: string + artefact-name: + description: "CDK Layer Artefact name to download" + required: true + type: string + environment: + description: "GitHub Environment to use for encrypted secrets" + required: true + type: string + latest_published_version: + description: "Latest version that is published" + required: true + type: string + source_code_artifact_name: + description: "Artifact name to restore sealed source code" + type: string + required: true + source_code_integrity_hash: + description: "Sealed source code integrity hash" + type: string + required: true + +permissions: + contents: read + +env: + RELEASE_COMMIT: ${{ github.sha }} # it gets propagated from the caller for security reasons + +jobs: + deploy-cdk-stack: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + # lower privilege propagated from parent workflow (publish_v2_layer.yml) + permissions: + id-token: write + pull-requests: none + contents: read + pages: none + defaults: + run: + working-directory: ./layer + strategy: + fail-fast: false + matrix: + # To get a list of current regions, use: + # aws ec2 describe-regions --all-regions --query "Regions[].RegionName" --output text | tr "\t" "\n" | sort + include: + - region: "af-south-1" + has_arm64_support: "true" + - region: "ap-east-1" + has_arm64_support: "true" + - region: "ap-northeast-1" + has_arm64_support: "true" + - region: "ap-northeast-2" + has_arm64_support: "true" + - region: "ap-northeast-3" + has_arm64_support: "true" + - region: "ap-south-1" + has_arm64_support: "true" + - region: "ap-south-2" + has_arm64_support: "true" + - region: "ap-southeast-1" + has_arm64_support: "true" + - region: "ap-southeast-2" + has_arm64_support: "true" + - region: "ap-southeast-3" + has_arm64_support: "true" + - region: "ap-southeast-4" + has_arm64_support: "true" + - region: "ca-central-1" + has_arm64_support: "true" + - region: "ca-west-1" + has_arm64_support: "false" + - region: "eu-central-1" + has_arm64_support: "true" + - region: "eu-central-2" + has_arm64_support: "true" + - region: "eu-north-1" + has_arm64_support: "true" + - region: "eu-south-1" + has_arm64_support: "true" + - region: "eu-south-2" + has_arm64_support: "true" + - region: "eu-west-1" + has_arm64_support: "true" + - region: "eu-west-2" + has_arm64_support: "true" + - region: "eu-west-3" + has_arm64_support: "true" + - region: "il-central-1" + has_arm64_support: "true" + - region: "me-central-1" + has_arm64_support: "true" + - region: "me-south-1" + has_arm64_support: "true" + - region: "sa-east-1" + has_arm64_support: "true" + - region: "us-east-1" + has_arm64_support: "true" + - region: "us-east-2" + has_arm64_support: "true" + - region: "us-west-1" + has_arm64_support: "true" + - region: "us-west-2" + has_arm64_support: "true" + steps: + - name: checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ inputs.source_code_integrity_hash }} + artifact_name: ${{ inputs.source_code_artifact_name }} + + - name: Install poetry + run: pipx install git+https://github.com/python-poetry/poetry@68b88e5390720a3dd84f02940ec5200bfce39ac6 # v1.5.0 + - name: aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: ${{ matrix.region }} + role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }} + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: "16.12" + - name: Setup python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "pip" + - name: Resolve and install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install --require-hashes -r requirements.txt + - name: install cdk and deps + working-directory: ./ + run: | + npm ci + npx cdk --version + - name: install deps + run: poetry install + - name: Download artifact + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{ inputs.artefact-name }} + path: layer + - name: unzip artefact + run: unzip cdk.out.zip + - name: CDK Deploy Layer + run: npx cdk deploy --app cdk.out --context region=${{ matrix.region }} --parameters HasARM64Support=${{ matrix.has_arm64_support }} 'LayerV2Stack' --require-approval never --verbose --outputs-file cdk-outputs.json + - name: Store latest Layer ARN + if: ${{ inputs.stage == 'PROD' }} + run: | + mkdir cdk-layer-stack + jq -r -c '.LayerV2Stack.LatestLayerArn' cdk-outputs.json > cdk-layer-stack/${{ matrix.region }}-layer-version.txt + jq -r -c '.LayerV2Stack.LatestLayerArm64Arn' cdk-outputs.json >> cdk-layer-stack/${{ matrix.region }}-layer-version.txt + cat cdk-layer-stack/${{ matrix.region }}-layer-version.txt + - name: Save Layer ARN artifact + if: ${{ inputs.stage == 'PROD' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: cdk-layer-stack-${{ matrix.region }} + path: ./layer/cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting. + if-no-files-found: error + retention-days: 1 + - name: CDK Deploy Canary + run: npx cdk deploy --app cdk.out --context region=${{ matrix.region }} --parameters DeployStage="${{ inputs.stage }}" --parameters HasARM64Support=${{ matrix.has_arm64_support }} 'CanaryV2Stack' --require-approval never --verbose diff --git a/.github/workflows/reusable_deploy_v2_sar.yml b/.github/workflows/reusable_deploy_v2_sar.yml new file mode 100644 index 00000000000..d5fd9ea8dfa --- /dev/null +++ b/.github/workflows/reusable_deploy_v2_sar.yml @@ -0,0 +1,209 @@ +name: Deploy V2 SAR + +# PROCESS +# +# 1. This workflow starts after the layer artifact is produced on `publish_v2_layer` +# 2. We use the same layer artifact to ensure the SAR app is consistent with the published Lambda Layer +# 3. We publish the SAR for both x86_64 and arm64 (see `matrix` section) +# 4. We use `sam package` and `sam publish` to publish the SAR app +# 5. We remove the previous Canary stack (if present) and deploy a new one to test the SAR App. We retain the Canary in the account for debugging purposes +# 6. Finally the published SAR app is made public on the PROD environment + +# USAGE +# +# NOTE: meant to be used with ./.github/workflows/publish_v2_layer.yml +# +# sar-beta: +# needs: build-layer +# permissions: +# # lower privilege propagated from parent workflow (release.yml) +# id-token: write +# contents: read +# pull-requests: none +# pages: none +# uses: ./.github/workflows/reusable_deploy_v2_sar.yml +# secrets: inherit +# with: +# stage: "BETA" +# artefact-name: "cdk-layer-artefact" +# environment: "layer-beta" +# package-version: ${{ inputs.latest_published_version }} +# source_code_artifact_name: ${{ inputs.source_code_artifact_name }} +# source_code_integrity_hash: ${{ inputs.source_code_integrity_hash }} + +permissions: + id-token: write + contents: read + +env: + NODE_VERSION: 16.12 + AWS_REGION: eu-west-1 + SAR_NAME: aws-lambda-powertools-python-layer + TEST_STACK_NAME: serverlessrepo-v2-powertools-layer-test-stack + RELEASE_COMMIT: ${{ github.sha }} # it gets propagated from the caller for security reasons + +on: + workflow_call: + inputs: + stage: + description: "Deployment stage (BETA, PROD)" + required: true + type: string + artefact-name: + description: "CDK Layer Artefact name to download" + required: true + type: string + package-version: + description: "The version of the package to deploy" + required: true + type: string + environment: + description: "GitHub Environment to use for encrypted secrets" + required: true + type: string + source_code_artifact_name: + description: "Artifact name to restore sealed source code" + type: string + required: true + source_code_integrity_hash: + description: "Sealed source code integrity hash" + type: string + required: true + +jobs: + deploy-sar-app: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + strategy: + matrix: + architecture: ["x86_64", "arm64"] + steps: + - name: checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env.RELEASE_COMMIT }} + + - name: Restore sealed source code + uses: ./.github/actions/seal-restore + with: + integrity_hash: ${{ inputs.source_code_integrity_hash }} + artifact_name: ${{ inputs.source_code_artifact_name }} + + + - name: AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }} + + # NOTE + # We connect to Layers account to log our intent to publish a SAR Layer + # we then jump to our specific SAR Account with the correctly scoped IAM Role + # this allows us to have a single trail when a release occurs for a given layer (beta+prod+SAR beta+SAR prod) + - name: AWS credentials SAR role + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + id: aws-credentials-sar-role + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ env.AWS_SESSION_TOKEN }} + role-duration-seconds: 1200 + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_SAR_V2_ROLE_ARN }} + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Download artifact + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: ${{ inputs.artefact-name }} + - name: Unzip artefact + run: unzip cdk.out.zip + - name: Configure SAR name + run: | + if [[ "${{ inputs.stage }}" == "BETA" ]]; then + SAR_NAME="test-${SAR_NAME}" + fi + echo SAR_NAME="${SAR_NAME}" >> "$GITHUB_ENV" + - name: Adds arm64 suffix to SAR name + if: ${{ matrix.architecture == 'arm64' }} + run: echo SAR_NAME="${SAR_NAME}-arm64" >> "$GITHUB_ENV" + - name: Normalize semantic version + id: semantic-version # v2.0.0a0 -> v2.0.0-a0 + env: + VERSION: ${{ inputs.package-version }} + run: | + VERSION="${VERSION/a/-a}" + echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT" + - name: Prepare SAR App + env: + VERSION: ${{ steps.semantic-version.outputs.VERSION }} + run: | + # From the generated LayerStack cdk.out artifact, find the layer asset path for the correct architecture. + # We'll use this as the source directory of our SAR. This way we are re-using the same layer asset for our SAR. + asset=$(jq -jc '.Resources[] | select(.Properties.CompatibleArchitectures == ["${{ matrix.architecture }}"]) | .Metadata."aws:asset:path"' cdk.out/LayerV2Stack.template.json) + + # fill in the SAR SAM template + sed \ + -e "s|<VERSION>|${VERSION}|g" \ + -e "s/<SAR_APP_NAME>/${{ env.SAR_NAME }}/g" \ + -e "s|<LAYER_CONTENT_PATH>|./cdk.out/$asset|g" \ + layer/sar/template.txt > template.yml + + # SAR needs a README and a LICENSE, so just copy the ones from the repo + cp README.md LICENSE "./cdk.out/$asset/" + + # Debug purposes + cat template.yml + - name: Deploy SAR + run: | + # Package the SAR to our SAR S3 bucket, and publish it + sam package --template-file template.yml --output-template-file packaged.yml --s3-bucket ${{ secrets.AWS_SAR_S3_BUCKET }} + sam publish --template packaged.yml --region "$AWS_REGION" + - name: Deploy BETA canary + if: ${{ inputs.stage == 'BETA' }} + run: | + if [[ "${{ matrix.architecture }}" == "arm64" ]]; then + TEST_STACK_NAME="${TEST_STACK_NAME}-arm64" + fi + + echo "Check if stack does not exist" + stack_exists=$(aws cloudformation list-stacks --query "StackSummaries[?(StackName == '$TEST_STACK_NAME' && StackStatus == 'CREATE_COMPLETE')].{StackId:StackId, StackName:StackName, CreationTime:CreationTime, StackStatus:StackStatus}" --output text) + + if [[ -n "$stack_exists" ]] ; then + echo "Found test deployment stack, removing..." + aws cloudformation delete-stack --stack-name "$TEST_STACK_NAME" + aws cloudformation wait stack-delete-complete --stack-name "$TEST_STACK_NAME" + fi + + echo "Creating canary stack" + echo "Stack name: $TEST_STACK_NAME" + aws serverlessrepo create-cloud-formation-change-set \ + --application-id arn:aws:serverlessrepo:${{ env.AWS_REGION }}:${{ steps.aws-credentials-sar-role.outputs.aws-account-id }}:applications/${{ env.SAR_NAME }} \ + --stack-name "${TEST_STACK_NAME/serverlessrepo-/}" \ + --capabilities CAPABILITY_NAMED_IAM + + CHANGE_SET_ID=$(aws cloudformation list-change-sets --stack-name "$TEST_STACK_NAME" --query 'Summaries[*].ChangeSetId' --output text) + aws cloudformation wait change-set-create-complete --change-set-name "$CHANGE_SET_ID" + aws cloudformation execute-change-set --change-set-name "$CHANGE_SET_ID" + aws cloudformation wait stack-create-complete --stack-name "$TEST_STACK_NAME" + + echo "Waiting until stack deployment completes..." + + echo "Exit with error if stack is not in CREATE_COMPLETE" + stack_exists=$(aws cloudformation list-stacks --query "StackSummaries[?(StackName == '$TEST_STACK_NAME' && StackStatus == 'CREATE_COMPLETE')].{StackId:StackId, StackName:StackName, CreationTime:CreationTime, StackStatus:StackStatus}") + if [[ -z "$stack_exists" ]] ; then + echo "Could find successful deployment, exit error..." + exit 1 + fi + echo "Deployment successful" + - name: Publish SAR + if: ${{ inputs.stage == 'PROD' }} + run: | + # wait until SAR registers the app, otherwise it fails to make it public + sleep 15 + echo "Make SAR app public" + aws serverlessrepo put-application-policy \ + --application-id arn:aws:serverlessrepo:${{ env.AWS_REGION }}:${{ steps.aws-credentials-sar-role.outputs.aws-account-id }}:applications/${{ env.SAR_NAME }} \ + --statements Principals='*',Actions=Deploy diff --git a/.github/workflows/reusable_export_pr_details.yml b/.github/workflows/reusable_export_pr_details.yml new file mode 100644 index 00000000000..9c929521908 --- /dev/null +++ b/.github/workflows/reusable_export_pr_details.yml @@ -0,0 +1,115 @@ +name: Export previously recorded PR + +# PROCESS +# +# 1. Fetch PR details previously saved from untrusted location +# 2. Parse details for safety +# 3. Export only what's needed for automation, e.g., PR number, title, body, author, action, whether is merged + +# USAGE +# +# see .github/workflows/on_merged_pr.yml and related for full example. +# +# NOTE: meant to be used with workflows that react to a given PR state (labeling, new, merged, etc.) +# done separately to isolate security practices and make it reusable. + + +on: + workflow_call: + inputs: + record_pr_workflow_id: + description: "Record PR workflow execution ID to download PR details" + required: true + type: number + workflow_origin: # see https://github.com/aws-powertools/powertools-lambda-python/issues/1349 + description: "Repository full name for runner integrity" + required: true + type: string + secrets: + token: + description: "GitHub Actions temporary and scoped token" + required: true + # Map the workflow outputs to job outputs + outputs: + prNumber: + description: "PR Number" + value: ${{ jobs.export_pr_details.outputs.prNumber }} + prTitle: + description: "PR Title" + value: ${{ jobs.export_pr_details.outputs.prTitle }} + prBody: + description: "PR Body as string" + value: ${{ jobs.export_pr_details.outputs.prBody }} + prAuthor: + description: "PR author username" + value: ${{ jobs.export_pr_details.outputs.prAuthor }} + prAction: + description: "PR event action" + value: ${{ jobs.export_pr_details.outputs.prAction }} + prIsMerged: + description: "Whether PR is merged" + value: ${{ jobs.export_pr_details.outputs.prIsMerged }} + prLabels: + description: "PR Labels" + value: ${{ jobs.export_pr_details.outputs.prLabels }} + +permissions: + contents: read + +jobs: + export_pr_details: + permissions: + actions: read # download PR artifact + # see https://github.com/aws-powertools/powertools-lambda-python/issues/1349 + if: inputs.workflow_origin == 'aws-powertools/powertools-lambda-python' + runs-on: ubuntu-latest + env: + FILENAME: pr.txt + # Map the job outputs to step outputs + outputs: + prNumber: ${{ steps.prNumber.outputs.prNumber }} + prTitle: ${{ steps.prTitle.outputs.prTitle }} + prBody: ${{ steps.prBody.outputs.prBody }} + prAuthor: ${{ steps.prAuthor.outputs.prAuthor }} + prAction: ${{ steps.prAction.outputs.prAction }} + prIsMerged: ${{ steps.prIsMerged.outputs.prIsMerged }} + prLabels: ${{ steps.prLabels.outputs.prLabels }} + steps: + - name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: "Download previously saved PR" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + WORKFLOW_ID: ${{ inputs.record_pr_workflow_id }} + # For security, we only download artifacts tied to the successful PR recording workflow + with: + github-token: ${{ secrets.token }} + script: | + const script = require('.github/scripts/download_pr_artifact.js') + await script({github, context, core}) + # NodeJS standard library doesn't provide ZIP capabilities; use system `unzip` command instead + - name: "Unzip PR artifact" + run: unzip pr.zip + # NOTE: We need separate steps for each mapped output and respective IDs + # otherwise the parent caller won't see them regardless on how outputs are set. + - name: "Export Pull Request Number" + id: prNumber + run: echo prNumber="$(jq -c '.number' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request Title" + id: prTitle + run: echo prTitle="$(jq -c '.pull_request.title' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request Body" + id: prBody + run: echo prBody="$(jq -c '.pull_request.body' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request Author" + id: prAuthor + run: echo prAuthor="$(jq -c '.pull_request.user.login' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request Action" + id: prAction + run: echo prAction="$(jq -c '.action' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request Merged status" + id: prIsMerged + run: echo prIsMerged="$(jq -c '.pull_request.merged' "${FILENAME}")" >> "$GITHUB_OUTPUT" + - name: "Export Pull Request labels" + id: prLabels + run: echo prLabels="$(jq -c '.labels' "${FILENAME}")" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml new file mode 100644 index 00000000000..599c035ff3b --- /dev/null +++ b/.github/workflows/reusable_publish_changelog.yml @@ -0,0 +1,41 @@ +name: Build and publish latest changelog + +# see ./.github/workflows/build_changelog.yml for docs + +on: + workflow_call: + +env: + TEMP_BRANCH_PREFIX: "ci-changelog" + PULL_REQUEST_TITLE: "chore(ci): changelog rebuild" + FILES_TO_COMMIT: "CHANGELOG.md" + +permissions: + contents: read + +jobs: + publish_changelog: + if: github.repository == 'aws-powertools/powertools-lambda-python' + # Force Github action to run only a single job at a time (based on the group name) + # This is to prevent race-condition and inconsistencies with changelog push + concurrency: + group: changelog-build + runs-on: ubuntu-latest + permissions: + contents: write # create temporary branch with changelog + pull-requests: write # create PR + steps: + - name: Checkout repository # reusable workflows start clean, so we need to checkout again + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 0 + - name: "Generate latest changelog" + run: make changelog + - name: Create PR + id: create-pr + uses: ./.github/actions/create-pr + with: + files: ${{ env.FILES_TO_COMMIT }} + temp_branch_prefix: ${{ env.TEMP_BRANCH_PREFIX }} + pull_request_title: ${{ env.PULL_REQUEST_TITLE }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reusable_publish_docs.yml b/.github/workflows/reusable_publish_docs.yml new file mode 100644 index 00000000000..39556da71e9 --- /dev/null +++ b/.github/workflows/reusable_publish_docs.yml @@ -0,0 +1,129 @@ +name: Reusable publish documentation + +# see .github/workflows/on_push_docs.yml for docs + +env: + ORIGIN: aws-powertools/powertools-lambda-python + +on: + workflow_call: + inputs: + version: + description: "Version to build and publish docs (1.28.0, develop)" + required: true + type: string + alias: + description: "Alias to associate version (latest, stage)" + required: true + type: string + detached_mode: + description: "Whether it's running in git detached mode to ensure git is sync'd" + required: false + default: false + type: boolean + git_ref: + description: "Branch or commit ID to checkout from" + required: false + type: string + default: develop + +permissions: + contents: read + +jobs: + publish_docs: + if: github.repository == 'aws-powertools/powertools-lambda-python' + # Force Github action to run only a single job at a time (based on the group name) + # This is to prevent "race-condition" in publishing a new version of doc to `gh-pages` + concurrency: + group: on-docs-rebuild + runs-on: ubuntu-latest + environment: "Docs" + permissions: + contents: write # push to gh-pages + id-token: write # trade JWT token for AWS credentials in AWS Docs account + pages: write # uncomment if mike fails as we migrated to S3 hosting + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 0 + ref: ${{ inputs.git_ref }} + - name: Install poetry + run: pipx install poetry + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.12" + cache: "poetry" + - name: Install dependencies + run: make dev + - name: Git client setup + run: | + git config --global user.name Docs deploy + git config --global user.email aws-devax-open-source@amazon.com + - name: Git refresh tip (detached mode) + # Git Detached mode (release notes) doesn't have origin + if: ${{ inputs.detached_mode }} + run: | + git config pull.rebase true + git config remote.origin.url >&- || git remote add origin https://github.com/"$ORIGIN" + git pull origin "$BRANCH" + env: + BRANCH: ${{ inputs.git_ref }} + - name: Build docs website and API reference + env: + VERSION: ${{ inputs.version }} + ALIAS: ${{ inputs.alias }} + run: | + make release-docs VERSION="$VERSION" ALIAS="$ALIAS" + poetry run mike set-default --push latest + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_DOCS_ROLE_ARN }} + - name: Copy API Docs + run: | + cp -r api site/ + - name: Deploy Docs (Version) + env: + VERSION: ${{ inputs.version }} + ALIAS: ${{ inputs.alias }} + run: | + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-python/${{ env.VERSION }}/ + - name: Deploy Docs (Alias) + env: + VERSION: ${{ inputs.version }} + ALIAS: ${{ inputs.alias }} + run: | + aws s3 sync \ + site/ \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-python/${{ env.ALIAS }}/ + - name: Deploy Docs (Version JSON) + env: + VERSION: ${{ inputs.version }} + ALIAS: ${{ inputs.alias }} + # We originally used "mike" from PyPi to manage versions for us, but since we moved to S3, we can't use it to manage versions any more. + # Instead, we're using some shell script that manages the versions. + # + # Operations: + # 1. Download the versions.json file from S3 + # 2. Find any reference to the alias and delete it from the versions file + # 3. This is voodoo (don't use JQ): + # - we assign the input as $o and the new version/alias as $n, + # - we check if the version number exists in the file already (for republishing docs) + # - if it's an alias (stage/latest/*) or old version, we do nothing and output $o (original input) + # - if it's a new version number, we add it at position 0 in the array. + # 4. Once done, we'll upload it back to S3. + run: | + aws s3 cp \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-python/versions.json \ + versions_old.json + jq 'del(.[].aliases[] | select(. == "${{ env.ALIAS }}"))' < versions_old.json > versions_proc.json + jq '. as $o | [{"title": "${{ env.VERSION }}", "version": "${{ env.VERSION }}", "aliases": ["${{ env.ALIAS }}"] }] as $n | $n | if .[0].title | test("[a-z]+") or any($o[].title == $n[0].title;.) then [($o | .[] | select(.title == $n[0].title).aliases += $n[0].aliases | . )] else $n + $o end' < versions_proc.json > versions.json + aws s3 cp \ + versions.json \ + s3://${{ secrets.AWS_DOCS_BUCKET }}/lambda-python/versions.json diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml new file mode 100644 index 00000000000..e30ad094127 --- /dev/null +++ b/.github/workflows/run-e2e-tests.yml @@ -0,0 +1,80 @@ +name: Run end-to-end tests + +# PROCESS +# +# 1. Install all dependencies and spin off containers for all supported Python versions +# 2. Install pinned CDK version +# 3. Trade JWT token for AWS credentials to Test account +# 4. Run E2E in parallel for each feature + +# USAGE +# +# see MAINTAINERS.md#internals for full details on mechanics. +# +# Always triggered on new PR merge. + +on: + workflow_dispatch: + + push: + branches: + - develop + - v3 + paths: + - "aws_lambda_powertools/**" + - "tests/e2e/**" + - "pyproject.toml" + - "poetry.lock" + - "mypy.ini" + - "parallel_run_e2e.py" + +# Maintenance: Add support for triggering on `run-e2e` label +# and enforce repo origin to prevent abuse + +env: + AWS_DEFAULT_REGION: us-east-1 + +concurrency: e2e + +permissions: + contents: read + +jobs: + run: + runs-on: aws-powertools_ubuntu-latest_8-core + permissions: + id-token: write # needed to request JWT with GitHub's OIDC Token endpoint. docs: https://bit.ly/3MNgQO9 + contents: read # checkout code + strategy: + fail-fast: false # needed so if a version fails, the others will still be able to complete and cleanup + matrix: + version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + if: ${{ github.actor != 'dependabot[bot]' && github.repository == 'aws-powertools/powertools-lambda-python' }} + steps: + - name: "Checkout" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Install poetry + run: pipx install poetry + - name: "Use Python" + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: ${{ matrix.version }} + architecture: "x64" + cache: "poetry" + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: "20.10.0" + - name: Install CDK CLI + run: | + npm ci + npx cdk --version + - name: Install dependencies + run: make dev + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_TEST_ROLE_ARN }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Test + run: make e2e-test diff --git a/.github/workflows/secure_workflows.yml b/.github/workflows/secure_workflows.yml new file mode 100644 index 00000000000..32f73d4cbc0 --- /dev/null +++ b/.github/workflows/secure_workflows.yml @@ -0,0 +1,39 @@ +name: Lockdown untrusted workflows + +# PROCESS +# +# 1. Scans for any external GitHub Action being used without version pinning (@<commit-sha> vs @v3) +# 2. Scans for insecure practices for inline bash scripts (shellcheck) +# 3. Fail CI and prevent PRs to be merged if any malpractice is found + +# USAGE +# +# Always triggered on new PR, PR changes and PR merge. + + +on: + push: + paths: + - ".github/workflows/**" + pull_request: + paths: + - ".github/workflows/**" + +permissions: + contents: read + +jobs: + enforce_pinned_workflows: + name: Harden Security + runs-on: ubuntu-latest + permissions: + contents: read # checkout code and subsequently GitHub action workflows + steps: + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Ensure 3rd party workflows have SHA pinned + uses: zgosalvez/github-actions-ensure-sha-pinned-actions@74606c30450304eee8660aae751818321754feb1 # v3.0.9 + with: + allowlist: | + slsa-framework/slsa-github-generator + aws-powertools/actions diff --git a/.gitignore b/.gitignore index d053f6b9248..990f6517fe9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode # Edit at https://www.gitignore.io/?templates=osx,linux,python,windows,pycharm,visualstudiocode @@ -253,6 +252,7 @@ dmypy.json .pyre/ ### VisualStudioCode ### +.vscode .vscode/* !.vscode/tasks.json !.vscode/launch.json @@ -291,5 +291,27 @@ $RECYCLE.BIN/ # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode # Misc - -test_report \ No newline at end of file +test_report +wheelhouse +/.idea/* +*.html +TMP_CHANGELOG.md + +# Docs files +docs/.cache/ +docs/public +node_modules +api/ +site/ +!404.html +!docs/overrides/*.html + +# CDK +.cdk + +!.github/workflows/lib +examples/**/sam/.aws-sam + +cdk.out +# NOTE: different accounts will be used for E2E thus creating unnecessary git clutter +cdk.context.json \ No newline at end of file diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 00000000000..0622b57c118 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,15 @@ +# Title for the gitleaks configuration file. +title = "Gitleaks" + +[extend] +# useDefault will extend the base configuration with the default gitleaks config: +# https://github.com/zricethezav/gitleaks/blob/master/config/gitleaks.toml +useDefault = true + +[allowlist] +description = "Allow list false positive" + +# Allow list paths to ignore due to false positives. +paths = [ + '''tests/unit/parser/test_kinesis_firehose\.py''', +] diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000000..d501e5cc212 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,3 @@ +examples/batch_processing/src/context_manager_access_output_pydantic.txt:aws-access-token:10 +examples/batch_processing/src/context_manager_access_output_pydantic.txt:aws-access-token:15 +examples/batch_processing/src/context_manager_access_output.txt:aws-access-token:10 diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 00000000000..9fa927ddac6 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,11 @@ +# See here all gitpod images available: https://hub.docker.com/r/gitpod/workspace-python-3.11/tags +# Current python version: 3.11.9 +FROM gitpod/workspace-python-3.11@sha256:2d9a242844bef5710ab4622899a5254a0c59f0ac58c0d3ac998f749323f43951 + +WORKDIR /app +ADD . /app + +# Installing pre-commit as system package and not user package. Git needs this to execute pre-commit hooks. +RUN export PIP_USER=no +# pre-commit v3.7.1 +RUN python3 -m pip install --require-hashes -r .gitpod_requirements.txt \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000000..d831f067bdd --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,19 @@ +image: + file: .gitpod.Dockerfile +tasks: + - init: make dev-gitpod +vscode: + extensions: + - ms-python.python # IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more. + - littlefoxteam.vscode-python-test-adapter # Run your Python tests in the Sidebar of Visual Studio Code + - ms-azuretools.vscode-docker # Makes it easy to create, manage, and debug containerized applications. + - davidanson.vscode-markdownlint # Markdown linting and style checking for Visual Studio Code + - bungcip.better-toml # Better TOML Language support + - oderwat.indent-rainbow # Makes indentation easier to read + - yzhang.markdown-all-in-one # Autoformat, better visualization, snippets, and markdown export to multiple fmts + - bierner.markdown-mermaid # Previews mermaid diagrams when previewing markdown + - matangover.mypy # Highlight mypy issues + - njpwerner.autodocstring # Auto-generate docsstrings in numpy format that we use + - netcorext.uuid-generator # For those helping create code snippets for docs + - streetsidesoftware.code-spell-checker # Spell checker that works with camel case too + - bungcip.better-toml # In case GitPod doesn't have support for TOML pyproject.toml diff --git a/.gitpod_requirements.in b/.gitpod_requirements.in new file mode 100644 index 00000000000..b427b003fa9 --- /dev/null +++ b/.gitpod_requirements.in @@ -0,0 +1 @@ +pre-commit==3.7.1 \ No newline at end of file diff --git a/.gitpod_requirements.txt b/.gitpod_requirements.txt new file mode 100644 index 00000000000..a9643d7dfdf --- /dev/null +++ b/.gitpod_requirements.txt @@ -0,0 +1,85 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes --output-file=.gitpod_requirements.txt .gitpod_requirements.in +# +cfgv==3.3.1 \ + --hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \ + --hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736 + # via pre-commit +distlib==0.3.6 \ + --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ + --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e + # via virtualenv +filelock==3.12.2 \ + --hash=sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81 \ + --hash=sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec + # via virtualenv +identify==2.5.24 \ + --hash=sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4 \ + --hash=sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d + # via pre-commit +nodeenv==1.8.0 \ + --hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \ + --hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec + # via pre-commit +platformdirs==3.8.0 \ + --hash=sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc \ + --hash=sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e + # via virtualenv +pre-commit==3.7.1 \ + --hash=sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a \ + --hash=sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5 + # via -r .gitpod_requirements.in +pyyaml==6.0 \ + --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \ + --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ + --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ + --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ + --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ + --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ + --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ + --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ + --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ + --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ + --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ + --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ + --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \ + --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ + --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ + --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ + --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ + --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ + --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \ + --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ + --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ + --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ + --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ + --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ + --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ + --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \ + --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ + --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ + --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \ + --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ + --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ + --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ + --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \ + --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ + --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ + --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ + --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ + --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \ + --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ + --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 + # via pre-commit +virtualenv==20.23.1 \ + --hash=sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419 \ + --hash=sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1 + # via pre-commit + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes and the requirement is not +# satisfied by a package already installed. Consider using the --allow-unsafe flag. +# setuptools diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000000..4529480ad19 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,240 @@ +# Rules: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md + +# Default state for all rules +default: true + +# Path to configuration file to extend +extends: null + +# MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time +MD001: true + +# MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading +# NOTE: We use h2 due to font size +MD002: false + +# MD003/heading-style/header-style - Heading style +MD003: + # Heading style + style: "consistent" + +# MD004/ul-style - Unordered list style +MD004: + # List style + style: "consistent" + +# MD005/list-indent - Inconsistent indentation for list items at the same level +MD005: true + +# MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line +MD006: true + +# MD007/ul-indent - Unordered list indentation +MD007: + # Spaces for indent + indent: 4 + # Whether to indent the first level of the list + start_indented: false + # Spaces for first level indent (when start_indented is set) + start_indent: 2 + +# MD009/no-trailing-spaces - Trailing spaces +MD009: + # Spaces for line break + br_spaces: 2 + # Allow spaces for empty lines in list items + list_item_empty_lines: false + # Include unnecessary breaks + strict: false + +# MD010/no-hard-tabs - Hard tabs +# NOTE: Mkdocs Material theme features like code annotations, tabbed content require it +MD010: false + +# MD011/no-reversed-links - Reversed link syntax +MD011: true + +# MD012/no-multiple-blanks - Multiple consecutive blank lines +MD012: + # Consecutive blank lines + maximum: 1 + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 380 + # Number of characters for headings + heading_line_length: 80 + # Number of characters for code blocks + code_block_line_length: 265 + # Include code blocks + code_blocks: true + # Include tables + tables: false + # Include headings + headings: true + # Strict length checking + strict: false + # Stern length checking + stern: false + +# MD014/commands-show-output - Dollar signs used before commands without showing output +MD014: true + +# MD018/no-missing-space-atx - No space after hash on atx style heading +MD018: true + +# MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading +MD019: true + +# MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading +MD020: true + +# MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading +MD021: true + +# MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines +MD022: + # Blank lines above heading + lines_above: 1 + # Blank lines below heading + lines_below: 1 + +# MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line +MD023: true + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: false + +# MD025/single-title/single-h1 - Multiple top-level headings in the same document +MD025: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD026/no-trailing-punctuation - Trailing punctuation in heading +MD026: + # Punctuation characters + punctuation: ".,;:!。,;:!" + +# MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol +MD027: true + +# MD028/no-blanks-blockquote - Blank line inside blockquote +MD028: true + +# MD029/ol-prefix - Ordered list item prefix +MD029: + # List style + style: "one_or_ordered" + +# MD030/list-marker-space - Spaces after list markers +MD030: + # Spaces for single-line unordered list items + ul_single: 1 + # Spaces for single-line ordered list items + ol_single: 1 + # Spaces for multi-line unordered list items + ul_multi: 1 + # Spaces for multi-line ordered list items + ol_multi: 1 + +# MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines +MD031: + # Include list items + list_items: true + +# MD032/blanks-around-lists - Lists should be surrounded by blank lines +MD032: true + +# MD033/no-inline-html - Inline HTML +# NOTE: Some content like Logger '<module>' triggers false positives +MD033: false + +# MD034/no-bare-urls - Bare URL used +MD034: true + +# MD035/hr-style - Horizontal rule style +MD035: + # Horizontal rule style + style: "consistent" + +# MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading +# NOTE: We use **<topic>** instead of yet another sub-heading that might not appear in the navigation. +# this is a trade-off we make to not a gigantic right-navigation +MD036: false + +# MD037/no-space-in-emphasis - Spaces inside emphasis markers +MD037: true + +# MD038/no-space-in-code - Spaces inside code span elements +# mkdocs-material requires these in tab content +MD038: false + +# MD039/no-space-in-links - Spaces inside link text +MD039: true + +# MD040/fenced-code-language - Fenced code blocks should have a language specified +MD040: true + +# MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading +MD041: + # Heading level + level: 2 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD042/no-empty-links - No empty links +# NOTE: Clipboard links like Lambda Layers use empty links +MD042: false + +# MD043/required-headings/required-headers - Required heading structure +# NOTE: Enforce our minimum headers across the docs +MD043: + # List of headings + headings: + [ + "*", + "## Key features", + "*", + "## Getting started", + "*", + "## Advanced", + "*", + "## Testing your code", + "*", + ] + +# MD044/proper-names - Proper names should have the correct capitalization +MD044: + # List of proper names + names: [] + # Include code blocks + code_blocks: true + # Include HTML elements + html_elements: true + +# MD045/no-alt-text - Images should have alternate text (alt text) +MD045: true + +# MD046/code-block-style - Code block style +# Material theme tabbed content feature use indented and simple use fenced; can't support both +MD046: false + +# MD047/single-trailing-newline - Files should end with a single newline character +MD047: true + +# MD048/code-fence-style - Code fence style +MD048: false + +# MD051/link-fragments - Link fragments should be valid +MD051: true + +# MD052/reference-links-images - Reference links and images should use a label that is defined +MD052: true + +# MD053/link-image-reference-definitions - Link and image reference definitions should be needed +MD053: true diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000000..11b6d7ffe29 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,2 @@ +docs/core/metrics/index.md +includes/abbreviations.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..1fbd55f3197 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,53 @@ +# We use poetry to run formatting and linting before commit/push +# Longer checks such as tests, security and complexity baseline +# are run as part of CI to prevent slower feedback loop +# All checks can be run locally via `make pr` + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "f71fa2c1f9cf5cb705f73dffe4b21f7c61470ba9" # v4.4.0 + hooks: + - id: check-merge-conflict + - id: trailing-whitespace + - id: check-toml + - repo: local + hooks: + - id: black + name: formatting::black + entry: poetry run black + language: system + types: [python] + - id: ruff + name: linting-format::ruff + entry: poetry run ruff + language: system + types: [python] + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: "ce0d77ac47dc921b62429804fe763d4d35f66a76" # v0.34.0 + hooks: + - id: markdownlint-docker + args: ["--fix"] + - repo: local + hooks: + - id: cloudformation + name: linting::cloudformation + entry: poetry run cfn-lint + language: system + types: [yaml] + exclude: examples/homepage/install/.*?/serverless\.yml$ + files: examples/.* + - repo: https://github.com/rhysd/actionlint + rev: "fd7ba3c382e13dcc0248e425b4cbc3f1185fa3ee" # v1.6.24 + hooks: + - id: actionlint-docker + args: [-pyflakes=] + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: 3420134c37197c21edffc7e6093b14ffae8402f2 # v1.81.0 + hooks: + - id: terraform_fmt + args: + - --args=-recursive + - repo: https://github.com/gitleaks/gitleaks + rev: "7804d652c0460f0b61ea9bb2c1f202bfcdbbf144" # v8.16.3 + hooks: + - id: gitleaks diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000000..cf0445d7d27 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,12 @@ +[MESSAGES CONTROL] +disable= + too-many-arguments, + too-many-instance-attributes, + too-few-public-methods, + anomalous-backslash-in-string, + missing-class-docstring, + missing-module-docstring, + missing-function-docstring, + +[FORMAT] +max-line-length=120 diff --git a/404.html b/404.html new file mode 100644 index 00000000000..f09a446555b --- /dev/null +++ b/404.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script> + const websitePath = window.location.pathname; + const versionRegex = /(\w.+)\/(latest|([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?)/; + + if (websitePath.search(versionRegex) === -1) { + let projectName = "powertools-lambda-python" + // redirect old links to latest version alias + window.location = websitePath.replace(projectName, `${projectName}/latest`) + } + </script> +</head> + +</html> diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..eacec403e94 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5178 @@ +<!-- changelog is partially generated, so it doesn't follow headings and required structure, so we disable it. --> +<!-- markdownlint-disable --> + +<a name="unreleased"></a> +# Unreleased + +## Bug Fixes + +* **event_sources:** change partition and offset field types in KafkaEventRecord ([#4515](https://github.com/aws-powertools/powertools-lambda-python/issues/4515)) + +## Documentation + +* **homepage:** Fix homepage link ([#4587](https://github.com/aws-powertools/powertools-lambda-python/issues/4587)) +* **i-made-this:** add new article about best practices for accelerating serverless development ([#4518](https://github.com/aws-powertools/powertools-lambda-python/issues/4518)) +* **public reference:** add Brsk as a public reference ([#4597](https://github.com/aws-powertools/powertools-lambda-python/issues/4597)) + +## Features + +* **validation:** support JSON Schema referencing in validation utils ([#4508](https://github.com/aws-powertools/powertools-lambda-python/issues/4508)) + +## Maintenance + +* **ci:** add the Data Masking feature to nox tests ([#4574](https://github.com/aws-powertools/powertools-lambda-python/issues/4574)) +* **ci:** new pre-release 2.39.2a2 ([#4610](https://github.com/aws-powertools/powertools-lambda-python/issues/4610)) +* **ci:** add the Metrics feature to nox tests ([#4552](https://github.com/aws-powertools/powertools-lambda-python/issues/4552)) +* **ci:** new pre-release 2.39.2a1 ([#4598](https://github.com/aws-powertools/powertools-lambda-python/issues/4598)) +* **ci:** add the Middleware Factory feature to nox tests ([#4568](https://github.com/aws-powertools/powertools-lambda-python/issues/4568)) +* **ci:** add the Parameters feature to nox tests ([#4569](https://github.com/aws-powertools/powertools-lambda-python/issues/4569)) +* **ci:** new pre-release 2.39.2a0 ([#4590](https://github.com/aws-powertools/powertools-lambda-python/issues/4590)) +* **ci:** introduce daily pre-releases ([#4535](https://github.com/aws-powertools/powertools-lambda-python/issues/4535)) +* **ci:** add the Batch Processor feature to nox tests ([#4586](https://github.com/aws-powertools/powertools-lambda-python/issues/4586)) +* **ci:** add the Idempotency feature to nox tests ([#4585](https://github.com/aws-powertools/powertools-lambda-python/issues/4585)) +* **ci:** add the Parser feature to nox tests ([#4584](https://github.com/aws-powertools/powertools-lambda-python/issues/4584)) +* **ci:** add the Data Class feature to nox tests ([#4583](https://github.com/aws-powertools/powertools-lambda-python/issues/4583)) +* **ci:** add the Event Handler feature to nox tests ([#4581](https://github.com/aws-powertools/powertools-lambda-python/issues/4581)) +* **ci:** add the Streaming feature to nox tests ([#4575](https://github.com/aws-powertools/powertools-lambda-python/issues/4575)) +* **ci:** add the Feature Flags feature to nox tests ([#4570](https://github.com/aws-powertools/powertools-lambda-python/issues/4570)) +* **ci:** add the Validation feature to nox tests ([#4571](https://github.com/aws-powertools/powertools-lambda-python/issues/4571)) +* **ci:** add the Typing feature to nox tests ([#4572](https://github.com/aws-powertools/powertools-lambda-python/issues/4572)) +* **ci:** add the Tracer feature to nox tests ([#4567](https://github.com/aws-powertools/powertools-lambda-python/issues/4567)) +* **ci:** introduce tests with Nox ([#4537](https://github.com/aws-powertools/powertools-lambda-python/issues/4537)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.7 to 3.0.9 ([#4539](https://github.com/aws-powertools/powertools-lambda-python/issues/4539)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 ([#4538](https://github.com/aws-powertools/powertools-lambda-python/issues/4538)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4546](https://github.com/aws-powertools/powertools-lambda-python/issues/4546)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4580](https://github.com/aws-powertools/powertools-lambda-python/issues/4580)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 ([#4592](https://github.com/aws-powertools/powertools-lambda-python/issues/4592)) +* **deps:** bump redis from 5.0.5 to 5.0.6 ([#4527](https://github.com/aws-powertools/powertools-lambda-python/issues/4527)) +* **deps:** bump codecov/codecov-action from 4.4.1 to 4.5.0 ([#4514](https://github.com/aws-powertools/powertools-lambda-python/issues/4514)) +* **deps:** bump squidfunk/mkdocs-material from `96abcbb` to `257eca8` in /docs ([#4540](https://github.com/aws-powertools/powertools-lambda-python/issues/4540)) +* **deps:** bump fastjsonschema from 2.19.1 to 2.20.0 ([#4543](https://github.com/aws-powertools/powertools-lambda-python/issues/4543)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4565](https://github.com/aws-powertools/powertools-lambda-python/issues/4565)) +* **deps:** bump pydantic from 1.10.16 to 1.10.17 ([#4595](https://github.com/aws-powertools/powertools-lambda-python/issues/4595)) +* **deps-dev:** bump sentry-sdk from 2.5.1 to 2.6.0 ([#4579](https://github.com/aws-powertools/powertools-lambda-python/issues/4579)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.184 to 0.1.188 ([#4550](https://github.com/aws-powertools/powertools-lambda-python/issues/4550)) +* **deps-dev:** bump mkdocs-material from 9.5.26 to 9.5.27 ([#4544](https://github.com/aws-powertools/powertools-lambda-python/issues/4544)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.145.0a0 to 2.146.0a0 ([#4542](https://github.com/aws-powertools/powertools-lambda-python/issues/4542)) +* **deps-dev:** bump urllib3 from 1.26.18 to 1.26.19 in /layer ([#4547](https://github.com/aws-powertools/powertools-lambda-python/issues/4547)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.188 to 0.1.189 ([#4564](https://github.com/aws-powertools/powertools-lambda-python/issues/4564)) +* **deps-dev:** bump hvac from 2.2.0 to 2.3.0 ([#4563](https://github.com/aws-powertools/powertools-lambda-python/issues/4563)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.189 to 0.1.192 ([#4578](https://github.com/aws-powertools/powertools-lambda-python/issues/4578)) +* **deps-dev:** bump cfn-lint from 0.87.7 to 1.3.0 ([#4577](https://github.com/aws-powertools/powertools-lambda-python/issues/4577)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.125 to 1.34.128 in the boto-typing group ([#4541](https://github.com/aws-powertools/powertools-lambda-python/issues/4541)) +* **deps-dev:** bump filelock from 3.15.1 to 3.15.3 ([#4576](https://github.com/aws-powertools/powertools-lambda-python/issues/4576)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.114 to 1.34.131 in the boto-typing group ([#4593](https://github.com/aws-powertools/powertools-lambda-python/issues/4593)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.192 to 0.1.193 ([#4596](https://github.com/aws-powertools/powertools-lambda-python/issues/4596)) +* **deps-dev:** bump ruff from 0.4.9 to 0.4.10 ([#4594](https://github.com/aws-powertools/powertools-lambda-python/issues/4594)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.193 to 0.1.194 ([#4601](https://github.com/aws-powertools/powertools-lambda-python/issues/4601)) +* **deps-dev:** bump ruff from 0.4.8 to 0.4.9 ([#4528](https://github.com/aws-powertools/powertools-lambda-python/issues/4528)) +* **deps-dev:** bump aws-cdk-lib from 2.145.0 to 2.146.0 ([#4526](https://github.com/aws-powertools/powertools-lambda-python/issues/4526)) +* **deps-dev:** bump aws-cdk from 2.146.0 to 2.147.0 ([#4604](https://github.com/aws-powertools/powertools-lambda-python/issues/4604)) +* **deps-dev:** bump aws-cdk from 2.145.0 to 2.146.0 ([#4525](https://github.com/aws-powertools/powertools-lambda-python/issues/4525)) +* **deps-dev:** bump aws-cdk-lib from 2.146.0 to 2.147.0 ([#4603](https://github.com/aws-powertools/powertools-lambda-python/issues/4603)) +* **deps-dev:** bump bandit from 1.7.8 to 1.7.9 ([#4511](https://github.com/aws-powertools/powertools-lambda-python/issues/4511)) +* **deps-dev:** bump cfn-lint from 0.87.6 to 0.87.7 ([#4513](https://github.com/aws-powertools/powertools-lambda-python/issues/4513)) +* **deps-dev:** bump filelock from 3.14.0 to 3.15.1 ([#4512](https://github.com/aws-powertools/powertools-lambda-python/issues/4512)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.179 to 0.1.182 ([#4510](https://github.com/aws-powertools/powertools-lambda-python/issues/4510)) +* **deps-dev:** bump cfn-lint from 1.3.0 to 1.3.3 ([#4602](https://github.com/aws-powertools/powertools-lambda-python/issues/4602)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.109 to 1.34.125 in the boto-typing group ([#4509](https://github.com/aws-powertools/powertools-lambda-python/issues/4509)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.182 to 0.1.184 ([#4529](https://github.com/aws-powertools/powertools-lambda-python/issues/4529)) +* **governance:** fix errors when creating Gitpod environment ([#4532](https://github.com/aws-powertools/powertools-lambda-python/issues/4532)) + + +<a name="v2.39.1"></a> +## [v2.39.1] - 2024-06-13 +## Bug Fixes + +* **event_handler:** regression making pydantic required (it should not) ([#4500](https://github.com/aws-powertools/powertools-lambda-python/issues/4500)) + +## Maintenance + +* version bump + + +<a name="v2.39.0"></a> +## [v2.39.0] - 2024-06-13 +## Bug Fixes + +* **event_handler:** do not skip middleware and exception handlers on 404 error ([#4492](https://github.com/aws-powertools/powertools-lambda-python/issues/4492)) +* **event_handler:** raise more specific SerializationError exception for unsupported types in data validation ([#4415](https://github.com/aws-powertools/powertools-lambda-python/issues/4415)) +* **event_handler:** security scheme unhashable list when working with router ([#4421](https://github.com/aws-powertools/powertools-lambda-python/issues/4421)) +* **event_handler:** CORS Origin for ALBResolver multi-headers ([#4385](https://github.com/aws-powertools/powertools-lambda-python/issues/4385)) +* **idempotency:** POWERTOOLS_IDEMPOTENCY_DISABLED should respect truthy values ([#4391](https://github.com/aws-powertools/powertools-lambda-python/issues/4391)) + +## Documentation + +* **homepage:** Change installation to CDK v2 ([#4351](https://github.com/aws-powertools/powertools-lambda-python/issues/4351)) +* **public reference:** add Recast as a public reference ([#4491](https://github.com/aws-powertools/powertools-lambda-python/issues/4491)) + +## Features + +* **event_source:** add CloudFormationCustomResourceEvent data class. ([#4342](https://github.com/aws-powertools/powertools-lambda-python/issues/4342)) +* **events:** Update and Add Cognito User Pool Events ([#4423](https://github.com/aws-powertools/powertools-lambda-python/issues/4423)) + +## Maintenance + +* version bump +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4369](https://github.com/aws-powertools/powertools-lambda-python/issues/4369)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4468](https://github.com/aws-powertools/powertools-lambda-python/issues/4468)) +* **deps:** bump datadog-lambda from 5.94.0 to 6.95.0 ([#4471](https://github.com/aws-powertools/powertools-lambda-python/issues/4471)) +* **deps:** bump redis from 5.0.4 to 5.0.5 ([#4464](https://github.com/aws-powertools/powertools-lambda-python/issues/4464)) +* **deps:** bump aws-encryption-sdk from 3.2.0 to 3.3.0 ([#4393](https://github.com/aws-powertools/powertools-lambda-python/issues/4393)) +* **deps:** bump codecov/codecov-action from 4.4.0 to 4.4.1 ([#4376](https://github.com/aws-powertools/powertools-lambda-python/issues/4376)) +* **deps:** bump squidfunk/mkdocs-material from `8a87f05` to `96abcbb` in /docs ([#4461](https://github.com/aws-powertools/powertools-lambda-python/issues/4461)) +* **deps:** bump typing-extensions from 4.12.1 to 4.12.2 ([#4470](https://github.com/aws-powertools/powertools-lambda-python/issues/4470)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#4396](https://github.com/aws-powertools/powertools-lambda-python/issues/4396)) +* **deps:** bump aws-xray-sdk from 2.13.0 to 2.13.1 ([#4379](https://github.com/aws-powertools/powertools-lambda-python/issues/4379)) +* **deps:** bump actions/dependency-review-action from 4.3.2 to 4.3.3 ([#4456](https://github.com/aws-powertools/powertools-lambda-python/issues/4456)) +* **deps:** bump aws-xray-sdk from 2.13.1 to 2.14.0 ([#4453](https://github.com/aws-powertools/powertools-lambda-python/issues/4453)) +* **deps:** bump typing-extensions from 4.11.0 to 4.12.0 ([#4404](https://github.com/aws-powertools/powertools-lambda-python/issues/4404)) +* **deps:** bump squidfunk/mkdocs-material from `5358893` to `8a87f05` in /docs ([#4408](https://github.com/aws-powertools/powertools-lambda-python/issues/4408)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.6 to 3.0.7 ([#4478](https://github.com/aws-powertools/powertools-lambda-python/issues/4478)) +* **deps:** bump squidfunk/mkdocs-material from `48d1914` to `5358893` in /docs ([#4377](https://github.com/aws-powertools/powertools-lambda-python/issues/4377)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4444](https://github.com/aws-powertools/powertools-lambda-python/issues/4444)) +* **deps:** bump pydantic from 1.10.15 to 1.10.16 ([#4485](https://github.com/aws-powertools/powertools-lambda-python/issues/4485)) +* **deps:** bump datadog-lambda from 6.95.0 to 6.96.0 ([#4489](https://github.com/aws-powertools/powertools-lambda-python/issues/4489)) +* **deps:** bump actions/checkout from 4.1.6 to 4.1.7 ([#4493](https://github.com/aws-powertools/powertools-lambda-python/issues/4493)) +* **deps:** bump typing-extensions from 4.12.0 to 4.12.1 ([#4440](https://github.com/aws-powertools/powertools-lambda-python/issues/4440)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.5 to 3.0.6 ([#4445](https://github.com/aws-powertools/powertools-lambda-python/issues/4445)) +* **deps:** bump requests from 2.31.0 to 2.32.0 ([#4383](https://github.com/aws-powertools/powertools-lambda-python/issues/4383)) +* **deps-dev:** bump aws-cdk from 2.143.1 to 2.144.0 ([#4443](https://github.com/aws-powertools/powertools-lambda-python/issues/4443)) +* **deps-dev:** bump aws-cdk-lib from 2.143.1 to 2.144.0 ([#4441](https://github.com/aws-powertools/powertools-lambda-python/issues/4441)) +* **deps-dev:** bump ruff from 0.4.6 to 0.4.7 ([#4435](https://github.com/aws-powertools/powertools-lambda-python/issues/4435)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.143.0a0 to 2.143.1a0 ([#4433](https://github.com/aws-powertools/powertools-lambda-python/issues/4433)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.164 to 0.1.169 ([#4442](https://github.com/aws-powertools/powertools-lambda-python/issues/4442)) +* **deps-dev:** bump pytest from 8.2.1 to 8.2.2 ([#4450](https://github.com/aws-powertools/powertools-lambda-python/issues/4450)) +* **deps-dev:** bump aws-cdk from 2.143.0 to 2.143.1 ([#4430](https://github.com/aws-powertools/powertools-lambda-python/issues/4430)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.163 to 0.1.164 ([#4428](https://github.com/aws-powertools/powertools-lambda-python/issues/4428)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.161 to 0.1.163 ([#4425](https://github.com/aws-powertools/powertools-lambda-python/issues/4425)) +* **deps-dev:** bump cfn-lint from 0.87.5 to 0.87.6 ([#4486](https://github.com/aws-powertools/powertools-lambda-python/issues/4486)) +* **deps-dev:** bump sentry-sdk from 2.3.1 to 2.4.0 ([#4449](https://github.com/aws-powertools/powertools-lambda-python/issues/4449)) +* **deps-dev:** bump ruff from 0.4.5 to 0.4.6 ([#4417](https://github.com/aws-powertools/powertools-lambda-python/issues/4417)) +* **deps-dev:** bump cfn-lint from 0.87.3 to 0.87.4 ([#4419](https://github.com/aws-powertools/powertools-lambda-python/issues/4419)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.159 to 0.1.161 ([#4420](https://github.com/aws-powertools/powertools-lambda-python/issues/4420)) +* **deps-dev:** bump coverage from 7.5.2 to 7.5.3 ([#4418](https://github.com/aws-powertools/powertools-lambda-python/issues/4418)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.113 to 1.34.114 in the boto-typing group ([#4416](https://github.com/aws-powertools/powertools-lambda-python/issues/4416)) +* **deps-dev:** bump mkdocs-material from 9.5.24 to 9.5.25 ([#4411](https://github.com/aws-powertools/powertools-lambda-python/issues/4411)) +* **deps-dev:** bump aws-cdk-lib from 2.143.0 to 2.143.1 ([#4429](https://github.com/aws-powertools/powertools-lambda-python/issues/4429)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.142.1a0 to 2.143.0a0 ([#4410](https://github.com/aws-powertools/powertools-lambda-python/issues/4410)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.97 to 1.34.113 in the boto-typing group ([#4409](https://github.com/aws-powertools/powertools-lambda-python/issues/4409)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.158 to 0.1.159 ([#4412](https://github.com/aws-powertools/powertools-lambda-python/issues/4412)) +* **deps-dev:** bump coverage from 7.5.1 to 7.5.2 ([#4413](https://github.com/aws-powertools/powertools-lambda-python/issues/4413)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.143.1a0 to 2.144.0a0 ([#4448](https://github.com/aws-powertools/powertools-lambda-python/issues/4448)) +* **deps-dev:** bump mypy-boto3-s3 from 1.34.105 to 1.34.120 in the boto-typing group ([#4452](https://github.com/aws-powertools/powertools-lambda-python/issues/4452)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.169 to 0.1.173 ([#4459](https://github.com/aws-powertools/powertools-lambda-python/issues/4459)) +* **deps-dev:** bump aws-cdk-lib from 2.142.1 to 2.143.0 ([#4403](https://github.com/aws-powertools/powertools-lambda-python/issues/4403)) +* **deps-dev:** bump aws-cdk from 2.142.1 to 2.143.0 ([#4402](https://github.com/aws-powertools/powertools-lambda-python/issues/4402)) +* **deps-dev:** bump ruff from 0.4.4 to 0.4.5 ([#4399](https://github.com/aws-powertools/powertools-lambda-python/issues/4399)) +* **deps-dev:** bump sentry-sdk from 2.2.1 to 2.3.1 ([#4398](https://github.com/aws-powertools/powertools-lambda-python/issues/4398)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.157 to 0.1.158 ([#4397](https://github.com/aws-powertools/powertools-lambda-python/issues/4397)) +* **deps-dev:** bump ruff from 0.4.7 to 0.4.8 ([#4455](https://github.com/aws-powertools/powertools-lambda-python/issues/4455)) +* **deps-dev:** bump sentry-sdk from 2.4.0 to 2.5.0 ([#4462](https://github.com/aws-powertools/powertools-lambda-python/issues/4462)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.155 to 0.1.157 ([#4394](https://github.com/aws-powertools/powertools-lambda-python/issues/4394)) +* **deps-dev:** bump mkdocs-material from 9.5.25 to 9.5.26 ([#4463](https://github.com/aws-powertools/powertools-lambda-python/issues/4463)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.34.84 to 1.34.111 in the boto-typing group ([#4392](https://github.com/aws-powertools/powertools-lambda-python/issues/4392)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.154 to 0.1.155 ([#4386](https://github.com/aws-powertools/powertools-lambda-python/issues/4386)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.173 to 0.1.174 ([#4466](https://github.com/aws-powertools/powertools-lambda-python/issues/4466)) +* **deps-dev:** bump pytest-asyncio from 0.23.6 to 0.23.7 ([#4387](https://github.com/aws-powertools/powertools-lambda-python/issues/4387)) +* **deps-dev:** bump sentry-sdk from 2.2.0 to 2.2.1 ([#4388](https://github.com/aws-powertools/powertools-lambda-python/issues/4388)) +* **deps-dev:** bump ijson from 3.2.3 to 3.3.0 ([#4465](https://github.com/aws-powertools/powertools-lambda-python/issues/4465)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.152 to 0.1.154 ([#4382](https://github.com/aws-powertools/powertools-lambda-python/issues/4382)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.174 to 0.1.175 ([#4472](https://github.com/aws-powertools/powertools-lambda-python/issues/4472)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.107 to 1.34.109 in the boto-typing group ([#4378](https://github.com/aws-powertools/powertools-lambda-python/issues/4378)) +* **deps-dev:** bump sentry-sdk from 2.5.0 to 2.5.1 ([#4469](https://github.com/aws-powertools/powertools-lambda-python/issues/4469)) +* **deps-dev:** bump cfn-lint from 0.87.4 to 0.87.5 ([#4479](https://github.com/aws-powertools/powertools-lambda-python/issues/4479)) +* **deps-dev:** bump mkdocs-material from 9.5.23 to 9.5.24 ([#4380](https://github.com/aws-powertools/powertools-lambda-python/issues/4380)) +* **deps-dev:** bump pytest from 8.2.0 to 8.2.1 ([#4381](https://github.com/aws-powertools/powertools-lambda-python/issues/4381)) +* **deps-dev:** bump aws-cdk from 2.144.0 to 2.145.0 ([#4482](https://github.com/aws-powertools/powertools-lambda-python/issues/4482)) +* **deps-dev:** bump aws-cdk-lib from 2.144.0 to 2.145.0 ([#4481](https://github.com/aws-powertools/powertools-lambda-python/issues/4481)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.141.0a0 to 2.142.1a0 ([#4367](https://github.com/aws-powertools/powertools-lambda-python/issues/4367)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.144.0a0 to 2.145.0a0 ([#4487](https://github.com/aws-powertools/powertools-lambda-python/issues/4487)) +* **deps-dev:** bump aws-cdk from 2.142.0 to 2.142.1 ([#4366](https://github.com/aws-powertools/powertools-lambda-python/issues/4366)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.150 to 0.1.152 ([#4368](https://github.com/aws-powertools/powertools-lambda-python/issues/4368)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.176 to 0.1.179 ([#4488](https://github.com/aws-powertools/powertools-lambda-python/issues/4488)) +* **deps-dev:** bump cfn-lint from 0.87.2 to 0.87.3 ([#4370](https://github.com/aws-powertools/powertools-lambda-python/issues/4370)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.175 to 0.1.176 ([#4480](https://github.com/aws-powertools/powertools-lambda-python/issues/4480)) +* **libraries:** add jmespath as a required dependency ([#4422](https://github.com/aws-powertools/powertools-lambda-python/issues/4422)) + + +<a name="v2.38.1"></a> +## [v2.38.1] - 2024-05-17 +## Bug Fixes + +* **logger:** reverting logger child modification ([#4363](https://github.com/aws-powertools/powertools-lambda-python/issues/4363)) + +## Maintenance + +* version bump + + +<a name="v2.38.0"></a> +## [v2.38.0] - 2024-05-17 +## Bug Fixes + +* **ci:** apply lessons learned to monthly roadmap reminder cross-repo ([#4078](https://github.com/aws-powertools/powertools-lambda-python/issues/4078)) +* **event-sources:** sane defaults for authorizer v1 and v2 ([#4298](https://github.com/aws-powertools/powertools-lambda-python/issues/4298)) +* **logger:** correctly pick powertools or custom handler in custom environments ([#4295](https://github.com/aws-powertools/powertools-lambda-python/issues/4295)) +* **parser:** make etag optional field on S3 notification events ([#4173](https://github.com/aws-powertools/powertools-lambda-python/issues/4173)) +* **typing:** resolved_headers_field is not Optional ([#4148](https://github.com/aws-powertools/powertools-lambda-python/issues/4148)) + +## Code Refactoring + +* **data-masking:** remove Non-GA comments ([#4334](https://github.com/aws-powertools/powertools-lambda-python/issues/4334)) +* **parser:** only infer type hints when necessary ([#4183](https://github.com/aws-powertools/powertools-lambda-python/issues/4183)) + +## Documentation + +* **general:** update documentation to add info about v3 ([#4234](https://github.com/aws-powertools/powertools-lambda-python/issues/4234)) +* **homepage:** add link to new and official workshop ([#4292](https://github.com/aws-powertools/powertools-lambda-python/issues/4292)) +* **idempotency:** fix highlight and import path ([#4154](https://github.com/aws-powertools/powertools-lambda-python/issues/4154)) +* **roadmap:** april updates ([#4181](https://github.com/aws-powertools/powertools-lambda-python/issues/4181)) + +## Features + +* **event_handler:** add support for persisting authorization session in OpenAPI ([#4312](https://github.com/aws-powertools/powertools-lambda-python/issues/4312)) +* **event_handler:** add decorator for HTTP HEAD verb ([#4275](https://github.com/aws-powertools/powertools-lambda-python/issues/4275)) +* **logger-utils:** preserve log level for discovered third-party top-level loggers ([#4299](https://github.com/aws-powertools/powertools-lambda-python/issues/4299)) + +## Maintenance + +* version bump +* **ci:** bump upload artifact action to v4 ([#4355](https://github.com/aws-powertools/powertools-lambda-python/issues/4355)) +* **ci:** add branch v3 to quality check and e2e actions ([#4232](https://github.com/aws-powertools/powertools-lambda-python/issues/4232)) +* **ci:** bump download artifact action to v4 ([#4358](https://github.com/aws-powertools/powertools-lambda-python/issues/4358)) +* **deps:** bump actions/download-artifact from 4.1.4 to 4.1.5 ([#4161](https://github.com/aws-powertools/powertools-lambda-python/issues/4161)) +* **deps:** bump actions/checkout from 4.1.3 to 4.1.4 ([#4206](https://github.com/aws-powertools/powertools-lambda-python/issues/4206)) +* **deps:** bump ossf/scorecard-action from 2.3.1 to 2.3.3 ([#4315](https://github.com/aws-powertools/powertools-lambda-python/issues/4315)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.12 to 1.27.13 in /layer/scripts/layer-balancer in the layer-balancer group ([#4319](https://github.com/aws-powertools/powertools-lambda-python/issues/4319)) +* **deps:** bump actions/download-artifact from 4.1.6 to 4.1.7 ([#4205](https://github.com/aws-powertools/powertools-lambda-python/issues/4205)) +* **deps:** bump squidfunk/mkdocs-material from `521644b` to `e309089` in /docs ([#4216](https://github.com/aws-powertools/powertools-lambda-python/issues/4216)) +* **deps:** bump squidfunk/mkdocs-material from `11d7ec0` to `8ef47d7` in /docs ([#4323](https://github.com/aws-powertools/powertools-lambda-python/issues/4323)) +* **deps:** bump datadog-lambda from 5.92.0 to 5.93.0 ([#4211](https://github.com/aws-powertools/powertools-lambda-python/issues/4211)) +* **deps:** bump redis from 5.0.3 to 5.0.4 ([#4187](https://github.com/aws-powertools/powertools-lambda-python/issues/4187)) +* **deps:** bump actions/upload-artifact from 4.3.2 to 4.3.3 ([#4177](https://github.com/aws-powertools/powertools-lambda-python/issues/4177)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#4302](https://github.com/aws-powertools/powertools-lambda-python/issues/4302)) +* **deps:** bump squidfunk/mkdocs-material from `8ef47d7` to `48d1914` in /docs ([#4336](https://github.com/aws-powertools/powertools-lambda-python/issues/4336)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4337](https://github.com/aws-powertools/powertools-lambda-python/issues/4337)) +* **deps:** bump squidfunk/mkdocs-material from `e309089` to `98c9809` in /docs ([#4236](https://github.com/aws-powertools/powertools-lambda-python/issues/4236)) +* **deps:** bump actions/dependency-review-action from 4.3.1 to 4.3.2 ([#4244](https://github.com/aws-powertools/powertools-lambda-python/issues/4244)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.4 to 3.0.5 ([#4281](https://github.com/aws-powertools/powertools-lambda-python/issues/4281)) +* **deps:** bump actions/checkout from 4.1.4 to 4.1.5 ([#4282](https://github.com/aws-powertools/powertools-lambda-python/issues/4282)) +* **deps:** bump jinja2 from 3.1.3 to 3.1.4 in /docs ([#4284](https://github.com/aws-powertools/powertools-lambda-python/issues/4284)) +* **deps:** bump codecov/codecov-action from 4.3.0 to 4.3.1 ([#4252](https://github.com/aws-powertools/powertools-lambda-python/issues/4252)) +* **deps:** bump datadog-lambda from 5.93.0 to 5.94.0 ([#4253](https://github.com/aws-powertools/powertools-lambda-python/issues/4253)) +* **deps:** bump actions/checkout from 4.1.2 to 4.1.3 ([#4168](https://github.com/aws-powertools/powertools-lambda-python/issues/4168)) +* **deps:** bump actions/dependency-review-action from 4.2.5 to 4.3.1 ([#4240](https://github.com/aws-powertools/powertools-lambda-python/issues/4240)) +* **deps:** bump actions/checkout from 4.1.5 to 4.1.6 ([#4344](https://github.com/aws-powertools/powertools-lambda-python/issues/4344)) +* **deps:** bump squidfunk/mkdocs-material from `98c9809` to `11d7ec0` in /docs ([#4269](https://github.com/aws-powertools/powertools-lambda-python/issues/4269)) +* **deps:** bump actions/upload-artifact from 4.3.1 to 4.3.2 ([#4162](https://github.com/aws-powertools/powertools-lambda-python/issues/4162)) +* **deps:** bump codecov/codecov-action from 4.3.1 to 4.4.0 ([#4328](https://github.com/aws-powertools/powertools-lambda-python/issues/4328)) +* **deps:** bump slsa-framework/slsa-github-generator from 1.10.0 to 2.0.0 ([#4179](https://github.com/aws-powertools/powertools-lambda-python/issues/4179)) +* **deps:** bump actions/download-artifact from 4.1.5 to 4.1.6 ([#4178](https://github.com/aws-powertools/powertools-lambda-python/issues/4178)) +* **deps-dev:** bump mkdocs-material from 9.5.20 to 9.5.21 ([#4271](https://github.com/aws-powertools/powertools-lambda-python/issues/4271)) +* **deps-dev:** bump mike from 2.1.0 to 2.1.1 ([#4268](https://github.com/aws-powertools/powertools-lambda-python/issues/4268)) +* **deps-dev:** bump cfn-lint from 0.87.0 to 0.87.1 ([#4272](https://github.com/aws-powertools/powertools-lambda-python/issues/4272)) +* **deps-dev:** bump mike from 1.1.2 to 2.1.0 ([#4258](https://github.com/aws-powertools/powertools-lambda-python/issues/4258)) +* **deps-dev:** bump aws-cdk-lib from 2.139.1 to 2.140.0 ([#4259](https://github.com/aws-powertools/powertools-lambda-python/issues/4259)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.139.1a0 to 2.140.0a0 ([#4270](https://github.com/aws-powertools/powertools-lambda-python/issues/4270)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.139.0a0 to 2.139.1a0 ([#4261](https://github.com/aws-powertools/powertools-lambda-python/issues/4261)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.133 to 0.1.134 ([#4260](https://github.com/aws-powertools/powertools-lambda-python/issues/4260)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.134 to 0.1.135 ([#4273](https://github.com/aws-powertools/powertools-lambda-python/issues/4273)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.34.91 to 1.34.97 in the boto-typing group ([#4257](https://github.com/aws-powertools/powertools-lambda-python/issues/4257)) +* **deps-dev:** bump sentry-sdk from 2.0.1 to 2.1.1 ([#4287](https://github.com/aws-powertools/powertools-lambda-python/issues/4287)) +* **deps-dev:** bump aws-cdk-lib from 2.139.0 to 2.139.1 ([#4248](https://github.com/aws-powertools/powertools-lambda-python/issues/4248)) +* **deps-dev:** bump cfn-lint from 0.86.4 to 0.87.0 ([#4249](https://github.com/aws-powertools/powertools-lambda-python/issues/4249)) +* **deps-dev:** bump pytest-xdist from 3.5.0 to 3.6.1 ([#4247](https://github.com/aws-powertools/powertools-lambda-python/issues/4247)) +* **deps-dev:** bump ruff from 0.4.2 to 0.4.3 ([#4286](https://github.com/aws-powertools/powertools-lambda-python/issues/4286)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.132 to 0.1.133 ([#4246](https://github.com/aws-powertools/powertools-lambda-python/issues/4246)) +* **deps-dev:** bump jinja2 from 3.1.3 to 3.1.4 ([#4283](https://github.com/aws-powertools/powertools-lambda-python/issues/4283)) +* **deps-dev:** bump aws-cdk from 2.139.0 to 2.139.1 ([#4245](https://github.com/aws-powertools/powertools-lambda-python/issues/4245)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.135 to 0.1.136 ([#4285](https://github.com/aws-powertools/powertools-lambda-python/issues/4285)) +* **deps-dev:** bump filelock from 3.13.4 to 3.14.0 ([#4241](https://github.com/aws-powertools/powertools-lambda-python/issues/4241)) +* **deps-dev:** bump hvac from 2.1.0 to 2.2.0 ([#4238](https://github.com/aws-powertools/powertools-lambda-python/issues/4238)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.131 to 0.1.132 ([#4239](https://github.com/aws-powertools/powertools-lambda-python/issues/4239)) +* **deps-dev:** bump mkdocs-material from 9.5.19 to 9.5.20 ([#4242](https://github.com/aws-powertools/powertools-lambda-python/issues/4242)) +* **deps-dev:** bump aws-cdk from 2.139.1 to 2.140.0 ([#4256](https://github.com/aws-powertools/powertools-lambda-python/issues/4256)) +* **deps-dev:** bump pytest from 8.1.1 to 8.2.0 ([#4237](https://github.com/aws-powertools/powertools-lambda-python/issues/4237)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.136 to 0.1.139 ([#4293](https://github.com/aws-powertools/powertools-lambda-python/issues/4293)) +* **deps-dev:** bump aws-cdk-lib from 2.141.0 to 2.142.1 ([#4352](https://github.com/aws-powertools/powertools-lambda-python/issues/4352)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.139 to 0.1.140 ([#4301](https://github.com/aws-powertools/powertools-lambda-python/issues/4301)) +* **deps-dev:** bump sentry-sdk from 1.45.0 to 2.0.1 ([#4223](https://github.com/aws-powertools/powertools-lambda-python/issues/4223)) +* **deps-dev:** bump mkdocs-material from 9.5.18 to 9.5.19 ([#4224](https://github.com/aws-powertools/powertools-lambda-python/issues/4224)) +* **deps-dev:** bump black from 24.4.1 to 24.4.2 ([#4222](https://github.com/aws-powertools/powertools-lambda-python/issues/4222)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.138.0a0 to 2.139.0a0 ([#4225](https://github.com/aws-powertools/powertools-lambda-python/issues/4225)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.130 to 0.1.131 ([#4221](https://github.com/aws-powertools/powertools-lambda-python/issues/4221)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.140 to 0.1.142 ([#4307](https://github.com/aws-powertools/powertools-lambda-python/issues/4307)) +* **deps-dev:** bump ruff from 0.4.1 to 0.4.2 ([#4212](https://github.com/aws-powertools/powertools-lambda-python/issues/4212)) +* **deps-dev:** bump aws-cdk-lib from 2.138.0 to 2.139.0 ([#4213](https://github.com/aws-powertools/powertools-lambda-python/issues/4213)) +* **deps-dev:** bump aws-cdk from 2.138.0 to 2.139.0 ([#4215](https://github.com/aws-powertools/powertools-lambda-python/issues/4215)) +* **deps-dev:** bump aws-cdk from 2.140.0 to 2.141.0 ([#4306](https://github.com/aws-powertools/powertools-lambda-python/issues/4306)) +* **deps-dev:** bump types-redis from 4.6.0.20240423 to 4.6.0.20240425 ([#4214](https://github.com/aws-powertools/powertools-lambda-python/issues/4214)) +* **deps-dev:** bump aws-cdk-lib from 2.140.0 to 2.141.0 ([#4308](https://github.com/aws-powertools/powertools-lambda-python/issues/4308)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#4210](https://github.com/aws-powertools/powertools-lambda-python/issues/4210)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.126 to 0.1.130 ([#4209](https://github.com/aws-powertools/powertools-lambda-python/issues/4209)) +* **deps-dev:** bump ruff from 0.4.3 to 0.4.4 ([#4309](https://github.com/aws-powertools/powertools-lambda-python/issues/4309)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.140.0a0 to 2.141.0a0 ([#4318](https://github.com/aws-powertools/powertools-lambda-python/issues/4318)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.142 to 0.1.144 ([#4316](https://github.com/aws-powertools/powertools-lambda-python/issues/4316)) +* **deps-dev:** bump black from 24.4.0 to 24.4.1 ([#4203](https://github.com/aws-powertools/powertools-lambda-python/issues/4203)) +* **deps-dev:** bump mypy from 1.9.0 to 1.10.0 ([#4202](https://github.com/aws-powertools/powertools-lambda-python/issues/4202)) +* **deps-dev:** bump mypy-boto3-ssm from 1.34.61 to 1.34.91 in the boto-typing group ([#4201](https://github.com/aws-powertools/powertools-lambda-python/issues/4201)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.123 to 0.1.126 ([#4188](https://github.com/aws-powertools/powertools-lambda-python/issues/4188)) +* **deps-dev:** bump cfn-lint from 0.87.1 to 0.87.2 ([#4317](https://github.com/aws-powertools/powertools-lambda-python/issues/4317)) +* **deps-dev:** bump coverage from 7.4.4 to 7.5.0 ([#4186](https://github.com/aws-powertools/powertools-lambda-python/issues/4186)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.144 to 0.1.145 ([#4325](https://github.com/aws-powertools/powertools-lambda-python/issues/4325)) +* **deps-dev:** bump types-redis from 4.6.0.20240417 to 4.6.0.20240423 ([#4185](https://github.com/aws-powertools/powertools-lambda-python/issues/4185)) +* **deps-dev:** bump mkdocs-material from 9.5.21 to 9.5.22 ([#4324](https://github.com/aws-powertools/powertools-lambda-python/issues/4324)) +* **deps-dev:** bump mypy-boto3-s3 from 1.34.91 to 1.34.105 in the boto-typing group ([#4329](https://github.com/aws-powertools/powertools-lambda-python/issues/4329)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.145 to 0.1.146 ([#4330](https://github.com/aws-powertools/powertools-lambda-python/issues/4330)) +* **deps-dev:** bump cfn-lint from 0.86.3 to 0.86.4 ([#4180](https://github.com/aws-powertools/powertools-lambda-python/issues/4180)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.121 to 0.1.123 ([#4176](https://github.com/aws-powertools/powertools-lambda-python/issues/4176)) +* **deps-dev:** bump mkdocs-material from 9.5.22 to 9.5.23 ([#4338](https://github.com/aws-powertools/powertools-lambda-python/issues/4338)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.137.0a0 to 2.138.0a0 ([#4169](https://github.com/aws-powertools/powertools-lambda-python/issues/4169)) +* **deps-dev:** bump aws-cdk from 2.141.0 to 2.142.0 ([#4343](https://github.com/aws-powertools/powertools-lambda-python/issues/4343)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.119 to 0.1.121 ([#4167](https://github.com/aws-powertools/powertools-lambda-python/issues/4167)) +* **deps-dev:** bump ruff from 0.3.7 to 0.4.1 ([#4166](https://github.com/aws-powertools/powertools-lambda-python/issues/4166)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.34.72 to 1.34.107 in the boto-typing group ([#4345](https://github.com/aws-powertools/powertools-lambda-python/issues/4345)) +* **deps-dev:** bump aws-cdk from 2.137.0 to 2.138.0 ([#4157](https://github.com/aws-powertools/powertools-lambda-python/issues/4157)) +* **deps-dev:** bump aws-cdk-lib from 2.137.0 to 2.138.0 ([#4160](https://github.com/aws-powertools/powertools-lambda-python/issues/4160)) +* **deps-dev:** bump sentry-sdk from 2.1.1 to 2.2.0 ([#4348](https://github.com/aws-powertools/powertools-lambda-python/issues/4348)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.146 to 0.1.150 ([#4346](https://github.com/aws-powertools/powertools-lambda-python/issues/4346)) +* **deps-dev:** bump coverage from 7.5.0 to 7.5.1 ([#4288](https://github.com/aws-powertools/powertools-lambda-python/issues/4288)) +* **governance:** add FastAPI third party license attribution ([#4297](https://github.com/aws-powertools/powertools-lambda-python/issues/4297)) + + +<a name="v2.37.0"></a> +## [v2.37.0] - 2024-04-18 +## Bug Fixes + +* **docs:** clarified usage of validation with fine grained responses ([#4101](https://github.com/aws-powertools/powertools-lambda-python/issues/4101)) +* **event_source:** fix typo in physicalname attribute for AmazonMQ events ([#4053](https://github.com/aws-powertools/powertools-lambda-python/issues/4053)) +* **typing:** make the case_sensitive field a boolean only ([#4128](https://github.com/aws-powertools/powertools-lambda-python/issues/4128)) +* **typing:** improve overloads to ensure the return type follows the default_value type ([#4114](https://github.com/aws-powertools/powertools-lambda-python/issues/4114)) + +## Documentation + +* **we-made-this:** new article on how to stream data with AWS Lambda & Powertools for AWS Lambda ([#4068](https://github.com/aws-powertools/powertools-lambda-python/issues/4068)) + +## Features + +* **Idempotency:** add feature for manipulating idempotent responses ([#4037](https://github.com/aws-powertools/powertools-lambda-python/issues/4037)) +* **event_handler:** add support for OpenAPI security schemes ([#4103](https://github.com/aws-powertools/powertools-lambda-python/issues/4103)) +* **logger:** add method to return currently configured keys ([#4033](https://github.com/aws-powertools/powertools-lambda-python/issues/4033)) + +## Maintenance + +* version bump +* **ci:** add monthly roadmap reminder workflow ([#4075](https://github.com/aws-powertools/powertools-lambda-python/issues/4075)) +* **ci:** prevent deprecated custom runner from being used ([#4061](https://github.com/aws-powertools/powertools-lambda-python/issues/4061)) +* **deps:** bump squidfunk/mkdocs-material from `065f3af` to `6b124e1` in /docs ([#4055](https://github.com/aws-powertools/powertools-lambda-python/issues/4055)) +* **deps:** bump squidfunk/mkdocs-material from `3307665` to `065f3af` in /docs ([#4052](https://github.com/aws-powertools/powertools-lambda-python/issues/4052)) +* **deps:** bump idna from 3.6 to 3.7 ([#4121](https://github.com/aws-powertools/powertools-lambda-python/issues/4121)) +* **deps:** bump sqlparse from 0.4.4 to 0.5.0 ([#4138](https://github.com/aws-powertools/powertools-lambda-python/issues/4138)) +* **deps:** bump squidfunk/mkdocs-material from `6b124e1` to `521644b` in /docs ([#4141](https://github.com/aws-powertools/powertools-lambda-python/issues/4141)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#4066](https://github.com/aws-powertools/powertools-lambda-python/issues/4066)) +* **deps:** bump pydantic from 1.10.14 to 1.10.15 ([#4064](https://github.com/aws-powertools/powertools-lambda-python/issues/4064)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#4042](https://github.com/aws-powertools/powertools-lambda-python/issues/4042)) +* **deps:** bump golang.org/x/sync from 0.6.0 to 0.7.0 in /layer/scripts/layer-balancer in the layer-balancer group ([#4071](https://github.com/aws-powertools/powertools-lambda-python/issues/4071)) +* **deps:** bump codecov/codecov-action from 4.1.1 to 4.2.0 ([#4072](https://github.com/aws-powertools/powertools-lambda-python/issues/4072)) +* **deps:** bump datadog-lambda from 5.91.0 to 5.92.0 ([#4038](https://github.com/aws-powertools/powertools-lambda-python/issues/4038)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.27.10 to 1.27.11 in /layer/scripts/layer-balancer in the layer-balancer group ([#4079](https://github.com/aws-powertools/powertools-lambda-python/issues/4079)) +* **deps:** bump typing-extensions from 4.10.0 to 4.11.0 ([#4080](https://github.com/aws-powertools/powertools-lambda-python/issues/4080)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.3 to 3.0.4 ([#4099](https://github.com/aws-powertools/powertools-lambda-python/issues/4099)) +* **deps:** bump codecov/codecov-action from 4.2.0 to 4.3.0 ([#4098](https://github.com/aws-powertools/powertools-lambda-python/issues/4098)) +* **deps:** bump docker/setup-buildx-action from 3.2.0 to 3.3.0 ([#4091](https://github.com/aws-powertools/powertools-lambda-python/issues/4091)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.112 to 0.1.113 ([#4136](https://github.com/aws-powertools/powertools-lambda-python/issues/4136)) +* **deps-dev:** bump aws-cdk from 2.135.0 to 2.136.0 ([#4090](https://github.com/aws-powertools/powertools-lambda-python/issues/4090)) +* **deps-dev:** bump types-redis from 4.6.0.20240311 to 4.6.0.20240409 ([#4094](https://github.com/aws-powertools/powertools-lambda-python/issues/4094)) +* **deps-dev:** bump aws-cdk-lib from 2.135.0 to 2.136.0 ([#4092](https://github.com/aws-powertools/powertools-lambda-python/issues/4092)) +* **deps-dev:** bump cfn-lint from 0.86.1 to 0.86.2 ([#4081](https://github.com/aws-powertools/powertools-lambda-python/issues/4081)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.135.0a0 to 2.136.0a0 ([#4095](https://github.com/aws-powertools/powertools-lambda-python/issues/4095)) +* **deps-dev:** bump filelock from 3.13.3 to 3.13.4 ([#4096](https://github.com/aws-powertools/powertools-lambda-python/issues/4096)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.106 to 0.1.107 ([#4082](https://github.com/aws-powertools/powertools-lambda-python/issues/4082)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.107 to 0.1.110 ([#4097](https://github.com/aws-powertools/powertools-lambda-python/issues/4097)) +* **deps-dev:** bump aws-cdk from 2.136.0 to 2.136.1 ([#4106](https://github.com/aws-powertools/powertools-lambda-python/issues/4106)) +* **deps-dev:** bump aws-cdk-lib from 2.136.0 to 2.136.1 ([#4107](https://github.com/aws-powertools/powertools-lambda-python/issues/4107)) +* **deps-dev:** bump sentry-sdk from 1.44.1 to 1.45.0 ([#4108](https://github.com/aws-powertools/powertools-lambda-python/issues/4108)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.110 to 0.1.112 ([#4109](https://github.com/aws-powertools/powertools-lambda-python/issues/4109)) +* **deps-dev:** bump sentry-sdk from 1.44.0 to 1.44.1 ([#4065](https://github.com/aws-powertools/powertools-lambda-python/issues/4065)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.134.0a0 to 2.135.0a0 ([#4063](https://github.com/aws-powertools/powertools-lambda-python/issues/4063)) +* **deps-dev:** bump aws-cdk from 2.136.1 to 2.137.0 ([#4115](https://github.com/aws-powertools/powertools-lambda-python/issues/4115)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#4062](https://github.com/aws-powertools/powertools-lambda-python/issues/4062)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.34.75 to 1.34.83 in the boto-typing group ([#4116](https://github.com/aws-powertools/powertools-lambda-python/issues/4116)) +* **deps-dev:** bump ruff from 0.3.5 to 0.3.7 ([#4123](https://github.com/aws-powertools/powertools-lambda-python/issues/4123)) +* **deps-dev:** bump aws-cdk-lib from 2.136.1 to 2.137.0 ([#4119](https://github.com/aws-powertools/powertools-lambda-python/issues/4119)) +* **deps-dev:** bump aws-cdk from 2.134.0 to 2.135.0 ([#4058](https://github.com/aws-powertools/powertools-lambda-python/issues/4058)) +* **deps-dev:** bump aws-cdk-lib from 2.134.0 to 2.135.0 ([#4057](https://github.com/aws-powertools/powertools-lambda-python/issues/4057)) +* **deps-dev:** bump mkdocs-material from 9.5.16 to 9.5.17 ([#4056](https://github.com/aws-powertools/powertools-lambda-python/issues/4056)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.136.0a0 to 2.137.0a0 ([#4124](https://github.com/aws-powertools/powertools-lambda-python/issues/4124)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.34.77 to 1.34.84 in the boto-typing group ([#4126](https://github.com/aws-powertools/powertools-lambda-python/issues/4126)) +* **deps-dev:** bump ruff from 0.3.4 to 0.3.5 ([#4049](https://github.com/aws-powertools/powertools-lambda-python/issues/4049)) +* **deps-dev:** bump mkdocs-material from 9.5.15 to 9.5.16 ([#4050](https://github.com/aws-powertools/powertools-lambda-python/issues/4050)) +* **deps-dev:** bump the boto-typing group with 1 update ([#4047](https://github.com/aws-powertools/powertools-lambda-python/issues/4047)) +* **deps-dev:** bump black from 24.3.0 to 24.4.0 ([#4135](https://github.com/aws-powertools/powertools-lambda-python/issues/4135)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.105 to 0.1.106 ([#4048](https://github.com/aws-powertools/powertools-lambda-python/issues/4048)) +* **deps-dev:** bump cfn-lint from 0.86.2 to 0.86.3 ([#4137](https://github.com/aws-powertools/powertools-lambda-python/issues/4137)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.113 to 0.1.115 ([#4142](https://github.com/aws-powertools/powertools-lambda-python/issues/4142)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.133.0a0 to 2.134.0a0 ([#4039](https://github.com/aws-powertools/powertools-lambda-python/issues/4039)) +* **deps-dev:** bump mkdocs-material from 9.5.17 to 9.5.18 ([#4143](https://github.com/aws-powertools/powertools-lambda-python/issues/4143)) +* **deps-dev:** bump types-redis from 4.6.0.20240409 to 4.6.0.20240417 ([#4145](https://github.com/aws-powertools/powertools-lambda-python/issues/4145)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.115 to 0.1.119 ([#4150](https://github.com/aws-powertools/powertools-lambda-python/issues/4150)) +* **deps-dev:** bump aws-cdk-lib from 2.133.0 to 2.134.0 ([#4031](https://github.com/aws-powertools/powertools-lambda-python/issues/4031)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.104 to 0.1.105 ([#4030](https://github.com/aws-powertools/powertools-lambda-python/issues/4030)) +* **deps-dev:** bump aws-cdk from 2.133.0 to 2.134.0 ([#4032](https://github.com/aws-powertools/powertools-lambda-python/issues/4032)) +* **deps-dev:** bump the boto-typing group with 1 update ([#4029](https://github.com/aws-powertools/powertools-lambda-python/issues/4029)) +* **deps-dev:** bump sentry-sdk from 1.43.0 to 1.44.0 ([#4040](https://github.com/aws-powertools/powertools-lambda-python/issues/4040)) +* **docs:** update highlighted lines on the Typing examples ([#4131](https://github.com/aws-powertools/powertools-lambda-python/issues/4131)) + + +<a name="v2.36.0"></a> +## [v2.36.0] - 2024-03-27 +## Bug Fixes + +* **event_handler:** always add 422 response to the schema ([#3995](https://github.com/aws-powertools/powertools-lambda-python/issues/3995)) +* **event_handler:** make decoded_body field optional in ApiGateway resolver ([#3937](https://github.com/aws-powertools/powertools-lambda-python/issues/3937)) +* **tracer:** add name sanitization for X-Ray subsegments ([#4005](https://github.com/aws-powertools/powertools-lambda-python/issues/4005)) + +## Code Refactoring + +* **logger:** add type annotation for append_keys method ([#3988](https://github.com/aws-powertools/powertools-lambda-python/issues/3988)) +* **parameters:** improve typing for get_secret method ([#3910](https://github.com/aws-powertools/powertools-lambda-python/issues/3910)) + +## Documentation + +* **batch:** improved the example demonstrating how to create a custom partial processor. ([#4024](https://github.com/aws-powertools/powertools-lambda-python/issues/4024)) +* **bedrock-agents:** fix type in Bedrock operation example ([#3948](https://github.com/aws-powertools/powertools-lambda-python/issues/3948)) +* **tutorial:** fix "Simplifying with Tracer" section in the tutorial ([#3962](https://github.com/aws-powertools/powertools-lambda-python/issues/3962)) + +## Features + +* **batch:** add flag in SqsFifoProcessor to enable continuous message processing ([#3954](https://github.com/aws-powertools/powertools-lambda-python/issues/3954)) +* **data_classes:** Add CloudWatchAlarmEvent data class ([#3868](https://github.com/aws-powertools/powertools-lambda-python/issues/3868)) +* **event-handler:** add compress option when serving Swagger HTML ([#3946](https://github.com/aws-powertools/powertools-lambda-python/issues/3946)) +* **event_handler:** define exception_handler directly from the router ([#3979](https://github.com/aws-powertools/powertools-lambda-python/issues/3979)) +* **metrics:** allow custom timestamps for metrics ([#4006](https://github.com/aws-powertools/powertools-lambda-python/issues/4006)) +* **parameters:** add feature for creating and updating Parameters and Secrets ([#2858](https://github.com/aws-powertools/powertools-lambda-python/issues/2858)) +* **tracer:** auto-disable tracer when for AWS SAM and Chalice environments ([#3949](https://github.com/aws-powertools/powertools-lambda-python/issues/3949)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `3678304` to `6c81a89` in /docs ([#3973](https://github.com/aws-powertools/powertools-lambda-python/issues/3973)) +* **deps:** bump datadog-lambda from 5.89.0 to 5.90.0 ([#3941](https://github.com/aws-powertools/powertools-lambda-python/issues/3941)) +* **deps:** bump actions/checkout from 4.1.1 to 4.1.2 ([#3939](https://github.com/aws-powertools/powertools-lambda-python/issues/3939)) +* **deps:** bump redis from 5.0.2 to 5.0.3 ([#3929](https://github.com/aws-powertools/powertools-lambda-python/issues/3929)) +* **deps:** bump slsa-framework/slsa-github-generator from 1.9.0 to 1.10.0 ([#3997](https://github.com/aws-powertools/powertools-lambda-python/issues/3997)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#4001](https://github.com/aws-powertools/powertools-lambda-python/issues/4001)) +* **deps:** bump actions/dependency-review-action from 4.2.3 to 4.2.4 ([#4012](https://github.com/aws-powertools/powertools-lambda-python/issues/4012)) +* **deps:** bump docker/setup-buildx-action from 3.1.0 to 3.2.0 ([#3955](https://github.com/aws-powertools/powertools-lambda-python/issues/3955)) +* **deps:** bump actions/dependency-review-action from 4.1.3 to 4.2.3 ([#3993](https://github.com/aws-powertools/powertools-lambda-python/issues/3993)) +* **deps:** bump datadog-lambda from 5.90.0 to 5.91.0 ([#3958](https://github.com/aws-powertools/powertools-lambda-python/issues/3958)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 ([#3918](https://github.com/aws-powertools/powertools-lambda-python/issues/3918)) +* **deps:** bump squidfunk/mkdocs-material from `6c81a89` to `3307665` in /docs ([#4017](https://github.com/aws-powertools/powertools-lambda-python/issues/4017)) +* **deps:** bump actions/dependency-review-action from 4.2.4 to 4.2.5 ([#4023](https://github.com/aws-powertools/powertools-lambda-python/issues/4023)) +* **deps:** bump aws-encryption-sdk from 3.1.1 to 3.2.0 ([#3983](https://github.com/aws-powertools/powertools-lambda-python/issues/3983)) +* **deps:** bump actions/setup-python from 5.0.0 to 5.1.0 ([#4022](https://github.com/aws-powertools/powertools-lambda-python/issues/4022)) +* **deps:** bump codecov/codecov-action from 4.1.0 to 4.1.1 ([#4021](https://github.com/aws-powertools/powertools-lambda-python/issues/4021)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3972](https://github.com/aws-powertools/powertools-lambda-python/issues/3972)) +* **deps-dev:** bump filelock from 3.13.1 to 3.13.3 ([#4014](https://github.com/aws-powertools/powertools-lambda-python/issues/4014)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.90 to 0.1.91 ([#3975](https://github.com/aws-powertools/powertools-lambda-python/issues/3975)) +* **deps-dev:** bump types-python-dateutil from 2.9.0.20240315 to 2.9.0.20240316 ([#3977](https://github.com/aws-powertools/powertools-lambda-python/issues/3977)) +* **deps-dev:** bump mkdocs-material from 9.5.13 to 9.5.14 ([#3978](https://github.com/aws-powertools/powertools-lambda-python/issues/3978)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.132.1a0 to 2.133.0a0 ([#3976](https://github.com/aws-powertools/powertools-lambda-python/issues/3976)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3974](https://github.com/aws-powertools/powertools-lambda-python/issues/3974)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3982](https://github.com/aws-powertools/powertools-lambda-python/issues/3982)) +* **deps-dev:** bump ruff from 0.3.2 to 0.3.3 ([#3967](https://github.com/aws-powertools/powertools-lambda-python/issues/3967)) +* **deps-dev:** bump aws-cdk-lib from 2.132.1 to 2.133.0 ([#3965](https://github.com/aws-powertools/powertools-lambda-python/issues/3965)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.91 to 0.1.94 ([#3985](https://github.com/aws-powertools/powertools-lambda-python/issues/3985)) +* **deps-dev:** bump black from 24.2.0 to 24.3.0 ([#3968](https://github.com/aws-powertools/powertools-lambda-python/issues/3968)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.20240311 to 2.9.0.20240315 ([#3966](https://github.com/aws-powertools/powertools-lambda-python/issues/3966)) +* **deps-dev:** bump aws-cdk from 2.132.1 to 2.133.0 ([#3963](https://github.com/aws-powertools/powertools-lambda-python/issues/3963)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3964](https://github.com/aws-powertools/powertools-lambda-python/issues/3964)) +* **deps-dev:** bump pytest-asyncio from 0.23.5.post1 to 0.23.6 ([#3984](https://github.com/aws-powertools/powertools-lambda-python/issues/3984)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3991](https://github.com/aws-powertools/powertools-lambda-python/issues/3991)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.89 to 0.1.90 ([#3957](https://github.com/aws-powertools/powertools-lambda-python/issues/3957)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3956](https://github.com/aws-powertools/powertools-lambda-python/issues/3956)) +* **deps-dev:** bump sentry-sdk from 1.42.0 to 1.43.0 ([#3992](https://github.com/aws-powertools/powertools-lambda-python/issues/3992)) +* **deps-dev:** bump coverage from 7.4.3 to 7.4.4 ([#3959](https://github.com/aws-powertools/powertools-lambda-python/issues/3959)) +* **deps-dev:** bump ruff from 0.3.3 to 0.3.4 ([#3996](https://github.com/aws-powertools/powertools-lambda-python/issues/3996)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.88 to 0.1.89 ([#3952](https://github.com/aws-powertools/powertools-lambda-python/issues/3952)) +* **deps-dev:** bump sentry-sdk from 1.41.0 to 1.42.0 ([#3951](https://github.com/aws-powertools/powertools-lambda-python/issues/3951)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3950](https://github.com/aws-powertools/powertools-lambda-python/issues/3950)) +* **deps-dev:** bump pytest-mock from 3.12.0 to 3.13.0 ([#3999](https://github.com/aws-powertools/powertools-lambda-python/issues/3999)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.94 to 0.1.96 ([#4002](https://github.com/aws-powertools/powertools-lambda-python/issues/4002)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3940](https://github.com/aws-powertools/powertools-lambda-python/issues/3940)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.87 to 0.1.88 ([#3942](https://github.com/aws-powertools/powertools-lambda-python/issues/3942)) +* **deps-dev:** bump pytest from 8.0.2 to 8.1.1 ([#3943](https://github.com/aws-powertools/powertools-lambda-python/issues/3943)) +* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.131.0a0 to 2.132.1a0 ([#3944](https://github.com/aws-powertools/powertools-lambda-python/issues/3944)) +* **deps-dev:** bump cfn-lint from 0.86.0 to 0.86.1 ([#3998](https://github.com/aws-powertools/powertools-lambda-python/issues/3998)) +* **deps-dev:** bump aws-cdk from 2.132.0 to 2.132.1 ([#3938](https://github.com/aws-powertools/powertools-lambda-python/issues/3938)) +* **deps-dev:** bump aws-cdk-lib from 2.131.0 to 2.132.1 ([#3936](https://github.com/aws-powertools/powertools-lambda-python/issues/3936)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.96 to 0.1.99 ([#4008](https://github.com/aws-powertools/powertools-lambda-python/issues/4008)) +* **deps-dev:** bump aws-cdk from 2.131.0 to 2.132.0 ([#3928](https://github.com/aws-powertools/powertools-lambda-python/issues/3928)) +* **deps-dev:** bump types-redis from 4.6.0.20240218 to 4.6.0.20240311 ([#3931](https://github.com/aws-powertools/powertools-lambda-python/issues/3931)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.20240106 to 2.8.19.20240311 ([#3932](https://github.com/aws-powertools/powertools-lambda-python/issues/3932)) +* **deps-dev:** bump pytest-mock from 3.13.0 to 3.14.0 ([#4007](https://github.com/aws-powertools/powertools-lambda-python/issues/4007)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.99 to 0.1.101 ([#4015](https://github.com/aws-powertools/powertools-lambda-python/issues/4015)) +* **deps-dev:** bump ruff from 0.3.0 to 0.3.2 ([#3925](https://github.com/aws-powertools/powertools-lambda-python/issues/3925)) +* **deps-dev:** bump mypy from 1.8.0 to 1.9.0 ([#3921](https://github.com/aws-powertools/powertools-lambda-python/issues/3921)) +* **deps-dev:** bump bandit from 1.7.7 to 1.7.8 ([#3920](https://github.com/aws-powertools/powertools-lambda-python/issues/3920)) +* **deps-dev:** bump pytest-cov from 4.1.0 to 5.0.0 ([#4013](https://github.com/aws-powertools/powertools-lambda-python/issues/4013)) +* **deps-dev:** bump pytest-asyncio from 0.23.5 to 0.23.5.post1 ([#3923](https://github.com/aws-powertools/powertools-lambda-python/issues/3923)) +* **deps-dev:** bump mkdocs-material from 9.5.14 to 9.5.15 ([#4016](https://github.com/aws-powertools/powertools-lambda-python/issues/4016)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3919](https://github.com/aws-powertools/powertools-lambda-python/issues/3919)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.101 to 0.1.104 ([#4020](https://github.com/aws-powertools/powertools-lambda-python/issues/4020)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.83 to 0.1.87 ([#3930](https://github.com/aws-powertools/powertools-lambda-python/issues/3930)) + + +<a name="v2.35.1"></a> +## [v2.35.1] - 2024-03-08 +## Bug Fixes + +* **data_sources:** ensure correct types on SQSMessageAttributes ([#3898](https://github.com/aws-powertools/powertools-lambda-python/issues/3898)) +* **event_handler:** validate POST bodies on BedrockAgentResolver ([#3903](https://github.com/aws-powertools/powertools-lambda-python/issues/3903)) +* **internal:** call ruff with correct args ([#3901](https://github.com/aws-powertools/powertools-lambda-python/issues/3901)) + +## Features + +* **event_handler:** use custom serializer during openapi serialization ([#3900](https://github.com/aws-powertools/powertools-lambda-python/issues/3900)) + +## Maintenance + +* version bump +* **deps:** bump aws-xray-sdk from 2.12.1 to 2.13.0 ([#3906](https://github.com/aws-powertools/powertools-lambda-python/issues/3906)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3911](https://github.com/aws-powertools/powertools-lambda-python/issues/3911)) +* **deps:** bump squidfunk/mkdocs-material from `7be068b` to `3678304` in /docs ([#3894](https://github.com/aws-powertools/powertools-lambda-python/issues/3894)) +* **deps:** bump datadog-lambda from 5.88.0 to 5.89.0 ([#3907](https://github.com/aws-powertools/powertools-lambda-python/issues/3907)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.81 to 0.1.82 ([#3896](https://github.com/aws-powertools/powertools-lambda-python/issues/3896)) +* **deps-dev:** bump sentry-sdk from 1.40.6 to 1.41.0 ([#3905](https://github.com/aws-powertools/powertools-lambda-python/issues/3905)) +* **deps-dev:** bump mkdocs-material from 9.5.12 to 9.5.13 ([#3895](https://github.com/aws-powertools/powertools-lambda-python/issues/3895)) +* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.82 to 0.1.83 ([#3908](https://github.com/aws-powertools/powertools-lambda-python/issues/3908)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3904](https://github.com/aws-powertools/powertools-lambda-python/issues/3904)) + + +<a name="v2.35.0"></a> +## [v2.35.0] - 2024-03-06 +## Bug Fixes + +* **event_handler:** OpenAPI schema version respects Pydantic version ([#3860](https://github.com/aws-powertools/powertools-lambda-python/issues/3860)) + +## Code Refactoring + +* **logger:** improve typing ([#3869](https://github.com/aws-powertools/powertools-lambda-python/issues/3869)) + +## Documentation + +* **event_handler:** add bedrock agent resolver documentation ([#3602](https://github.com/aws-powertools/powertools-lambda-python/issues/3602)) + +## Maintenance + +* version bump +* **deps:** bump docker/setup-buildx-action from 3.0.0 to 3.1.0 ([#3864](https://github.com/aws-powertools/powertools-lambda-python/issues/3864)) +* **deps:** bump actions/download-artifact from 4.1.3 to 4.1.4 ([#3875](https://github.com/aws-powertools/powertools-lambda-python/issues/3875)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3884](https://github.com/aws-powertools/powertools-lambda-python/issues/3884)) +* **deps:** bump squidfunk/mkdocs-material from `49d1bfd` to `7be068b` in /docs ([#3872](https://github.com/aws-powertools/powertools-lambda-python/issues/3872)) +* **deps:** bump squidfunk/mkdocs-material from `43b898a` to `49d1bfd` in /docs ([#3857](https://github.com/aws-powertools/powertools-lambda-python/issues/3857)) +* **deps:** bump codecov/codecov-action from 4.0.2 to 4.1.0 ([#3856](https://github.com/aws-powertools/powertools-lambda-python/issues/3856)) +* **deps:** bump redis from 5.0.1 to 5.0.2 ([#3867](https://github.com/aws-powertools/powertools-lambda-python/issues/3867)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3887](https://github.com/aws-powertools/powertools-lambda-python/issues/3887)) +* **deps:** bump actions/download-artifact from 4.1.2 to 4.1.3 ([#3862](https://github.com/aws-powertools/powertools-lambda-python/issues/3862)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.12 ([#3863](https://github.com/aws-powertools/powertools-lambda-python/issues/3863)) +* **deps-dev:** bump aws-cdk-lib from 2.130.0 to 2.131.0 ([#3881](https://github.com/aws-powertools/powertools-lambda-python/issues/3881)) +* **deps-dev:** bump cfn-lint from 0.85.3 to 0.86.0 ([#3882](https://github.com/aws-powertools/powertools-lambda-python/issues/3882)) +* **deps-dev:** bump black from 24.1.1 to 24.2.0 ([#3760](https://github.com/aws-powertools/powertools-lambda-python/issues/3760)) +* **deps-dev:** bump cfn-lint from 0.85.2 to 0.85.3 ([#3861](https://github.com/aws-powertools/powertools-lambda-python/issues/3861)) +* **deps-dev:** bump aws-cdk from 2.130.0 to 2.131.0 ([#3883](https://github.com/aws-powertools/powertools-lambda-python/issues/3883)) +* **deps-dev:** bump mkdocs-material from 9.5.11 to 9.5.12 ([#3870](https://github.com/aws-powertools/powertools-lambda-python/issues/3870)) +* **deps-dev:** bump ruff from 0.2.2 to 0.3.0 ([#3871](https://github.com/aws-powertools/powertools-lambda-python/issues/3871)) +* **docs:** add Bedrock Agents to feature list ([#3889](https://github.com/aws-powertools/powertools-lambda-python/issues/3889)) + + +<a name="v2.34.2"></a> +## [v2.34.2] - 2024-02-26 +## Bug Fixes + +* **typing:** ensure return type is a str when default_value is set ([#3840](https://github.com/aws-powertools/powertools-lambda-python/issues/3840)) + +## Documentation + +* **install:** make minimum install the default option then extra ([#3834](https://github.com/aws-powertools/powertools-lambda-python/issues/3834)) + +## Features + +* **event-source:** add function to get multi-value query string params by name ([#3846](https://github.com/aws-powertools/powertools-lambda-python/issues/3846)) + +## Maintenance + +* version bump +* **ci:** remove aws-encryption-sdk from Lambda layer due to cffi being tied to python version ([#3853](https://github.com/aws-powertools/powertools-lambda-python/issues/3853)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3844](https://github.com/aws-powertools/powertools-lambda-python/issues/3844)) +* **deps:** bump cryptography from 42.0.2 to 42.0.4 ([#3827](https://github.com/aws-powertools/powertools-lambda-python/issues/3827)) +* **deps:** bump codecov/codecov-action from 4.0.1 to 4.0.2 ([#3842](https://github.com/aws-powertools/powertools-lambda-python/issues/3842)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3835](https://github.com/aws-powertools/powertools-lambda-python/issues/3835)) +* **deps-dev:** bump httpx from 0.26.0 to 0.27.0 ([#3828](https://github.com/aws-powertools/powertools-lambda-python/issues/3828)) +* **deps-dev:** bump aws-cdk from 2.128.0 to 2.129.0 ([#3831](https://github.com/aws-powertools/powertools-lambda-python/issues/3831)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3836](https://github.com/aws-powertools/powertools-lambda-python/issues/3836)) +* **deps-dev:** bump aws-cdk from 2.129.0 to 2.130.0 ([#3843](https://github.com/aws-powertools/powertools-lambda-python/issues/3843)) +* **deps-dev:** bump aws-cdk-lib from 2.128.0 to 2.130.0 ([#3838](https://github.com/aws-powertools/powertools-lambda-python/issues/3838)) + + +<a name="v2.34.1"></a> +## [v2.34.1] - 2024-02-21 +## Bug Fixes + +* **ci:** inject PR_LABELS env for PR Label automation ([#3819](https://github.com/aws-powertools/powertools-lambda-python/issues/3819)) +* **ci:** revert layer version bump write-only back to append ([#3818](https://github.com/aws-powertools/powertools-lambda-python/issues/3818)) +* **event-handler:** return dict on missing multi_value_headers ([#3824](https://github.com/aws-powertools/powertools-lambda-python/issues/3824)) +* **idempotency:** validate before saving to cache ([#3822](https://github.com/aws-powertools/powertools-lambda-python/issues/3822)) + +## Maintenance + +* version bump +* **deps-dev:** bump ruff from 0.2.1 to 0.2.2 ([#3802](https://github.com/aws-powertools/powertools-lambda-python/issues/3802)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3810](https://github.com/aws-powertools/powertools-lambda-python/issues/3810)) + + +<a name="v2.34.0"></a> +## [v2.34.0] - 2024-02-21 +## Bug Fixes + +* **ci:** create one layer artifact per region & merge ([#3808](https://github.com/aws-powertools/powertools-lambda-python/issues/3808)) +* **event-handler:** multi-value query string and validation of scalar parameters ([#3795](https://github.com/aws-powertools/powertools-lambda-python/issues/3795)) +* **event-handler:** swagger schema respects api stage ([#3796](https://github.com/aws-powertools/powertools-lambda-python/issues/3796)) +* **event-handler:** handle aliased parameters e.g., Query(alias="categoryType") ([#3766](https://github.com/aws-powertools/powertools-lambda-python/issues/3766)) + +## Code Refactoring + +* **feature-flags:** add intersection tests; structure refinement ([#3775](https://github.com/aws-powertools/powertools-lambda-python/issues/3775)) + +## Documentation + +* **feature_flags:** fix incorrect line markers and envelope name ([#3792](https://github.com/aws-powertools/powertools-lambda-python/issues/3792)) +* **home:** update layer version to 62 for package version 2.33.1 ([#3778](https://github.com/aws-powertools/powertools-lambda-python/issues/3778)) +* **home:** add note about POWERTOOLS_DEV side effects in CloudWatch Logs ([#3770](https://github.com/aws-powertools/powertools-lambda-python/issues/3770)) +* **homepage:** discord flat badge style; remove former devax email ([#3768](https://github.com/aws-powertools/powertools-lambda-python/issues/3768)) +* **homepage:** remove leftover announcement banner ([#3783](https://github.com/aws-powertools/powertools-lambda-python/issues/3783)) +* **roadmap:** latest roadmap update; use new grid to de-clutter homepage ([#3755](https://github.com/aws-powertools/powertools-lambda-python/issues/3755)) +* **we-made-this:** add swagger post ([#3799](https://github.com/aws-powertools/powertools-lambda-python/issues/3799)) +* **we-made-this:** add reinvent 2023 session ([#3790](https://github.com/aws-powertools/powertools-lambda-python/issues/3790)) + +## Features + +* **feature_flags:** add intersect actions for conditions ([#3692](https://github.com/aws-powertools/powertools-lambda-python/issues/3692)) + +## Maintenance + +* version bump +* **deps:** bump actions/dependency-review-action from 4.1.2 to 4.1.3 ([#3813](https://github.com/aws-powertools/powertools-lambda-python/issues/3813)) +* **deps:** bump actions/dependency-review-action from 4.1.0 to 4.1.2 ([#3800](https://github.com/aws-powertools/powertools-lambda-python/issues/3800)) +* **deps:** bump actions/dependency-review-action from 4.0.0 to 4.1.0 ([#3771](https://github.com/aws-powertools/powertools-lambda-python/issues/3771)) +* **deps:** bump squidfunk/mkdocs-material from `62d3668` to `43b898a` in /docs ([#3801](https://github.com/aws-powertools/powertools-lambda-python/issues/3801)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3764](https://github.com/aws-powertools/powertools-lambda-python/issues/3764)) +* **deps:** bump squidfunk/mkdocs-material from `6a72238` to `62d3668` in /docs ([#3756](https://github.com/aws-powertools/powertools-lambda-python/issues/3756)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3814](https://github.com/aws-powertools/powertools-lambda-python/issues/3814)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3784](https://github.com/aws-powertools/powertools-lambda-python/issues/3784)) +* **deps-dev:** bump pytest from 8.0.0 to 8.0.1 ([#3812](https://github.com/aws-powertools/powertools-lambda-python/issues/3812)) +* **deps-dev:** bump aws-cdk from 2.127.0 to 2.128.0 ([#3776](https://github.com/aws-powertools/powertools-lambda-python/issues/3776)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3797](https://github.com/aws-powertools/powertools-lambda-python/issues/3797)) +* **deps-dev:** bump cfn-lint from 0.85.1 to 0.85.2 ([#3786](https://github.com/aws-powertools/powertools-lambda-python/issues/3786)) +* **deps-dev:** bump pytest-asyncio from 0.21.1 to 0.23.5 ([#3773](https://github.com/aws-powertools/powertools-lambda-python/issues/3773)) +* **deps-dev:** bump aws-cdk-lib from 2.127.0 to 2.128.0 ([#3777](https://github.com/aws-powertools/powertools-lambda-python/issues/3777)) +* **deps-dev:** bump sentry-sdk from 1.40.3 to 1.40.4 ([#3765](https://github.com/aws-powertools/powertools-lambda-python/issues/3765)) +* **deps-dev:** bump sentry-sdk from 1.40.4 to 1.40.5 ([#3805](https://github.com/aws-powertools/powertools-lambda-python/issues/3805)) +* **deps-dev:** bump mkdocs-material from 9.5.9 to 9.5.10 ([#3803](https://github.com/aws-powertools/powertools-lambda-python/issues/3803)) +* **deps-dev:** bump types-redis from 4.6.0.20240106 to 4.6.0.20240218 ([#3804](https://github.com/aws-powertools/powertools-lambda-python/issues/3804)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3757](https://github.com/aws-powertools/powertools-lambda-python/issues/3757)) +* **deps-dev:** bump aws-cdk-lib from 2.126.0 to 2.127.0 ([#3758](https://github.com/aws-powertools/powertools-lambda-python/issues/3758)) +* **deps-dev:** bump aws-cdk from 2.126.0 to 2.127.0 ([#3761](https://github.com/aws-powertools/powertools-lambda-python/issues/3761)) +* **deps-dev:** bump mkdocs-material from 9.5.8 to 9.5.9 ([#3759](https://github.com/aws-powertools/powertools-lambda-python/issues/3759)) +* **deps-dev:** bump sentry-sdk from 1.40.2 to 1.40.3 ([#3750](https://github.com/aws-powertools/powertools-lambda-python/issues/3750)) +* **deps-dev:** bump cfn-lint from 0.85.0 to 0.85.1 ([#3749](https://github.com/aws-powertools/powertools-lambda-python/issues/3749)) +* **deps-dev:** bump coverage from 7.4.1 to 7.4.2 ([#3811](https://github.com/aws-powertools/powertools-lambda-python/issues/3811)) +* **tests:** increase idempotency coverage with nested payload tampering tests ([#3809](https://github.com/aws-powertools/powertools-lambda-python/issues/3809)) + + +<a name="v2.33.1"></a> +## [v2.33.1] - 2024-02-09 +## Bug Fixes + +* **typing:** make Response headers covariant ([#3745](https://github.com/aws-powertools/powertools-lambda-python/issues/3745)) + +## Documentation + +* Add nathan hanks post community ([#3727](https://github.com/aws-powertools/powertools-lambda-python/issues/3727)) + +## Maintenance + +* version bump +* **ci:** drop support for Python 3.7 ([#3638](https://github.com/aws-powertools/powertools-lambda-python/issues/3638)) +* **ci:** enable Redis e2e tests ([#3718](https://github.com/aws-powertools/powertools-lambda-python/issues/3718)) +* **deps:** bump actions/setup-node from 4.0.1 to 4.0.2 ([#3737](https://github.com/aws-powertools/powertools-lambda-python/issues/3737)) +* **deps:** bump squidfunk/mkdocs-material from `e0d6c67` to `6a72238` in /docs ([#3735](https://github.com/aws-powertools/powertools-lambda-python/issues/3735)) +* **deps:** bump actions/dependency-review-action from 3.1.5 to 4.0.0 ([#3646](https://github.com/aws-powertools/powertools-lambda-python/issues/3646)) +* **deps:** bump release-drafter/release-drafter from 5.25.0 to 6.0.0 ([#3699](https://github.com/aws-powertools/powertools-lambda-python/issues/3699)) +* **deps:** bump actions/download-artifact from 4.1.1 to 4.1.2 ([#3725](https://github.com/aws-powertools/powertools-lambda-python/issues/3725)) +* **deps:** bump squidfunk/mkdocs-material from `a4a2029` to `e0d6c67` in /docs ([#3708](https://github.com/aws-powertools/powertools-lambda-python/issues/3708)) +* **deps:** bump codecov/codecov-action from 3.1.6 to 4.0.1 ([#3700](https://github.com/aws-powertools/powertools-lambda-python/issues/3700)) +* **deps:** bump actions/download-artifact from 3.0.2 to 4.1.1 ([#3612](https://github.com/aws-powertools/powertools-lambda-python/issues/3612)) +* **deps:** revert aws-cdk-lib as a runtime dep ([#3730](https://github.com/aws-powertools/powertools-lambda-python/issues/3730)) +* **deps:** bump actions/upload-artifact from 3.1.3 to 4.3.1 ([#3714](https://github.com/aws-powertools/powertools-lambda-python/issues/3714)) +* **deps-dev:** bump cfn-lint from 0.83.8 to 0.85.0 ([#3724](https://github.com/aws-powertools/powertools-lambda-python/issues/3724)) +* **deps-dev:** bump httpx from 0.24.1 to 0.26.0 ([#3712](https://github.com/aws-powertools/powertools-lambda-python/issues/3712)) +* **deps-dev:** bump pytest from 7.4.4 to 8.0.0 ([#3711](https://github.com/aws-powertools/powertools-lambda-python/issues/3711)) +* **deps-dev:** bump sentry-sdk from 1.40.1 to 1.40.2 ([#3740](https://github.com/aws-powertools/powertools-lambda-python/issues/3740)) +* **deps-dev:** bump coverage from 7.2.7 to 7.4.1 ([#3713](https://github.com/aws-powertools/powertools-lambda-python/issues/3713)) +* **deps-dev:** bump the boto-typing group with 7 updates ([#3709](https://github.com/aws-powertools/powertools-lambda-python/issues/3709)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.14 to 2.8.19.20240106 ([#3720](https://github.com/aws-powertools/powertools-lambda-python/issues/3720)) +* **deps-dev:** bump mypy from 1.4.1 to 1.8.0 ([#3710](https://github.com/aws-powertools/powertools-lambda-python/issues/3710)) +* **deps-dev:** bump ruff from 0.2.0 to 0.2.1 ([#3742](https://github.com/aws-powertools/powertools-lambda-python/issues/3742)) +* **deps-dev:** bump isort from 5.11.5 to 5.13.2 ([#3723](https://github.com/aws-powertools/powertools-lambda-python/issues/3723)) +* **deps-dev:** bump pytest-socket from 0.6.0 to 0.7.0 ([#3721](https://github.com/aws-powertools/powertools-lambda-python/issues/3721)) +* **deps-dev:** bump ruff from 0.1.15 to 0.2.0 ([#3702](https://github.com/aws-powertools/powertools-lambda-python/issues/3702)) +* **deps-dev:** bump aws-cdk from 2.125.0 to 2.126.0 ([#3701](https://github.com/aws-powertools/powertools-lambda-python/issues/3701)) +* **deps-dev:** bump hvac from 1.2.1 to 2.1.0 ([#3738](https://github.com/aws-powertools/powertools-lambda-python/issues/3738)) +* **deps-dev:** bump black from 23.12.1 to 24.1.1 ([#3739](https://github.com/aws-powertools/powertools-lambda-python/issues/3739)) + + +<a name="v2.33.0"></a> +## [v2.33.0] - 2024-02-02 +## Bug Fixes + +* **data-masking:** fix and improve e2e tests for DataMasking ([#3695](https://github.com/aws-powertools/powertools-lambda-python/issues/3695)) +* **event-handler:** strip whitespace from Content-Type headers during OpenAPI schema validation ([#3677](https://github.com/aws-powertools/powertools-lambda-python/issues/3677)) + +## Documentation + +* **data-masking:** add docs for data masking utility ([#3186](https://github.com/aws-powertools/powertools-lambda-python/issues/3186)) +* **metrics:** fix empty metric warning filter ([#3660](https://github.com/aws-powertools/powertools-lambda-python/issues/3660)) +* **proccess:** add versioning and maintenance policy ([#3682](https://github.com/aws-powertools/powertools-lambda-python/issues/3682)) + +## Features + +* **event_handler:** support Header parameter validation in OpenAPI schema ([#3687](https://github.com/aws-powertools/powertools-lambda-python/issues/3687)) +* **event_handler:** add support for multiValueQueryStringParameters in OpenAPI schema ([#3667](https://github.com/aws-powertools/powertools-lambda-python/issues/3667)) + +## Maintenance + +* version bump +* **deps:** bump codecov/codecov-action from 3.1.5 to 3.1.6 ([#3683](https://github.com/aws-powertools/powertools-lambda-python/issues/3683)) +* **deps:** bump codecov/codecov-action from 3.1.4 to 3.1.5 ([#3674](https://github.com/aws-powertools/powertools-lambda-python/issues/3674)) +* **deps:** bump pydantic from 1.10.13 to 1.10.14 ([#3655](https://github.com/aws-powertools/powertools-lambda-python/issues/3655)) +* **deps:** bump squidfunk/mkdocs-material from `58eef6c` to `9aad7af` in /docs ([#3670](https://github.com/aws-powertools/powertools-lambda-python/issues/3670)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3665](https://github.com/aws-powertools/powertools-lambda-python/issues/3665)) +* **deps:** bump squidfunk/mkdocs-material from `9aad7af` to `a4a2029` in /docs ([#3679](https://github.com/aws-powertools/powertools-lambda-python/issues/3679)) +* **deps-dev:** bump sentry-sdk from 1.39.2 to 1.40.0 ([#3684](https://github.com/aws-powertools/powertools-lambda-python/issues/3684)) +* **deps-dev:** bump ruff from 0.1.14 to 0.1.15 ([#3685](https://github.com/aws-powertools/powertools-lambda-python/issues/3685)) +* **deps-dev:** bump ruff from 0.1.13 to 0.1.14 ([#3656](https://github.com/aws-powertools/powertools-lambda-python/issues/3656)) +* **deps-dev:** bump aws-cdk from 2.122.0 to 2.123.0 ([#3673](https://github.com/aws-powertools/powertools-lambda-python/issues/3673)) +* **deps-dev:** bump aws-cdk from 2.124.0 to 2.125.0 ([#3693](https://github.com/aws-powertools/powertools-lambda-python/issues/3693)) +* **deps-dev:** bump aws-cdk from 2.123.0 to 2.124.0 ([#3678](https://github.com/aws-powertools/powertools-lambda-python/issues/3678)) + + +<a name="v2.32.0"></a> +## [v2.32.0] - 2024-01-19 +## Bug Fixes + +* **event_handler:** escape OpenAPI schema on Swagger UI ([#3606](https://github.com/aws-powertools/powertools-lambda-python/issues/3606)) + +## Code Refactoring + +* **event-handler:** Inject CSS and JS files into SwaggerUI route when no custom CDN is used. ([#3562](https://github.com/aws-powertools/powertools-lambda-python/issues/3562)) +* **event_handler:** fix BedrockAgentResolver docstring ([#3645](https://github.com/aws-powertools/powertools-lambda-python/issues/3645)) + +## Documentation + +* **homepage:** add banner about Python 3.7 deprecation ([#3618](https://github.com/aws-powertools/powertools-lambda-python/issues/3618)) +* **i-made-this:** added new article on how to create a serverless API with CDK and Powertools ([#3605](https://github.com/aws-powertools/powertools-lambda-python/issues/3605)) + +## Features + +* **event_handler:** add support for additional response models ([#3591](https://github.com/aws-powertools/powertools-lambda-python/issues/3591)) +* **event_handler:** add support to download OpenAPI spec file ([#3571](https://github.com/aws-powertools/powertools-lambda-python/issues/3571)) +* **event_source:** Add support for S3 batch operations ([#3572](https://github.com/aws-powertools/powertools-lambda-python/issues/3572)) +* **event_source:** Add support for policyLevel field in CloudWatch Logs event and parser ([#3624](https://github.com/aws-powertools/powertools-lambda-python/issues/3624)) +* **idempotency:** leverage new DynamoDB Failed conditional writes behavior with ReturnValuesOnConditionCheckFailure ([#3446](https://github.com/aws-powertools/powertools-lambda-python/issues/3446)) +* **idempotency:** adding redis as idempotency backend ([#2567](https://github.com/aws-powertools/powertools-lambda-python/issues/2567)) + +## Maintenance + +* version bump +* **ci:** Disable Redis e2e until we drop Python 3.7 ([#3652](https://github.com/aws-powertools/powertools-lambda-python/issues/3652)) +* **ci:** update boto3 library version to 1.26.164+ ([#3632](https://github.com/aws-powertools/powertools-lambda-python/issues/3632)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3649](https://github.com/aws-powertools/powertools-lambda-python/issues/3649)) +* **deps:** bump jinja2 from 3.1.2 to 3.1.3 in /docs ([#3620](https://github.com/aws-powertools/powertools-lambda-python/issues/3620)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3639](https://github.com/aws-powertools/powertools-lambda-python/issues/3639)) +* **deps:** bump gitpython from 3.1.37 to 3.1.41 in /docs ([#3610](https://github.com/aws-powertools/powertools-lambda-python/issues/3610)) +* **deps:** bump squidfunk/mkdocs-material from `2f29d71` to `58eef6c` in /docs ([#3633](https://github.com/aws-powertools/powertools-lambda-python/issues/3633)) +* **deps:** bump redis from 4.6.0 to 5.0.1 ([#3613](https://github.com/aws-powertools/powertools-lambda-python/issues/3613)) +* **deps-dev:** bump gitpython from 3.1.40 to 3.1.41 ([#3611](https://github.com/aws-powertools/powertools-lambda-python/issues/3611)) +* **deps-dev:** bump sentry-sdk from 1.39.1 to 1.39.2 ([#3614](https://github.com/aws-powertools/powertools-lambda-python/issues/3614)) +* **deps-dev:** bump aws-cdk from 2.120.0 to 2.121.1 ([#3634](https://github.com/aws-powertools/powertools-lambda-python/issues/3634)) +* **deps-dev:** bump jinja2 from 3.1.2 to 3.1.3 ([#3619](https://github.com/aws-powertools/powertools-lambda-python/issues/3619)) +* **deps-dev:** bump cfn-lint from 0.83.7 to 0.83.8 ([#3603](https://github.com/aws-powertools/powertools-lambda-python/issues/3603)) +* **deps-dev:** bump aws-cdk from 2.121.1 to 2.122.0 ([#3648](https://github.com/aws-powertools/powertools-lambda-python/issues/3648)) +* **deps-dev:** bump ruff from 0.1.11 to 0.1.13 ([#3625](https://github.com/aws-powertools/powertools-lambda-python/issues/3625)) +* **deps-dev:** bump aws-cdk from 2.118.0 to 2.120.0 ([#3627](https://github.com/aws-powertools/powertools-lambda-python/issues/3627)) + + +<a name="v2.31.0"></a> +## [v2.31.0] - 2024-01-05 +## Bug Fixes + +* **ci:** fail dispatch analytics job when Lambda call fails ([#3579](https://github.com/aws-powertools/powertools-lambda-python/issues/3579)) + +## Code Refactoring + +* **parameters:** add overload signatures for get_parameter and get_parameters ([#3534](https://github.com/aws-powertools/powertools-lambda-python/issues/3534)) +* **parser:** Improve error message when parsing models and envelopes ([#3587](https://github.com/aws-powertools/powertools-lambda-python/issues/3587)) + +## Documentation + +* **middleware-factory:** Fix and improve typing ([#3569](https://github.com/aws-powertools/powertools-lambda-python/issues/3569)) + +## Features + +* **event-handler:** add description to request body in OpenAPI schema ([#3548](https://github.com/aws-powertools/powertools-lambda-python/issues/3548)) +* **event_handler:** support richer top level Tags ([#3543](https://github.com/aws-powertools/powertools-lambda-python/issues/3543)) +* **layers:** add new comercial region Canada - ca-west-1 ([#3549](https://github.com/aws-powertools/powertools-lambda-python/issues/3549)) + +## Maintenance + +* version bump +* **ci:** Remove dev dependencies locked to Pydantic v1 within the Pydantic v2 workflow. ([#3582](https://github.com/aws-powertools/powertools-lambda-python/issues/3582)) +* **deps:** bump squidfunk/mkdocs-material from `9af3b7e` to `2f29d71` in /docs ([#3559](https://github.com/aws-powertools/powertools-lambda-python/issues/3559)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 4 updates ([#3593](https://github.com/aws-powertools/powertools-lambda-python/issues/3593)) +* **deps:** bump actions/setup-node from 4.0.0 to 4.0.1 ([#3535](https://github.com/aws-powertools/powertools-lambda-python/issues/3535)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.2 to 3.0.3 ([#3536](https://github.com/aws-powertools/powertools-lambda-python/issues/3536)) +* **deps:** bump actions/dependency-review-action from 3.1.4 to 3.1.5 ([#3592](https://github.com/aws-powertools/powertools-lambda-python/issues/3592)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3544](https://github.com/aws-powertools/powertools-lambda-python/issues/3544)) +* **deps:** bump fastjsonschema from 2.19.0 to 2.19.1 ([#3567](https://github.com/aws-powertools/powertools-lambda-python/issues/3567)) +* **deps-dev:** bump ruff from 0.1.8 to 0.1.9 ([#3550](https://github.com/aws-powertools/powertools-lambda-python/issues/3550)) +* **deps-dev:** bump aws-cdk from 2.115.0 to 2.116.1 ([#3553](https://github.com/aws-powertools/powertools-lambda-python/issues/3553)) +* **deps-dev:** bump aws-cdk from 2.117.0 to 2.118.0 ([#3589](https://github.com/aws-powertools/powertools-lambda-python/issues/3589)) +* **deps-dev:** bump cfn-lint from 0.83.6 to 0.83.7 ([#3554](https://github.com/aws-powertools/powertools-lambda-python/issues/3554)) +* **deps-dev:** bump ruff from 0.1.9 to 0.1.10 ([#3583](https://github.com/aws-powertools/powertools-lambda-python/issues/3583)) +* **deps-dev:** bump pytest from 7.4.3 to 7.4.4 ([#3576](https://github.com/aws-powertools/powertools-lambda-python/issues/3576)) +* **deps-dev:** bump aws-cdk from 2.116.1 to 2.117.0 ([#3565](https://github.com/aws-powertools/powertools-lambda-python/issues/3565)) +* **deps-dev:** bump ruff from 0.1.10 to 0.1.11 ([#3588](https://github.com/aws-powertools/powertools-lambda-python/issues/3588)) + + +<a name="v2.30.2"></a> +## [v2.30.2] - 2023-12-18 +## Bug Fixes + +* **event-handler:** fix operation tags schema generation ([#3528](https://github.com/aws-powertools/powertools-lambda-python/issues/3528)) +* **event-handler:** set default OpenAPI version to 3.0.0 ([#3527](https://github.com/aws-powertools/powertools-lambda-python/issues/3527)) +* **event-handler:** upgrade Swagger UI to fix regressions ([#3526](https://github.com/aws-powertools/powertools-lambda-python/issues/3526)) + +## Maintenance + +* version bump +* **deps-dev:** bump cfn-lint from 0.83.5 to 0.83.6 ([#3521](https://github.com/aws-powertools/powertools-lambda-python/issues/3521)) + + +<a name="v2.30.1"></a> +## [v2.30.1] - 2023-12-15 +## Bug Fixes + +* **event_handler:** allow responses and metadata when using Router ([#3514](https://github.com/aws-powertools/powertools-lambda-python/issues/3514)) + +## Maintenance + +* version bump +* **deps-dev:** bump aws-cdk from 2.114.1 to 2.115.0 ([#3508](https://github.com/aws-powertools/powertools-lambda-python/issues/3508)) +* **deps-dev:** bump the boto-typing group with 11 updates ([#3509](https://github.com/aws-powertools/powertools-lambda-python/issues/3509)) +* **deps-dev:** bump sentry-sdk from 1.39.0 to 1.39.1 ([#3512](https://github.com/aws-powertools/powertools-lambda-python/issues/3512)) + + +<a name="v2.30.0"></a> +## [v2.30.0] - 2023-12-14 +## Bug Fixes + +* **docs:** make the Lambda Layer version consistent ([#3498](https://github.com/aws-powertools/powertools-lambda-python/issues/3498)) + +## Documentation + +* **customer-reference:** add Transformity as a customer reference ([#3497](https://github.com/aws-powertools/powertools-lambda-python/issues/3497)) + +## Features + +* **general:** add support for Python 3.12 ([#3304](https://github.com/aws-powertools/powertools-lambda-python/issues/3304)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `876b39c` to `9af3b7e` in /docs ([#3486](https://github.com/aws-powertools/powertools-lambda-python/issues/3486)) +* **deps-dev:** bump sentry-sdk from 1.38.0 to 1.39.0 ([#3495](https://github.com/aws-powertools/powertools-lambda-python/issues/3495)) +* **deps-dev:** bump cfn-lint from 0.83.4 to 0.83.5 ([#3487](https://github.com/aws-powertools/powertools-lambda-python/issues/3487)) +* **deps-dev:** bump ruff from 0.1.7 to 0.1.8 ([#3501](https://github.com/aws-powertools/powertools-lambda-python/issues/3501)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3500](https://github.com/aws-powertools/powertools-lambda-python/issues/3500)) +* **tests:** temporarily disable E2E parallelism ([#3484](https://github.com/aws-powertools/powertools-lambda-python/issues/3484)) + + +<a name="v2.29.1"></a> +## [v2.29.1] - 2023-12-11 +## Bug Fixes + +* **logger:** log non-ascii characters as is when JSON stringifying ([#3475](https://github.com/aws-powertools/powertools-lambda-python/issues/3475)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `8c72011` to `20241c6` in /docs ([#3470](https://github.com/aws-powertools/powertools-lambda-python/issues/3470)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3476](https://github.com/aws-powertools/powertools-lambda-python/issues/3476)) +* **deps:** bump actions/setup-python from 4.8.0 to 5.0.0 ([#3465](https://github.com/aws-powertools/powertools-lambda-python/issues/3465)) +* **deps:** bump squidfunk/mkdocs-material from `20241c6` to `876b39c` in /docs ([#3477](https://github.com/aws-powertools/powertools-lambda-python/issues/3477)) +* **deps:** bump datadog-lambda from 5.84.0 to 5.85.0 ([#3466](https://github.com/aws-powertools/powertools-lambda-python/issues/3466)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3467](https://github.com/aws-powertools/powertools-lambda-python/issues/3467)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3469](https://github.com/aws-powertools/powertools-lambda-python/issues/3469)) +* **deps-dev:** bump aws-cdk from 2.113.0 to 2.114.1 ([#3464](https://github.com/aws-powertools/powertools-lambda-python/issues/3464)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3478](https://github.com/aws-powertools/powertools-lambda-python/issues/3478)) + + +<a name="v2.29.0"></a> +## [v2.29.0] - 2023-12-06 +## Bug Fixes + +* **event_handler:** serialize pydantic/dataclasses in exception handler ([#3455](https://github.com/aws-powertools/powertools-lambda-python/issues/3455)) +* **metrics:** lambda_handler typing, and **kwargs preservation all middlewares ([#3460](https://github.com/aws-powertools/powertools-lambda-python/issues/3460)) + +## Features + +* **event_sources:** add get_context() to standardize API Gateway Lambda Authorizer context in v1 and v2 ([#3454](https://github.com/aws-powertools/powertools-lambda-python/issues/3454)) + +## Maintenance + +* version bump +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3441](https://github.com/aws-powertools/powertools-lambda-python/issues/3441)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.1 to 3.0.2 ([#3449](https://github.com/aws-powertools/powertools-lambda-python/issues/3449)) +* **deps:** bump datadog-lambda from 5.83.0 to 5.84.0 ([#3438](https://github.com/aws-powertools/powertools-lambda-python/issues/3438)) +* **deps:** bump cryptography from 41.0.4 to 41.0.6 ([#3431](https://github.com/aws-powertools/powertools-lambda-python/issues/3431)) +* **deps:** bump squidfunk/mkdocs-material from `fc42bac` to `8c72011` in /docs ([#3416](https://github.com/aws-powertools/powertools-lambda-python/issues/3416)) +* **deps:** bump actions/dependency-review-action from 3.1.3 to 3.1.4 ([#3426](https://github.com/aws-powertools/powertools-lambda-python/issues/3426)) +* **deps:** bump actions/setup-python from 4.7.1 to 4.8.0 ([#3456](https://github.com/aws-powertools/powertools-lambda-python/issues/3456)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 ([#3433](https://github.com/aws-powertools/powertools-lambda-python/issues/3433)) +* **deps-dev:** bump cfn-lint from 0.83.3 to 0.83.4 ([#3450](https://github.com/aws-powertools/powertools-lambda-python/issues/3450)) +* **deps-dev:** bump ruff from 0.1.6 to 0.1.7 ([#3458](https://github.com/aws-powertools/powertools-lambda-python/issues/3458)) +* **deps-dev:** bump sentry-sdk from 1.36.0 to 1.38.0 ([#3435](https://github.com/aws-powertools/powertools-lambda-python/issues/3435)) +* **deps-dev:** bump aws-cdk-lib from 2.110.1 to 2.111.0 ([#3428](https://github.com/aws-powertools/powertools-lambda-python/issues/3428)) +* **deps-dev:** bump aws-cdk from 2.112.0 to 2.113.0 ([#3448](https://github.com/aws-powertools/powertools-lambda-python/issues/3448)) +* **deps-dev:** bump aws-cdk from 2.110.1 to 2.111.0 ([#3418](https://github.com/aws-powertools/powertools-lambda-python/issues/3418)) +* **deps-dev:** bump the boto-typing group with 11 updates ([#3427](https://github.com/aws-powertools/powertools-lambda-python/issues/3427)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3457](https://github.com/aws-powertools/powertools-lambda-python/issues/3457)) +* **deps-dev:** bump aws-cdk from 2.111.0 to 2.112.0 ([#3440](https://github.com/aws-powertools/powertools-lambda-python/issues/3440)) +* **layers:** Update log retention to 10 years ([#3424](https://github.com/aws-powertools/powertools-lambda-python/issues/3424)) + + +<a name="v2.28.1"></a> +## [v2.28.1] - 2023-11-28 +## Bug Fixes + +* **event_handler:** fix compress handling ([#3420](https://github.com/aws-powertools/powertools-lambda-python/issues/3420)) + +## Maintenance + +* version bump + + +<a name="v2.28.0"></a> +## [v2.28.0] - 2023-11-23 +## Bug Fixes + +* **event_handler:** hide error details by default ([#3406](https://github.com/aws-powertools/powertools-lambda-python/issues/3406)) +* **event_handler:** fix format for OpenAPI path templating ([#3399](https://github.com/aws-powertools/powertools-lambda-python/issues/3399)) +* **event_handler:** lazy load Pydantic to improve cold start ([#3397](https://github.com/aws-powertools/powertools-lambda-python/issues/3397)) +* **event_handler:** allow fine grained Response with data validation ([#3394](https://github.com/aws-powertools/powertools-lambda-python/issues/3394)) +* **event_handler:** apply serialization as the last operation for middlewares ([#3392](https://github.com/aws-powertools/powertools-lambda-python/issues/3392)) + +## Documentation + +* **event_handlers:** new data validation and OpenAPI feature ([#3386](https://github.com/aws-powertools/powertools-lambda-python/issues/3386)) + +## Features + +* **event_handler:** allow customers to catch request validation errors ([#3396](https://github.com/aws-powertools/powertools-lambda-python/issues/3396)) + +## Maintenance + +* version bump +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3389](https://github.com/aws-powertools/powertools-lambda-python/issues/3389)) +* **deps:** bump datadog-lambda from 4.82.0 to 5.83.0 ([#3401](https://github.com/aws-powertools/powertools-lambda-python/issues/3401)) +* **deps-dev:** bump aws-cdk-lib from 2.110.0 to 2.110.1 ([#3402](https://github.com/aws-powertools/powertools-lambda-python/issues/3402)) +* **deps-dev:** bump pytest-xdist from 3.4.0 to 3.5.0 ([#3387](https://github.com/aws-powertools/powertools-lambda-python/issues/3387)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3400](https://github.com/aws-powertools/powertools-lambda-python/issues/3400)) +* **deps-dev:** bump sentry-sdk from 1.35.0 to 1.36.0 ([#3388](https://github.com/aws-powertools/powertools-lambda-python/issues/3388)) +* **deps-dev:** bump aws-cdk from 2.110.0 to 2.110.1 ([#3403](https://github.com/aws-powertools/powertools-lambda-python/issues/3403)) + + +<a name="v2.27.1"></a> +## [v2.27.1] - 2023-11-21 +## Bug Fixes + +* **logger:** allow custom JMESPath functions to extract correlation ID ([#3382](https://github.com/aws-powertools/powertools-lambda-python/issues/3382)) + +## Documentation + +* **event_handlers:** note that CORS and */* binary mime type don't work in API Gateway ([#3383](https://github.com/aws-powertools/powertools-lambda-python/issues/3383)) +* **logger:** improve ALC messaging in the PT context ([#3359](https://github.com/aws-powertools/powertools-lambda-python/issues/3359)) +* **logger:** Fix ALC link ([#3352](https://github.com/aws-powertools/powertools-lambda-python/issues/3352)) + +## Features + +* **logger:** implement addFilter/removeFilter to address static typing errors ([#3380](https://github.com/aws-powertools/powertools-lambda-python/issues/3380)) + +## Maintenance + +* version bump +* **ci:** lint and type checking removal in Pydantic v2 quality check ([#3360](https://github.com/aws-powertools/powertools-lambda-python/issues/3360)) +* **deps:** bump actions/github-script from 7.0.0 to 7.0.1 ([#3377](https://github.com/aws-powertools/powertools-lambda-python/issues/3377)) +* **deps:** bump squidfunk/mkdocs-material from `2c57e4d` to `fc42bac` in /docs ([#3375](https://github.com/aws-powertools/powertools-lambda-python/issues/3375)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3353](https://github.com/aws-powertools/powertools-lambda-python/issues/3353)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3374](https://github.com/aws-powertools/powertools-lambda-python/issues/3374)) +* **deps:** bump squidfunk/mkdocs-material from `f486dc9` to `2c57e4d` in /docs ([#3366](https://github.com/aws-powertools/powertools-lambda-python/issues/3366)) +* **deps-dev:** bump cfn-lint from 0.83.2 to 0.83.3 ([#3363](https://github.com/aws-powertools/powertools-lambda-python/issues/3363)) +* **deps-dev:** bump the boto-typing group with 11 updates ([#3362](https://github.com/aws-powertools/powertools-lambda-python/issues/3362)) +* **deps-dev:** bump aws-cdk-lib from 2.108.1 to 2.110.0 ([#3365](https://github.com/aws-powertools/powertools-lambda-python/issues/3365)) +* **deps-dev:** bump aws-cdk from 2.108.1 to 2.109.0 ([#3354](https://github.com/aws-powertools/powertools-lambda-python/issues/3354)) +* **deps-dev:** bump aws-cdk from 2.109.0 to 2.110.0 ([#3361](https://github.com/aws-powertools/powertools-lambda-python/issues/3361)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3376](https://github.com/aws-powertools/powertools-lambda-python/issues/3376)) +* **deps-dev:** bump ruff from 0.1.5 to 0.1.6 ([#3364](https://github.com/aws-powertools/powertools-lambda-python/issues/3364)) + + +<a name="v2.27.0"></a> +## [v2.27.0] - 2023-11-16 +## Features + +* **logger:** Adding support to new env variables ([#3348](https://github.com/aws-powertools/powertools-lambda-python/issues/3348)) + +## Maintenance + +* version bump +* **deps:** bump actions/github-script from 6.4.1 to 7.0.0 ([#3330](https://github.com/aws-powertools/powertools-lambda-python/issues/3330)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3340](https://github.com/aws-powertools/powertools-lambda-python/issues/3340)) +* **deps:** bump fastjsonschema from 2.18.1 to 2.19.0 ([#3337](https://github.com/aws-powertools/powertools-lambda-python/issues/3337)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3345](https://github.com/aws-powertools/powertools-lambda-python/issues/3345)) +* **deps:** bump actions/dependency-review-action from 3.1.2 to 3.1.3 ([#3331](https://github.com/aws-powertools/powertools-lambda-python/issues/3331)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3329](https://github.com/aws-powertools/powertools-lambda-python/issues/3329)) +* **deps:** bump datadog-lambda from 4.81.0 to 4.82.0 ([#3338](https://github.com/aws-powertools/powertools-lambda-python/issues/3338)) +* **deps-dev:** bump cfn-lint from 0.83.1 to 0.83.2 ([#3335](https://github.com/aws-powertools/powertools-lambda-python/issues/3335)) +* **deps-dev:** bump aws-cdk from 2.108.0 to 2.108.1 ([#3344](https://github.com/aws-powertools/powertools-lambda-python/issues/3344)) +* **deps-dev:** bump sentry-sdk from 1.34.0 to 1.35.0 ([#3334](https://github.com/aws-powertools/powertools-lambda-python/issues/3334)) +* **deps-dev:** bump pytest-xdist from 3.3.1 to 3.4.0 ([#3332](https://github.com/aws-powertools/powertools-lambda-python/issues/3332)) +* **deps-dev:** bump aws-cdk-lib from 2.107.0 to 2.108.1 ([#3343](https://github.com/aws-powertools/powertools-lambda-python/issues/3343)) +* **deps-dev:** bump aws-cdk from 2.106.0 to 2.106.1 ([#3328](https://github.com/aws-powertools/powertools-lambda-python/issues/3328)) +* **deps-dev:** bump aws-cdk-lib from 2.105.0 to 2.106.0 ([#3319](https://github.com/aws-powertools/powertools-lambda-python/issues/3319)) +* **deps-dev:** bump aws-cdk from 2.105.0 to 2.106.0 ([#3320](https://github.com/aws-powertools/powertools-lambda-python/issues/3320)) +* **deps-dev:** bump aws-cdk from 2.106.1 to 2.108.0 ([#3341](https://github.com/aws-powertools/powertools-lambda-python/issues/3341)) +* **deps-dev:** bump aws-cdk-lib from 2.106.0 to 2.107.0 ([#3333](https://github.com/aws-powertools/powertools-lambda-python/issues/3333)) + + +<a name="v2.26.1"></a> +## [v2.26.1] - 2023-11-10 +## Bug Fixes + +* **event-handler:** enable path parameters on Bedrock handler ([#3312](https://github.com/aws-powertools/powertools-lambda-python/issues/3312)) +* **event_handler:** Router prefix mismatch regression after Middleware feat ([#3302](https://github.com/aws-powertools/powertools-lambda-python/issues/3302)) +* **event_source:** kinesis subsequenceNumber str type to int ([#3275](https://github.com/aws-powertools/powertools-lambda-python/issues/3275)) +* **parameters:** Respect POWERTOOLS_PARAMETERS_SSM_DECRYPT environment variable when getting multiple ssm parameters. ([#3241](https://github.com/aws-powertools/powertools-lambda-python/issues/3241)) + +## Documentation + +* **customer-reference:** add Vertex Pharmaceuticals as a customer reference ([#3210](https://github.com/aws-powertools/powertools-lambda-python/issues/3210)) +* **event-handler:** fixed SchemaValidationMiddleware link ([#3247](https://github.com/aws-powertools/powertools-lambda-python/issues/3247)) + +## Features + +* **data_classes:** add support for Bedrock Agents event ([#3262](https://github.com/aws-powertools/powertools-lambda-python/issues/3262)) +* **event_handler:** add Bedrock Agent event handler ([#3285](https://github.com/aws-powertools/powertools-lambda-python/issues/3285)) +* **event_handler:** add ability to expose a Swagger UI ([#3254](https://github.com/aws-powertools/powertools-lambda-python/issues/3254)) +* **event_handler:** generate OpenAPI specifications and validate input/output ([#3109](https://github.com/aws-powertools/powertools-lambda-python/issues/3109)) +* **parser:** add BedrockEventModel parser and envelope ([#3286](https://github.com/aws-powertools/powertools-lambda-python/issues/3286)) + +## Maintenance + +* version bump +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.5 to 3.0.0 ([#3289](https://github.com/aws-powertools/powertools-lambda-python/issues/3289)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3287](https://github.com/aws-powertools/powertools-lambda-python/issues/3287)) +* **deps:** bump actions/checkout from 4.1.0 to 4.1.1 ([#3220](https://github.com/aws-powertools/powertools-lambda-python/issues/3220)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3282](https://github.com/aws-powertools/powertools-lambda-python/issues/3282)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.4 to 2.1.5 ([#3281](https://github.com/aws-powertools/powertools-lambda-python/issues/3281)) +* **deps:** bump release-drafter/release-drafter from 5.24.0 to 5.25.0 ([#3219](https://github.com/aws-powertools/powertools-lambda-python/issues/3219)) +* **deps:** bump squidfunk/mkdocs-material from `cb38dc2` to `df9409b` in /docs ([#3216](https://github.com/aws-powertools/powertools-lambda-python/issues/3216)) +* **deps:** bump urllib3 from 1.26.17 to 1.26.18 ([#3222](https://github.com/aws-powertools/powertools-lambda-python/issues/3222)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3298](https://github.com/aws-powertools/powertools-lambda-python/issues/3298)) +* **deps:** bump squidfunk/mkdocs-material from `772e14e` to `f486dc9` in /docs ([#3299](https://github.com/aws-powertools/powertools-lambda-python/issues/3299)) +* **deps:** bump datadog-lambda from 4.80.0 to 4.81.0 ([#3228](https://github.com/aws-powertools/powertools-lambda-python/issues/3228)) +* **deps:** bump actions/setup-node from 3.8.1 to 4.0.0 ([#3244](https://github.com/aws-powertools/powertools-lambda-python/issues/3244)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.0 to 3.0.1 ([#3300](https://github.com/aws-powertools/powertools-lambda-python/issues/3300)) +* **deps:** bump actions/dependency-review-action from 3.1.0 to 3.1.1 ([#3301](https://github.com/aws-powertools/powertools-lambda-python/issues/3301)) +* **deps:** bump squidfunk/mkdocs-material from `df9409b` to `772e14e` in /docs ([#3265](https://github.com/aws-powertools/powertools-lambda-python/issues/3265)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3305](https://github.com/aws-powertools/powertools-lambda-python/issues/3305)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3248](https://github.com/aws-powertools/powertools-lambda-python/issues/3248)) +* **deps:** bump actions/dependency-review-action from 3.1.1 to 3.1.2 ([#3308](https://github.com/aws-powertools/powertools-lambda-python/issues/3308)) +* **deps:** bump ossf/scorecard-action from 2.3.0 to 2.3.1 ([#3245](https://github.com/aws-powertools/powertools-lambda-python/issues/3245)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3310](https://github.com/aws-powertools/powertools-lambda-python/issues/3310)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3215](https://github.com/aws-powertools/powertools-lambda-python/issues/3215)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3313](https://github.com/aws-powertools/powertools-lambda-python/issues/3313)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3278](https://github.com/aws-powertools/powertools-lambda-python/issues/3278)) +* **deps-dev:** bump pytest from 7.4.2 to 7.4.3 ([#3249](https://github.com/aws-powertools/powertools-lambda-python/issues/3249)) +* **deps-dev:** bump ruff from 0.1.1 to 0.1.2 ([#3250](https://github.com/aws-powertools/powertools-lambda-python/issues/3250)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3242](https://github.com/aws-powertools/powertools-lambda-python/issues/3242)) +* **deps-dev:** bump aws-cdk-lib from 2.102.0 to 2.103.0 ([#3258](https://github.com/aws-powertools/powertools-lambda-python/issues/3258)) +* **deps-dev:** bump cfn-lint from 0.82.2 to 0.83.0 ([#3243](https://github.com/aws-powertools/powertools-lambda-python/issues/3243)) +* **deps-dev:** bump ruff from 0.1.2 to 0.1.3 ([#3257](https://github.com/aws-powertools/powertools-lambda-python/issues/3257)) +* **deps-dev:** bump aws-cdk from 2.102.0 to 2.103.0 ([#3259](https://github.com/aws-powertools/powertools-lambda-python/issues/3259)) +* **deps-dev:** bump ruff from 0.1.0 to 0.1.1 ([#3235](https://github.com/aws-powertools/powertools-lambda-python/issues/3235)) +* **deps-dev:** bump aws-cdk-lib from 2.103.0 to 2.103.1 ([#3263](https://github.com/aws-powertools/powertools-lambda-python/issues/3263)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3231](https://github.com/aws-powertools/powertools-lambda-python/issues/3231)) +* **deps-dev:** bump aws-cdk from 2.101.1 to 2.102.0 ([#3232](https://github.com/aws-powertools/powertools-lambda-python/issues/3232)) +* **deps-dev:** bump aws-cdk from 2.103.0 to 2.103.1 ([#3264](https://github.com/aws-powertools/powertools-lambda-python/issues/3264)) +* **deps-dev:** bump cfn-lint from 0.82.0 to 0.82.2 ([#3229](https://github.com/aws-powertools/powertools-lambda-python/issues/3229)) +* **deps-dev:** bump cfn-lint from 0.83.0 to 0.83.1 ([#3274](https://github.com/aws-powertools/powertools-lambda-python/issues/3274)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3273](https://github.com/aws-powertools/powertools-lambda-python/issues/3273)) +* **deps-dev:** bump cfn-lint from 0.81.0 to 0.82.0 ([#3224](https://github.com/aws-powertools/powertools-lambda-python/issues/3224)) +* **deps-dev:** bump aws-cdk from 2.101.0 to 2.101.1 ([#3223](https://github.com/aws-powertools/powertools-lambda-python/issues/3223)) +* **deps-dev:** bump sentry-sdk from 1.32.0 to 1.33.1 ([#3277](https://github.com/aws-powertools/powertools-lambda-python/issues/3277)) +* **deps-dev:** bump urllib3 from 1.26.17 to 1.26.18 in /layer ([#3221](https://github.com/aws-powertools/powertools-lambda-python/issues/3221)) +* **deps-dev:** bump aws-cdk from 2.103.1 to 2.104.0 ([#3288](https://github.com/aws-powertools/powertools-lambda-python/issues/3288)) +* **deps-dev:** bump sentry-sdk from 1.33.1 to 1.34.0 ([#3290](https://github.com/aws-powertools/powertools-lambda-python/issues/3290)) +* **deps-dev:** bump aws-cdk-lib from 2.103.1 to 2.104.0 ([#3291](https://github.com/aws-powertools/powertools-lambda-python/issues/3291)) +* **deps-dev:** bump aws-cdk-lib from 2.100.0 to 2.101.1 ([#3217](https://github.com/aws-powertools/powertools-lambda-python/issues/3217)) +* **deps-dev:** bump aws-cdk from 2.100.0 to 2.101.0 ([#3214](https://github.com/aws-powertools/powertools-lambda-python/issues/3214)) +* **deps-dev:** bump aws-cdk from 2.104.0 to 2.105.0 ([#3307](https://github.com/aws-powertools/powertools-lambda-python/issues/3307)) +* **deps-dev:** bump ruff from 0.1.3 to 0.1.4 ([#3297](https://github.com/aws-powertools/powertools-lambda-python/issues/3297)) +* **deps-dev:** bump aws-cdk-lib from 2.104.0 to 2.105.0 ([#3309](https://github.com/aws-powertools/powertools-lambda-python/issues/3309)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3211](https://github.com/aws-powertools/powertools-lambda-python/issues/3211)) +* **deps-dev:** bump the boto-typing group with 3 updates ([#3314](https://github.com/aws-powertools/powertools-lambda-python/issues/3314)) +* **deps-dev:** bump ruff from 0.1.4 to 0.1.5 ([#3315](https://github.com/aws-powertools/powertools-lambda-python/issues/3315)) +* **deps-dev:** bump ruff from 0.0.292 to 0.1.0 ([#3213](https://github.com/aws-powertools/powertools-lambda-python/issues/3213)) + + +<a name="v2.26.0"></a> +## [v2.26.0] - 2023-10-13 +## Bug Fixes + +* **logger:** force Logger to use local timezone when UTC flag is not set ([#3168](https://github.com/aws-powertools/powertools-lambda-python/issues/3168)) +* **parameter:** improve AppConfig cached configuration retrieval ([#3195](https://github.com/aws-powertools/powertools-lambda-python/issues/3195)) + +## Code Refactoring + +* **data-masking:** disable e2e tests. ([#3204](https://github.com/aws-powertools/powertools-lambda-python/issues/3204)) +* **data_masking:** move Data Masking utility to a private folder ([#3202](https://github.com/aws-powertools/powertools-lambda-python/issues/3202)) + +## Documentation + +* **contributing:** initial structure for revamped contributing guide ([#3133](https://github.com/aws-powertools/powertools-lambda-python/issues/3133)) +* **event_handler:** add information about case-insensitive header lookup function ([#3183](https://github.com/aws-powertools/powertools-lambda-python/issues/3183)) + +## Features + +* **data_masking:** add new sensitive data masking utility ([#2197](https://github.com/aws-powertools/powertools-lambda-python/issues/2197)) +* **event_handler:** add support to VPC Lattice payload v2 ([#3153](https://github.com/aws-powertools/powertools-lambda-python/issues/3153)) +* **layers:** add arm64 support in more regions ([#3151](https://github.com/aws-powertools/powertools-lambda-python/issues/3151)) +* **logger:** new stack_trace field with rich exception details ([#3147](https://github.com/aws-powertools/powertools-lambda-python/issues/3147)) +* **parser:** infer model from type hint ([#3181](https://github.com/aws-powertools/powertools-lambda-python/issues/3181)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `cbfecae` to `a4cfa88` in /docs ([#3175](https://github.com/aws-powertools/powertools-lambda-python/issues/3175)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3174](https://github.com/aws-powertools/powertools-lambda-python/issues/3174)) +* **deps:** bump squidfunk/mkdocs-material from `b41ba6d` to `06673a1` in /docs ([#3124](https://github.com/aws-powertools/powertools-lambda-python/issues/3124)) +* **deps:** bump ossf/scorecard-action from 2.2.0 to 2.3.0 ([#3178](https://github.com/aws-powertools/powertools-lambda-python/issues/3178)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3198](https://github.com/aws-powertools/powertools-lambda-python/issues/3198)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#3177](https://github.com/aws-powertools/powertools-lambda-python/issues/3177)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3127](https://github.com/aws-powertools/powertools-lambda-python/issues/3127)) +* **deps:** bump urllib3 from 1.26.16 to 1.26.17 ([#3162](https://github.com/aws-powertools/powertools-lambda-python/issues/3162)) +* **deps:** bump aws-xray-sdk from 2.12.0 to 2.12.1 ([#3197](https://github.com/aws-powertools/powertools-lambda-python/issues/3197)) +* **deps:** bump fastjsonschema from 2.18.0 to 2.18.1 ([#3159](https://github.com/aws-powertools/powertools-lambda-python/issues/3159)) +* **deps:** bump actions/setup-python from 4.7.0 to 4.7.1 ([#3158](https://github.com/aws-powertools/powertools-lambda-python/issues/3158)) +* **deps:** bump actions/checkout from 4.0.0 to 4.1.0 ([#3128](https://github.com/aws-powertools/powertools-lambda-python/issues/3128)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3156](https://github.com/aws-powertools/powertools-lambda-python/issues/3156)) +* **deps:** bump squidfunk/mkdocs-material from `e5f28aa` to `cbfecae` in /docs ([#3157](https://github.com/aws-powertools/powertools-lambda-python/issues/3157)) +* **deps:** bump squidfunk/mkdocs-material from `06673a1` to `e5f28aa` in /docs ([#3134](https://github.com/aws-powertools/powertools-lambda-python/issues/3134)) +* **deps:** bump squidfunk/mkdocs-material from `a4cfa88` to `cb38dc2` in /docs ([#3189](https://github.com/aws-powertools/powertools-lambda-python/issues/3189)) +* **deps:** bump pydantic from 1.10.12 to 1.10.13 ([#3144](https://github.com/aws-powertools/powertools-lambda-python/issues/3144)) +* **deps:** bump gitpython from 3.1.35 to 3.1.37 in /docs ([#3188](https://github.com/aws-powertools/powertools-lambda-python/issues/3188)) +* **deps-dev:** bump types-requests from 2.31.0.5 to 2.31.0.6 ([#3145](https://github.com/aws-powertools/powertools-lambda-python/issues/3145)) +* **deps-dev:** bump aws-cdk from 2.98.0 to 2.99.0 ([#3148](https://github.com/aws-powertools/powertools-lambda-python/issues/3148)) +* **deps-dev:** bump the boto-typing group with 2 updates ([#3143](https://github.com/aws-powertools/powertools-lambda-python/issues/3143)) +* **deps-dev:** bump aws-cdk from 2.99.1 to 2.100.0 ([#3185](https://github.com/aws-powertools/powertools-lambda-python/issues/3185)) +* **deps-dev:** bump aws-cdk from 2.97.0 to 2.98.0 ([#3139](https://github.com/aws-powertools/powertools-lambda-python/issues/3139)) +* **deps-dev:** bump aws-cdk from 2.96.2 to 2.97.0 ([#3129](https://github.com/aws-powertools/powertools-lambda-python/issues/3129)) +* **deps-dev:** bump types-requests from 2.31.0.3 to 2.31.0.5 ([#3136](https://github.com/aws-powertools/powertools-lambda-python/issues/3136)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3135](https://github.com/aws-powertools/powertools-lambda-python/issues/3135)) +* **deps-dev:** bump ruff from 0.0.291 to 0.0.292 ([#3161](https://github.com/aws-powertools/powertools-lambda-python/issues/3161)) +* **deps-dev:** bump ruff from 0.0.290 to 0.0.291 ([#3126](https://github.com/aws-powertools/powertools-lambda-python/issues/3126)) +* **deps-dev:** bump aws-cdk from 2.99.0 to 2.99.1 ([#3155](https://github.com/aws-powertools/powertools-lambda-python/issues/3155)) +* **deps-dev:** bump sentry-sdk from 1.31.0 to 1.32.0 ([#3192](https://github.com/aws-powertools/powertools-lambda-python/issues/3192)) +* **deps-dev:** bump urllib3 from 1.26.16 to 1.26.17 in /layer ([#3163](https://github.com/aws-powertools/powertools-lambda-python/issues/3163)) +* **deps-dev:** bump cfn-lint from 0.80.3 to 0.80.4 ([#3166](https://github.com/aws-powertools/powertools-lambda-python/issues/3166)) +* **deps-dev:** bump cfn-lint from 0.80.2 to 0.80.3 ([#3125](https://github.com/aws-powertools/powertools-lambda-python/issues/3125)) +* **deps-dev:** bump cfn-lint from 0.80.4 to 0.81.0 ([#3179](https://github.com/aws-powertools/powertools-lambda-python/issues/3179)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3196](https://github.com/aws-powertools/powertools-lambda-python/issues/3196)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3170](https://github.com/aws-powertools/powertools-lambda-python/issues/3170)) + + +<a name="v2.25.1"></a> +## [v2.25.1] - 2023-09-22 +## Bug Fixes + +* **logger:** add explicit None return type annotations ([#3113](https://github.com/aws-powertools/powertools-lambda-python/issues/3113)) +* **metrics:** support additional arguments in functions wrapped with log_metrics decorator ([#3120](https://github.com/aws-powertools/powertools-lambda-python/issues/3120)) + +## Maintenance + +* version bump +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3108](https://github.com/aws-powertools/powertools-lambda-python/issues/3108)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3115](https://github.com/aws-powertools/powertools-lambda-python/issues/3115)) +* **deps:** bump squidfunk/mkdocs-material from `4ff781e` to `b41ba6d` in /docs ([#3117](https://github.com/aws-powertools/powertools-lambda-python/issues/3117)) +* **deps:** bump squidfunk/mkdocs-material from `c4890ab` to `4ff781e` in /docs ([#3110](https://github.com/aws-powertools/powertools-lambda-python/issues/3110)) +* **deps-dev:** bump ruff from 0.0.289 to 0.0.290 ([#3105](https://github.com/aws-powertools/powertools-lambda-python/issues/3105)) +* **deps-dev:** bump aws-cdk from 2.96.1 to 2.96.2 ([#3102](https://github.com/aws-powertools/powertools-lambda-python/issues/3102)) +* **deps-dev:** bump the boto-typing group with 3 updates ([#3118](https://github.com/aws-powertools/powertools-lambda-python/issues/3118)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3101](https://github.com/aws-powertools/powertools-lambda-python/issues/3101)) +* **deps-dev:** bump cfn-lint from 0.79.11 to 0.80.2 ([#3107](https://github.com/aws-powertools/powertools-lambda-python/issues/3107)) +* **deps-dev:** bump types-requests from 2.31.0.2 to 2.31.0.3 ([#3114](https://github.com/aws-powertools/powertools-lambda-python/issues/3114)) + + +<a name="v2.25.0"></a> +## [v2.25.0] - 2023-09-15 +## Code Refactoring + +* **parameters:** BaseProvider._get to also support Dict ([#3090](https://github.com/aws-powertools/powertools-lambda-python/issues/3090)) + +## Documentation + +* **event_handler:** fix typing in micro function example ([#3098](https://github.com/aws-powertools/powertools-lambda-python/issues/3098)) +* **event_handler:** add micro function examples ([#3056](https://github.com/aws-powertools/powertools-lambda-python/issues/3056)) +* **we-made-this:** fix broken Twitch video embeds ([#3096](https://github.com/aws-powertools/powertools-lambda-python/issues/3096)) + +## Features + +* **event_source:** add Kinesis Firehose Data Transformation data class ([#3029](https://github.com/aws-powertools/powertools-lambda-python/issues/3029)) +* **event_sources:** add Secrets Manager secret rotation event ([#3061](https://github.com/aws-powertools/powertools-lambda-python/issues/3061)) + +## Maintenance + +* version bump +* **automation:** remove previous labels when PR is updated ([#3066](https://github.com/aws-powertools/powertools-lambda-python/issues/3066)) +* **deps:** bump actions/dependency-review-action from 3.0.8 to 3.1.0 ([#3071](https://github.com/aws-powertools/powertools-lambda-python/issues/3071)) +* **deps:** bump docker/setup-qemu-action from 2.2.0 to 3.0.0 ([#3081](https://github.com/aws-powertools/powertools-lambda-python/issues/3081)) +* **deps:** bump docker/setup-buildx-action from 2.10.0 to 3.0.0 ([#3083](https://github.com/aws-powertools/powertools-lambda-python/issues/3083)) +* **deps:** bump squidfunk/mkdocs-material from `dd1770c` to `c4890ab` in /docs ([#3078](https://github.com/aws-powertools/powertools-lambda-python/issues/3078)) +* **deps-dev:** bump cfn-lint from 0.79.9 to 0.79.10 ([#3077](https://github.com/aws-powertools/powertools-lambda-python/issues/3077)) +* **deps-dev:** bump hvac from 1.2.0 to 1.2.1 ([#3075](https://github.com/aws-powertools/powertools-lambda-python/issues/3075)) +* **deps-dev:** bump ruff from 0.0.288 to 0.0.289 ([#3080](https://github.com/aws-powertools/powertools-lambda-python/issues/3080)) +* **deps-dev:** bump ruff from 0.0.287 to 0.0.288 ([#3076](https://github.com/aws-powertools/powertools-lambda-python/issues/3076)) +* **deps-dev:** bump aws-cdk from 2.95.0 to 2.95.1 ([#3074](https://github.com/aws-powertools/powertools-lambda-python/issues/3074)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3085](https://github.com/aws-powertools/powertools-lambda-python/issues/3085)) +* **deps-dev:** bump aws-cdk from 2.95.1 to 2.96.0 ([#3087](https://github.com/aws-powertools/powertools-lambda-python/issues/3087)) +* **deps-dev:** bump sentry-sdk from 1.30.0 to 1.31.0 ([#3086](https://github.com/aws-powertools/powertools-lambda-python/issues/3086)) +* **deps-dev:** bump aws-cdk from 2.94.0 to 2.95.0 ([#3070](https://github.com/aws-powertools/powertools-lambda-python/issues/3070)) +* **deps-dev:** bump cfn-lint from 0.79.10 to 0.79.11 ([#3088](https://github.com/aws-powertools/powertools-lambda-python/issues/3088)) +* **deps-dev:** bump aws-cdk from 2.96.0 to 2.96.1 ([#3093](https://github.com/aws-powertools/powertools-lambda-python/issues/3093)) +* **typing:** move backwards compat types to shared types ([#3092](https://github.com/aws-powertools/powertools-lambda-python/issues/3092)) + + +<a name="v2.24.0"></a> +## [v2.24.0] - 2023-09-08 +## Bug Fixes + +* **event_handler:** expanding safe URI characters to include +$& ([#3026](https://github.com/aws-powertools/powertools-lambda-python/issues/3026)) +* **parser:** change ApproximateCreationDateTime field to datetime in DynamoDBStreamChangedRecordModel ([#3049](https://github.com/aws-powertools/powertools-lambda-python/issues/3049)) + +## Code Refactoring + +* **batch:** type response() method ([#3023](https://github.com/aws-powertools/powertools-lambda-python/issues/3023)) + +## Documentation + +* **event_handler:** demonstrate how to combine logger correlation ID and middleware ([#3064](https://github.com/aws-powertools/powertools-lambda-python/issues/3064)) +* **event_handler:** use correct correlation_id for logger in middleware example ([#3063](https://github.com/aws-powertools/powertools-lambda-python/issues/3063)) +* **idempotency:** use tab navigation, improves custom serializer example, and additional explanations ([#3067](https://github.com/aws-powertools/powertools-lambda-python/issues/3067)) + +## Features + +* **event_handler:** add Middleware support for REST Event Handler ([#2917](https://github.com/aws-powertools/powertools-lambda-python/issues/2917)) +* **idempotency:** add support to custom serialization/deserialization on idempotency decorator ([#2951](https://github.com/aws-powertools/powertools-lambda-python/issues/2951)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `b1f7f94` to `f4764d1` in /docs ([#3031](https://github.com/aws-powertools/powertools-lambda-python/issues/3031)) +* **deps:** bump gitpython from 3.1.32 to 3.1.35 in /docs ([#3059](https://github.com/aws-powertools/powertools-lambda-python/issues/3059)) +* **deps:** bump squidfunk/mkdocs-material from `f4764d1` to `dd1770c` in /docs ([#3044](https://github.com/aws-powertools/powertools-lambda-python/issues/3044)) +* **deps:** bump actions/checkout from 3.6.0 to 4.0.0 ([#3041](https://github.com/aws-powertools/powertools-lambda-python/issues/3041)) +* **deps:** bump squidfunk/mkdocs-material from `97da15b` to `b1f7f94` in /docs ([#3021](https://github.com/aws-powertools/powertools-lambda-python/issues/3021)) +* **deps:** bump docker/setup-buildx-action from 2.9.1 to 2.10.0 ([#3022](https://github.com/aws-powertools/powertools-lambda-python/issues/3022)) +* **deps:** bump actions/upload-artifact from 3.1.2 to 3.1.3 ([#3053](https://github.com/aws-powertools/powertools-lambda-python/issues/3053)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3052](https://github.com/aws-powertools/powertools-lambda-python/issues/3052)) +* **deps-dev:** bump mkdocs-material from 9.2.6 to 9.2.7 ([#3043](https://github.com/aws-powertools/powertools-lambda-python/issues/3043)) +* **deps-dev:** bump cfn-lint from 0.79.7 to 0.79.8 ([#3033](https://github.com/aws-powertools/powertools-lambda-python/issues/3033)) +* **deps-dev:** bump mkdocs-material from 9.2.5 to 9.2.6 ([#3032](https://github.com/aws-powertools/powertools-lambda-python/issues/3032)) +* **deps-dev:** bump ruff from 0.0.286 to 0.0.287 ([#3035](https://github.com/aws-powertools/powertools-lambda-python/issues/3035)) +* **deps-dev:** bump sentry-sdk from 1.29.2 to 1.30.0 ([#3028](https://github.com/aws-powertools/powertools-lambda-python/issues/3028)) +* **deps-dev:** bump the boto-typing group with 11 updates ([#3027](https://github.com/aws-powertools/powertools-lambda-python/issues/3027)) +* **deps-dev:** bump pytest from 7.4.1 to 7.4.2 ([#3057](https://github.com/aws-powertools/powertools-lambda-python/issues/3057)) +* **deps-dev:** bump hvac from 1.1.1 to 1.2.0 ([#3054](https://github.com/aws-powertools/powertools-lambda-python/issues/3054)) +* **deps-dev:** bump cfn-lint from 0.79.8 to 0.79.9 ([#3046](https://github.com/aws-powertools/powertools-lambda-python/issues/3046)) +* **deps-dev:** bump the boto-typing group with 1 update ([#3013](https://github.com/aws-powertools/powertools-lambda-python/issues/3013)) +* **deps-dev:** bump pytest from 7.4.0 to 7.4.1 ([#3042](https://github.com/aws-powertools/powertools-lambda-python/issues/3042)) +* **deps-dev:** bump ruff from 0.0.285 to 0.0.286 ([#3014](https://github.com/aws-powertools/powertools-lambda-python/issues/3014)) +* **deps-dev:** bump gitpython from 3.1.32 to 3.1.35 ([#3060](https://github.com/aws-powertools/powertools-lambda-python/issues/3060)) +* **deps-dev:** bump aws-cdk from 2.93.0 to 2.94.0 ([#3036](https://github.com/aws-powertools/powertools-lambda-python/issues/3036)) + + +<a name="v2.23.1"></a> +## [v2.23.1] - 2023-08-25 +## Bug Fixes + +* **ci:** revert aws credentials action ([#3010](https://github.com/aws-powertools/powertools-lambda-python/issues/3010)) +* **ci:** change SAR assume role options ([#3005](https://github.com/aws-powertools/powertools-lambda-python/issues/3005)) +* **event_handler:** make invalid chars a raw str to fix DeprecationWarning ([#2982](https://github.com/aws-powertools/powertools-lambda-python/issues/2982)) +* **metrics:** preserve default_tags when metric-specific tag is set in Datadog provider ([#2997](https://github.com/aws-powertools/powertools-lambda-python/issues/2997)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `cd3a522` to `97da15b` in /docs ([#2987](https://github.com/aws-powertools/powertools-lambda-python/issues/2987)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#2978](https://github.com/aws-powertools/powertools-lambda-python/issues/2978)) +* **deps:** bump aws-actions/configure-aws-credentials from 2.2.0 to 3.0.0 ([#3000](https://github.com/aws-powertools/powertools-lambda-python/issues/3000)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#2983](https://github.com/aws-powertools/powertools-lambda-python/issues/2983)) +* **deps:** bump slsa-framework/slsa-github-generator from 1.8.0 to 1.9.0 ([#2992](https://github.com/aws-powertools/powertools-lambda-python/issues/2992)) +* **deps:** bump actions/checkout from 3.5.3 to 3.6.0 ([#2999](https://github.com/aws-powertools/powertools-lambda-python/issues/2999)) +* **deps-dev:** bump ruff from 0.0.284 to 0.0.285 ([#2977](https://github.com/aws-powertools/powertools-lambda-python/issues/2977)) +* **deps-dev:** bump aws-cdk from 2.92.0 to 2.93.0 ([#2993](https://github.com/aws-powertools/powertools-lambda-python/issues/2993)) +* **deps-dev:** bump mkdocs-material from 9.1.21 to 9.2.0 ([#2984](https://github.com/aws-powertools/powertools-lambda-python/issues/2984)) +* **deps-dev:** bump mkdocs-material from 9.2.0 to 9.2.3 ([#2988](https://github.com/aws-powertools/powertools-lambda-python/issues/2988)) + + +<a name="v2.23.0"></a> +## [v2.23.0] - 2023-08-18 +## Bug Fixes + +* **logger:** strip xray_trace_id when explicitly disabled ([#2852](https://github.com/aws-powertools/powertools-lambda-python/issues/2852)) +* **metrics:** proxy service and namespace attrs to provider ([#2910](https://github.com/aws-powertools/powertools-lambda-python/issues/2910)) +* **parser:** API Gateway V2 request context scope field should be optional ([#2961](https://github.com/aws-powertools/powertools-lambda-python/issues/2961)) + +## Code Refactoring + +* **e2e:** support fail fast in get_lambda_response ([#2912](https://github.com/aws-powertools/powertools-lambda-python/issues/2912)) +* **metrics:** move from protocol to ABC; split provider tests ([#2934](https://github.com/aws-powertools/powertools-lambda-python/issues/2934)) + +## Documentation + +* **batch:** new visuals and error handling section ([#2857](https://github.com/aws-powertools/powertools-lambda-python/issues/2857)) +* **batch:** explain record type discrepancy in failure and success handler ([#2868](https://github.com/aws-powertools/powertools-lambda-python/issues/2868)) +* **metrics:** update Datadog integration diagram ([#2954](https://github.com/aws-powertools/powertools-lambda-python/issues/2954)) +* **navigation:** remove nofollow attribute for internal links ([#2867](https://github.com/aws-powertools/powertools-lambda-python/issues/2867)) +* **navigation:** add nofollow attribute ([#2842](https://github.com/aws-powertools/powertools-lambda-python/issues/2842)) +* **roadmap:** update roadmap themes ([#2915](https://github.com/aws-powertools/powertools-lambda-python/issues/2915)) +* **roadmap:** add GovCloud and China region item ([#2960](https://github.com/aws-powertools/powertools-lambda-python/issues/2960)) +* **tutorial:** add support for Python 3.11 ([#2860](https://github.com/aws-powertools/powertools-lambda-python/issues/2860)) + +## Features + +* **event_handler:** allow stripping route prefixes using regexes ([#2521](https://github.com/aws-powertools/powertools-lambda-python/issues/2521)) +* **layers:** add new comercial region Israel(Tel Aviv) ([#2907](https://github.com/aws-powertools/powertools-lambda-python/issues/2907)) +* **metrics:** add Datadog observability provider ([#2906](https://github.com/aws-powertools/powertools-lambda-python/issues/2906)) +* **metrics:** support to bring your own metrics provider ([#2194](https://github.com/aws-powertools/powertools-lambda-python/issues/2194)) + +## Maintenance + +* version bump +* **ci:** enable protected branch auditing ([#2913](https://github.com/aws-powertools/powertools-lambda-python/issues/2913)) +* **ci:** group dependabot updates ([#2896](https://github.com/aws-powertools/powertools-lambda-python/issues/2896)) +* **deps:** bump github.com/aws/aws-sdk-go-v2 from 1.19.0 to 1.19.1 in /layer/scripts/layer-balancer ([#2877](https://github.com/aws-powertools/powertools-lambda-python/issues/2877)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.9 ([#2943](https://github.com/aws-powertools/powertools-lambda-python/issues/2943)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.38.0 to 1.38.1 in /layer/scripts/layer-balancer ([#2876](https://github.com/aws-powertools/powertools-lambda-python/issues/2876)) +* **deps:** bump actions/dependency-review-action from 3.0.6 to 3.0.7 ([#2941](https://github.com/aws-powertools/powertools-lambda-python/issues/2941)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.18.29 to 1.18.30 in /layer/scripts/layer-balancer ([#2875](https://github.com/aws-powertools/powertools-lambda-python/issues/2875)) +* **deps:** bump actions/dependency-review-action from 3.0.7 to 3.0.8 ([#2963](https://github.com/aws-powertools/powertools-lambda-python/issues/2963)) +* **deps:** bump gitpython from 3.1.31 to 3.1.32 in /docs ([#2948](https://github.com/aws-powertools/powertools-lambda-python/issues/2948)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#2904](https://github.com/aws-powertools/powertools-lambda-python/issues/2904)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#2933](https://github.com/aws-powertools/powertools-lambda-python/issues/2933)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.9 to 1.8.10 ([#2946](https://github.com/aws-powertools/powertools-lambda-python/issues/2946)) +* **deps:** bump actions/setup-node from 3.7.0 to 3.8.0 ([#2957](https://github.com/aws-powertools/powertools-lambda-python/issues/2957)) +* **deps:** bump slsa-framework/slsa-github-generator from 1.7.0 to 1.8.0 ([#2927](https://github.com/aws-powertools/powertools-lambda-python/issues/2927)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.18.28 to 1.18.29 in /layer/scripts/layer-balancer ([#2844](https://github.com/aws-powertools/powertools-lambda-python/issues/2844)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.37.1 to 1.38.0 in /layer/scripts/layer-balancer ([#2843](https://github.com/aws-powertools/powertools-lambda-python/issues/2843)) +* **deps:** bump pydantic from 1.10.11 to 1.10.12 ([#2846](https://github.com/aws-powertools/powertools-lambda-python/issues/2846)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 3 updates ([#2971](https://github.com/aws-powertools/powertools-lambda-python/issues/2971)) +* **deps:** bump actions/setup-node from 3.8.0 to 3.8.1 ([#2970](https://github.com/aws-powertools/powertools-lambda-python/issues/2970)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.18.30 to 1.18.31 in /layer/scripts/layer-balancer ([#2889](https://github.com/aws-powertools/powertools-lambda-python/issues/2889)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.38.1 to 1.39.0 in /layer/scripts/layer-balancer ([#2890](https://github.com/aws-powertools/powertools-lambda-python/issues/2890)) +* **deps:** bump squidfunk/mkdocs-material from `33e28bd` to `cd3a522` in /docs ([#2859](https://github.com/aws-powertools/powertools-lambda-python/issues/2859)) +* **deps-dev:** bump ruff from 0.0.283 to 0.0.284 ([#2940](https://github.com/aws-powertools/powertools-lambda-python/issues/2940)) +* **deps-dev:** bump cfn-lint from 0.79.5 to 0.79.6 ([#2899](https://github.com/aws-powertools/powertools-lambda-python/issues/2899)) +* **deps-dev:** bump the boto-typing group with 11 updates ([#2901](https://github.com/aws-powertools/powertools-lambda-python/issues/2901)) +* **deps-dev:** bump ruff from 0.0.281 to 0.0.282 ([#2905](https://github.com/aws-powertools/powertools-lambda-python/issues/2905)) +* **deps-dev:** bump aws-cdk from 2.88.0 to 2.89.0 ([#2887](https://github.com/aws-powertools/powertools-lambda-python/issues/2887)) +* **deps-dev:** bump aws-cdk from 2.89.0 to 2.90.0 ([#2932](https://github.com/aws-powertools/powertools-lambda-python/issues/2932)) +* **deps-dev:** bump mkdocs-material from 9.1.19 to 9.1.21 ([#2894](https://github.com/aws-powertools/powertools-lambda-python/issues/2894)) +* **deps-dev:** bump the boto-typing group with 3 updates ([#2967](https://github.com/aws-powertools/powertools-lambda-python/issues/2967)) +* **deps-dev:** bump radon from 5.1.0 to 6.0.1 ([#2964](https://github.com/aws-powertools/powertools-lambda-python/issues/2964)) +* **deps-dev:** bump the boto-typing group with 4 updates ([#2928](https://github.com/aws-powertools/powertools-lambda-python/issues/2928)) +* **deps-dev:** bump mypy-boto3-logs from 1.28.1 to 1.28.15 ([#2880](https://github.com/aws-powertools/powertools-lambda-python/issues/2880)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.28.0 to 1.28.15 ([#2879](https://github.com/aws-powertools/powertools-lambda-python/issues/2879)) +* **deps-dev:** bump mypy-boto3-lambda from 1.28.11 to 1.28.15 ([#2878](https://github.com/aws-powertools/powertools-lambda-python/issues/2878)) +* **deps-dev:** bump mypy-boto3-xray from 1.28.0 to 1.28.15 ([#2881](https://github.com/aws-powertools/powertools-lambda-python/issues/2881)) +* **deps-dev:** bump ruff from 0.0.282 to 0.0.283 ([#2937](https://github.com/aws-powertools/powertools-lambda-python/issues/2937)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.28.0 to 1.28.11 ([#2847](https://github.com/aws-powertools/powertools-lambda-python/issues/2847)) +* **deps-dev:** bump sentry-sdk from 1.28.1 to 1.29.0 ([#2900](https://github.com/aws-powertools/powertools-lambda-python/issues/2900)) +* **deps-dev:** bump cfn-lint from 0.79.4 to 0.79.5 ([#2870](https://github.com/aws-powertools/powertools-lambda-python/issues/2870)) +* **deps-dev:** bump the boto-typing group with 1 update ([#2944](https://github.com/aws-powertools/powertools-lambda-python/issues/2944)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.28.10 to 1.28.12 ([#2864](https://github.com/aws-powertools/powertools-lambda-python/issues/2864)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.28.0 to 1.28.12 ([#2865](https://github.com/aws-powertools/powertools-lambda-python/issues/2865)) +* **deps-dev:** bump cfn-lint from 0.79.3 to 0.79.4 ([#2862](https://github.com/aws-powertools/powertools-lambda-python/issues/2862)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.28.0 to 1.28.12 ([#2861](https://github.com/aws-powertools/powertools-lambda-python/issues/2861)) +* **deps-dev:** bump mypy-boto3-ssm from 1.28.0 to 1.28.12 ([#2863](https://github.com/aws-powertools/powertools-lambda-python/issues/2863)) +* **deps-dev:** bump aws-cdk from 2.90.0 to 2.91.0 ([#2947](https://github.com/aws-powertools/powertools-lambda-python/issues/2947)) +* **deps-dev:** bump xenon from 0.9.0 to 0.9.1 ([#2955](https://github.com/aws-powertools/powertools-lambda-python/issues/2955)) +* **deps-dev:** bump cfn-lint from 0.78.2 to 0.79.3 ([#2854](https://github.com/aws-powertools/powertools-lambda-python/issues/2854)) +* **deps-dev:** bump mypy-boto3-lambda from 1.28.0 to 1.28.11 ([#2845](https://github.com/aws-powertools/powertools-lambda-python/issues/2845)) +* **deps-dev:** bump cfn-lint from 0.79.6 to 0.79.7 ([#2956](https://github.com/aws-powertools/powertools-lambda-python/issues/2956)) +* **deps-dev:** bump aws-cdk from 2.91.0 to 2.92.0 ([#2965](https://github.com/aws-powertools/powertools-lambda-python/issues/2965)) +* **deps-dev:** bump ruff from 0.0.280 to 0.0.281 ([#2891](https://github.com/aws-powertools/powertools-lambda-python/issues/2891)) +* **docs:** include the environment variables section in the utilities documentation ([#2925](https://github.com/aws-powertools/powertools-lambda-python/issues/2925)) +* **docs:** disable line length rule using older syntax ([#2920](https://github.com/aws-powertools/powertools-lambda-python/issues/2920)) +* **maintenance:** enables publishing docs and changelog, running e2e tests only in the main repository ([#2924](https://github.com/aws-powertools/powertools-lambda-python/issues/2924)) + + +<a name="v2.22.0"></a> +## [v2.22.0] - 2023-07-25 +## Bug Fixes + +* **parameters:** distinct cache key for single vs path with same name ([#2839](https://github.com/aws-powertools/powertools-lambda-python/issues/2839)) + +## Documentation + +* **community:** new batch processing article ([#2828](https://github.com/aws-powertools/powertools-lambda-python/issues/2828)) +* **parameters:** improve readability on error handling get_parameter… ([#2833](https://github.com/aws-powertools/powertools-lambda-python/issues/2833)) + +## Features + +* **general:** add support for Python 3.11 ([#2820](https://github.com/aws-powertools/powertools-lambda-python/issues/2820)) + +## Maintenance + +* version bump +* **ci:** add baking time for layer build ([#2834](https://github.com/aws-powertools/powertools-lambda-python/issues/2834)) +* **ci:** build changelog on a schedule only ([#2832](https://github.com/aws-powertools/powertools-lambda-python/issues/2832)) +* **deps:** bump actions/setup-python from 4.6.1 to 4.7.0 ([#2821](https://github.com/aws-powertools/powertools-lambda-python/issues/2821)) +* **deps-dev:** bump ruff from 0.0.278 to 0.0.279 ([#2822](https://github.com/aws-powertools/powertools-lambda-python/issues/2822)) +* **deps-dev:** bump cfn-lint from 0.78.1 to 0.78.2 ([#2823](https://github.com/aws-powertools/powertools-lambda-python/issues/2823)) +* **deps-dev:** bump ruff from 0.0.279 to 0.0.280 ([#2836](https://github.com/aws-powertools/powertools-lambda-python/issues/2836)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.28.0 to 1.28.10 ([#2837](https://github.com/aws-powertools/powertools-lambda-python/issues/2837)) + + +<a name="v2.21.0"></a> +## [v2.21.0] - 2023-07-21 +## Bug Fixes + +* **docs:** remove redundant code ([#2796](https://github.com/aws-powertools/powertools-lambda-python/issues/2796)) + +## Documentation + +* **customer-reference:** add Jit Security as a customer reference ([#2801](https://github.com/aws-powertools/powertools-lambda-python/issues/2801)) + +## Features + +* **parser:** add support for Pydantic v2 ([#2733](https://github.com/aws-powertools/powertools-lambda-python/issues/2733)) + +## Maintenance + +* version bump +* **deps:** bump squidfunk/mkdocs-material from `a28ed81` to `33e28bd` in /docs ([#2797](https://github.com/aws-powertools/powertools-lambda-python/issues/2797)) +* **deps-dev:** bump mypy-boto3-s3 from 1.28.3.post2 to 1.28.8 ([#2808](https://github.com/aws-powertools/powertools-lambda-python/issues/2808)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.13 to 2.8.19.14 ([#2807](https://github.com/aws-powertools/powertools-lambda-python/issues/2807)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.28.3.post1 to 1.28.3.post2 ([#2794](https://github.com/aws-powertools/powertools-lambda-python/issues/2794)) +* **deps-dev:** bump types-requests from 2.31.0.1 to 2.31.0.2 ([#2806](https://github.com/aws-powertools/powertools-lambda-python/issues/2806)) +* **deps-dev:** bump mypy-boto3-s3 from 1.28.3.post1 to 1.28.3.post2 ([#2793](https://github.com/aws-powertools/powertools-lambda-python/issues/2793)) +* **deps-dev:** bump aws-cdk from 2.87.0 to 2.88.0 ([#2812](https://github.com/aws-powertools/powertools-lambda-python/issues/2812)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.28.3 to 1.28.3.post1 ([#2785](https://github.com/aws-powertools/powertools-lambda-python/issues/2785)) +* **deps-dev:** bump mypy-boto3-s3 from 1.28.3 to 1.28.3.post1 ([#2786](https://github.com/aws-powertools/powertools-lambda-python/issues/2786)) +* **deps-dev:** bump mkdocs-material from 9.1.18 to 9.1.19 ([#2798](https://github.com/aws-powertools/powertools-lambda-python/issues/2798)) +* **security:** improve debugging for provenance script ([#2784](https://github.com/aws-powertools/powertools-lambda-python/issues/2784)) + + +<a name="v2.20.0"></a> +## [v2.20.0] - 2023-07-14 +## Bug Fixes + +* **docs:** ensure alias is applied to versioned releases ([#2644](https://github.com/aws-powertools/powertools-lambda-python/issues/2644)) +* **docs:** ensure version alias is in an array to prevent "you're not viewing the latest version" incorrect message ([#2629](https://github.com/aws-powertools/powertools-lambda-python/issues/2629)) +* **logger:** ensure logs stream to stdout by default, not stderr ([#2736](https://github.com/aws-powertools/powertools-lambda-python/issues/2736)) + +## Code Refactoring + +* **parser:** convert functional tests to unit tests ([#2656](https://github.com/aws-powertools/powertools-lambda-python/issues/2656)) + +## Documentation + +* **batch:** fix custom batch processor example ([#2714](https://github.com/aws-powertools/powertools-lambda-python/issues/2714)) +* **contributing:** add code integration journey graph ([#2685](https://github.com/aws-powertools/powertools-lambda-python/issues/2685)) +* **maintainers:** add cicd pipeline diagram ([#2692](https://github.com/aws-powertools/powertools-lambda-python/issues/2692)) +* **process:** explain our integration automated checks; revamp navigation ([#2764](https://github.com/aws-powertools/powertools-lambda-python/issues/2764)) + +## Features + +* **metrics:** support to set default dimension in EphemeralMetrics ([#2748](https://github.com/aws-powertools/powertools-lambda-python/issues/2748)) + +## Maintenance + +* version bump +* **ci:** enforce pip --require-hashes to maybe satistify scorecard ([#2679](https://github.com/aws-powertools/powertools-lambda-python/issues/2679)) +* **ci:** prevent merging PRs that do not meet minimum requirements ([#2639](https://github.com/aws-powertools/powertools-lambda-python/issues/2639)) +* **ci:** enforce top-level permission to minimum fail-safe permission as per openssf ([#2638](https://github.com/aws-powertools/powertools-lambda-python/issues/2638)) +* **ci:** propagate checkout permission to nested workflows ([#2642](https://github.com/aws-powertools/powertools-lambda-python/issues/2642)) +* **ci:** improves dependabot based on ossf scorecard recommendations ([#2647](https://github.com/aws-powertools/powertools-lambda-python/issues/2647)) +* **ci:** use deps sha for docs and gitpod images based on ossf findings ([#2662](https://github.com/aws-powertools/powertools-lambda-python/issues/2662)) +* **ci:** use sast on every commit on any supported language ([#2646](https://github.com/aws-powertools/powertools-lambda-python/issues/2646)) +* **ci:** add gitleaks in pre-commit hooks as an extra safety measure ([#2677](https://github.com/aws-powertools/powertools-lambda-python/issues/2677)) +* **ci:** address ossf scorecard findings on npm, pip, and top-level permission leftover ([#2694](https://github.com/aws-powertools/powertools-lambda-python/issues/2694)) +* **ci:** prevent sast codeql to run in forks ([#2711](https://github.com/aws-powertools/powertools-lambda-python/issues/2711)) +* **ci:** introduce provenance and attestation in release ([#2746](https://github.com/aws-powertools/powertools-lambda-python/issues/2746)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 ([#2754](https://github.com/aws-powertools/powertools-lambda-python/issues/2754)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.37.0 to 1.37.1 in /layer/scripts/layer-balancer ([#2769](https://github.com/aws-powertools/powertools-lambda-python/issues/2769)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.3 to 2.1.4 ([#2738](https://github.com/aws-powertools/powertools-lambda-python/issues/2738)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.18.27 to 1.18.28 in /layer/scripts/layer-balancer ([#2770](https://github.com/aws-powertools/powertools-lambda-python/issues/2770)) +* **deps:** bump actions/setup-python from 4.6.1 to 4.7.0 ([#2768](https://github.com/aws-powertools/powertools-lambda-python/issues/2768)) +* **deps:** bump github.com/aws/aws-sdk-go-v2 from 1.16.16 to 1.18.1 in /layer/scripts/layer-balancer ([#2654](https://github.com/aws-powertools/powertools-lambda-python/issues/2654)) +* **deps:** bump golang.org/x/sync from 0.1.0 to 0.3.0 in /layer/scripts/layer-balancer ([#2649](https://github.com/aws-powertools/powertools-lambda-python/issues/2649)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/service/lambda from 1.24.6 to 1.37.0 in /layer/scripts/layer-balancer ([#2653](https://github.com/aws-powertools/powertools-lambda-python/issues/2653)) +* **deps:** bump docker/setup-buildx-action from 2.8.0 to 2.9.0 ([#2718](https://github.com/aws-powertools/powertools-lambda-python/issues/2718)) +* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.17.8 to 1.18.27 in /layer/scripts/layer-balancer ([#2651](https://github.com/aws-powertools/powertools-lambda-python/issues/2651)) +* **deps:** bump github.com/aws/aws-sdk-go-v2 from 1.18.1 to 1.19.0 in /layer/scripts/layer-balancer ([#2771](https://github.com/aws-powertools/powertools-lambda-python/issues/2771)) +* **deps:** migrate from retry to retry2 to address CVE-2022-42969 ([#2665](https://github.com/aws-powertools/powertools-lambda-python/issues/2665)) +* **deps:** bump pydantic from 1.10.9 to 1.10.10 ([#2624](https://github.com/aws-powertools/powertools-lambda-python/issues/2624)) +* **deps:** bump squidfunk/mkdocs-material from `3837c0f` to `a28ed81` in /docs ([#2669](https://github.com/aws-powertools/powertools-lambda-python/issues/2669)) +* **deps:** bump pydantic from 1.10.10 to 1.10.11 ([#2671](https://github.com/aws-powertools/powertools-lambda-python/issues/2671)) +* **deps:** bump docker/setup-buildx-action from 2.9.0 to 2.9.1 ([#2755](https://github.com/aws-powertools/powertools-lambda-python/issues/2755)) +* **deps:** bump actions/dependency-review-action from 2.5.1 to 3.0.6 ([#2650](https://github.com/aws-powertools/powertools-lambda-python/issues/2650)) +* **deps:** bump actions/setup-node from 3.6.0 to 3.7.0 ([#2689](https://github.com/aws-powertools/powertools-lambda-python/issues/2689)) +* **deps-dev:** bump mypy-boto3-lambda from 1.27.0 to 1.28.0 ([#2698](https://github.com/aws-powertools/powertools-lambda-python/issues/2698)) +* **deps-dev:** bump sentry-sdk from 1.27.0 to 1.27.1 ([#2701](https://github.com/aws-powertools/powertools-lambda-python/issues/2701)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.27.0 to 1.28.0 ([#2700](https://github.com/aws-powertools/powertools-lambda-python/issues/2700)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.27.0 to 1.28.0 ([#2699](https://github.com/aws-powertools/powertools-lambda-python/issues/2699)) +* **deps-dev:** bump ruff from 0.0.276 to 0.0.277 ([#2682](https://github.com/aws-powertools/powertools-lambda-python/issues/2682)) +* **deps-dev:** bump pytest-asyncio from 0.21.0 to 0.21.1 ([#2756](https://github.com/aws-powertools/powertools-lambda-python/issues/2756)) +* **deps-dev:** bump cfn-lint from 0.77.10 to 0.78.1 ([#2757](https://github.com/aws-powertools/powertools-lambda-python/issues/2757)) +* **deps-dev:** bump aws-cdk from 2.86.0 to 2.87.0 ([#2696](https://github.com/aws-powertools/powertools-lambda-python/issues/2696)) +* **deps-dev:** bump typed-ast from 1.5.4 to 1.5.5 ([#2670](https://github.com/aws-powertools/powertools-lambda-python/issues/2670)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.27.0 to 1.28.0 ([#2697](https://github.com/aws-powertools/powertools-lambda-python/issues/2697)) +* **deps-dev:** bump ruff from 0.0.275 to 0.0.276 ([#2655](https://github.com/aws-powertools/powertools-lambda-python/issues/2655)) +* **deps-dev:** bump sentry-sdk from 1.26.0 to 1.27.0 ([#2652](https://github.com/aws-powertools/powertools-lambda-python/issues/2652)) +* **deps-dev:** bump ruff from 0.0.277 to 0.0.278 ([#2758](https://github.com/aws-powertools/powertools-lambda-python/issues/2758)) +* **deps-dev:** bump mypy-boto3-s3 from 1.28.0 to 1.28.3 ([#2774](https://github.com/aws-powertools/powertools-lambda-python/issues/2774)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.158 to 1.26.164 ([#2622](https://github.com/aws-powertools/powertools-lambda-python/issues/2622)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.28.0 to 1.28.3 ([#2773](https://github.com/aws-powertools/powertools-lambda-python/issues/2773)) +* **deps-dev:** bump sentry-sdk from 1.27.1 to 1.28.0 ([#2741](https://github.com/aws-powertools/powertools-lambda-python/issues/2741)) +* **deps-dev:** bump mypy-boto3-s3 from 1.27.0 to 1.28.0 ([#2721](https://github.com/aws-powertools/powertools-lambda-python/issues/2721)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.27.0 to 1.28.0 ([#2722](https://github.com/aws-powertools/powertools-lambda-python/issues/2722)) +* **deps-dev:** bump mypy-boto3-logs from 1.27.0 to 1.28.1 ([#2723](https://github.com/aws-powertools/powertools-lambda-python/issues/2723)) +* **deps-dev:** bump mypy-boto3-ssm from 1.27.0 to 1.28.0 ([#2724](https://github.com/aws-powertools/powertools-lambda-python/issues/2724)) +* **deps-dev:** bump mypy-boto3-xray from 1.27.0 to 1.28.0 ([#2720](https://github.com/aws-powertools/powertools-lambda-python/issues/2720)) +* **deps-dev:** bump sentry-sdk from 1.28.0 to 1.28.1 ([#2772](https://github.com/aws-powertools/powertools-lambda-python/issues/2772)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.27.0 to 1.28.0 ([#2740](https://github.com/aws-powertools/powertools-lambda-python/issues/2740)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.70 to 1.27.0 ([#2636](https://github.com/aws-powertools/powertools-lambda-python/issues/2636)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.27.0 to 1.28.0 ([#2739](https://github.com/aws-powertools/powertools-lambda-python/issues/2739)) +* **governance:** update active maintainers list ([#2715](https://github.com/aws-powertools/powertools-lambda-python/issues/2715)) +* **streaming:** replace deprecated Version classes from distutils ([#2752](https://github.com/aws-powertools/powertools-lambda-python/issues/2752)) +* **user-agent:** support patching botocore session ([#2614](https://github.com/aws-powertools/powertools-lambda-python/issues/2614)) + + +<a name="v2.19.0"></a> +## [v2.19.0] - 2023-06-30 +## Bug Fixes + +* **e2e:** fix idempotency tests ([#2576](https://github.com/aws-powertools/powertools-lambda-python/issues/2576)) + +## Code Refactoring + +* **event_source:** convert functional tests to unit tests ([#2506](https://github.com/aws-powertools/powertools-lambda-python/issues/2506)) + +## Documentation + +* **i-made-this:** added new article on idempotency ([#2582](https://github.com/aws-powertools/powertools-lambda-python/issues/2582)) +* **i-made-this:** article on idempotency w/ CDK and Powertools ([#2569](https://github.com/aws-powertools/powertools-lambda-python/issues/2569)) +* **idempotency:** split snippets, improve wording and lint examples ([#2492](https://github.com/aws-powertools/powertools-lambda-python/issues/2492)) + +## Features + +* **event_handler:** add VPCLatticeResolver ([#2601](https://github.com/aws-powertools/powertools-lambda-python/issues/2601)) +* **event_source:** decode nested messages on SQS events ([#2349](https://github.com/aws-powertools/powertools-lambda-python/issues/2349)) +* **parser:** add support to VpcLatticeModel ([#2584](https://github.com/aws-powertools/powertools-lambda-python/issues/2584)) + +## Maintenance + +* version bump +* **analytics:** update docs base origin url ([#2560](https://github.com/aws-powertools/powertools-lambda-python/issues/2560)) +* **ci:** replace flake8 with Ruff as a linter ([#2495](https://github.com/aws-powertools/powertools-lambda-python/issues/2495)) +* **ci:** enable Ruff rule E501 and fix errors ([#2587](https://github.com/aws-powertools/powertools-lambda-python/issues/2587)) +* **ci:** enable Ruff rule COM812 and fix the errors ([#2595](https://github.com/aws-powertools/powertools-lambda-python/issues/2595)) +* **ci:** enable Ruff rules PLW, PLR, PLC and PLE and fix the errors ([#2593](https://github.com/aws-powertools/powertools-lambda-python/issues/2593)) +* **ci:** enable Ruff autofix rules ([#2599](https://github.com/aws-powertools/powertools-lambda-python/issues/2599)) +* **ci:** enable Ruff rules ISC, I001, B018 and fix the errors ([#2597](https://github.com/aws-powertools/powertools-lambda-python/issues/2597)) +* **ci:** enable Ruff rule ERA001 and fix errors ([#2591](https://github.com/aws-powertools/powertools-lambda-python/issues/2591)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.6 to 1.8.7 ([#2573](https://github.com/aws-powertools/powertools-lambda-python/issues/2573)) +* **deps:** bump docker/setup-buildx-action from 2.7.0 to 2.8.0 ([#2604](https://github.com/aws-powertools/powertools-lambda-python/issues/2604)) +* **deps:** bump ossf/scorecard-action from 2.1.3 to 2.2.0 ([#2563](https://github.com/aws-powertools/powertools-lambda-python/issues/2563)) +* **deps:** bump release-drafter/release-drafter from 5.23.0 to 5.24.0 ([#2603](https://github.com/aws-powertools/powertools-lambda-python/issues/2603)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.155 to 1.26.163 ([#2608](https://github.com/aws-powertools/powertools-lambda-python/issues/2608)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.157 to 1.26.163 ([#2607](https://github.com/aws-powertools/powertools-lambda-python/issues/2607)) +* **deps-dev:** bump mkdocs-material from 9.1.16 to 9.1.17 ([#2564](https://github.com/aws-powertools/powertools-lambda-python/issues/2564)) +* **deps-dev:** bump ruff from 0.0.272 to 0.0.275 ([#2586](https://github.com/aws-powertools/powertools-lambda-python/issues/2586)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.97 to 1.26.162 ([#2606](https://github.com/aws-powertools/powertools-lambda-python/issues/2606)) +* **deps-dev:** bump pytest from 7.3.2 to 7.4.0 ([#2557](https://github.com/aws-powertools/powertools-lambda-python/issues/2557)) +* **deps-dev:** bump aws-cdk from 2.85.0 to 2.86.0 ([#2613](https://github.com/aws-powertools/powertools-lambda-python/issues/2613)) +* **deps-dev:** bump mypy from 1.4.0 to 1.4.1 ([#2574](https://github.com/aws-powertools/powertools-lambda-python/issues/2574)) + + +<a name="v2.18.0"></a> +## [v2.18.0] - 2023-06-23 +## Bug Fixes + +* **docs:** ensure versions.json is updated ([#2505](https://github.com/aws-powertools/powertools-lambda-python/issues/2505)) +* **event_source:** centralizing helper functions for query, header and base64 ([#2496](https://github.com/aws-powertools/powertools-lambda-python/issues/2496)) + +## Documentation + +* **homepage:** fix .NET repository link ([#2549](https://github.com/aws-powertools/powertools-lambda-python/issues/2549)) +* **homepage:** add Open Source Security Foundation badge; update links to new url ([#2545](https://github.com/aws-powertools/powertools-lambda-python/issues/2545)) +* **navigation:** make Key Feature the first section ([#2517](https://github.com/aws-powertools/powertools-lambda-python/issues/2517)) + +## Features + +* **event_handler:** support to enable or disable compression in custom responses ([#2544](https://github.com/aws-powertools/powertools-lambda-python/issues/2544)) +* **feature_flags:** add modulo range condition for segmented experimentation support ([#2331](https://github.com/aws-powertools/powertools-lambda-python/issues/2331)) + +## Maintenance + +* version bump +* **ci:** fix changelog build permissions ([#2519](https://github.com/aws-powertools/powertools-lambda-python/issues/2519)) +* **ci:** remove GH pages action ([#2501](https://github.com/aws-powertools/powertools-lambda-python/issues/2501)) +* **ci:** updates runner names in workflows ([#2510](https://github.com/aws-powertools/powertools-lambda-python/issues/2510)) +* **ci:** introduces OSSF Scorecard ([#2512](https://github.com/aws-powertools/powertools-lambda-python/issues/2512)) +* **ci:** fix codeowners team name ([#2516](https://github.com/aws-powertools/powertools-lambda-python/issues/2516)) +* **deps:** bump actions/upload-artifact from 3.1.0 to 3.1.2 ([#2522](https://github.com/aws-powertools/powertools-lambda-python/issues/2522)) +* **deps:** bump actions/checkout from 3.1.0 to 3.5.3 ([#2523](https://github.com/aws-powertools/powertools-lambda-python/issues/2523)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.153 to 1.26.155 ([#2498](https://github.com/aws-powertools/powertools-lambda-python/issues/2498)) +* **deps-dev:** bump aws-cdk from 2.84.0 to 2.85.0 ([#2524](https://github.com/aws-powertools/powertools-lambda-python/issues/2524)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.147 to 1.26.157 ([#2507](https://github.com/aws-powertools/powertools-lambda-python/issues/2507)) +* **deps-dev:** bump cfn-lint from 0.77.9 to 0.77.10 ([#2508](https://github.com/aws-powertools/powertools-lambda-python/issues/2508)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.149 to 1.26.156 ([#2503](https://github.com/aws-powertools/powertools-lambda-python/issues/2503)) +* **deps-dev:** bump sentry-sdk from 1.25.1 to 1.26.0 ([#2527](https://github.com/aws-powertools/powertools-lambda-python/issues/2527)) +* **deps-dev:** bump hvac from 1.1.0 to 1.1.1 ([#2497](https://github.com/aws-powertools/powertools-lambda-python/issues/2497)) +* **deps-dev:** bump flake8-variables-names from 0.0.5 to 0.0.6 ([#2525](https://github.com/aws-powertools/powertools-lambda-python/issues/2525)) +* **deps-dev:** bump ijson from 3.2.1 to 3.2.2 ([#2526](https://github.com/aws-powertools/powertools-lambda-python/issues/2526)) +* **deps-dev:** bump pytest-mock from 3.10.0 to 3.11.1 ([#2485](https://github.com/aws-powertools/powertools-lambda-python/issues/2485)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.152 to 1.26.158 ([#2528](https://github.com/aws-powertools/powertools-lambda-python/issues/2528)) +* **deps-dev:** bump mypy from 1.3.0 to 1.4.0 ([#2509](https://github.com/aws-powertools/powertools-lambda-python/issues/2509)) +* **documentation:** updating repository URL and name to the new location ([#2499](https://github.com/aws-powertools/powertools-lambda-python/issues/2499)) + + +<a name="v2.17.0"></a> +## [v2.17.0] - 2023-06-16 +## Bug Fixes + +* **event_handler:** prioritize static over dynamic route to prevent order of route registration mismatch ([#2458](https://github.com/aws-powertools/powertools-lambda-python/issues/2458)) +* **idempotency:** treat missing idempotency key as non-idempotent transaction (no-op) when raise_on_no_idempotency_key is False ([#2477](https://github.com/aws-powertools/powertools-lambda-python/issues/2477)) + +## Documentation + +* **event_handler:** improve compress example using Response class ([#2426](https://github.com/aws-powertools/powertools-lambda-python/issues/2426)) +* **event_sources:** fix DynamoDB stream event docstring ([#2468](https://github.com/aws-powertools/powertools-lambda-python/issues/2468)) +* **idempotency:** new sequence flow when idempotency key is optional ([#2480](https://github.com/aws-powertools/powertools-lambda-python/issues/2480)) +* **idempotency:** add CDK example ([#2434](https://github.com/aws-powertools/powertools-lambda-python/issues/2434)) +* **maintainers:** visual representation of release process ([#2399](https://github.com/aws-powertools/powertools-lambda-python/issues/2399)) +* **navigation:** standardize link targets to enhance customer experience ([#2420](https://github.com/aws-powertools/powertools-lambda-python/issues/2420)) +* **we-made-this:** new article about idempotency design ([#2425](https://github.com/aws-powertools/powertools-lambda-python/issues/2425)) + +## Features + +* **event_sources:** add AWS Config Rule event data class ([#2175](https://github.com/aws-powertools/powertools-lambda-python/issues/2175)) +* **event_sources:** add support for VPC Lattice events ([#2358](https://github.com/aws-powertools/powertools-lambda-python/issues/2358)) +* **logger:** type log record in LambdaPowertoolsFormatter with TypedDict ([#2419](https://github.com/aws-powertools/powertools-lambda-python/issues/2419)) +* **parser:** support for CloudFormation Custom Resources ([#2335](https://github.com/aws-powertools/powertools-lambda-python/issues/2335)) + +## Maintenance + +* version bump +* **ci:** document all github action workflows and enforce least-privilege ([#2395](https://github.com/aws-powertools/powertools-lambda-python/issues/2395)) +* **ci:** fix PR labeling permission scope ([#2396](https://github.com/aws-powertools/powertools-lambda-python/issues/2396)) +* **deps:** bump aws-actions/configure-aws-credentials from 2.1.0 to 2.2.0 ([#2469](https://github.com/aws-powertools/powertools-lambda-python/issues/2469)) +* **deps:** bump docker/setup-buildx-action from 2.5.0 to 2.6.0 ([#2403](https://github.com/aws-powertools/powertools-lambda-python/issues/2403)) +* **deps:** bump docker/setup-qemu-action from 2.1.0 to 2.2.0 ([#2404](https://github.com/aws-powertools/powertools-lambda-python/issues/2404)) +* **deps:** bump docker/setup-buildx-action from 2.6.0 to 2.7.0 ([#2450](https://github.com/aws-powertools/powertools-lambda-python/issues/2450)) +* **deps:** bump pydantic from 1.10.8 to 1.10.9 ([#2405](https://github.com/aws-powertools/powertools-lambda-python/issues/2405)) +* **deps:** bump actions/checkout from 3.5.2 to 3.5.3 ([#2431](https://github.com/aws-powertools/powertools-lambda-python/issues/2431)) +* **deps-dev:** bump ijson from 3.2.0.post0 to 3.2.1 ([#2441](https://github.com/aws-powertools/powertools-lambda-python/issues/2441)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.115 to 1.26.152 ([#2444](https://github.com/aws-powertools/powertools-lambda-python/issues/2444)) +* **deps-dev:** bump filelock from 3.12.0 to 3.12.2 ([#2446](https://github.com/aws-powertools/powertools-lambda-python/issues/2446)) +* **deps-dev:** bump aws-cdk from 2.83.0 to 2.83.1 ([#2432](https://github.com/aws-powertools/powertools-lambda-python/issues/2432)) +* **deps-dev:** bump cfn-lint from 0.77.6 to 0.77.7 ([#2414](https://github.com/aws-powertools/powertools-lambda-python/issues/2414)) +* **deps-dev:** bump pytest from 7.3.1 to 7.3.2 ([#2443](https://github.com/aws-powertools/powertools-lambda-python/issues/2443)) +* **deps-dev:** bump sentry-sdk from 1.25.0 to 1.25.1 ([#2408](https://github.com/aws-powertools/powertools-lambda-python/issues/2408)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.147 to 1.26.149 ([#2410](https://github.com/aws-powertools/powertools-lambda-python/issues/2410)) +* **deps-dev:** bump aws-cdk from 2.82.0 to 2.83.0 ([#2406](https://github.com/aws-powertools/powertools-lambda-python/issues/2406)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.53 to 1.26.149 ([#2409](https://github.com/aws-powertools/powertools-lambda-python/issues/2409)) +* **deps-dev:** bump cfn-lint from 0.77.7 to 0.77.8 ([#2451](https://github.com/aws-powertools/powertools-lambda-python/issues/2451)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.127 to 1.26.153 ([#2452](https://github.com/aws-powertools/powertools-lambda-python/issues/2452)) +* **deps-dev:** bump cfn-lint from 0.77.8 to 0.77.9 ([#2472](https://github.com/aws-powertools/powertools-lambda-python/issues/2472)) +* **deps-dev:** bump flake8-comprehensions from 3.12.0 to 3.13.0 ([#2471](https://github.com/aws-powertools/powertools-lambda-python/issues/2471)) +* **deps-dev:** bump mkdocs-material from 9.1.15 to 9.1.16 ([#2470](https://github.com/aws-powertools/powertools-lambda-python/issues/2470)) +* **deps-dev:** bump aws-cdk from 2.83.1 to 2.84.0 ([#2460](https://github.com/aws-powertools/powertools-lambda-python/issues/2460)) + + +<a name="v2.16.2"></a> +## [v2.16.2] - 2023-06-06 +## Bug Fixes + +* **parameters:** AppConfigProvider when retrieving multiple unique configuration names ([#2378](https://github.com/aws-powertools/powertools-lambda-python/issues/2378)) +* **shared:** move to static version bumping to prevent issues with customers custom builds ([#2386](https://github.com/aws-powertools/powertools-lambda-python/issues/2386)) + +## Maintenance + +* version bump +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.108 to 1.26.147 ([#2383](https://github.com/aws-powertools/powertools-lambda-python/issues/2383)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.122 to 1.26.147 ([#2382](https://github.com/aws-powertools/powertools-lambda-python/issues/2382)) +* **deps-dev:** bump sentry-sdk from 1.24.0 to 1.25.0 ([#2374](https://github.com/aws-powertools/powertools-lambda-python/issues/2374)) +* **deps-dev:** bump aws-cdk from 2.81.0 to 2.82.0 ([#2373](https://github.com/aws-powertools/powertools-lambda-python/issues/2373)) +* **typing:** add setLevel and addHandler to Logger for mypy/pyright ([#2388](https://github.com/aws-powertools/powertools-lambda-python/issues/2388)) + + +<a name="v2.16.1"></a> +## [v2.16.1] - 2023-06-02 +## Bug Fixes + +* **shared:** skip user agent on much older botocore versions ([#2366](https://github.com/aws-powertools/powertools-lambda-python/issues/2366)) + +## Maintenance + +* version bump + + +<a name="v2.16.0"></a> +## [v2.16.0] - 2023-06-02 +## Bug Fixes + +* **docs:** use concrete secrets from settings ([#2322](https://github.com/aws-powertools/powertools-lambda-python/issues/2322)) +* **event_source:** change the import location of boto3 in CodePipelineJobEvent data class ([#2353](https://github.com/aws-powertools/powertools-lambda-python/issues/2353)) +* **logger:** add setLevel function to set level programmatically ([#2320](https://github.com/aws-powertools/powertools-lambda-python/issues/2320)) + +## Code Refactoring + +* **logger:** remove subclassing and move unnecessary APIs ([#2334](https://github.com/aws-powertools/powertools-lambda-python/issues/2334)) + +## Documentation + +* **batch:** add encryption at rest for SQS ([#2290](https://github.com/aws-powertools/powertools-lambda-python/issues/2290)) +* **batch_processing:** snippets split, improved, and lint ([#2231](https://github.com/aws-powertools/powertools-lambda-python/issues/2231)) +* **feature_flags:** snippets split, improved, and lint ([#2222](https://github.com/aws-powertools/powertools-lambda-python/issues/2222)) +* **project:** rename project to Powertools for AWS Lambda (Python) ([#2313](https://github.com/aws-powertools/powertools-lambda-python/issues/2313)) + +## Features + +* **docs:** Move docs to S3 ([#2277](https://github.com/aws-powertools/powertools-lambda-python/issues/2277)) +* **event_source:** allow multiple CORS origins ([#2279](https://github.com/aws-powertools/powertools-lambda-python/issues/2279)) +* **parser:** add support for parsing SQS events wrapped in Kinesis Firehose ([#2294](https://github.com/aws-powertools/powertools-lambda-python/issues/2294)) +* **user-agent:** add custom header User-Agent to AWS SDK requests ([#2267](https://github.com/aws-powertools/powertools-lambda-python/issues/2267)) + +## Maintenance + +* version bump +* **ci:** remove auto-merge workflow ([#2214](https://github.com/aws-powertools/powertools-lambda-python/issues/2214)) +* **ci:** schedule changelog to rebuild daily at 8am, and on release only ([#2216](https://github.com/aws-powertools/powertools-lambda-python/issues/2216)) +* **ci:** create pull request on changelog update ([#2224](https://github.com/aws-powertools/powertools-lambda-python/issues/2224)) +* **ci:** skip analytics on forks ([#2225](https://github.com/aws-powertools/powertools-lambda-python/issues/2225)) +* **ci:** enforce zero trust for third party workflows ([#2215](https://github.com/aws-powertools/powertools-lambda-python/issues/2215)) +* **ci:** convert create-pr steps into composite action ([#2238](https://github.com/aws-powertools/powertools-lambda-python/issues/2238)) +* **ci:** bump package version after release via pull request ([#2239](https://github.com/aws-powertools/powertools-lambda-python/issues/2239)) +* **ci:** update layer ARN docs and create PR during release ([#2240](https://github.com/aws-powertools/powertools-lambda-python/issues/2240)) +* **ci:** fail create-pr when branch cannot be created or behind tip +* **ci:** filter out bot commits from CHANGELOG +* **ci:** add more permissions to analytics +* **ci:** source code tampering protection for release ([#2301](https://github.com/aws-powertools/powertools-lambda-python/issues/2301)) +* **deps:** bump fastjsonschema from 2.16.3 to 2.17.1 ([#2307](https://github.com/aws-powertools/powertools-lambda-python/issues/2307)) +* **deps:** bump aws-actions/configure-aws-credentials from 2.0.0 to 2.1.0 ([#2350](https://github.com/aws-powertools/powertools-lambda-python/issues/2350)) +* **deps:** bump typing-extensions from 4.5.0 to 4.6.2 ([#2345](https://github.com/aws-powertools/powertools-lambda-python/issues/2345)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.2 to 2.1.3 ([#2227](https://github.com/aws-powertools/powertools-lambda-python/issues/2227)) +* **deps:** bump actions/setup-python from 4.6.0 to 4.6.1 ([#2325](https://github.com/aws-powertools/powertools-lambda-python/issues/2325)) +* **deps:** update mkdocs configuration to support pymdown-extensions 10.0 ([#2271](https://github.com/aws-powertools/powertools-lambda-python/issues/2271)) +* **deps:** bump pymdown-extensions from 9.11 to 10.0 ([#2262](https://github.com/aws-powertools/powertools-lambda-python/issues/2262)) +* **deps:** bump pydantic from 1.10.7 to 1.10.8 ([#2316](https://github.com/aws-powertools/powertools-lambda-python/issues/2316)) +* **deps:** bump codecov/codecov-action from 3.1.3 to 3.1.4 ([#2263](https://github.com/aws-powertools/powertools-lambda-python/issues/2263)) +* **deps:** bump requests from 2.28.2 to 2.31.0 ([#2308](https://github.com/aws-powertools/powertools-lambda-python/issues/2308)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.116 to 1.26.135 ([#2282](https://github.com/aws-powertools/powertools-lambda-python/issues/2282)) +* **deps-dev:** bump pytest-xdist from 3.2.1 to 3.3.0 ([#2251](https://github.com/aws-powertools/powertools-lambda-python/issues/2251)) +* **deps-dev:** bump aws-cdk from 2.79.0 to 2.79.1 ([#2252](https://github.com/aws-powertools/powertools-lambda-python/issues/2252)) +* **deps-dev:** bump mkdocs-material from 9.1.11 to 9.1.12 ([#2253](https://github.com/aws-powertools/powertools-lambda-python/issues/2253)) +* **deps-dev:** bump aws-cdk from 2.79.1 to 2.80.0 ([#2305](https://github.com/aws-powertools/powertools-lambda-python/issues/2305)) +* **deps-dev:** bump mkdocs-material from 9.1.13 to 9.1.14 ([#2304](https://github.com/aws-powertools/powertools-lambda-python/issues/2304)) +* **deps-dev:** bump mkdocs-material from 9.1.12 to 9.1.13 ([#2280](https://github.com/aws-powertools/powertools-lambda-python/issues/2280)) +* **deps-dev:** bump aws-cdk from 2.80.0 to 2.81.0 ([#2332](https://github.com/aws-powertools/powertools-lambda-python/issues/2332)) +* **deps-dev:** bump sentry-sdk from 1.22.2 to 1.23.0 ([#2264](https://github.com/aws-powertools/powertools-lambda-python/issues/2264)) +* **deps-dev:** bump sentry-sdk from 1.23.1 to 1.24.0 ([#2314](https://github.com/aws-powertools/powertools-lambda-python/issues/2314)) +* **deps-dev:** bump types-requests from 2.30.0.0 to 2.31.0.0 ([#2315](https://github.com/aws-powertools/powertools-lambda-python/issues/2315)) +* **deps-dev:** bump httpx from 0.24.0 to 0.24.1 ([#2298](https://github.com/aws-powertools/powertools-lambda-python/issues/2298)) +* **deps-dev:** bump aws-cdk from 2.78.0 to 2.79.0 ([#2235](https://github.com/aws-powertools/powertools-lambda-python/issues/2235)) +* **deps-dev:** bump mypy from 1.2.0 to 1.3.0 ([#2233](https://github.com/aws-powertools/powertools-lambda-python/issues/2233)) +* **deps-dev:** bump pytest-cov from 4.0.0 to 4.1.0 ([#2327](https://github.com/aws-powertools/powertools-lambda-python/issues/2327)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.12 to 2.8.19.13 ([#2234](https://github.com/aws-powertools/powertools-lambda-python/issues/2234)) +* **deps-dev:** bump coverage from 7.2.5 to 7.2.6 ([#2326](https://github.com/aws-powertools/powertools-lambda-python/issues/2326)) +* **deps-dev:** bump mkdocs-material from 9.1.14 to 9.1.15 ([#2337](https://github.com/aws-powertools/powertools-lambda-python/issues/2337)) +* **deps-dev:** bump mkdocs-material from 9.1.9 to 9.1.11 ([#2229](https://github.com/aws-powertools/powertools-lambda-python/issues/2229)) +* **deps-dev:** bump cfn-lint from 0.77.4 to 0.77.5 ([#2228](https://github.com/aws-powertools/powertools-lambda-python/issues/2228)) +* **deps-dev:** bump cfn-lint from 0.77.5 to 0.77.6 ([#2360](https://github.com/aws-powertools/powertools-lambda-python/issues/2360)) +* **deps-dev:** bump coverage from 7.2.6 to 7.2.7 ([#2338](https://github.com/aws-powertools/powertools-lambda-python/issues/2338)) +* **deps-dev:** bump types-requests from 2.31.0.0 to 2.31.0.1 ([#2339](https://github.com/aws-powertools/powertools-lambda-python/issues/2339)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.99 to 1.26.127 ([#2219](https://github.com/aws-powertools/powertools-lambda-python/issues/2219)) +* **deps-dev:** bump types-requests from 2.29.0.0 to 2.30.0.0 ([#2220](https://github.com/aws-powertools/powertools-lambda-python/issues/2220)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.116 to 1.26.127 ([#2218](https://github.com/aws-powertools/powertools-lambda-python/issues/2218)) +* **deps-dev:** bump pytest-xdist from 3.3.0 to 3.3.1 ([#2297](https://github.com/aws-powertools/powertools-lambda-python/issues/2297)) +* **deps-dev:** bump sentry-sdk from 1.23.0 to 1.23.1 ([#2283](https://github.com/aws-powertools/powertools-lambda-python/issues/2283)) +* **deps-dev:** bump aws-cdk from 2.77.0 to 2.78.0 ([#2202](https://github.com/aws-powertools/powertools-lambda-python/issues/2202)) +* **governance:** Fix python version in issue templates ([#2275](https://github.com/aws-powertools/powertools-lambda-python/issues/2275)) + + +<a name="v2.15.0"></a> +## [v2.15.0] - 2023-05-04 +## Bug Fixes + +* typo +* **ci:** pypi publishing was targetting test endpoint + +## Documentation + +* **batch:** fixed typo in DynamoDB Streams section ([#2189](https://github.com/aws-powertools/powertools-lambda-python/issues/2189)) +* **examples:** standardize lambda handler function name ([#2192](https://github.com/aws-powertools/powertools-lambda-python/issues/2192)) +* **homepage:** add customer references section ([#2159](https://github.com/aws-powertools/powertools-lambda-python/issues/2159)) +* **jmespath:** fix MD037/no-space-in-emphasis +* **tutorial:** use newer sam cli template; update to py3.10 ([#2167](https://github.com/aws-powertools/powertools-lambda-python/issues/2167)) +* **we-made-this:** add serverless transactional message app ([#2182](https://github.com/aws-powertools/powertools-lambda-python/issues/2182)) + +## Features + +* **ci:** dispatch GitHub analytics action ([#2161](https://github.com/aws-powertools/powertools-lambda-python/issues/2161)) +* **event_source:** support custom json_deserializer; add json_body in SQSEvent ([#2200](https://github.com/aws-powertools/powertools-lambda-python/issues/2200)) +* **event_source:** add support for dynamic partitions in the Api Gateway Authorizer event ([#2176](https://github.com/aws-powertools/powertools-lambda-python/issues/2176)) +* **event_sources:** Add __str__ to Data Classes base DictWrapper ([#2129](https://github.com/aws-powertools/powertools-lambda-python/issues/2129)) +* **jmespath:** new built-in envelopes to unwrap S3 events ([#2169](https://github.com/aws-powertools/powertools-lambda-python/issues/2169)) +* **logger:** add DatadogLogFormatter and observability provider ([#2183](https://github.com/aws-powertools/powertools-lambda-python/issues/2183)) +* **metrics:** add flush_metrics() method to allow manual flushing of metrics ([#2171](https://github.com/aws-powertools/powertools-lambda-python/issues/2171)) +* **parser:** add support for SQS-wrapped S3 event notifications ([#2108](https://github.com/aws-powertools/powertools-lambda-python/issues/2108)) + +## Maintenance + +* update v2 layer ARN on documentation +* add dummy reusable dispatch analytics job +* **ci:** remove build step from release env; no more secrets need +* **ci:** use new pypi trusted publisher for increase security ([#2198](https://github.com/aws-powertools/powertools-lambda-python/issues/2198)) +* **deps:** bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6 ([#2201](https://github.com/aws-powertools/powertools-lambda-python/issues/2201)) +* **deps-dev:** bump cfn-lint from 0.77.3 to 0.77.4 ([#2178](https://github.com/aws-powertools/powertools-lambda-python/issues/2178)) +* **deps-dev:** bump types-requests from 2.28.11.17 to 2.29.0.0 ([#2187](https://github.com/aws-powertools/powertools-lambda-python/issues/2187)) +* **deps-dev:** bump coverage from 7.2.4 to 7.2.5 ([#2186](https://github.com/aws-powertools/powertools-lambda-python/issues/2186)) +* **deps-dev:** bump mkdocs-material from 9.1.8 to 9.1.9 ([#2190](https://github.com/aws-powertools/powertools-lambda-python/issues/2190)) +* **deps-dev:** bump importlib-metadata from 6.5.0 to 6.6.0 ([#2163](https://github.com/aws-powertools/powertools-lambda-python/issues/2163)) +* **deps-dev:** bump mypy-boto3-xray from 1.26.11.post1 to 1.26.122 ([#2173](https://github.com/aws-powertools/powertools-lambda-python/issues/2173)) +* **deps-dev:** bump aws-cdk from 2.76.0 to 2.77.0 ([#2174](https://github.com/aws-powertools/powertools-lambda-python/issues/2174)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.115 to 1.26.122 ([#2172](https://github.com/aws-powertools/powertools-lambda-python/issues/2172)) +* **deps-dev:** bump cfn-lint from 0.77.2 to 0.77.3 ([#2165](https://github.com/aws-powertools/powertools-lambda-python/issues/2165)) +* **deps-dev:** bump mkdocs-material from 9.1.6 to 9.1.8 ([#2162](https://github.com/aws-powertools/powertools-lambda-python/issues/2162)) +* **deps-dev:** bump coverage from 7.2.3 to 7.2.4 ([#2179](https://github.com/aws-powertools/powertools-lambda-python/issues/2179)) +* **governance:** add Lambda Powertools for .NET in issue templates ([#2196](https://github.com/aws-powertools/powertools-lambda-python/issues/2196)) + + +<a name="v2.14.1"></a> +## [v2.14.1] - 2023-04-21 +## Bug Fixes + +* **batch:** resolve use of ValidationError in batch ([#2157](https://github.com/aws-powertools/powertools-lambda-python/issues/2157)) +* **e2e:** fix test brittleness ([#2152](https://github.com/aws-powertools/powertools-lambda-python/issues/2152)) + +## Documentation + +* **readme:** update python version badge to 3.10 + +## Features + +* **event_sources:** add queue_url field in SQS EventSource DataClass ([#2146](https://github.com/aws-powertools/powertools-lambda-python/issues/2146)) + +## Maintenance + +* update v2 layer ARN on documentation +* add Python 3.10 PyPi language classifier ([#2144](https://github.com/aws-powertools/powertools-lambda-python/issues/2144)) +* update v2 layer ARN on documentation +* **batch:** safeguard custom use of BatchProcessingError exception ([#2155](https://github.com/aws-powertools/powertools-lambda-python/issues/2155)) +* **deps:** bump codecov/codecov-action from 3.1.2 to 3.1.3 ([#2153](https://github.com/aws-powertools/powertools-lambda-python/issues/2153)) +* **deps:** bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 ([#2140](https://github.com/aws-powertools/powertools-lambda-python/issues/2140)) +* **deps-dev:** bump aws-cdk from 2.75.0 to 2.75.1 ([#2150](https://github.com/aws-powertools/powertools-lambda-python/issues/2150)) +* **deps-dev:** bump aws-cdk from 2.75.1 to 2.76.0 ([#2154](https://github.com/aws-powertools/powertools-lambda-python/issues/2154)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.89 to 1.26.116 ([#2147](https://github.com/aws-powertools/powertools-lambda-python/issues/2147)) +* **deps-dev:** bump importlib-metadata from 6.4.1 to 6.5.0 ([#2141](https://github.com/aws-powertools/powertools-lambda-python/issues/2141)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.104 to 1.26.116 ([#2149](https://github.com/aws-powertools/powertools-lambda-python/issues/2149)) +* **deps-dev:** bump filelock from 3.11.0 to 3.12.0 ([#2142](https://github.com/aws-powertools/powertools-lambda-python/issues/2142)) +* **deps-dev:** bump cfn-lint from 0.77.1 to 0.77.2 ([#2148](https://github.com/aws-powertools/powertools-lambda-python/issues/2148)) + + +<a name="v2.14.0"></a> +## [v2.14.0] - 2023-04-18 +## Bug Fixes + +* enable python 3.10 on SAR template +* **ci:** fix layer version in tracer, logger and metrics +* **ci:** typo +* **docs:** add Layer ARN for new 5 regions +* **layers:** add debug to update layer arn script + +## Features + +* **runtime:** add support for python 3.10 ([#2137](https://github.com/aws-powertools/powertools-lambda-python/issues/2137)) + +## Maintenance + +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* **ci:** add support for x86-64 regions only ([#2122](https://github.com/aws-powertools/powertools-lambda-python/issues/2122)) +* **deps-dev:** bump importlib-metadata from 6.3.0 to 6.4.1 ([#2134](https://github.com/aws-powertools/powertools-lambda-python/issues/2134)) +* **deps-dev:** bump cfn-lint from 0.77.0 to 0.77.1 ([#2133](https://github.com/aws-powertools/powertools-lambda-python/issues/2133)) +* **deps-dev:** bump pytest from 7.3.0 to 7.3.1 ([#2127](https://github.com/aws-powertools/powertools-lambda-python/issues/2127)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.109 to 1.26.114 ([#2126](https://github.com/aws-powertools/powertools-lambda-python/issues/2126)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.114 to 1.26.115 ([#2135](https://github.com/aws-powertools/powertools-lambda-python/issues/2135)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.97.post1 to 1.26.115 ([#2132](https://github.com/aws-powertools/powertools-lambda-python/issues/2132)) +* **github:** new tech debt issue form ([#2131](https://github.com/aws-powertools/powertools-lambda-python/issues/2131)) +* **layer:** change layer-balance script to support new regions + +## Reverts +* chore: update v2 layer ARN on documentation + + +<a name="v2.13.0"></a> +## [v2.13.0] - 2023-04-14 +## Bug Fixes + +* **ci:** replace the correct files for Layer ARN +* **ci:** fix working directory +* **ci:** add debug log to NPM install +* **ci:** use project's CDK version when building layers +* **ci:** add the rest of the changed docs +* **ci:** update layer version on logger, tracer and metrics docs ([#2120](https://github.com/aws-powertools/powertools-lambda-python/issues/2120)) +* **event_sources:** Update CodePipeline event source to include optional encryption_key field and make user_parameters field optional ([#2113](https://github.com/aws-powertools/powertools-lambda-python/issues/2113)) + +## Features + +* **parameters:** Configure max_age and decrypt parameters via environment variables ([#2088](https://github.com/aws-powertools/powertools-lambda-python/issues/2088)) + +## Maintenance + +* update v2 layer ARN on documentation +* **ci:** bump the cdk-aws-lambda-powertools-layer version ([#2121](https://github.com/aws-powertools/powertools-lambda-python/issues/2121)) +* **deps:** bump codecov/codecov-action from 3.1.1 to 3.1.2 ([#2110](https://github.com/aws-powertools/powertools-lambda-python/issues/2110)) +* **deps-dev:** bump httpx from 0.23.3 to 0.24.0 ([#2111](https://github.com/aws-powertools/powertools-lambda-python/issues/2111)) +* **deps-dev:** bump aws-cdk-lib from 2.73.0 to 2.74.0 ([#2123](https://github.com/aws-powertools/powertools-lambda-python/issues/2123)) +* **deps-dev:** bump mkdocs-material from 9.1.5 to 9.1.6 ([#2104](https://github.com/aws-powertools/powertools-lambda-python/issues/2104)) +* **deps-dev:** bump aws-cdk from 2.73.0 to 2.74.0 ([#2125](https://github.com/aws-powertools/powertools-lambda-python/issues/2125)) +* **deps-dev:** bump flake8-comprehensions from 3.11.1 to 3.12.0 ([#2124](https://github.com/aws-powertools/powertools-lambda-python/issues/2124)) +* **deps-dev:** bump mypy from 1.1.1 to 1.2.0 ([#2096](https://github.com/aws-powertools/powertools-lambda-python/issues/2096)) +* **deps-dev:** bump cfn-lint from 0.76.2 to 0.77.0 ([#2107](https://github.com/aws-powertools/powertools-lambda-python/issues/2107)) +* **deps-dev:** bump pytest from 7.2.2 to 7.3.0 ([#2106](https://github.com/aws-powertools/powertools-lambda-python/issues/2106)) +* **deps-dev:** bump importlib-metadata from 6.1.0 to 6.3.0 ([#2105](https://github.com/aws-powertools/powertools-lambda-python/issues/2105)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.80 to 1.26.109 ([#2103](https://github.com/aws-powertools/powertools-lambda-python/issues/2103)) +* **maintenance:** validate acknowledgement section is present ([#2112](https://github.com/aws-powertools/powertools-lambda-python/issues/2112)) + + +<a name="v2.12.0"></a> +## [v2.12.0] - 2023-04-07 +## Bug Fixes + +* **batch:** handle early validation errors for pydantic models (poison pill) [#2091](https://github.com/aws-powertools/powertools-lambda-python/issues/2091) ([#2099](https://github.com/aws-powertools/powertools-lambda-python/issues/2099)) + +## Documentation + +* **batch:** use newly supported Json model ([#2100](https://github.com/aws-powertools/powertools-lambda-python/issues/2100)) +* **homepage:** remove banner for end-of-support v1 ([#2098](https://github.com/aws-powertools/powertools-lambda-python/issues/2098)) +* **idempotency:** fixes to testing your code section ([#2073](https://github.com/aws-powertools/powertools-lambda-python/issues/2073)) +* **idempotency:** new sequence diagrams, fix idempotency record vs DynamoDB TTL confusion ([#2074](https://github.com/aws-powertools/powertools-lambda-python/issues/2074)) +* **parser:** fix highlighted line ([#2064](https://github.com/aws-powertools/powertools-lambda-python/issues/2064)) + +## Features + +* **batch:** reduce boilerplate with process_partial_response ([#2090](https://github.com/aws-powertools/powertools-lambda-python/issues/2090)) +* **idempotency:** allow custom sdk clients in DynamoDBPersistenceLayer ([#2087](https://github.com/aws-powertools/powertools-lambda-python/issues/2087)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump peaceiris/actions-gh-pages from 3.9.2 to 3.9.3 ([#2069](https://github.com/aws-powertools/powertools-lambda-python/issues/2069)) +* **deps:** bump aws-xray-sdk from 2.11.0 to 2.12.0 ([#2080](https://github.com/aws-powertools/powertools-lambda-python/issues/2080)) +* **deps-dev:** bump coverage from 7.2.2 to 7.2.3 ([#2092](https://github.com/aws-powertools/powertools-lambda-python/issues/2092)) +* **deps-dev:** bump aws-cdk from 2.72.1 to 2.73.0 ([#2093](https://github.com/aws-powertools/powertools-lambda-python/issues/2093)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.60 to 1.26.108 ([#2095](https://github.com/aws-powertools/powertools-lambda-python/issues/2095)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.11 to 2.8.19.12 ([#2085](https://github.com/aws-powertools/powertools-lambda-python/issues/2085)) +* **deps-dev:** bump cfn-lint from 0.76.1 to 0.76.2 ([#2084](https://github.com/aws-powertools/powertools-lambda-python/issues/2084)) +* **deps-dev:** bump aws-cdk from 2.72.0 to 2.72.1 ([#2081](https://github.com/aws-powertools/powertools-lambda-python/issues/2081)) +* **deps-dev:** bump filelock from 3.10.7 to 3.11.0 ([#2094](https://github.com/aws-powertools/powertools-lambda-python/issues/2094)) +* **deps-dev:** bump mkdocs-material from 9.1.4 to 9.1.5 ([#2077](https://github.com/aws-powertools/powertools-lambda-python/issues/2077)) +* **deps-dev:** bump aws-cdk-lib from 2.72.0 to 2.72.1 ([#2076](https://github.com/aws-powertools/powertools-lambda-python/issues/2076)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.99 to 1.26.104 ([#2075](https://github.com/aws-powertools/powertools-lambda-python/issues/2075)) +* **deps-dev:** bump aws-cdk from 2.71.0 to 2.72.0 ([#2071](https://github.com/aws-powertools/powertools-lambda-python/issues/2071)) +* **deps-dev:** bump aws-cdk-lib from 2.72.1 to 2.73.0 ([#2097](https://github.com/aws-powertools/powertools-lambda-python/issues/2097)) +* **deps-dev:** bump aws-cdk-lib from 2.71.0 to 2.72.0 ([#2070](https://github.com/aws-powertools/powertools-lambda-python/issues/2070)) +* **deps-dev:** bump black from 23.1.0 to 23.3.0 ([#2066](https://github.com/aws-powertools/powertools-lambda-python/issues/2066)) +* **deps-dev:** bump aws-cdk from 2.70.0 to 2.71.0 ([#2067](https://github.com/aws-powertools/powertools-lambda-python/issues/2067)) +* **deps-dev:** bump aws-cdk-lib from 2.70.0 to 2.71.0 ([#2065](https://github.com/aws-powertools/powertools-lambda-python/issues/2065)) + + +<a name="v2.11.0"></a> +## [v2.11.0] - 2023-03-29 +## Bug Fixes + +* **feature_flags:** make test conditions deterministic ([#2059](https://github.com/aws-powertools/powertools-lambda-python/issues/2059)) +* **feature_flags:** handle expected falsy values in conditions ([#2052](https://github.com/aws-powertools/powertools-lambda-python/issues/2052)) + +## Documentation + +* **logger:** warn append_keys on not being thread-safe ([#2046](https://github.com/aws-powertools/powertools-lambda-python/issues/2046)) + +## Features + +* **event_sources:** support for S3 Event Notifications through EventBridge ([#2024](https://github.com/aws-powertools/powertools-lambda-python/issues/2024)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump pydantic from 1.10.6 to 1.10.7 ([#2034](https://github.com/aws-powertools/powertools-lambda-python/issues/2034)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.97 to 1.26.97.post2 ([#2043](https://github.com/aws-powertools/powertools-lambda-python/issues/2043)) +* **deps-dev:** bump cfn-lint from 0.75.1 to 0.76.1 ([#2056](https://github.com/aws-powertools/powertools-lambda-python/issues/2056)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.10 to 2.8.19.11 ([#2057](https://github.com/aws-powertools/powertools-lambda-python/issues/2057)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.97.post2 to 1.26.99 ([#2054](https://github.com/aws-powertools/powertools-lambda-python/issues/2054)) +* **deps-dev:** bump mkdocs-material from 9.1.3 to 9.1.4 ([#2050](https://github.com/aws-powertools/powertools-lambda-python/issues/2050)) +* **deps-dev:** bump filelock from 3.10.2 to 3.10.4 ([#2048](https://github.com/aws-powertools/powertools-lambda-python/issues/2048)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.52 to 1.26.99 ([#2049](https://github.com/aws-powertools/powertools-lambda-python/issues/2049)) +* **deps-dev:** bump filelock from 3.10.1 to 3.10.2 ([#2045](https://github.com/aws-powertools/powertools-lambda-python/issues/2045)) +* **deps-dev:** bump types-requests from 2.28.11.15 to 2.28.11.16 ([#2044](https://github.com/aws-powertools/powertools-lambda-python/issues/2044)) +* **deps-dev:** bump filelock from 3.10.4 to 3.10.7 ([#2055](https://github.com/aws-powertools/powertools-lambda-python/issues/2055)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.97 to 1.26.97.post1 ([#2042](https://github.com/aws-powertools/powertools-lambda-python/issues/2042)) +* **deps-dev:** bump filelock from 3.10.0 to 3.10.1 ([#2036](https://github.com/aws-powertools/powertools-lambda-python/issues/2036)) +* **deps-dev:** bump aws-cdk from 2.69.0 to 2.70.0 ([#2039](https://github.com/aws-powertools/powertools-lambda-python/issues/2039)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.87 to 1.26.97 ([#2035](https://github.com/aws-powertools/powertools-lambda-python/issues/2035)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.62 to 1.26.97 ([#2037](https://github.com/aws-powertools/powertools-lambda-python/issues/2037)) +* **deps-dev:** bump aws-cdk-lib from 2.69.0 to 2.70.0 ([#2038](https://github.com/aws-powertools/powertools-lambda-python/issues/2038)) +* **deps-dev:** bump types-requests from 2.28.11.16 to 2.28.11.17 ([#2061](https://github.com/aws-powertools/powertools-lambda-python/issues/2061)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.77 to 1.26.97 ([#2033](https://github.com/aws-powertools/powertools-lambda-python/issues/2033)) +* **deps-dev:** bump flake8-comprehensions from 3.11.0 to 3.11.1 ([#2029](https://github.com/aws-powertools/powertools-lambda-python/issues/2029)) +* **deps-dev:** bump cfn-lint from 0.75.0 to 0.75.1 ([#2027](https://github.com/aws-powertools/powertools-lambda-python/issues/2027)) +* **deps-dev:** bump pytest-asyncio from 0.20.3 to 0.21.0 ([#2026](https://github.com/aws-powertools/powertools-lambda-python/issues/2026)) + + +<a name="v2.10.0"></a> +## [v2.10.0] - 2023-03-17 +## Bug Fixes + +* only allow one e2e test at a time +* **build:** auto-generate setup.py for legacy build tools ([#2013](https://github.com/aws-powertools/powertools-lambda-python/issues/2013)) +* **ci:** bump CDK version +* **typing:** swap NoReturn with None for methods with no return value ([#2004](https://github.com/aws-powertools/powertools-lambda-python/issues/2004)) + +## Documentation + +* **homepage:** revamp install UX & share how we build Lambda Layer ([#1978](https://github.com/aws-powertools/powertools-lambda-python/issues/1978)) +* **metrics:** fix high-resolution metrics announcement link ([#2017](https://github.com/aws-powertools/powertools-lambda-python/issues/2017)) + +## Features + +* **event_sources:** support for custom properties in ActiveMQEvent ([#1999](https://github.com/aws-powertools/powertools-lambda-python/issues/1999)) +* **parser:** support for S3 Event Notifications via EventBridge ([#1982](https://github.com/aws-powertools/powertools-lambda-python/issues/1982)) + +## Maintenance + +* update v2 layer ARN on documentation +* **ci:** allow dependabot to upgrade CDK for JS +* **deps:** bump docker/setup-buildx-action from 2.4.1 to 2.5.0 ([#1995](https://github.com/aws-powertools/powertools-lambda-python/issues/1995)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.1 to 2.1.2 ([#1979](https://github.com/aws-powertools/powertools-lambda-python/issues/1979)) +* **deps:** bump aws-actions/configure-aws-credentials from 1 to 2 ([#1987](https://github.com/aws-powertools/powertools-lambda-python/issues/1987)) +* **deps:** bump pydantic from 1.10.5 to 1.10.6 ([#1991](https://github.com/aws-powertools/powertools-lambda-python/issues/1991)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.49 to 1.26.89 ([#1996](https://github.com/aws-powertools/powertools-lambda-python/issues/1996)) +* **deps-dev:** bump cfn-lint from 0.74.2 to 0.74.3 ([#2008](https://github.com/aws-powertools/powertools-lambda-python/issues/2008)) +* **deps-dev:** bump filelock from 3.9.0 to 3.9.1 ([#2006](https://github.com/aws-powertools/powertools-lambda-python/issues/2006)) +* **deps-dev:** bump aws-cdk-lib from 2.68.0 to 2.69.0 ([#2007](https://github.com/aws-powertools/powertools-lambda-python/issues/2007)) +* **deps-dev:** bump cfn-lint from 0.74.1 to 0.74.2 ([#2005](https://github.com/aws-powertools/powertools-lambda-python/issues/2005)) +* **deps-dev:** bump mypy from 0.982 to 1.1.1 ([#1985](https://github.com/aws-powertools/powertools-lambda-python/issues/1985)) +* **deps-dev:** bump pytest-xdist from 3.2.0 to 3.2.1 ([#2000](https://github.com/aws-powertools/powertools-lambda-python/issues/2000)) +* **deps-dev:** bump flake8-bugbear from 23.2.13 to 23.3.12 ([#2001](https://github.com/aws-powertools/powertools-lambda-python/issues/2001)) +* **deps-dev:** bump bandit from 1.7.4 to 1.7.5 ([#1997](https://github.com/aws-powertools/powertools-lambda-python/issues/1997)) +* **deps-dev:** bump mkdocs-material from 9.1.2 to 9.1.3 ([#2009](https://github.com/aws-powertools/powertools-lambda-python/issues/2009)) +* **deps-dev:** bump aws-cdk from 2.67.0 to 2.69.0 ([#2010](https://github.com/aws-powertools/powertools-lambda-python/issues/2010)) +* **deps-dev:** bump mkdocs-material from 9.1.1 to 9.1.2 ([#1994](https://github.com/aws-powertools/powertools-lambda-python/issues/1994)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.84 to 1.26.87 ([#1993](https://github.com/aws-powertools/powertools-lambda-python/issues/1993)) +* **deps-dev:** bump filelock from 3.9.1 to 3.10.0 ([#2019](https://github.com/aws-powertools/powertools-lambda-python/issues/2019)) +* **deps-dev:** bump aws-cdk-lib from 2.67.0 to 2.68.0 ([#1992](https://github.com/aws-powertools/powertools-lambda-python/issues/1992)) +* **deps-dev:** bump cfn-lint from 0.74.0 to 0.74.1 ([#1988](https://github.com/aws-powertools/powertools-lambda-python/issues/1988)) +* **deps-dev:** bump coverage from 7.2.1 to 7.2.2 ([#2021](https://github.com/aws-powertools/powertools-lambda-python/issues/2021)) +* **deps-dev:** bump pytest from 7.2.1 to 7.2.2 ([#1980](https://github.com/aws-powertools/powertools-lambda-python/issues/1980)) +* **deps-dev:** bump cfn-lint from 0.74.3 to 0.75.0 ([#2020](https://github.com/aws-powertools/powertools-lambda-python/issues/2020)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.9 to 2.8.19.10 ([#1973](https://github.com/aws-powertools/powertools-lambda-python/issues/1973)) +* **deps-dev:** bump hvac from 1.0.2 to 1.1.0 ([#1983](https://github.com/aws-powertools/powertools-lambda-python/issues/1983)) +* **deps-dev:** bump mkdocs-material from 9.1.0 to 9.1.1 ([#1984](https://github.com/aws-powertools/powertools-lambda-python/issues/1984)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.24 to 1.26.84 ([#1981](https://github.com/aws-powertools/powertools-lambda-python/issues/1981)) +* **deps-dev:** bump mkdocs-material from 9.0.15 to 9.1.0 ([#1976](https://github.com/aws-powertools/powertools-lambda-python/issues/1976)) +* **deps-dev:** bump cfn-lint from 0.67.0 to 0.74.0 ([#1974](https://github.com/aws-powertools/powertools-lambda-python/issues/1974)) +* **deps-dev:** bump aws-cdk-lib from 2.66.1 to 2.67.0 ([#1977](https://github.com/aws-powertools/powertools-lambda-python/issues/1977)) + + +<a name="v2.9.1"></a> +## [v2.9.1] - 2023-03-01 +## Bug Fixes + +* **idempotency:** revert dict mutation that impacted static_pk_value feature ([#1970](https://github.com/aws-powertools/powertools-lambda-python/issues/1970)) + +## Documentation + +* **appsync:** add mutation example and infrastructure fix ([#1964](https://github.com/aws-powertools/powertools-lambda-python/issues/1964)) +* **parameters:** fix typos and inconsistencies ([#1966](https://github.com/aws-powertools/powertools-lambda-python/issues/1966)) + +## Maintenance + +* update project description +* update v2 layer ARN on documentation +* **ci:** disable pypi test due to maintenance mode +* **ci:** replace deprecated set-output commands ([#1957](https://github.com/aws-powertools/powertools-lambda-python/issues/1957)) +* **deps:** bump fastjsonschema from 2.16.2 to 2.16.3 ([#1961](https://github.com/aws-powertools/powertools-lambda-python/issues/1961)) +* **deps:** bump release-drafter/release-drafter from 5.22.0 to 5.23.0 ([#1947](https://github.com/aws-powertools/powertools-lambda-python/issues/1947)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.1.0 to 2.1.1 ([#1958](https://github.com/aws-powertools/powertools-lambda-python/issues/1958)) +* **deps-dev:** bump coverage from 7.2.0 to 7.2.1 ([#1963](https://github.com/aws-powertools/powertools-lambda-python/issues/1963)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.8 to 2.8.19.9 ([#1960](https://github.com/aws-powertools/powertools-lambda-python/issues/1960)) +* **deps-dev:** bump mkdocs-material from 9.0.14 to 9.0.15 ([#1959](https://github.com/aws-powertools/powertools-lambda-python/issues/1959)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.55 to 1.26.80 ([#1967](https://github.com/aws-powertools/powertools-lambda-python/issues/1967)) +* **deps-dev:** bump types-requests from 2.28.11.14 to 2.28.11.15 ([#1962](https://github.com/aws-powertools/powertools-lambda-python/issues/1962)) +* **deps-dev:** bump aws-cdk-lib from 2.66.0 to 2.66.1 ([#1954](https://github.com/aws-powertools/powertools-lambda-python/issues/1954)) +* **deps-dev:** bump coverage from 7.1.0 to 7.2.0 ([#1951](https://github.com/aws-powertools/powertools-lambda-python/issues/1951)) +* **deps-dev:** bump mkdocs-material from 9.0.13 to 9.0.14 ([#1952](https://github.com/aws-powertools/powertools-lambda-python/issues/1952)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.43 to 1.26.77 ([#1949](https://github.com/aws-powertools/powertools-lambda-python/issues/1949)) +* **deps-dev:** bump types-requests from 2.28.11.13 to 2.28.11.14 ([#1946](https://github.com/aws-powertools/powertools-lambda-python/issues/1946)) +* **deps-dev:** bump aws-cdk-lib from 2.65.0 to 2.66.0 ([#1948](https://github.com/aws-powertools/powertools-lambda-python/issues/1948)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.7 to 2.8.19.8 ([#1945](https://github.com/aws-powertools/powertools-lambda-python/issues/1945)) +* **parser:** add workaround to make API GW test button work ([#1971](https://github.com/aws-powertools/powertools-lambda-python/issues/1971)) + + +<a name="v2.9.0"></a> +## [v2.9.0] - 2023-02-21 +## Bug Fixes + +* **ci:** upgraded cdk to match the version used on e2e tests +* **feature-flags:** revert RuleAction Enum inheritance on str ([#1910](https://github.com/aws-powertools/powertools-lambda-python/issues/1910)) +* **logger:** support exception and exception_name fields at any log level ([#1930](https://github.com/aws-powertools/powertools-lambda-python/issues/1930)) +* **metrics:** clarify no-metrics user warning ([#1935](https://github.com/aws-powertools/powertools-lambda-python/issues/1935)) + +## Documentation + +* **event_handlers:** Fix REST API - HTTP Methods documentation ([#1936](https://github.com/aws-powertools/powertools-lambda-python/issues/1936)) +* **home:** update powertools definition +* **we-made-this:** add CI/CD using Feature Flags video ([#1940](https://github.com/aws-powertools/powertools-lambda-python/issues/1940)) +* **we-made-this:** add Feature Flags post ([#1939](https://github.com/aws-powertools/powertools-lambda-python/issues/1939)) + +## Features + +* **batch:** add support to SQS FIFO queues (SqsFifoPartialProcessor) ([#1934](https://github.com/aws-powertools/powertools-lambda-python/issues/1934)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.5 to 2.1.0 ([#1943](https://github.com/aws-powertools/powertools-lambda-python/issues/1943)) +* **deps:** bump pydantic from 1.10.4 to 1.10.5 ([#1931](https://github.com/aws-powertools/powertools-lambda-python/issues/1931)) +* **deps-dev:** bump mkdocs-material from 9.0.12 to 9.0.13 ([#1944](https://github.com/aws-powertools/powertools-lambda-python/issues/1944)) +* **deps-dev:** bump aws-cdk-lib from 2.64.0 to 2.65.0 ([#1938](https://github.com/aws-powertools/powertools-lambda-python/issues/1938)) +* **deps-dev:** bump types-python-dateutil from 2.8.19.6 to 2.8.19.7 ([#1932](https://github.com/aws-powertools/powertools-lambda-python/issues/1932)) +* **deps-dev:** bump types-requests from 2.28.11.12 to 2.28.11.13 ([#1933](https://github.com/aws-powertools/powertools-lambda-python/issues/1933)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.63 to 1.26.71 ([#1928](https://github.com/aws-powertools/powertools-lambda-python/issues/1928)) +* **deps-dev:** bump flake8-bugbear from 23.1.20 to 23.2.13 ([#1924](https://github.com/aws-powertools/powertools-lambda-python/issues/1924)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.26.0.post1 to 1.26.70 ([#1925](https://github.com/aws-powertools/powertools-lambda-python/issues/1925)) + + +<a name="v2.8.0"></a> +## [v2.8.0] - 2023-02-10 +## Bug Fixes + +* **idempotency:** make idempotent_function decorator thread safe ([#1899](https://github.com/aws-powertools/powertools-lambda-python/issues/1899)) + +## Documentation + +* **engine:** re-enable clipboard button for code snippets +* **homepage:** Replace poetry command to add group parameter ([#1917](https://github.com/aws-powertools/powertools-lambda-python/issues/1917)) +* **homepage:** set url for end-of-support in announce block ([#1893](https://github.com/aws-powertools/powertools-lambda-python/issues/1893)) +* **idempotency:** add IAM permissions section ([#1902](https://github.com/aws-powertools/powertools-lambda-python/issues/1902)) +* **metrics:** remove reduntant wording before release +* **metrics:** fix syntax highlighting for new default_dimensions + +## Features + +* **batch:** add async_batch_processor for concurrent processing ([#1724](https://github.com/aws-powertools/powertools-lambda-python/issues/1724)) +* **metrics:** add default_dimensions to single_metric ([#1880](https://github.com/aws-powertools/powertools-lambda-python/issues/1880)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump docker/setup-buildx-action from 2.4.0 to 2.4.1 ([#1903](https://github.com/aws-powertools/powertools-lambda-python/issues/1903)) +* **deps-dev:** bump aws-cdk-lib from 2.63.0 to 2.63.2 ([#1904](https://github.com/aws-powertools/powertools-lambda-python/issues/1904)) +* **deps-dev:** bump black from 22.12.0 to 23.1.0 ([#1886](https://github.com/aws-powertools/powertools-lambda-python/issues/1886)) +* **deps-dev:** bump types-requests from 2.28.11.8 to 2.28.11.12 ([#1906](https://github.com/aws-powertools/powertools-lambda-python/issues/1906)) +* **deps-dev:** bump pytest-xdist from 3.1.0 to 3.2.0 ([#1905](https://github.com/aws-powertools/powertools-lambda-python/issues/1905)) +* **deps-dev:** bump aws-cdk-lib from 2.63.2 to 2.64.0 ([#1918](https://github.com/aws-powertools/powertools-lambda-python/issues/1918)) +* **deps-dev:** bump mkdocs-material from 9.0.11 to 9.0.12 ([#1919](https://github.com/aws-powertools/powertools-lambda-python/issues/1919)) +* **deps-dev:** bump mkdocs-material from 9.0.10 to 9.0.11 ([#1896](https://github.com/aws-powertools/powertools-lambda-python/issues/1896)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.26.0.post1 to 1.26.63 ([#1895](https://github.com/aws-powertools/powertools-lambda-python/issues/1895)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.58 to 1.26.62 ([#1889](https://github.com/aws-powertools/powertools-lambda-python/issues/1889)) +* **deps-dev:** bump mkdocs-material from 9.0.9 to 9.0.10 ([#1888](https://github.com/aws-powertools/powertools-lambda-python/issues/1888)) +* **deps-dev:** bump aws-cdk-lib from 2.62.2 to 2.63.0 ([#1887](https://github.com/aws-powertools/powertools-lambda-python/issues/1887)) +* **maintainers:** fix release workflow rename +* **pypi:** add new links to Pypi package homepage ([#1912](https://github.com/aws-powertools/powertools-lambda-python/issues/1912)) + + +<a name="v2.7.1"></a> +## [v2.7.1] - 2023-02-01 +## Bug Fixes + +* parallel_run should fail when e2e tests fail +* bump aws-cdk version +* **ci:** scope e2e tests by python version +* **ci:** add auth to API HTTP Gateway and Lambda Function Url ([#1882](https://github.com/aws-powertools/powertools-lambda-python/issues/1882)) +* **license:** correction to MIT + MIT-0 (no proprietary anymore) ([#1883](https://github.com/aws-powertools/powertools-lambda-python/issues/1883)) +* **license:** add MIT-0 license header ([#1871](https://github.com/aws-powertools/powertools-lambda-python/issues/1871)) +* **tests:** make logs fetching more robust ([#1878](https://github.com/aws-powertools/powertools-lambda-python/issues/1878)) +* **tests:** remove custom workers +* **tests:** make sure multiple e2e tests run concurrently ([#1861](https://github.com/aws-powertools/powertools-lambda-python/issues/1861)) + +## Documentation + +* **event-source:** fix incorrect method in example CloudWatch Logs ([#1857](https://github.com/aws-powertools/powertools-lambda-python/issues/1857)) +* **homepage:** add banner for end-of-support v1 ([#1879](https://github.com/aws-powertools/powertools-lambda-python/issues/1879)) +* **parameters:** snippets split, improved, and lint ([#1564](https://github.com/aws-powertools/powertools-lambda-python/issues/1564)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump docker/setup-buildx-action from 2.0.0 to 2.4.0 ([#1873](https://github.com/aws-powertools/powertools-lambda-python/issues/1873)) +* **deps:** bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 ([#1855](https://github.com/aws-powertools/powertools-lambda-python/issues/1855)) +* **deps-dev:** bump mypy-boto3-s3 from 1.26.0.post1 to 1.26.58 ([#1868](https://github.com/aws-powertools/powertools-lambda-python/issues/1868)) +* **deps-dev:** bump isort from 5.11.4 to 5.11.5 ([#1875](https://github.com/aws-powertools/powertools-lambda-python/issues/1875)) +* **deps-dev:** bump aws-cdk-lib from 2.62.1 to 2.62.2 ([#1869](https://github.com/aws-powertools/powertools-lambda-python/issues/1869)) +* **deps-dev:** bump mkdocs-material from 9.0.6 to 9.0.8 ([#1874](https://github.com/aws-powertools/powertools-lambda-python/issues/1874)) +* **deps-dev:** bump aws-cdk-lib from 2.62.0 to 2.62.1 ([#1866](https://github.com/aws-powertools/powertools-lambda-python/issues/1866)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.35.post1 to 1.26.57 ([#1865](https://github.com/aws-powertools/powertools-lambda-python/issues/1865)) +* **deps-dev:** bump coverage from 7.0.5 to 7.1.0 ([#1862](https://github.com/aws-powertools/powertools-lambda-python/issues/1862)) +* **deps-dev:** bump aws-cdk-lib from 2.61.1 to 2.62.0 ([#1863](https://github.com/aws-powertools/powertools-lambda-python/issues/1863)) +* **deps-dev:** bump flake8-bugbear from 22.12.6 to 23.1.20 ([#1854](https://github.com/aws-powertools/powertools-lambda-python/issues/1854)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.49 to 1.26.55 ([#1856](https://github.com/aws-powertools/powertools-lambda-python/issues/1856)) + +## Reverts +* fix(tests): remove custom workers + + +<a name="v2.7.0"></a> +## [v2.7.0] - 2023-01-24 +## Bug Fixes + +* git-chlg docker image is broken + +## Features + +* **feature_flags:** Add Time based feature flags actions ([#1846](https://github.com/aws-powertools/powertools-lambda-python/issues/1846)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump peaceiris/actions-gh-pages from 3.9.1 to 3.9.2 ([#1841](https://github.com/aws-powertools/powertools-lambda-python/issues/1841)) +* **deps:** bump future from 0.18.2 to 0.18.3 ([#1836](https://github.com/aws-powertools/powertools-lambda-python/issues/1836)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.4 to 2.0.5 ([#1837](https://github.com/aws-powertools/powertools-lambda-python/issues/1837)) +* **deps-dev:** bump mkdocs-material from 9.0.4 to 9.0.5 ([#1840](https://github.com/aws-powertools/powertools-lambda-python/issues/1840)) +* **deps-dev:** bump types-requests from 2.28.11.7 to 2.28.11.8 ([#1843](https://github.com/aws-powertools/powertools-lambda-python/issues/1843)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.30 to 1.26.52 ([#1847](https://github.com/aws-powertools/powertools-lambda-python/issues/1847)) +* **deps-dev:** bump pytest from 7.2.0 to 7.2.1 ([#1838](https://github.com/aws-powertools/powertools-lambda-python/issues/1838)) +* **deps-dev:** bump aws-cdk-lib from 2.60.0 to 2.61.1 ([#1849](https://github.com/aws-powertools/powertools-lambda-python/issues/1849)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.49 to 1.26.53 ([#1850](https://github.com/aws-powertools/powertools-lambda-python/issues/1850)) +* **deps-dev:** bump mkdocs-material from 9.0.5 to 9.0.6 ([#1851](https://github.com/aws-powertools/powertools-lambda-python/issues/1851)) +* **deps-dev:** bump mkdocs-material from 9.0.3 to 9.0.4 ([#1833](https://github.com/aws-powertools/powertools-lambda-python/issues/1833)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.43 to 1.26.49 ([#1834](https://github.com/aws-powertools/powertools-lambda-python/issues/1834)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.40 to 1.26.49 ([#1835](https://github.com/aws-powertools/powertools-lambda-python/issues/1835)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.18 to 1.26.49 ([#1832](https://github.com/aws-powertools/powertools-lambda-python/issues/1832)) +* **deps-dev:** bump aws-cdk-lib from 2.59.0 to 2.60.0 ([#1831](https://github.com/aws-powertools/powertools-lambda-python/issues/1831)) + + +<a name="v2.6.0"></a> +## [v2.6.0] - 2023-01-12 +## Bug Fixes + +* **api_gateway:** fixed custom metrics issue when using debug mode ([#1827](https://github.com/aws-powertools/powertools-lambda-python/issues/1827)) + +## Documentation + +* **logger:** fix incorrect field names in example structured logs ([#1830](https://github.com/aws-powertools/powertools-lambda-python/issues/1830)) +* **logger:** Add warning of uncaught exceptions ([#1826](https://github.com/aws-powertools/powertools-lambda-python/issues/1826)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump pydantic from 1.10.2 to 1.10.4 ([#1817](https://github.com/aws-powertools/powertools-lambda-python/issues/1817)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.1 to 2.0.3 ([#1801](https://github.com/aws-powertools/powertools-lambda-python/issues/1801)) +* **deps:** bump release-drafter/release-drafter from 5.21.1 to 5.22.0 ([#1802](https://github.com/aws-powertools/powertools-lambda-python/issues/1802)) +* **deps:** bump gitpython from 3.1.29 to 3.1.30 ([#1812](https://github.com/aws-powertools/powertools-lambda-python/issues/1812)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 2.0.3 to 2.0.4 ([#1821](https://github.com/aws-powertools/powertools-lambda-python/issues/1821)) +* **deps:** bump peaceiris/actions-gh-pages from 3.9.0 to 3.9.1 ([#1814](https://github.com/aws-powertools/powertools-lambda-python/issues/1814)) +* **deps-dev:** bump mkdocs-material from 8.5.11 to 9.0.2 ([#1808](https://github.com/aws-powertools/powertools-lambda-python/issues/1808)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.11.post1 to 1.26.43 ([#1819](https://github.com/aws-powertools/powertools-lambda-python/issues/1819)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.27 to 1.26.43 ([#1820](https://github.com/aws-powertools/powertools-lambda-python/issues/1820)) +* **deps-dev:** bump filelock from 3.8.2 to 3.9.0 ([#1816](https://github.com/aws-powertools/powertools-lambda-python/issues/1816)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.11.post1 to 1.26.35.post1 ([#1818](https://github.com/aws-powertools/powertools-lambda-python/issues/1818)) +* **deps-dev:** bump ijson from 3.1.4 to 3.2.0.post0 ([#1815](https://github.com/aws-powertools/powertools-lambda-python/issues/1815)) +* **deps-dev:** bump coverage from 6.5.0 to 7.0.3 ([#1806](https://github.com/aws-powertools/powertools-lambda-python/issues/1806)) +* **deps-dev:** bump flake8-builtins from 2.0.1 to 2.1.0 ([#1799](https://github.com/aws-powertools/powertools-lambda-python/issues/1799)) +* **deps-dev:** bump coverage from 7.0.3 to 7.0.4 ([#1822](https://github.com/aws-powertools/powertools-lambda-python/issues/1822)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.12 to 1.26.40 ([#1811](https://github.com/aws-powertools/powertools-lambda-python/issues/1811)) +* **deps-dev:** bump isort from 5.11.3 to 5.11.4 ([#1809](https://github.com/aws-powertools/powertools-lambda-python/issues/1809)) +* **deps-dev:** bump aws-cdk-lib from 2.55.1 to 2.59.0 ([#1810](https://github.com/aws-powertools/powertools-lambda-python/issues/1810)) +* **deps-dev:** bump importlib-metadata from 5.1.0 to 6.0.0 ([#1804](https://github.com/aws-powertools/powertools-lambda-python/issues/1804)) +* **deps-dev:** bump mkdocs-material from 9.0.2 to 9.0.3 ([#1823](https://github.com/aws-powertools/powertools-lambda-python/issues/1823)) +* **deps-dev:** bump black from 22.10.0 to 22.12.0 ([#1770](https://github.com/aws-powertools/powertools-lambda-python/issues/1770)) +* **deps-dev:** bump flake8-black from 0.3.5 to 0.3.6 ([#1792](https://github.com/aws-powertools/powertools-lambda-python/issues/1792)) +* **deps-dev:** bump coverage from 7.0.4 to 7.0.5 ([#1829](https://github.com/aws-powertools/powertools-lambda-python/issues/1829)) +* **deps-dev:** bump types-requests from 2.28.11.5 to 2.28.11.7 ([#1795](https://github.com/aws-powertools/powertools-lambda-python/issues/1795)) + + +<a name="v2.5.0"></a> +## [v2.5.0] - 2022-12-21 +## Bug Fixes + +* **event_handlers:** omit explicit None HTTP header values ([#1793](https://github.com/aws-powertools/powertools-lambda-python/issues/1793)) + +## Documentation + +* **idempotency:** fix, improve, and increase visibility for batch integration ([#1776](https://github.com/aws-powertools/powertools-lambda-python/issues/1776)) +* **validation:** fix broken link; enrich built-in jmespath links ([#1777](https://github.com/aws-powertools/powertools-lambda-python/issues/1777)) + +## Features + +* **logger:** unwrap event from common models if asked to log ([#1778](https://github.com/aws-powertools/powertools-lambda-python/issues/1778)) + +## Maintenance + +* update v2 layer ARN on documentation +* **common:** reusable function to extract event from models +* **deps:** bump certifi from 2022.9.24 to 2022.12.7 ([#1768](https://github.com/aws-powertools/powertools-lambda-python/issues/1768)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.4.0 to 2.0.1 ([#1752](https://github.com/aws-powertools/powertools-lambda-python/issues/1752)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 1.3.0 to 1.4.0 ([#1749](https://github.com/aws-powertools/powertools-lambda-python/issues/1749)) +* **deps-dev:** bump pytest-asyncio from 0.20.2 to 0.20.3 ([#1767](https://github.com/aws-powertools/powertools-lambda-python/issues/1767)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.0.post1 to 1.26.17 ([#1753](https://github.com/aws-powertools/powertools-lambda-python/issues/1753)) +* **deps-dev:** bump isort from 5.10.1 to 5.11.2 ([#1782](https://github.com/aws-powertools/powertools-lambda-python/issues/1782)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.26.17 to 1.26.30 ([#1785](https://github.com/aws-powertools/powertools-lambda-python/issues/1785)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.13.post16 to 1.26.24 ([#1765](https://github.com/aws-powertools/powertools-lambda-python/issues/1765)) +* **deps-dev:** bump aws-cdk-lib from 2.54.0 to 2.55.1 ([#1787](https://github.com/aws-powertools/powertools-lambda-python/issues/1787)) +* **deps-dev:** bump aws-cdk-lib from 2.53.0 to 2.54.0 ([#1764](https://github.com/aws-powertools/powertools-lambda-python/issues/1764)) +* **deps-dev:** bump flake8-bugbear from 22.10.27 to 22.12.6 ([#1760](https://github.com/aws-powertools/powertools-lambda-python/issues/1760)) +* **deps-dev:** bump filelock from 3.8.0 to 3.8.2 ([#1759](https://github.com/aws-powertools/powertools-lambda-python/issues/1759)) +* **deps-dev:** bump pytest-xdist from 3.0.2 to 3.1.0 ([#1758](https://github.com/aws-powertools/powertools-lambda-python/issues/1758)) +* **deps-dev:** bump mkdocs-material from 8.5.10 to 8.5.11 ([#1756](https://github.com/aws-powertools/powertools-lambda-python/issues/1756)) +* **deps-dev:** bump importlib-metadata from 4.13.0 to 5.1.0 ([#1750](https://github.com/aws-powertools/powertools-lambda-python/issues/1750)) +* **deps-dev:** bump isort from 5.11.2 to 5.11.3 ([#1788](https://github.com/aws-powertools/powertools-lambda-python/issues/1788)) +* **deps-dev:** bump flake8-black from 0.3.3 to 0.3.5 ([#1738](https://github.com/aws-powertools/powertools-lambda-python/issues/1738)) +* **deps-dev:** bump mypy-boto3-logs from 1.26.17 to 1.26.27 ([#1775](https://github.com/aws-powertools/powertools-lambda-python/issues/1775)) +* **tests:** move shared_functions to unit tests + + +<a name="v2.4.0"></a> +## [v2.4.0] - 2022-11-24 +## Bug Fixes + +* **ci:** use gh-pages env as official docs are wrong +* **ci:** api docs path + +## Documentation + +* **idempotency:** fix register_lambda_context order ([#1747](https://github.com/aws-powertools/powertools-lambda-python/issues/1747)) +* **streaming:** fix leftover newline + +## Features + +* **streaming:** add new s3 streaming utility ([#1719](https://github.com/aws-powertools/powertools-lambda-python/issues/1719)) + +## Maintenance + +* update v2 layer ARN on documentation +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** re-create versioned API docs for new pages deployment +* **ci:** increase permission in parent job for docs publishing +* **ci:** attempt gh-pages deployment via beta route +* **deps:** bump aws-xray-sdk from 2.10.0 to 2.11.0 ([#1730](https://github.com/aws-powertools/powertools-lambda-python/issues/1730)) +* **deps-dev:** bump mypy-boto3-lambda from 1.26.0.post1 to 1.26.12 ([#1742](https://github.com/aws-powertools/powertools-lambda-python/issues/1742)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.26.0.post1 to 1.26.11.post1 ([#1746](https://github.com/aws-powertools/powertools-lambda-python/issues/1746)) +* **deps-dev:** bump aws-cdk-lib from 2.50.0 to 2.51.1 ([#1741](https://github.com/aws-powertools/powertools-lambda-python/issues/1741)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.26.0.post1 to 1.26.13.post16 ([#1743](https://github.com/aws-powertools/powertools-lambda-python/issues/1743)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.26.0.post1 to 1.26.12 ([#1744](https://github.com/aws-powertools/powertools-lambda-python/issues/1744)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.4 to 1.26.11.post1 ([#1740](https://github.com/aws-powertools/powertools-lambda-python/issues/1740)) +* **deps-dev:** bump types-requests from 2.28.11.4 to 2.28.11.5 ([#1729](https://github.com/aws-powertools/powertools-lambda-python/issues/1729)) +* **deps-dev:** bump mkdocs-material from 8.5.9 to 8.5.10 ([#1731](https://github.com/aws-powertools/powertools-lambda-python/issues/1731)) +* **governance:** remove markdown rendering from docs issue template + +## Regression + +* **ci:** new gh-pages beta doesn't work either; reverting as gh-pages is disrupted + + +<a name="v2.3.1"></a> +## [v2.3.1] - 2022-11-21 +## Bug Fixes + +* **apigateway:** support dynamic routes with equal sign (RFC3986) ([#1737](https://github.com/aws-powertools/powertools-lambda-python/issues/1737)) + +## Maintenance + +* update v2 layer ARN on documentation +* test build layer hardware to 8 core +* **deps-dev:** bump mypy-boto3-xray from 1.26.9 to 1.26.11.post1 ([#1734](https://github.com/aws-powertools/powertools-lambda-python/issues/1734)) + + +<a name="v2.3.0"></a> +## [v2.3.0] - 2022-11-17 +## Bug Fixes + +* **apigateway:** support nested router decorators ([#1709](https://github.com/aws-powertools/powertools-lambda-python/issues/1709)) +* **ci:** increase permission to allow version sync back to repo +* **ci:** disable pre-commit hook download from version bump +* **ci:** setup git client earlier to prevent dirty stash error +* **parameters:** get_secret correctly return SecretBinary value ([#1717](https://github.com/aws-powertools/powertools-lambda-python/issues/1717)) + +## Documentation + +* project name consistency +* **apigateway:** add all resolvers in testing your code section for accuracy ([#1688](https://github.com/aws-powertools/powertools-lambda-python/issues/1688)) +* **examples:** linting unnecessary whitespace +* **homepage:** update default value for `POWERTOOLS_DEV` ([#1695](https://github.com/aws-powertools/powertools-lambda-python/issues/1695)) +* **idempotency:** add missing Lambda Context; note on thread-safe ([#1732](https://github.com/aws-powertools/powertools-lambda-python/issues/1732)) +* **logger:** update uncaught exception message value + +## Features + +* **apigateway:** multiple exceptions in exception_handler ([#1707](https://github.com/aws-powertools/powertools-lambda-python/issues/1707)) +* **event_sources:** extract CloudWatch Logs in Kinesis streams ([#1710](https://github.com/aws-powertools/powertools-lambda-python/issues/1710)) +* **logger:** log uncaught exceptions via system's exception hook ([#1727](https://github.com/aws-powertools/powertools-lambda-python/issues/1727)) +* **parser:** export Pydantic.errors through escape hatch ([#1728](https://github.com/aws-powertools/powertools-lambda-python/issues/1728)) +* **parser:** extract CloudWatch Logs in Kinesis streams ([#1726](https://github.com/aws-powertools/powertools-lambda-python/issues/1726)) + +## Maintenance + +* apigw test event wrongly set with base64 +* update v2 layer ARN on documentation +* **ci:** revert custom hw for E2E due to lack of hw +* **ci:** try bigger hardware for e2e test +* **ci:** uncomment test pypi, fix version bump sync +* **ci:** limit to src only to prevent dependabot failures +* **ci:** use new custom hw for E2E +* **ci:** prevent dependabot updates to trigger E2E +* **ci:** bump hardware for build steps +* **deps:** bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 ([#1689](https://github.com/aws-powertools/powertools-lambda-python/issues/1689)) +* **deps-dev:** bump types-requests from 2.28.11.3 to 2.28.11.4 ([#1701](https://github.com/aws-powertools/powertools-lambda-python/issues/1701)) +* **deps-dev:** bump mypy-boto3-s3 from 1.25.0 to 1.26.0.post1 ([#1716](https://github.com/aws-powertools/powertools-lambda-python/issues/1716)) +* **deps-dev:** bump mypy-boto3-appconfigdata from 1.25.0 to 1.26.0.post1 ([#1704](https://github.com/aws-powertools/powertools-lambda-python/issues/1704)) +* **deps-dev:** bump mypy-boto3-xray from 1.25.0 to 1.26.0.post1 ([#1703](https://github.com/aws-powertools/powertools-lambda-python/issues/1703)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.25.0 to 1.26.0.post1 ([#1714](https://github.com/aws-powertools/powertools-lambda-python/issues/1714)) +* **deps-dev:** bump flake8-bugbear from 22.10.25 to 22.10.27 ([#1665](https://github.com/aws-powertools/powertools-lambda-python/issues/1665)) +* **deps-dev:** bump mypy-boto3-lambda from 1.25.0 to 1.26.0.post1 ([#1705](https://github.com/aws-powertools/powertools-lambda-python/issues/1705)) +* **deps-dev:** bump mypy-boto3-xray from 1.26.0.post1 to 1.26.9 ([#1720](https://github.com/aws-powertools/powertools-lambda-python/issues/1720)) +* **deps-dev:** bump mypy-boto3-logs from 1.25.0 to 1.26.3 ([#1702](https://github.com/aws-powertools/powertools-lambda-python/issues/1702)) +* **deps-dev:** bump mypy-boto3-ssm from 1.26.0.post1 to 1.26.4 ([#1721](https://github.com/aws-powertools/powertools-lambda-python/issues/1721)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.25.0 to 1.26.0.post1 ([#1722](https://github.com/aws-powertools/powertools-lambda-python/issues/1722)) +* **deps-dev:** bump pytest-asyncio from 0.20.1 to 0.20.2 ([#1723](https://github.com/aws-powertools/powertools-lambda-python/issues/1723)) +* **deps-dev:** bump flake8-builtins from 2.0.0 to 2.0.1 ([#1715](https://github.com/aws-powertools/powertools-lambda-python/issues/1715)) +* **deps-dev:** bump pytest-xdist from 2.5.0 to 3.0.2 ([#1655](https://github.com/aws-powertools/powertools-lambda-python/issues/1655)) +* **deps-dev:** bump mkdocs-material from 8.5.7 to 8.5.9 ([#1697](https://github.com/aws-powertools/powertools-lambda-python/issues/1697)) +* **deps-dev:** bump flake8-comprehensions from 3.10.0 to 3.10.1 ([#1699](https://github.com/aws-powertools/powertools-lambda-python/issues/1699)) +* **deps-dev:** bump types-requests from 2.28.11.2 to 2.28.11.3 ([#1698](https://github.com/aws-powertools/powertools-lambda-python/issues/1698)) +* **deps-dev:** bump pytest-benchmark from 3.4.1 to 4.0.0 ([#1659](https://github.com/aws-powertools/powertools-lambda-python/issues/1659)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.25.0 to 1.26.0.post1 ([#1691](https://github.com/aws-powertools/powertools-lambda-python/issues/1691)) +* **deps-dev:** bump mypy-boto3-ssm from 1.25.0 to 1.26.0.post1 ([#1690](https://github.com/aws-powertools/powertools-lambda-python/issues/1690)) +* **logger:** uncaught exception to use exception value as message +* **logger:** overload inject_lambda_context with generics ([#1583](https://github.com/aws-powertools/powertools-lambda-python/issues/1583)) + + +<a name="v2.2.0"></a> +## [v2.2.0] - 2022-11-07 +## Documentation + +* **homepage:** remove v1 layer limitation on pydantic not being included +* **tracer:** add note on why X-Ray SDK over ADOT closes [#1675](https://github.com/aws-powertools/powertools-lambda-python/issues/1675) + +## Features + +* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/aws-powertools/powertools-lambda-python/issues/1676)) +* **parameters:** add get_parameters_by_name for SSM params in distinct paths ([#1678](https://github.com/aws-powertools/powertools-lambda-python/issues/1678)) + +## Maintenance + +* update v2 layer ARN on documentation +* **deps:** bump package to 2.2.0 +* **deps-dev:** bump aws-cdk-lib from 2.49.0 to 2.50.0 ([#1683](https://github.com/aws-powertools/powertools-lambda-python/issues/1683)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.25.0 to 1.26.0.post1 ([#1682](https://github.com/aws-powertools/powertools-lambda-python/issues/1682)) +* **deps-dev:** bump mypy-boto3-cloudformation from 1.25.0 to 1.26.0.post1 ([#1679](https://github.com/aws-powertools/powertools-lambda-python/issues/1679)) +* **package:** correct pyproject version manually + + +<a name="v2.1.0"></a> +## [v2.1.0] - 2022-10-31 +## Bug Fixes + +* **ci:** linting issues after flake8-blackbear,mypy upgrades +* **deps:** update build system to poetry-core ([#1651](https://github.com/aws-powertools/powertools-lambda-python/issues/1651)) +* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/aws-powertools/powertools-lambda-python/issues/1669)) +* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/aws-powertools/powertools-lambda-python/issues/1670)) + +## Documentation + +* **community:** fix social handlers for Ran ([#1654](https://github.com/aws-powertools/powertools-lambda-python/issues/1654)) +* **community:** fix twitch parent domain for embedded video +* **homepage:** remove 3.6 and add hero image +* **homepage:** add Pulumi code example ([#1652](https://github.com/aws-powertools/powertools-lambda-python/issues/1652)) +* **index:** fold support us banner +* **index:** add quotes to pip for zsh customers +* **install:** address early v2 feedback on installation and project support +* **we-made-this:** new community content section ([#1650](https://github.com/aws-powertools/powertools-lambda-python/issues/1650)) + +## Features + +* **layers:** add layer balancer script ([#1643](https://github.com/aws-powertools/powertools-lambda-python/issues/1643)) +* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/aws-powertools/powertools-lambda-python/issues/1662)) +* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/aws-powertools/powertools-lambda-python/issues/1658)) + +## Maintenance + +* update v2 layer ARN on documentation +* **ci:** fix typo on version description +* **deps:** bump peaceiris/actions-gh-pages from 3.8.0 to 3.9.0 ([#1649](https://github.com/aws-powertools/powertools-lambda-python/issues/1649)) +* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/aws-powertools/powertools-lambda-python/issues/1627)) +* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/aws-powertools/powertools-lambda-python/issues/1664)) +* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/aws-powertools/powertools-lambda-python/issues/1628)) +* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/aws-powertools/powertools-lambda-python/issues/1635)) +* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/aws-powertools/powertools-lambda-python/issues/1671)) +* **docs:** remove v2 banner on top of the docs +* **governance:** remove 'area/' from PR labels + + +<a name="v2.0.0"></a> +## [v2.0.0] - 2022-10-24 +## Bug Fixes + +* lock dependencies +* mypy errors +* lint files +* **ci:** temporarly remove pypi test deployment +* **ci:** use docker driver on buildx +* **ci:** new artifact path, sed gnu/linux syntax, and pypi test +* **ci:** secret and OIDC inheritance in nested children workflow +* **ci:** build without buildkit +* **ci:** fix arm64 layer builds +* **ci:** remove v2 suffix from SAR apps ([#1633](https://github.com/aws-powertools/powertools-lambda-python/issues/1633)) +* **ci:** workflow should use npx for CDK CLI +* **parser:** S3Model Object Deleted omits size and eTag attr ([#1638](https://github.com/aws-powertools/powertools-lambda-python/issues/1638)) + +## Code Refactoring + +* **apigateway:** remove POWERTOOLS_EVENT_HANDLER_DEBUG env var ([#1620](https://github.com/aws-powertools/powertools-lambda-python/issues/1620)) +* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/aws-powertools/powertools-lambda-python/issues/1492)) +* **e2e:** make table name dynamic +* **e2e:** fix idempotency typing + +## Documentation + +* **batch:** remove legacy reference to sqs processor +* **homepage:** note about v2 version +* **homepage:** auto-update Layer ARN on every release ([#1610](https://github.com/aws-powertools/powertools-lambda-python/issues/1610)) +* **roadmap:** refresh roadmap post-v2 launch +* **roadmap:** include observability provider and lambda layer themes before v2 +* **upgrade_guide:** add latest changes and quick summary ([#1623](https://github.com/aws-powertools/powertools-lambda-python/issues/1623)) +* **v2:** document optional dependencies and local dev ([#1574](https://github.com/aws-powertools/powertools-lambda-python/issues/1574)) + +## Features + +* **apigateway:** ignore trailing slashes in routes (APIGatewayRestResolver) ([#1609](https://github.com/aws-powertools/powertools-lambda-python/issues/1609)) +* **ci:** release docs as alpha when doing a pre-release ([#1624](https://github.com/aws-powertools/powertools-lambda-python/issues/1624)) +* **data-classes:** replace AttributeValue in DynamoDBStreamEvent with deserialized Python values ([#1619](https://github.com/aws-powertools/powertools-lambda-python/issues/1619)) +* **data_classes:** add KinesisFirehoseEvent ([#1540](https://github.com/aws-powertools/powertools-lambda-python/issues/1540)) +* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/aws-powertools/powertools-lambda-python/issues/1455)) +* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/aws-powertools/powertools-lambda-python/issues/1487)) +* **idempotency:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1535](https://github.com/aws-powertools/powertools-lambda-python/issues/1535)) +* **layer:** publish SAR v2 via Github actions ([#1585](https://github.com/aws-powertools/powertools-lambda-python/issues/1585)) +* **layers:** add support for publishing v2 layer ([#1558](https://github.com/aws-powertools/powertools-lambda-python/issues/1558)) +* **parameters:** migrate AppConfig to new APIs due to API deprecation ([#1553](https://github.com/aws-powertools/powertools-lambda-python/issues/1553)) +* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/aws-powertools/powertools-lambda-python/issues/1486)) + +## Maintenance + +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* update v2 layer ARN on documentation +* merge v2 branch +* bump pyproject version to 2.0 +* **ci:** make release process manual +* **ci:** migrate E2E tests to CDK CLI and off Docker ([#1501](https://github.com/aws-powertools/powertools-lambda-python/issues/1501)) +* **ci:** remove v1 workflows ([#1617](https://github.com/aws-powertools/powertools-lambda-python/issues/1617)) +* **core:** expose modules in the Top-level package ([#1517](https://github.com/aws-powertools/powertools-lambda-python/issues/1517)) +* **dep:** add cfn-lint as a dev dependency; pre-commit ([#1612](https://github.com/aws-powertools/powertools-lambda-python/issues/1612)) +* **deps:** remove email-validator; use Str over EmailStr in SES model ([#1608](https://github.com/aws-powertools/powertools-lambda-python/issues/1608)) +* **deps:** bump release-drafter/release-drafter from 5.21.0 to 5.21.1 ([#1611](https://github.com/aws-powertools/powertools-lambda-python/issues/1611)) +* **deps:** lock importlib to 4.x +* **deps-dev:** bump mypy-boto3-s3 from 1.24.76 to 1.24.94 ([#1622](https://github.com/aws-powertools/powertools-lambda-python/issues/1622)) +* **deps-dev:** bump aws-cdk-lib from 2.46.0 to 2.47.0 ([#1629](https://github.com/aws-powertools/powertools-lambda-python/issues/1629)) +* **layer:** bump to 1.31.1 (v39) + + +<a name="v1.31.1"></a> +## [v1.31.1] - 2022-10-14 +## Bug Fixes + +* **parser:** loose validation on SNS fields to support FIFO ([#1606](https://github.com/aws-powertools/powertools-lambda-python/issues/1606)) + +## Documentation + +* **governance:** allow community to suggest feature content ([#1593](https://github.com/aws-powertools/powertools-lambda-python/issues/1593)) +* **governance:** new form to allow customers self-nominate as public reference ([#1589](https://github.com/aws-powertools/powertools-lambda-python/issues/1589)) +* **homepage:** include .NET powertools +* **idempotency:** "persisntence" typo ([#1596](https://github.com/aws-powertools/powertools-lambda-python/issues/1596)) +* **logger:** fix typo. ([#1587](https://github.com/aws-powertools/powertools-lambda-python/issues/1587)) + +## Maintenance + +* add dummy v2 sar deploy job +* bump layer version to 38 +* **deps-dev:** bump mypy-boto3-ssm from 1.24.81 to 1.24.90 ([#1594](https://github.com/aws-powertools/powertools-lambda-python/issues/1594)) +* **deps-dev:** bump flake8-builtins from 1.5.3 to 2.0.0 ([#1582](https://github.com/aws-powertools/powertools-lambda-python/issues/1582)) + + +<a name="v1.31.0"></a> +## [v1.31.0] - 2022-10-10 +## Bug Fixes + +* **metrics:** ensure dimension_set is reused across instances (pointer) ([#1581](https://github.com/aws-powertools/powertools-lambda-python/issues/1581)) + +## Documentation + +* **readme:** add lambda layer latest version badge + +## Features + +* **parser:** add KinesisFirehoseModel ([#1556](https://github.com/aws-powertools/powertools-lambda-python/issues/1556)) + +## Maintenance + +* **deps-dev:** bump types-requests from 2.28.11.1 to 2.28.11.2 ([#1576](https://github.com/aws-powertools/powertools-lambda-python/issues/1576)) +* **deps-dev:** bump typing-extensions from 4.3.0 to 4.4.0 ([#1575](https://github.com/aws-powertools/powertools-lambda-python/issues/1575)) +* **layer:** remove unsused GetFunction permission for the canary +* **layer:** bump to latest version 37 + + +<a name="v1.30.0"></a> +## [v1.30.0] - 2022-10-05 +## Bug Fixes + +* **apigateway:** update Response class to require status_code only ([#1560](https://github.com/aws-powertools/powertools-lambda-python/issues/1560)) +* **ci:** integrate isort 5.0 with black to resolve conflicts +* **event_sources:** implement Mapping protocol on DictWrapper for better interop with existing middlewares ([#1516](https://github.com/aws-powertools/powertools-lambda-python/issues/1516)) +* **typing:** fix mypy error +* **typing:** level arg in copy_config_to_registered_loggers ([#1534](https://github.com/aws-powertools/powertools-lambda-python/issues/1534)) + +## Documentation + +* **batch:** document the new lambda context feature +* **homepage:** introduce POWERTOOLS_DEV env var ([#1569](https://github.com/aws-powertools/powertools-lambda-python/issues/1569)) +* **multiple:** fix highlighting after new isort/black integration +* **parser:** add JSON string field extension example ([#1526](https://github.com/aws-powertools/powertools-lambda-python/issues/1526)) + +## Features + +* **batch:** inject lambda_context if record handler signature accepts it ([#1561](https://github.com/aws-powertools/powertools-lambda-python/issues/1561)) +* **event-handler:** context support to share data between routers ([#1567](https://github.com/aws-powertools/powertools-lambda-python/issues/1567)) +* **logger:** introduce POWERTOOLS_DEBUG for internal debugging ([#1572](https://github.com/aws-powertools/powertools-lambda-python/issues/1572)) +* **logger:** include logger name attribute when copy_config_to_registered_logger is used ([#1568](https://github.com/aws-powertools/powertools-lambda-python/issues/1568)) +* **logger:** pretty-print JSON when POWERTOOLS_DEV is set ([#1548](https://github.com/aws-powertools/powertools-lambda-python/issues/1548)) + +## Maintenance + +* **dep:** bump pyproject to pypi sync +* **deps:** bump fastjsonschema from 2.16.1 to 2.16.2 ([#1530](https://github.com/aws-powertools/powertools-lambda-python/issues/1530)) +* **deps:** bump actions/setup-python from 3 to 4 ([#1528](https://github.com/aws-powertools/powertools-lambda-python/issues/1528)) +* **deps:** bump codecov/codecov-action from 3.1.0 to 3.1.1 ([#1529](https://github.com/aws-powertools/powertools-lambda-python/issues/1529)) +* **deps:** bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 ([#1565](https://github.com/aws-powertools/powertools-lambda-python/issues/1565)) +* **deps:** bump email-validator from 1.2.1 to 1.3.0 ([#1533](https://github.com/aws-powertools/powertools-lambda-python/issues/1533)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.24.54 to 1.24.83 ([#1557](https://github.com/aws-powertools/powertools-lambda-python/issues/1557)) +* **deps-dev:** bump mkdocs-material from 8.5.3 to 8.5.4 ([#1563](https://github.com/aws-powertools/powertools-lambda-python/issues/1563)) +* **deps-dev:** bump pytest-cov from 3.0.0 to 4.0.0 ([#1551](https://github.com/aws-powertools/powertools-lambda-python/issues/1551)) +* **deps-dev:** bump flake8-bugbear from 22.9.11 to 22.9.23 ([#1541](https://github.com/aws-powertools/powertools-lambda-python/issues/1541)) +* **deps-dev:** bump types-requests from 2.28.11 to 2.28.11.1 ([#1571](https://github.com/aws-powertools/powertools-lambda-python/issues/1571)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.69 to 1.24.80 ([#1542](https://github.com/aws-powertools/powertools-lambda-python/issues/1542)) +* **deps-dev:** bump mako from 1.2.2 to 1.2.3 ([#1537](https://github.com/aws-powertools/powertools-lambda-python/issues/1537)) +* **deps-dev:** bump types-requests from 2.28.10 to 2.28.11 ([#1538](https://github.com/aws-powertools/powertools-lambda-python/issues/1538)) +* **deps-dev:** bump mkdocs-material from 8.5.1 to 8.5.3 ([#1532](https://github.com/aws-powertools/powertools-lambda-python/issues/1532)) +* **deps-dev:** bump mypy-boto3-ssm from 1.24.80 to 1.24.81 ([#1544](https://github.com/aws-powertools/powertools-lambda-python/issues/1544)) +* **deps-dev:** bump mypy-boto3-s3 from 1.24.36.post1 to 1.24.76 ([#1531](https://github.com/aws-powertools/powertools-lambda-python/issues/1531)) +* **docs:** bump layer version to 36 (1.29.2) +* **layers:** add dummy v2 layer automation +* **lint:** use new isort black integration +* **multiple:** localize powertools_dev env logic and warning ([#1570](https://github.com/aws-powertools/powertools-lambda-python/issues/1570)) + + +<a name="v1.29.2"></a> +## [v1.29.2] - 2022-09-19 +## Bug Fixes + +* **deps:** bump dev dep mako version to address CVE-2022-40023 ([#1524](https://github.com/aws-powertools/powertools-lambda-python/issues/1524)) + +## Maintenance + +* **deps:** bump release-drafter/release-drafter from 5.20.1 to 5.21.0 ([#1520](https://github.com/aws-powertools/powertools-lambda-python/issues/1520)) +* **deps-dev:** bump mkdocs-material from 8.5.0 to 8.5.1 ([#1521](https://github.com/aws-powertools/powertools-lambda-python/issues/1521)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.60 to 1.24.74 ([#1522](https://github.com/aws-powertools/powertools-lambda-python/issues/1522)) + + +<a name="v1.29.1"></a> +## [v1.29.1] - 2022-09-13 + +<a name="v1.29.0"></a> +## [v1.29.0] - 2022-09-13 +## Bug Fixes + +* **ci:** ignore v2 action for now +* **ci:** only run e2e tests on py 3.7 +* **ci:** pass core fns to large pr workflow script +* **ci:** on_label permissioning model & workflow execution +* **ci:** ensure PR_AUTHOR is present for large_pr_split workflow +* **ci:** gracefully and successful exit changelog upon no changes +* **ci:** event resolution for on_label_added workflow +* **core:** fixes leftovers from rebase + +## Documentation + +* **layer:** upgrade to 1.28.0 (v33) + +## Features + +* **ci:** add actionlint in pre-commit hook +* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/aws-powertools/powertools-lambda-python/issues/1485)) +* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/aws-powertools/powertools-lambda-python/issues/1474)) +* **parser:** add KafkaMskEventModel and KafkaSelfManagedEventModel ([#1499](https://github.com/aws-powertools/powertools-lambda-python/issues/1499)) + +## Maintenance + +* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/aws-powertools/powertools-lambda-python/issues/1480)) +* **ci:** remove unused and undeclared OS matrix env +* **ci:** disable v2 docs +* **ci:** limit E2E workflow run for source code change +* **ci:** add missing description fields +* **ci:** sync package version with pypi +* **ci:** fix invalid dependency leftover +* **ci:** create adhoc docs workflow for v2 +* **ci:** create adhoc docs workflow for v2 +* **ci:** remove dangling debug step +* **ci:** create docs workflow for v2 +* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/aws-powertools/powertools-lambda-python/issues/1482)) +* **ci:** format comment on comment_large_pr script +* **ci:** add note for state persistence on comment_large_pr +* **ci:** destructure assignment on comment_large_pr +* **ci:** record pr details upon labeling +* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/aws-powertools/powertools-lambda-python/issues/1479)) +* **ci:** enable ci checks for v2 +* **deps-dev:** bump black from 21.12b0 to 22.8.0 ([#1515](https://github.com/aws-powertools/powertools-lambda-python/issues/1515)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#1481](https://github.com/aws-powertools/powertools-lambda-python/issues/1481)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/aws-powertools/powertools-lambda-python/issues/306)) +* **deps-dev:** bump mkdocs-material from 8.4.1 to 8.4.2 ([#1483](https://github.com/aws-powertools/powertools-lambda-python/issues/1483)) +* **deps-dev:** revert to v1.28.0 dependencies +* **deps-dev:** bump mkdocs-material from 8.4.4 to 8.5.0 ([#1514](https://github.com/aws-powertools/powertools-lambda-python/issues/1514)) +* **maintainers:** update release workflow link +* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/aws-powertools/powertools-lambda-python/issues/1493)) + + +<a name="v1.28.0"></a> +## [v1.28.0] - 2022-08-25 +## Bug Fixes + +* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/aws-powertools/powertools-lambda-python/issues/1475)) +* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure +* **ci:** move from pip-tools to poetry on layers reusable workflow +* **ci:** move from pip-tools to poetry on layers to fix conflicts +* **ci:** typo and bust gh actions cache +* **ci:** use poetry to resolve layer deps; pip for CDK +* **ci:** disable poetry venv for layer workflow as cdk ignores venv +* **ci:** add cdk v2 dep for layers workflow +* **ci:** move from pip-tools to poetry on layers +* **ci:** temporarily disable changelog upon release +* **ci:** add explicit origin to fix release detached head +* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/aws-powertools/powertools-lambda-python/issues/1446)) + +## Documentation + +* **apigateway:** removes duplicate admonition ([#1426](https://github.com/aws-powertools/powertools-lambda-python/issues/1426)) +* **home:** fix discord syntax and add Discord badge +* **home:** add discord invitation link ([#1471](https://github.com/aws-powertools/powertools-lambda-python/issues/1471)) +* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/aws-powertools/powertools-lambda-python/issues/1419)) +* **layer:** upgrade to 1.27.0 +* **layer:** upgrade to 1.27.0 +* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/aws-powertools/powertools-lambda-python/issues/1451)) +* **parser:** minor grammar fix ([#1427](https://github.com/aws-powertools/powertools-lambda-python/issues/1427)) +* **typing:** snippets split, improved, and lint ([#1465](https://github.com/aws-powertools/powertools-lambda-python/issues/1465)) +* **validation:** snippets split, improved, and lint ([#1449](https://github.com/aws-powertools/powertools-lambda-python/issues/1449)) + +## Features + +* **parser:** add support for Lambda Function URL ([#1442](https://github.com/aws-powertools/powertools-lambda-python/issues/1442)) + +## Maintenance + +* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/aws-powertools/powertools-lambda-python/issues/1463)) +* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/aws-powertools/powertools-lambda-python/issues/1478)) +* **ci:** disable e2e py version matrix due to concurrent locking +* **ci:** revert e2e py version matrix +* **ci:** temp disable e2e matrix +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** reduce payload and only send prod notification +* **ci:** remove area/utilities conflicting label +* **ci:** include py version in stack and cache lock +* **ci:** remove conventional changelog commit to reduce noise +* **ci:** update changelog with latest changes +* **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/aws-powertools/powertools-lambda-python/issues/1458)) +* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/aws-powertools/powertools-lambda-python/issues/1448)) +* **deps-dev:** bump flake8-bugbear from 22.8.22 to 22.8.23 ([#1473](https://github.com/aws-powertools/powertools-lambda-python/issues/1473)) +* **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/aws-powertools/powertools-lambda-python/issues/1423)) +* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/aws-powertools/powertools-lambda-python/issues/1468)) +* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/aws-powertools/powertools-lambda-python/issues/1466)) +* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/aws-powertools/powertools-lambda-python/issues/1444)) +* **tests:** enable end-to-end test workflow ([#1470](https://github.com/aws-powertools/powertools-lambda-python/issues/1470)) +* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/aws-powertools/powertools-lambda-python/issues/1460)) +* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/aws-powertools/powertools-lambda-python/issues/1457)) + +## Reverts +* fix(ci): add explicit origin to fix release detached head + + +<a name="v1.27.0"></a> +## [v1.27.0] - 2022-08-05 +## Bug Fixes + +* **ci:** changelog workflow must receive git tags too +* **ci:** add additional input to accurately describe intent on skip +* **ci:** job permissions +* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/aws-powertools/powertools-lambda-python/issues/1421)) + +## Documentation + +* **layer:** upgrade to 1.26.7 + +## Features + +* **ci:** create reusable changelog generation ([#1418](https://github.com/aws-powertools/powertools-lambda-python/issues/1418)) +* **ci:** include changelog generation on docs build +* **ci:** create reusable changelog generation +* **event_handlers:** Add support for Lambda Function URLs ([#1408](https://github.com/aws-powertools/powertools-lambda-python/issues/1408)) +* **metrics:** update max user-defined dimensions from 9 to 29 ([#1417](https://github.com/aws-powertools/powertools-lambda-python/issues/1417)) + +## Maintenance + +* **ci:** sync area labels to prevent dedup +* **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes +* **ci:** add manual trigger for docs +* **ci:** update changelog with latest changes +* **ci:** temporarily disable changelog push on release +* **ci:** update changelog with latest changes +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** update project with version +* **ci:** update release automated activities +* **ci:** readd changelog step on release +* **ci:** move changelog generation to rebuild_latest_doc workflow +* **ci:** drop 3.6 from workflows +* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/aws-powertools/powertools-lambda-python/issues/1399)) +* **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/aws-powertools/powertools-lambda-python/issues/1414)) +* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/aws-powertools/powertools-lambda-python/issues/1400)) +* **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/aws-powertools/powertools-lambda-python/issues/1405)) +* **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/aws-powertools/powertools-lambda-python/issues/1397)) +* **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/aws-powertools/powertools-lambda-python/issues/1402)) +* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/aws-powertools/powertools-lambda-python/issues/1407)) +* **deps-dev:** bump types-requests from 2.28.5 to 2.28.6 ([#1401](https://github.com/aws-powertools/powertools-lambda-python/issues/1401)) +* **deps-dev:** bump types-requests from 2.28.6 to 2.28.7 ([#1406](https://github.com/aws-powertools/powertools-lambda-python/issues/1406)) +* **docs:** remove pause sentence from roadmap ([#1409](https://github.com/aws-powertools/powertools-lambda-python/issues/1409)) +* **docs:** update site name to test ci changelog +* **docs:** update CHANGELOG for v1.26.7 +* **docs:** update description to trigger changelog generation +* **governance:** remove devcontainer in favour of gitpod.io ([#1411](https://github.com/aws-powertools/powertools-lambda-python/issues/1411)) +* **governance:** add pre-configured dev environment with GitPod.io to ease contributions ([#1403](https://github.com/aws-powertools/powertools-lambda-python/issues/1403)) +* **layers:** upgrade cdk dep hashes to prevent ci fail + + +<a name="v1.26.7"></a> +## [v1.26.7] - 2022-07-29 +## Bug Fixes + +* **ci:** add missing oidc token generation permission +* **event_handlers:** ImportError when importing Response from top-level event_handler ([#1388](https://github.com/aws-powertools/powertools-lambda-python/issues/1388)) + +## Documentation + +* **examples:** enforce and fix all mypy errors ([#1393](https://github.com/aws-powertools/powertools-lambda-python/issues/1393)) + +## Features + +* **idempotency:** handle lambda timeout scenarios for INPROGRESS records ([#1387](https://github.com/aws-powertools/powertools-lambda-python/issues/1387)) + +## Maintenance + +* **ci:** increase skip_pypi logic to cover tests/changelog on re-run failures +* **ci:** update project with version 1.26.6 +* **ci:** drop 3.6 from workflows ([#1395](https://github.com/aws-powertools/powertools-lambda-python/issues/1395)) +* **ci:** add conditional to skip pypi release ([#1366](https://github.com/aws-powertools/powertools-lambda-python/issues/1366)) +* **ci:** remove leftover logic from on_merged_pr workflow +* **ci:** update project with version 1.26.6 +* **ci:** update project with version 1.26.6 +* **deps:** bump jsii from 1.57.0 to 1.63.1 ([#1390](https://github.com/aws-powertools/powertools-lambda-python/issues/1390)) +* **deps:** bump constructs from 10.1.1 to 10.1.59 ([#1396](https://github.com/aws-powertools/powertools-lambda-python/issues/1396)) +* **deps-dev:** bump flake8-isort from 4.1.1 to 4.1.2.post0 ([#1384](https://github.com/aws-powertools/powertools-lambda-python/issues/1384)) +* **layers:** bump to 1.26.6 using layer v26 +* **maintainers:** add Ruben as a maintainer ([#1392](https://github.com/aws-powertools/powertools-lambda-python/issues/1392)) + + +<a name="v1.26.6"></a> +## [v1.26.6] - 2022-07-25 +## Bug Fixes + +* **ci:** remove unsupported env in workflow_call +* **ci:** allow inherit secrets for reusable workflow +* **ci:** remove unused secret +* **ci:** label_related_issue unresolved var from history mixup +* **ci:** cond doesnt support two expr w/ env +* **ci:** only event is resolved in cond +* **ci:** unexpected symbol due to double quotes... +* **event_handlers:** handle lack of headers when using auto-compression feature ([#1325](https://github.com/aws-powertools/powertools-lambda-python/issues/1325)) + +## Maintenance + +* dummy for PR test +* print full event depth +* print full workflow event depth +* debug full event +* remove leftover from fork one more time +* **ci:** test env expr +* **ci:** test upstream job skip +* **ci:** lockdown workflow_run by origin ([#1350](https://github.com/aws-powertools/powertools-lambda-python/issues/1350)) +* **ci:** test default env +* **ci:** experiment hardening origin +* **ci:** experiment hardening origin +* **ci:** introduce codeowners ([#1352](https://github.com/aws-powertools/powertools-lambda-python/issues/1352)) +* **ci:** use OIDC and encrypt release secrets ([#1355](https://github.com/aws-powertools/powertools-lambda-python/issues/1355)) +* **ci:** remove core group from codeowners ([#1358](https://github.com/aws-powertools/powertools-lambda-python/issues/1358)) +* **ci:** confirm workflow_run event +* **ci:** use gh environment for beta and prod layer deploy ([#1356](https://github.com/aws-powertools/powertools-lambda-python/issues/1356)) +* **ci:** update project with version 1.26.5 +* **deps:** bump constructs from 10.1.1 to 10.1.52 ([#1343](https://github.com/aws-powertools/powertools-lambda-python/issues/1343)) +* **deps-dev:** bump mypy-boto3-cloudwatch from 1.24.0 to 1.24.35 ([#1342](https://github.com/aws-powertools/powertools-lambda-python/issues/1342)) +* **governance:** update wording tech debt to summary in maintenance template +* **governance:** add new maintenance issue template for tech debt ([#1326](https://github.com/aws-powertools/powertools-lambda-python/issues/1326)) +* **layers:** layer canary stack should not hardcode resource name +* **layers:** replace layers account secret ([#1329](https://github.com/aws-powertools/powertools-lambda-python/issues/1329)) +* **layers:** expand to all aws commercial regions ([#1324](https://github.com/aws-powertools/powertools-lambda-python/issues/1324)) +* **layers:** bump to 1.26.5 + +## Pull Requests + +* Merge pull request [#285](https://github.com/aws-powertools/powertools-lambda-python/issues/285) from heitorlessa/chore/skip-dep-workflow +* Merge pull request [#284](https://github.com/aws-powertools/powertools-lambda-python/issues/284) from heitorlessa/chore/dummy + + +<a name="v1.26.5"></a> +## [v1.26.5] - 2022-07-20 +## Bug Fixes + +* mathc the name of the cdk synth from the build phase +* typo in input for layer workflow +* no need to cache npm since we only install cdk cli and don't have .lock files +* add entire ARN role instead of account and role name +* path to artefact +* unzip the right artifact name +* download artefact into the layer dir +* sight, yes a whitespace character breaks the build +* **ci:** checkout project before validating related issue workflow +* **ci:** install poetry before calling setup/python with cache ([#1315](https://github.com/aws-powertools/powertools-lambda-python/issues/1315)) +* **ci:** remove additional quotes in PR action ([#1317](https://github.com/aws-powertools/powertools-lambda-python/issues/1317)) +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/aws-powertools/powertools-lambda-python/issues/1316)) +* **ci:** fetch all git info so we can check tags +* **ci:** lambda layer workflow release version and conditionals ([#1316](https://github.com/aws-powertools/powertools-lambda-python/issues/1316)) +* **ci:** keep layer version permission ([#1318](https://github.com/aws-powertools/powertools-lambda-python/issues/1318)) +* **ci:** regex to catch combination of related issues workflow +* **deps:** correct mypy types as dev dependency ([#1322](https://github.com/aws-powertools/powertools-lambda-python/issues/1322)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/aws-powertools/powertools-lambda-python/issues/1264)) + +## Documentation + +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/aws-powertools/powertools-lambda-python/issues/1279)) +* **governance:** typos on PR template fixes [#1314](https://github.com/aws-powertools/powertools-lambda-python/issues/1314) +* **governance:** add security doc to the root + +## Maintenance + +* **ci:** limits concurrency for docs workflow +* **ci:** adds caching when installing python dependencies ([#1311](https://github.com/aws-powertools/powertools-lambda-python/issues/1311)) +* **ci:** update project with version 1.26.4 +* **ci:** fix reference error in related_issue +* **deps:** bump constructs from 10.1.1 to 10.1.51 ([#1323](https://github.com/aws-powertools/powertools-lambda-python/issues/1323)) +* **deps-dev:** bump mypy from 0.961 to 0.971 ([#1320](https://github.com/aws-powertools/powertools-lambda-python/issues/1320)) +* **governance:** fix typo on semantic commit link introduced in [#1](https://github.com/aws-powertools/powertools-lambda-python/issues/1)aef4 +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/aws-powertools/powertools-lambda-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 + + +<a name="v1.26.4"></a> +## [v1.26.4] - 2022-07-18 +## Bug Fixes + +* **ci:** checkout project before validating related issue workflow +* **ci:** fixes typos and small issues on github scripts ([#1302](https://github.com/aws-powertools/powertools-lambda-python/issues/1302)) +* **ci:** address conditional type on_merge +* **ci:** address pr title semantic not found logic +* **ci:** address gh-actions additional quotes; remove debug +* **ci:** regex group name for on_merge workflow +* **ci:** escape outputs as certain PRs can break GH Actions expressions +* **ci:** move conditionals from yaml to code; leftover +* **ci:** move conditionals from yaml to code +* **ci:** accept core arg in label related issue workflow +* **ci:** match the name of the cdk synth from the build phase +* **ci:** regex to catch combination of related issues workflow +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/aws-powertools/powertools-lambda-python/issues/1264)) +* **parser:** raise ValidationError when SNS->SQS keys are intentionally missing ([#1299](https://github.com/aws-powertools/powertools-lambda-python/issues/1299)) + +## Documentation + +* **event-handler:** snippets split, improved, and lint ([#1279](https://github.com/aws-powertools/powertools-lambda-python/issues/1279)) +* **graphql:** snippets split, improved, and lint ([#1287](https://github.com/aws-powertools/powertools-lambda-python/issues/1287)) +* **homepage:** emphasize additional powertools languages ([#1292](https://github.com/aws-powertools/powertools-lambda-python/issues/1292)) +* **metrics:** snippets split, improved, and lint + +## Maintenance + +* **ci:** increase release automation and limit to one manual step ([#1297](https://github.com/aws-powertools/powertools-lambda-python/issues/1297)) +* **ci:** make export PR reusable +* **ci:** auto-merge cdk lib and lambda layer construct +* **ci:** convert inline gh-script to file +* **ci:** lockdown 3rd party workflows to pin sha ([#1301](https://github.com/aws-powertools/powertools-lambda-python/issues/1301)) +* **ci:** automatically add area label based on title ([#1300](https://github.com/aws-powertools/powertools-lambda-python/issues/1300)) +* **ci:** disable output debugging as pr body isnt accepted +* **ci:** experiment with conditional on outputs +* **ci:** improve error handling for non-issue numbers +* **ci:** add end to end testing mechanism ([#1247](https://github.com/aws-powertools/powertools-lambda-python/issues/1247)) +* **ci:** limits concurrency for docs workflow +* **ci:** fix reference error in related_issue +* **ci:** move error prone env to code as constants +* **ci:** move all scripts under .github/scripts +* **deps:** bump cdk-lambda-powertools-python-layer ([#1284](https://github.com/aws-powertools/powertools-lambda-python/issues/1284)) +* **deps:** bump jsii from 1.61.0 to 1.62.0 ([#1294](https://github.com/aws-powertools/powertools-lambda-python/issues/1294)) +* **deps:** bump constructs from 10.1.1 to 10.1.46 ([#1306](https://github.com/aws-powertools/powertools-lambda-python/issues/1306)) +* **deps:** bump actions/setup-node from 2 to 3 ([#1281](https://github.com/aws-powertools/powertools-lambda-python/issues/1281)) +* **deps:** bump fastjsonschema from 2.15.3 to 2.16.1 ([#1309](https://github.com/aws-powertools/powertools-lambda-python/issues/1309)) +* **deps:** bump constructs from 10.1.1 to 10.1.49 ([#1308](https://github.com/aws-powertools/powertools-lambda-python/issues/1308)) +* **deps:** bump attrs from 21.2.0 to 21.4.0 ([#1282](https://github.com/aws-powertools/powertools-lambda-python/issues/1282)) +* **deps:** bump aws-cdk-lib from 2.29.0 to 2.31.1 ([#1290](https://github.com/aws-powertools/powertools-lambda-python/issues/1290)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.12 to 1.24.27 ([#1293](https://github.com/aws-powertools/powertools-lambda-python/issues/1293)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.24.0 to 1.24.29 ([#1295](https://github.com/aws-powertools/powertools-lambda-python/issues/1295)) +* **governance:** remove any step relying on master branch +* **governance:** update emeritus affiliation +* **layers:** add release pipeline in GitHub Actions ([#1278](https://github.com/aws-powertools/powertools-lambda-python/issues/1278)) +* **layers:** bump to 22 for 1.26.3 + + +<a name="v1.26.3"></a> +## [v1.26.3] - 2022-07-04 +## Bug Fixes + +* **ci:** remove utf-8 body in octokit body req +* **ci:** improve msg visibility on closed issues +* **ci:** disable merged_pr workflow +* **ci:** merged_pr add issues write access +* **ci:** quote prBody GH expr on_opened_pr +* **ci:** reusable workflow secrets param +* **logger:** support additional args for handlers when injecting lambda context ([#1276](https://github.com/aws-powertools/powertools-lambda-python/issues/1276)) +* **logger:** preserve std keys when using custom formatters ([#1264](https://github.com/aws-powertools/powertools-lambda-python/issues/1264)) + +## Documentation + +* **lint:** add markdownlint rules and automation ([#1256](https://github.com/aws-powertools/powertools-lambda-python/issues/1256)) +* **logger:** document enriching logs with logrecord attributes ([#1271](https://github.com/aws-powertools/powertools-lambda-python/issues/1271)) +* **logger:** snippets split, improved, and lint ([#1262](https://github.com/aws-powertools/powertools-lambda-python/issues/1262)) +* **metrics:** snippets split, improved, and lint ([#1272](https://github.com/aws-powertools/powertools-lambda-python/issues/1272)) +* **tracer:** snippets split, improved, and lint ([#1261](https://github.com/aws-powertools/powertools-lambda-python/issues/1261)) +* **tracer:** split and lint code snippets ([#1260](https://github.com/aws-powertools/powertools-lambda-python/issues/1260)) + +## Maintenance + +* move to approach B for multiple IaC +* add sam build gitignore +* bump to version 1.26.3 +* **ci:** reactivate on_merged_pr workflow +* **ci:** improve wording on closed issues action +* **ci:** deactivate on_merged_pr workflow +* **deps:** bump aws-xray-sdk from 2.9.0 to 2.10.0 ([#1270](https://github.com/aws-powertools/powertools-lambda-python/issues/1270)) +* **deps:** bump dependabot/fetch-metadata from 1.1.1 to 1.3.2 ([#1269](https://github.com/aws-powertools/powertools-lambda-python/issues/1269)) +* **deps:** bump dependabot/fetch-metadata from 1.3.2 to 1.3.3 ([#1273](https://github.com/aws-powertools/powertools-lambda-python/issues/1273)) +* **deps-dev:** bump flake8-bugbear from 22.6.22 to 22.7.1 ([#1274](https://github.com/aws-powertools/powertools-lambda-python/issues/1274)) +* **deps-dev:** bump flake8-bugbear from 22.4.25 to 22.6.22 ([#1258](https://github.com/aws-powertools/powertools-lambda-python/issues/1258)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.0 to 1.24.12 ([#1255](https://github.com/aws-powertools/powertools-lambda-python/issues/1255)) +* **deps-dev:** bump mypy-boto3-secretsmanager ([#1252](https://github.com/aws-powertools/powertools-lambda-python/issues/1252)) +* **governance:** fix on_merged_pr workflow syntax +* **governance:** warn message on closed issues +* **layers:** bump to 21 for 1.26.2 +* **test-perf:** use pytest-benchmark to improve reliability ([#1250](https://github.com/aws-powertools/powertools-lambda-python/issues/1250)) + + +<a name="v1.26.2"></a> +## [v1.26.2] - 2022-06-16 +## Bug Fixes + +* **event-handler:** body to empty string in CORS preflight (ALB non-compliant) ([#1249](https://github.com/aws-powertools/powertools-lambda-python/issues/1249)) + +## Code Refactoring + +* rename to clear_state +* rename to remove_custom_keys + +## Documentation + +* fix anchor + +## Features + +* **logger:** add option to clear state per invocation + +## Maintenance + +* bump to 1.26.2 +* **deps:** bump actions/setup-python from 3 to 4 ([#1244](https://github.com/aws-powertools/powertools-lambda-python/issues/1244)) +* **deps-dev:** bump mypy from 0.960 to 0.961 ([#1241](https://github.com/aws-powertools/powertools-lambda-python/issues/1241)) +* **deps-dev:** bump mypy-boto3-ssm from 1.23.0.post1 to 1.24.0 ([#1231](https://github.com/aws-powertools/powertools-lambda-python/issues/1231)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.8 to 1.24.0 ([#1232](https://github.com/aws-powertools/powertools-lambda-python/issues/1232)) +* **deps-dev:** bump mypy-boto3-dynamodb from 1.23.0.post1 to 1.24.0 ([#1234](https://github.com/aws-powertools/powertools-lambda-python/issues/1234)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.23.0.post1 to 1.24.0 ([#1233](https://github.com/aws-powertools/powertools-lambda-python/issues/1233)) +* **governance:** auto-merge on all PR events +* **governance:** add release label on pr merge +* **governance:** enforce safe scope on pr merge labelling +* **governance:** limit build workflow to code changes only +* **governance:** auto-merge workflow_dispatch off +* **governance:** auto-merge to use squash +* **governance:** check for related issue in new PRs +* **governance:** auto-merge mypy-stub dependabot +* **governance:** address gh reusable workflow limitation +* **governance:** fix workflow action requirements & syntax +* **governance:** warn message on closed issues +* **metrics:** revert dimensions test before splitting ([#1243](https://github.com/aws-powertools/powertools-lambda-python/issues/1243)) + + +<a name="v1.26.1"></a> +## [v1.26.1] - 2022-06-07 +## Bug Fixes + +* **metrics:** raise SchemaValidationError for >8 metric dimensions ([#1240](https://github.com/aws-powertools/powertools-lambda-python/issues/1240)) + +## Documentation + +* **governance:** link roadmap and maintainers doc +* **maintainers:** initial maintainers playbook ([#1222](https://github.com/aws-powertools/powertools-lambda-python/issues/1222)) +* **roadmap:** use pinned pause issue instead + +## Maintenance + +* bump version 1.26.1 +* **deps-dev:** bump mypy from 0.950 to 0.960 ([#1224](https://github.com/aws-powertools/powertools-lambda-python/issues/1224)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.23.0.post1 to 1.23.8 ([#1225](https://github.com/aws-powertools/powertools-lambda-python/issues/1225)) + + +<a name="v1.26.0"></a> +## [v1.26.0] - 2022-05-20 +## Bug Fixes + +* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/aws-powertools/powertools-lambda-python/issues/1201)) +* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/aws-powertools/powertools-lambda-python/issues/1202)) +* **docs:** remove Slack link ([#1210](https://github.com/aws-powertools/powertools-lambda-python/issues/1210)) + +## Documentation + +* **layer:** upgrade to 1.25.10 +* **roadmap:** add new roadmap section ([#1204](https://github.com/aws-powertools/powertools-lambda-python/issues/1204)) + +## Features + +* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/aws-powertools/powertools-lambda-python/issues/1096)) + +## Maintenance + +* bump to 1.26.0 +* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/aws-powertools/powertools-lambda-python/issues/1221)) +* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/aws-powertools/powertools-lambda-python/issues/1199)) +* **deps-dev:** bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 ([#1218](https://github.com/aws-powertools/powertools-lambda-python/issues/1218)) +* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/aws-powertools/powertools-lambda-python/issues/1219)) +* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/aws-powertools/powertools-lambda-python/issues/1220)) + + +<a name="v1.25.10"></a> +## [v1.25.10] - 2022-04-29 +## Bug Fixes + +* **data-classes:** Add missing SES fields and ([#1045](https://github.com/aws-powertools/powertools-lambda-python/issues/1045)) +* **deps:** Ignore boto3 changes until needed ([#1151](https://github.com/aws-powertools/powertools-lambda-python/issues/1151)) +* **deps-dev:** remove jmespath due to dev deps conflict ([#1148](https://github.com/aws-powertools/powertools-lambda-python/issues/1148)) +* **event_handler:** exception_handler to handle ServiceError exceptions ([#1160](https://github.com/aws-powertools/powertools-lambda-python/issues/1160)) +* **event_handler:** Allow for event_source support ([#1159](https://github.com/aws-powertools/powertools-lambda-python/issues/1159)) +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/aws-powertools/powertools-lambda-python/issues/1027)) + +## Documentation + +* **layer:** upgrade to 1.25.9 + +## Features + +* **parameters:** add clear_cache method for providers ([#1194](https://github.com/aws-powertools/powertools-lambda-python/issues/1194)) + +## Maintenance + +* include regression in changelog +* bump to 1.25.10 +* **ci:** changelog pre-generation to fetch tags from origin +* **ci:** disable mergify configuration after breaking changes ([#1188](https://github.com/aws-powertools/powertools-lambda-python/issues/1188)) +* **ci:** post release on tagged issues too +* **deps:** bump codecov/codecov-action from 3.0.0 to 3.1.0 ([#1143](https://github.com/aws-powertools/powertools-lambda-python/issues/1143)) +* **deps:** bump github/codeql-action from 1 to 2 ([#1154](https://github.com/aws-powertools/powertools-lambda-python/issues/1154)) +* **deps-dev:** bump flake8-eradicate from 1.2.0 to 1.2.1 ([#1158](https://github.com/aws-powertools/powertools-lambda-python/issues/1158)) +* **deps-dev:** bump mypy from 0.942 to 0.950 ([#1162](https://github.com/aws-powertools/powertools-lambda-python/issues/1162)) +* **deps-dev:** bump mkdocs-git-revision-date-plugin ([#1146](https://github.com/aws-powertools/powertools-lambda-python/issues/1146)) +* **deps-dev:** bump flake8-bugbear from 22.1.11 to 22.4.25 ([#1156](https://github.com/aws-powertools/powertools-lambda-python/issues/1156)) +* **deps-dev:** bump xenon from 0.8.0 to 0.9.0 ([#1145](https://github.com/aws-powertools/powertools-lambda-python/issues/1145)) +* **deps-dev:** bump mypy from 0.931 to 0.942 ([#1133](https://github.com/aws-powertools/powertools-lambda-python/issues/1133)) + +## Regression + +* **parser:** Add missing fields for SESEvent ([#1027](https://github.com/aws-powertools/powertools-lambda-python/issues/1027)) ([#1190](https://github.com/aws-powertools/powertools-lambda-python/issues/1190)) + + +<a name="v1.25.9"></a> +## [v1.25.9] - 2022-04-21 +## Bug Fixes + +* **deps:** correct py36 marker for jmespath + +## Maintenance + +* bump to 1.25.9 + + +<a name="v1.25.8"></a> +## [v1.25.8] - 2022-04-21 +## Bug Fixes + +* removed ambiguous quotes from labels. +* **deps:** update jmespath marker to support 1.0 and py3.6 ([#1139](https://github.com/aws-powertools/powertools-lambda-python/issues/1139)) +* **governance:** update label in names in issues + +## Documentation + +* **install:** instructions to reduce pydantic package size ([#1077](https://github.com/aws-powertools/powertools-lambda-python/issues/1077)) +* **layer:** remove link from clipboard button ([#1135](https://github.com/aws-powertools/powertools-lambda-python/issues/1135)) +* **layer:** update to 1.25.7 + +## Maintenance + +* bump to 1.25.8 +* **deps:** bump codecov/codecov-action from 2.1.0 to 3.0.0 ([#1102](https://github.com/aws-powertools/powertools-lambda-python/issues/1102)) +* **deps:** bump actions/upload-artifact from 2 to 3 ([#1103](https://github.com/aws-powertools/powertools-lambda-python/issues/1103)) +* **deps-dev:** bump mkdocs-material from 8.2.4 to 8.2.7 ([#1131](https://github.com/aws-powertools/powertools-lambda-python/issues/1131)) +* **deps-dev:** bump pytest from 6.2.5 to 7.0.1 ([#1063](https://github.com/aws-powertools/powertools-lambda-python/issues/1063)) + + +<a name="v1.25.7"></a> +## [v1.25.7] - 2022-04-08 +## Bug Fixes + +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/aws-powertools/powertools-lambda-python/issues/1099)) +* **api_gateway:** allow whitespace in routes' path parameter ([#1099](https://github.com/aws-powertools/powertools-lambda-python/issues/1099)) +* **idempotency:** pass by value on idem key to guard inadvert mutations ([#1090](https://github.com/aws-powertools/powertools-lambda-python/issues/1090)) +* **logger:** clear_state should keep custom key formats ([#1095](https://github.com/aws-powertools/powertools-lambda-python/issues/1095)) +* **middleware_factory:** ret type annotation for handler dec ([#1066](https://github.com/aws-powertools/powertools-lambda-python/issues/1066)) + +## Documentation + +* **layer:** update to 1.25.6; cosmetic changes + +## Maintenance + +* bump to 1.25.7 +* **governance:** refresh pull request template sections +* **governance:** update external non-triage effort disclaimer +* **governance:** update static typing to a form +* **governance:** update rfc to a form +* **governance:** update feat request to a form +* **governance:** bug report form typo +* **governance:** update docs report to a form +* **governance:** update bug report to a form +* **governance:** new ask a question +* **governance:** new static typing report + + +<a name="v1.25.6"></a> +## [v1.25.6] - 2022-04-01 +## Bug Fixes + +* **logger:** clear_state regression on absent standard keys ([#1088](https://github.com/aws-powertools/powertools-lambda-python/issues/1088)) + +## Documentation + +* **layer:** bump to 1.25.5 + +## Maintenance + +* bump to 1.25.6 + + +<a name="v1.25.5"></a> +## [v1.25.5] - 2022-03-18 +## Bug Fixes + +* **logger-utils:** regression on exclude set leading to no formatter ([#1080](https://github.com/aws-powertools/powertools-lambda-python/issues/1080)) + +## Maintenance + +* bump to 1.25.5 + + +<a name="v1.25.4"></a> +## [v1.25.4] - 2022-03-17 +## Bug Fixes + +* package_logger as const over logger instance +* repurpose test to cover parent loggers case +* use addHandler over monkeypatch + +## Documentation + +* **appsync:** fix typo +* **contributing:** operational excellence pause +* **layer:** update to 1.25.3 + +## Maintenance + +* bump to 1.25.4 +* remove duplicate test +* comment reason for change +* remove unnecessary test +* lint unused import + +## Regression + +* service_name fixture + +## Pull Requests + +* Merge pull request [#1075](https://github.com/aws-powertools/powertools-lambda-python/issues/1075) from mploski/fix/existing-loggers-duplicated-logs + + +<a name="v1.25.3"></a> +## [v1.25.3] - 2022-03-09 +## Bug Fixes + +* **logger:** ensure state is cleared for custom formatters ([#1072](https://github.com/aws-powertools/powertools-lambda-python/issues/1072)) + +## Documentation + +* **plugin:** add mermaid to create diagram as code ([#1070](https://github.com/aws-powertools/powertools-lambda-python/issues/1070)) + +## Maintenance + +* bump to 1.25.3 + + +<a name="v1.25.2"></a> +## [v1.25.2] - 2022-03-07 +## Bug Fixes + +* **event_handler:** docs snippets, high-level import CorsConfig ([#1019](https://github.com/aws-powertools/powertools-lambda-python/issues/1019)) +* **lambda-authorizer:** allow proxy resources path in arn ([#1051](https://github.com/aws-powertools/powertools-lambda-python/issues/1051)) +* **metrics:** flush upon a single metric 100th data point ([#1046](https://github.com/aws-powertools/powertools-lambda-python/issues/1046)) + +## Documentation + +* **layer:** update to 1.25.1 +* **parser:** APIGatewayProxyEvent to APIGatewayProxyEventModel ([#1061](https://github.com/aws-powertools/powertools-lambda-python/issues/1061)) + +## Maintenance + +* bump to 1.25.2 +* **deps:** bump actions/setup-python from 2.3.1 to 3 ([#1048](https://github.com/aws-powertools/powertools-lambda-python/issues/1048)) +* **deps:** bump actions/checkout from 2 to 3 ([#1052](https://github.com/aws-powertools/powertools-lambda-python/issues/1052)) +* **deps:** bump actions/github-script from 5 to 6 ([#1023](https://github.com/aws-powertools/powertools-lambda-python/issues/1023)) +* **deps:** bump fastjsonschema from 2.15.2 to 2.15.3 ([#949](https://github.com/aws-powertools/powertools-lambda-python/issues/949)) +* **deps-dev:** bump mkdocs-material from 8.1.9 to 8.2.4 ([#1054](https://github.com/aws-powertools/powertools-lambda-python/issues/1054)) + + +<a name="v1.25.1"></a> +## [v1.25.1] - 2022-02-14 +## Bug Fixes + +* **batch:** bugfix to clear exceptions between executions ([#1022](https://github.com/aws-powertools/powertools-lambda-python/issues/1022)) + +## Maintenance + +* bump to 1.25.1 +* **layers:** bump to 10 for 1.25.0 + + +<a name="v1.25.0"></a> +## [v1.25.0] - 2022-02-09 +## Bug Fixes + +* **apigateway:** remove indentation in debug_mode ([#987](https://github.com/aws-powertools/powertools-lambda-python/issues/987)) +* **batch:** delete >10 messages in legacy sqs processor ([#818](https://github.com/aws-powertools/powertools-lambda-python/issues/818)) +* **ci:** pr label regex for special chars in title +* **logger:** exclude source_logger in copy_config_to_registered_loggers ([#1001](https://github.com/aws-powertools/powertools-lambda-python/issues/1001)) +* **logger:** test generates logfile + +## Documentation + +* fix syntax errors and line highlights ([#1004](https://github.com/aws-powertools/powertools-lambda-python/issues/1004)) +* add better BDD coments +* **event-handler:** improve testing section for graphql ([#996](https://github.com/aws-powertools/powertools-lambda-python/issues/996)) +* **layer:** update to 1.24.2 +* **parameters:** add testing your code section ([#1017](https://github.com/aws-powertools/powertools-lambda-python/issues/1017)) +* **theme:** upgrade mkdocs-material to 8.x ([#1002](https://github.com/aws-powertools/powertools-lambda-python/issues/1002)) +* **tutorial:** fix broken internal links ([#1000](https://github.com/aws-powertools/powertools-lambda-python/issues/1000)) + +## Features + +* **event-handler:** new resolvers to fix current_event typing ([#978](https://github.com/aws-powertools/powertools-lambda-python/issues/978)) +* **logger:** log_event support event data classes (e.g. S3Event) ([#984](https://github.com/aws-powertools/powertools-lambda-python/issues/984)) +* **mypy:** complete mypy support for the entire codebase ([#943](https://github.com/aws-powertools/powertools-lambda-python/issues/943)) + +## Maintenance + +* bump to 1.25.0 +* correct docs +* correct docs +* use isinstance over type +* **deps-dev:** bump flake8-bugbear from 21.11.29 to 22.1.11 ([#955](https://github.com/aws-powertools/powertools-lambda-python/issues/955)) +* **metrics:** fix tests when warnings are disabled ([#994](https://github.com/aws-powertools/powertools-lambda-python/issues/994)) + +## Pull Requests + +* Merge pull request [#971](https://github.com/aws-powertools/powertools-lambda-python/issues/971) from gyft/fix-logger-util-tests + + +<a name="v1.24.2"></a> +## [v1.24.2] - 2022-01-21 +## Bug Fixes + +* **data-classes:** underscore support in api gateway authorizer resource name ([#969](https://github.com/aws-powertools/powertools-lambda-python/issues/969)) + +## Documentation + +* **layer:** update to 1.24.1 + +## Maintenance + +* bump to 1.24.2 + + +<a name="v1.24.1"></a> +## [v1.24.1] - 2022-01-20 +## Bug Fixes + +* remove unused json import +* remove apigw contract when using event-handler, apigw tracing +* use decorators, split cold start to ease reading +* incorrect log keys, indentation, snippet consistency +* remove f-strings that doesn't evaluate expr +* **batch:** report multiple failures ([#967](https://github.com/aws-powertools/powertools-lambda-python/issues/967)) +* **data-classes:** docstring typos and clean up ([#937](https://github.com/aws-powertools/powertools-lambda-python/issues/937)) +* **parameters:** appconfig internal _get docstrings ([#934](https://github.com/aws-powertools/powertools-lambda-python/issues/934)) + +## Documentation + +* rename quickstart to tutorial in readme +* rename to tutorial given the size +* add final consideration section +* **batch:** snippet typo on batch processed messages iteration ([#951](https://github.com/aws-powertools/powertools-lambda-python/issues/951)) +* **batch:** fix typo in context manager keyword ([#938](https://github.com/aws-powertools/powertools-lambda-python/issues/938)) +* **homepage:** link to typescript version ([#950](https://github.com/aws-powertools/powertools-lambda-python/issues/950)) +* **install:** new lambda layer for 1.24.0 release +* **metrics:** keep it consistent with other sections, update metric names +* **nav:** make REST and GraphQL event handlers more explicit ([#959](https://github.com/aws-powertools/powertools-lambda-python/issues/959)) +* **quickstart:** expand on intro line +* **quickstart:** tidy requirements up +* **quickstart:** make section agnostic to json lib +* **quickstart:** same process for Logger +* **quickstart:** add sub-sections, fix highlight & code +* **quickstart:** sentence fragmentation, tidy up +* **tenets:** make core, non-core more explicit +* **tracer:** warning to note on local traces +* **tracer:** add initial image, requirements +* **tracer:** add annotation, metadata, and image +* **tracer:** update ServiceLens image w/ API GW, copywriting +* **tutorial:** fix path to images ([#963](https://github.com/aws-powertools/powertools-lambda-python/issues/963)) + +## Features + +* **ci:** auto-notify & close issues on release +* **logger:** clone powertools logger config to any Python logger ([#927](https://github.com/aws-powertools/powertools-lambda-python/issues/927)) + +## Maintenance + +* bump to 1.24.1 +* bump to 1.24.1 +* **ci:** run codeql analysis on push only +* **ci:** fix mergify dependabot queue +* **ci:** add queue name in mergify +* **ci:** remove mergify legacy key +* **ci:** update mergify bot breaking change +* **ci:** safely label PR based on title +* **deps:** bump pydantic from 1.8.2 to 1.9.0 ([#933](https://github.com/aws-powertools/powertools-lambda-python/issues/933)) +* **deps-dev:** bump mypy from 0.930 to 0.931 ([#941](https://github.com/aws-powertools/powertools-lambda-python/issues/941)) + +## Regression + +* order to APP logger/service name due to screenshots + +## Pull Requests + +* Merge pull request [#769](https://github.com/aws-powertools/powertools-lambda-python/issues/769) from mploski/docs/quick-start + + +<a name="v1.24.0"></a> +## [v1.24.0] - 2021-12-31 +## Bug Fixes + +* **apigateway:** support [@app](https://github.com/app).not_found() syntax & housekeeping ([#926](https://github.com/aws-powertools/powertools-lambda-python/issues/926)) +* **event-sources:** handle dynamodb null type as none, not bool ([#929](https://github.com/aws-powertools/powertools-lambda-python/issues/929)) +* **warning:** future distutils deprecation ([#921](https://github.com/aws-powertools/powertools-lambda-python/issues/921)) + +## Documentation + +* consistency around admonitions and snippets ([#919](https://github.com/aws-powertools/powertools-lambda-python/issues/919)) +* Added GraphQL Sample API to Examples section of README.md ([#930](https://github.com/aws-powertools/powertools-lambda-python/issues/930)) +* **batch:** remove leftover from legacy +* **layer:** bump Lambda Layer to version 6 +* **tracer:** new ignore_endpoint feature ([#931](https://github.com/aws-powertools/powertools-lambda-python/issues/931)) + +## Features + +* **event-sources:** cache parsed json in data class ([#909](https://github.com/aws-powertools/powertools-lambda-python/issues/909)) +* **feature_flags:** support beyond boolean values (JSON values) ([#804](https://github.com/aws-powertools/powertools-lambda-python/issues/804)) +* **idempotency:** support dataclasses & pydantic models payloads ([#908](https://github.com/aws-powertools/powertools-lambda-python/issues/908)) +* **logger:** support use_datetime_directive for timestamps ([#920](https://github.com/aws-powertools/powertools-lambda-python/issues/920)) +* **tracer:** ignore tracing for certain hostname(s) or url(s) ([#910](https://github.com/aws-powertools/powertools-lambda-python/issues/910)) + +## Maintenance + +* bump to 1.24.0 +* **deps-dev:** bump mypy from 0.920 to 0.930 ([#925](https://github.com/aws-powertools/powertools-lambda-python/issues/925)) + + +<a name="v1.23.0"></a> +## [v1.23.0] - 2021-12-20 +## Bug Fixes + +* **apigateway:** allow list of HTTP methods in route method ([#838](https://github.com/aws-powertools/powertools-lambda-python/issues/838)) +* **event-sources:** Pass authorizer data to APIGatewayEventAuthorizer ([#897](https://github.com/aws-powertools/powertools-lambda-python/issues/897)) +* **event-sources:** handle claimsOverrideDetails set to null ([#878](https://github.com/aws-powertools/powertools-lambda-python/issues/878)) +* **idempotency:** include decorated fn name in hash ([#869](https://github.com/aws-powertools/powertools-lambda-python/issues/869)) +* **metrics:** explicit type to single_metric ctx manager ([#865](https://github.com/aws-powertools/powertools-lambda-python/issues/865)) +* **parameters:** appconfig transform and return types ([#877](https://github.com/aws-powertools/powertools-lambda-python/issues/877)) +* **parser:** overload parse when using envelope ([#885](https://github.com/aws-powertools/powertools-lambda-python/issues/885)) +* **parser:** kinesis sequence number is str, not int ([#907](https://github.com/aws-powertools/powertools-lambda-python/issues/907)) +* **parser:** mypy support for payload type override as models ([#883](https://github.com/aws-powertools/powertools-lambda-python/issues/883)) +* **tracer:** add warm start annotation (ColdStart=False) ([#851](https://github.com/aws-powertools/powertools-lambda-python/issues/851)) + +## Documentation + +* external reference to cloudformation custom resource helper ([#914](https://github.com/aws-powertools/powertools-lambda-python/issues/914)) +* add new public Slack invite +* disable search blur in non-prod env +* update Lambda Layers version +* **apigateway:** add new not_found feature ([#915](https://github.com/aws-powertools/powertools-lambda-python/issues/915)) +* **apigateway:** fix sample layout provided ([#864](https://github.com/aws-powertools/powertools-lambda-python/issues/864)) +* **appsync:** fix users.py typo to locations [#830](https://github.com/aws-powertools/powertools-lambda-python/issues/830) +* **lambda_layer:** fix CDK layer syntax + +## Features + +* **apigateway:** add exception_handler support ([#898](https://github.com/aws-powertools/powertools-lambda-python/issues/898)) +* **apigateway:** access parent api resolver from router ([#842](https://github.com/aws-powertools/powertools-lambda-python/issues/842)) +* **batch:** new BatchProcessor for SQS, DynamoDB, Kinesis ([#886](https://github.com/aws-powertools/powertools-lambda-python/issues/886)) +* **logger:** allow handler with custom kwargs signature ([#913](https://github.com/aws-powertools/powertools-lambda-python/issues/913)) +* **tracer:** add service annotation when service is set ([#861](https://github.com/aws-powertools/powertools-lambda-python/issues/861)) + +## Maintenance + +* correct pr label order +* minor housekeeping before release ([#912](https://github.com/aws-powertools/powertools-lambda-python/issues/912)) +* bump to 1.23.0 +* **ci:** split latest docs workflow +* **deps:** bump fastjsonschema from 2.15.1 to 2.15.2 ([#891](https://github.com/aws-powertools/powertools-lambda-python/issues/891)) +* **deps:** bump actions/setup-python from 2.2.2 to 2.3.0 ([#831](https://github.com/aws-powertools/powertools-lambda-python/issues/831)) +* **deps:** bump aws-xray-sdk from 2.8.0 to 2.9.0 ([#876](https://github.com/aws-powertools/powertools-lambda-python/issues/876)) +* **deps:** support arm64 when developing locally ([#862](https://github.com/aws-powertools/powertools-lambda-python/issues/862)) +* **deps:** bump actions/setup-python from 2.3.0 to 2.3.1 ([#852](https://github.com/aws-powertools/powertools-lambda-python/issues/852)) +* **deps-dev:** bump flake8 from 3.9.2 to 4.0.1 ([#789](https://github.com/aws-powertools/powertools-lambda-python/issues/789)) +* **deps-dev:** bump black from 21.10b0 to 21.11b1 ([#839](https://github.com/aws-powertools/powertools-lambda-python/issues/839)) +* **deps-dev:** bump black from 21.11b1 to 21.12b0 ([#872](https://github.com/aws-powertools/powertools-lambda-python/issues/872)) +* **deps-dev:** bump mypy from 0.910 to 0.920 ([#903](https://github.com/aws-powertools/powertools-lambda-python/issues/903)) + + +<a name="v1.22.0"></a> +## [v1.22.0] - 2021-11-17 +## Bug Fixes + +* change supported python version from 3.6.1 to 3.6.2, bump black ([#807](https://github.com/aws-powertools/powertools-lambda-python/issues/807)) +* **ci:** comment custom publish version checker +* **ci:** skip sync master on docs hotfix +* **parser:** body/QS can be null or omitted in apigw v1/v2 ([#820](https://github.com/aws-powertools/powertools-lambda-python/issues/820)) + +## Code Refactoring + +* **apigateway:** Add BaseRouter and duplicate route check ([#757](https://github.com/aws-powertools/powertools-lambda-python/issues/757)) + +## Documentation + +* updated Lambda Layers definition & limitations. ([#775](https://github.com/aws-powertools/powertools-lambda-python/issues/775)) +* Idiomatic tenet updated to Progressive +* use higher contrast font ([#822](https://github.com/aws-powertools/powertools-lambda-python/issues/822)) +* use higher contrast font +* fix indentation of SAM snippets in install section ([#778](https://github.com/aws-powertools/powertools-lambda-python/issues/778)) +* improve public lambda layer wording, clipboard buttons ([#762](https://github.com/aws-powertools/powertools-lambda-python/issues/762)) +* add amplify-cli instructions for public layer ([#754](https://github.com/aws-powertools/powertools-lambda-python/issues/754)) +* **api-gateway:** add support for new router feature ([#767](https://github.com/aws-powertools/powertools-lambda-python/issues/767)) +* **apigateway:** re-add sample layout, add considerations ([#826](https://github.com/aws-powertools/powertools-lambda-python/issues/826)) +* **appsync:** add new router feature ([#821](https://github.com/aws-powertools/powertools-lambda-python/issues/821)) +* **idempotency:** add support for DynamoDB composite keys ([#808](https://github.com/aws-powertools/powertools-lambda-python/issues/808)) +* **tenets:** update Idiomatic tenet to Progressive ([#823](https://github.com/aws-powertools/powertools-lambda-python/issues/823)) + +## Features + +* **apigateway:** add Router to allow large routing composition ([#645](https://github.com/aws-powertools/powertools-lambda-python/issues/645)) +* **appsync:** add Router to allow large resolver composition ([#776](https://github.com/aws-powertools/powertools-lambda-python/issues/776)) +* **data-classes:** ActiveMQ and RabbitMQ support ([#770](https://github.com/aws-powertools/powertools-lambda-python/issues/770)) +* **logger:** add ALB correlation ID support ([#816](https://github.com/aws-powertools/powertools-lambda-python/issues/816)) + +## Maintenance + +* fix var expr +* remove Lambda Layer version tag +* bump to 1.22.0 +* conditional to publish docs only attempt 3 +* conditional to publish docs only attempt 2 +* conditional to publish docs only +* **deps:** bump boto3 from 1.18.58 to 1.18.59 ([#760](https://github.com/aws-powertools/powertools-lambda-python/issues/760)) +* **deps:** bump boto3 from 1.18.56 to 1.18.58 ([#755](https://github.com/aws-powertools/powertools-lambda-python/issues/755)) +* **deps:** bump urllib3 from 1.26.4 to 1.26.5 ([#787](https://github.com/aws-powertools/powertools-lambda-python/issues/787)) +* **deps:** bump boto3 from 1.19.6 to 1.20.3 ([#809](https://github.com/aws-powertools/powertools-lambda-python/issues/809)) +* **deps:** bump boto3 from 1.18.61 to 1.19.6 ([#783](https://github.com/aws-powertools/powertools-lambda-python/issues/783)) +* **deps:** bump boto3 from 1.20.3 to 1.20.5 ([#817](https://github.com/aws-powertools/powertools-lambda-python/issues/817)) +* **deps:** bump boto3 from 1.18.59 to 1.18.61 ([#766](https://github.com/aws-powertools/powertools-lambda-python/issues/766)) +* **deps-dev:** bump coverage from 6.0.1 to 6.0.2 ([#764](https://github.com/aws-powertools/powertools-lambda-python/issues/764)) +* **deps-dev:** bump pytest-asyncio from 0.15.1 to 0.16.0 ([#782](https://github.com/aws-powertools/powertools-lambda-python/issues/782)) +* **deps-dev:** bump flake8-eradicate from 1.1.0 to 1.2.0 ([#784](https://github.com/aws-powertools/powertools-lambda-python/issues/784)) +* **deps-dev:** bump flake8-isort from 4.0.0 to 4.1.1 ([#785](https://github.com/aws-powertools/powertools-lambda-python/issues/785)) +* **deps-dev:** bump mkdocs-material from 7.3.2 to 7.3.3 ([#758](https://github.com/aws-powertools/powertools-lambda-python/issues/758)) +* **deps-dev:** bump flake8-comprehensions from 3.6.1 to 3.7.0 ([#759](https://github.com/aws-powertools/powertools-lambda-python/issues/759)) +* **deps-dev:** bump mkdocs-material from 7.3.3 to 7.3.5 ([#781](https://github.com/aws-powertools/powertools-lambda-python/issues/781)) +* **deps-dev:** bump coverage from 6.0 to 6.0.1 ([#751](https://github.com/aws-powertools/powertools-lambda-python/issues/751)) +* **deps-dev:** bump mkdocs-material from 7.3.5 to 7.3.6 ([#791](https://github.com/aws-powertools/powertools-lambda-python/issues/791)) +* **deps-dev:** bump coverage from 6.0.2 to 6.1.2 ([#810](https://github.com/aws-powertools/powertools-lambda-python/issues/810)) +* **deps-dev:** bump isort from 5.9.3 to 5.10.1 ([#811](https://github.com/aws-powertools/powertools-lambda-python/issues/811)) + + +<a name="v1.21.1"></a> +## [v1.21.1] - 2021-10-07 +## Documentation + +* add new public layer ARNs ([#746](https://github.com/aws-powertools/powertools-lambda-python/issues/746)) + +## Maintenance + +* include public layers changelog +* bump to 1.21.1 +* include regression in changelog +* ignore constants in test cov ([#745](https://github.com/aws-powertools/powertools-lambda-python/issues/745)) +* ignore constants in tests cov +* add support for publishing fallback +* **deps:** bump boto3 from 1.18.54 to 1.18.56 ([#742](https://github.com/aws-powertools/powertools-lambda-python/issues/742)) +* **deps-dev:** bump mkdocs-material from 7.3.1 to 7.3.2 ([#741](https://github.com/aws-powertools/powertools-lambda-python/issues/741)) + +## Regression + +* **metrics:** typing regression on log_metrics callable ([#744](https://github.com/aws-powertools/powertools-lambda-python/issues/744)) + + +<a name="v1.21.0"></a> +## [v1.21.0] - 2021-10-05 +## Bug Fixes + +* **data-classes:** use correct asdict funciton ([#666](https://github.com/aws-powertools/powertools-lambda-python/issues/666)) +* **feature-flags:** rules should evaluate with an AND op ([#724](https://github.com/aws-powertools/powertools-lambda-python/issues/724)) +* **idempotency:** sorting keys before hashing ([#722](https://github.com/aws-powertools/powertools-lambda-python/issues/722)) +* **idempotency:** sorting keys before hashing +* **logger:** push extra keys to the end ([#722](https://github.com/aws-powertools/powertools-lambda-python/issues/722)) +* **mypy:** a few return types, type signatures, and untyped areas ([#718](https://github.com/aws-powertools/powertools-lambda-python/issues/718)) + +## Code Refactoring + +* **data-classes:** clean up internal logic for APIGatewayAuthorizerResponse ([#643](https://github.com/aws-powertools/powertools-lambda-python/issues/643)) + +## Documentation + +* Terraform reference for SAR Lambda Layer ([#716](https://github.com/aws-powertools/powertools-lambda-python/issues/716)) +* add team behind it and email +* **event-handler:** document catch-all routes ([#705](https://github.com/aws-powertools/powertools-lambda-python/issues/705)) +* **idempotency:** fix misleading idempotent examples ([#661](https://github.com/aws-powertools/powertools-lambda-python/issues/661)) +* **jmespath:** clarify envelope terminology +* **parser:** fix incorrect import in root_validator example ([#735](https://github.com/aws-powertools/powertools-lambda-python/issues/735)) + +## Features + +* expose jmespath powertools functions ([#736](https://github.com/aws-powertools/powertools-lambda-python/issues/736)) +* add get_raw_configuration property in store; expose store +* boto3 sessions in batch, parameters & idempotency ([#717](https://github.com/aws-powertools/powertools-lambda-python/issues/717)) +* **feature-flags:** Bring your own logger for debug ([#709](https://github.com/aws-powertools/powertools-lambda-python/issues/709)) +* **feature-flags:** improve "IN/NOT_IN"; new rule actions ([#710](https://github.com/aws-powertools/powertools-lambda-python/issues/710)) +* **feature-flags:** get_raw_configuration property in Store ([#720](https://github.com/aws-powertools/powertools-lambda-python/issues/720)) +* **feature_flags:** Added inequality conditions ([#721](https://github.com/aws-powertools/powertools-lambda-python/issues/721)) +* **idempotency:** makes customers unit testing easier ([#719](https://github.com/aws-powertools/powertools-lambda-python/issues/719)) +* **validator:** include missing data elements from a validation error ([#686](https://github.com/aws-powertools/powertools-lambda-python/issues/686)) + +## Maintenance + +* add python 3.9 support +* bump to 1.21.0 +* **deps:** bump boto3 from 1.18.41 to 1.18.49 ([#703](https://github.com/aws-powertools/powertools-lambda-python/issues/703)) +* **deps:** bump boto3 from 1.18.32 to 1.18.38 ([#671](https://github.com/aws-powertools/powertools-lambda-python/issues/671)) +* **deps:** bump boto3 from 1.18.38 to 1.18.41 ([#677](https://github.com/aws-powertools/powertools-lambda-python/issues/677)) +* **deps:** bump boto3 from 1.18.51 to 1.18.54 ([#733](https://github.com/aws-powertools/powertools-lambda-python/issues/733)) +* **deps:** bump boto3 from 1.18.49 to 1.18.51 ([#713](https://github.com/aws-powertools/powertools-lambda-python/issues/713)) +* **deps:** bump codecov/codecov-action from 2.0.2 to 2.1.0 ([#675](https://github.com/aws-powertools/powertools-lambda-python/issues/675)) +* **deps-dev:** bump flake8-bugbear from 21.9.1 to 21.9.2 ([#712](https://github.com/aws-powertools/powertools-lambda-python/issues/712)) +* **deps-dev:** bump mkdocs-material from 7.3.0 to 7.3.1 ([#731](https://github.com/aws-powertools/powertools-lambda-python/issues/731)) +* **deps-dev:** bump mkdocs-material from 7.2.8 to 7.3.0 ([#695](https://github.com/aws-powertools/powertools-lambda-python/issues/695)) +* **deps-dev:** bump mkdocs-material from 7.2.6 to 7.2.8 ([#682](https://github.com/aws-powertools/powertools-lambda-python/issues/682)) +* **deps-dev:** bump flake8-bugbear from 21.4.3 to 21.9.1 ([#676](https://github.com/aws-powertools/powertools-lambda-python/issues/676)) +* **deps-dev:** bump coverage from 5.5 to 6.0 ([#732](https://github.com/aws-powertools/powertools-lambda-python/issues/732)) +* **deps-dev:** bump radon from 4.5.2 to 5.1.0 ([#673](https://github.com/aws-powertools/powertools-lambda-python/issues/673)) +* **deps-dev:** bump pytest-cov from 2.12.1 to 3.0.0 ([#730](https://github.com/aws-powertools/powertools-lambda-python/issues/730)) +* **deps-dev:** bump xenon from 0.7.3 to 0.8.0 ([#669](https://github.com/aws-powertools/powertools-lambda-python/issues/669)) + + +<a name="v1.20.2"></a> +## [v1.20.2] - 2021-09-02 +## Bug Fixes + +* Fix issue with strip_prefixes ([#647](https://github.com/aws-powertools/powertools-lambda-python/issues/647)) + +## Maintenance + +* bump to 1.20.2 +* **deps:** bump boto3 from 1.18.26 to 1.18.32 ([#663](https://github.com/aws-powertools/powertools-lambda-python/issues/663)) +* **deps-dev:** bump mkdocs-material from 7.2.4 to 7.2.6 ([#665](https://github.com/aws-powertools/powertools-lambda-python/issues/665)) +* **deps-dev:** bump pytest from 6.2.4 to 6.2.5 ([#662](https://github.com/aws-powertools/powertools-lambda-python/issues/662)) +* **license:** Add THIRD-PARTY-LICENSES ([#641](https://github.com/aws-powertools/powertools-lambda-python/issues/641)) + + +<a name="v1.20.1"></a> +## [v1.20.1] - 2021-08-22 +## Bug Fixes + +* **idempotency:** sorting keys before hashing ([#639](https://github.com/aws-powertools/powertools-lambda-python/issues/639)) + +## Maintenance + +* bump to 1.20.1 +* markdown linter fixes ([#636](https://github.com/aws-powertools/powertools-lambda-python/issues/636)) +* setup codespaces ([#637](https://github.com/aws-powertools/powertools-lambda-python/issues/637)) +* **license:** add third party license ([#635](https://github.com/aws-powertools/powertools-lambda-python/issues/635)) + + +<a name="v1.20.0"></a> +## [v1.20.0] - 2021-08-21 +## Bug Fixes + +* **api-gateway:** HTTP API strip stage name from request path ([#622](https://github.com/aws-powertools/powertools-lambda-python/issues/622)) +* **docs:** correct feature_flags link and json exmaples ([#605](https://github.com/aws-powertools/powertools-lambda-python/issues/605)) + +## Code Refactoring + +* **event_handler:** match to match_results; 3.10 new keyword ([#616](https://github.com/aws-powertools/powertools-lambda-python/issues/616)) + +## Documentation + +* **api-gateway:** add new API mapping support +* **data-class:** fix invalid syntax in new AppSync Authorizer +* **data-classes:** make authorizer concise; use enum ([#630](https://github.com/aws-powertools/powertools-lambda-python/issues/630)) + +## Features + +* **data-classes:** authorizer for http api and rest api ([#620](https://github.com/aws-powertools/powertools-lambda-python/issues/620)) +* **data-classes:** data_as_bytes prop KinesisStreamRecordPayload ([#628](https://github.com/aws-powertools/powertools-lambda-python/issues/628)) +* **data-classes:** AppSync Lambda authorizer event ([#610](https://github.com/aws-powertools/powertools-lambda-python/issues/610)) +* **event-handler:** prefixes to strip for custom mappings ([#579](https://github.com/aws-powertools/powertools-lambda-python/issues/579)) +* **general:** support for Python 3.9 ([#626](https://github.com/aws-powertools/powertools-lambda-python/issues/626)) +* **idempotency:** support for any synchronous function ([#625](https://github.com/aws-powertools/powertools-lambda-python/issues/625)) + +## Maintenance + +* update changelog to reflect out-of-band commits +* bump to 1.20.0 +* update new changelog version tag +* **actions:** include new labels +* **api-docs:** enable allow_reuse to fix the docs ([#612](https://github.com/aws-powertools/powertools-lambda-python/issues/612)) +* **deps:** bump boto3 from 1.18.25 to 1.18.26 ([#627](https://github.com/aws-powertools/powertools-lambda-python/issues/627)) +* **deps:** bump boto3 from 1.18.24 to 1.18.25 ([#623](https://github.com/aws-powertools/powertools-lambda-python/issues/623)) +* **deps:** bump boto3 from 1.18.22 to 1.18.24 ([#619](https://github.com/aws-powertools/powertools-lambda-python/issues/619)) +* **deps:** bump boto3 from 1.18.21 to 1.18.22 ([#614](https://github.com/aws-powertools/powertools-lambda-python/issues/614)) +* **deps:** bump boto3 from 1.18.17 to 1.18.21 ([#608](https://github.com/aws-powertools/powertools-lambda-python/issues/608)) +* **deps-dev:** bump flake8-comprehensions from 3.6.0 to 3.6.1 ([#615](https://github.com/aws-powertools/powertools-lambda-python/issues/615)) +* **deps-dev:** bump flake8-comprehensions from 3.5.0 to 3.6.0 ([#609](https://github.com/aws-powertools/powertools-lambda-python/issues/609)) +* **deps-dev:** bump mkdocs-material from 7.2.3 to 7.2.4 ([#607](https://github.com/aws-powertools/powertools-lambda-python/issues/607)) +* **docs:** correct markdown based on markdown lint ([#603](https://github.com/aws-powertools/powertools-lambda-python/issues/603)) +* **shared:** fix cyclic import & refactor data extraction fn ([#613](https://github.com/aws-powertools/powertools-lambda-python/issues/613)) + + +<a name="v1.19.0"></a> +## [v1.19.0] - 2021-08-11 +## Bug Fixes + +* **deps:** bump poetry to latest ([#592](https://github.com/aws-powertools/powertools-lambda-python/issues/592)) +* **feature-flags:** bug handling multiple conditions ([#599](https://github.com/aws-powertools/powertools-lambda-python/issues/599)) +* **feature-toggles:** correct cdk example ([#601](https://github.com/aws-powertools/powertools-lambda-python/issues/601)) +* **parser:** apigw wss validation check_message_id; housekeeping ([#553](https://github.com/aws-powertools/powertools-lambda-python/issues/553)) + +## Code Refactoring + +* **feature-flags:** add debug for all features evaluation" ([#590](https://github.com/aws-powertools/powertools-lambda-python/issues/590)) +* **feature_flags:** optimize UX and maintenance ([#563](https://github.com/aws-powertools/powertools-lambda-python/issues/563)) + +## Documentation + +* **event-handler:** new custom serializer option +* **feature-flags:** add guidance when to use vs env vars vs parameters +* **feature-flags:** fix sample feature name in evaluate +* **feature-flags:** create concrete documentation ([#594](https://github.com/aws-powertools/powertools-lambda-python/issues/594)) +* **feature-toggles:** correct docs and typing ([#588](https://github.com/aws-powertools/powertools-lambda-python/issues/588)) +* **feature_flags:** fix SAM infra, convert CDK to Python +* **parameters:** auto-transforming values based on suffix ([#573](https://github.com/aws-powertools/powertools-lambda-python/issues/573)) +* **readme:** add code coverage badge ([#577](https://github.com/aws-powertools/powertools-lambda-python/issues/577)) +* **tracer:** update wording that it auto-disables on non-Lambda env + +## Features + +* **api-gateway:** add support for custom serializer ([#568](https://github.com/aws-powertools/powertools-lambda-python/issues/568)) +* **data-classes:** decode json_body if based64 encoded ([#560](https://github.com/aws-powertools/powertools-lambda-python/issues/560)) +* **feature flags:** Add not_in action and rename contains to in ([#589](https://github.com/aws-powertools/powertools-lambda-python/issues/589)) +* **params:** expose high level max_age, raise_on_transform_error ([#567](https://github.com/aws-powertools/powertools-lambda-python/issues/567)) +* **tracer:** disable tracer when for non-Lambda envs ([#598](https://github.com/aws-powertools/powertools-lambda-python/issues/598)) + +## Maintenance + +* only build docs on docs path +* update pypi description, keywords +* bump to 1.19.0 +* enable autolabel based on PR title +* include feature-flags docs hotfix +* **deps:** bump boto3 from 1.18.15 to 1.18.17 ([#597](https://github.com/aws-powertools/powertools-lambda-python/issues/597)) +* **deps:** bump boto3 from 1.18.1 to 1.18.15 ([#591](https://github.com/aws-powertools/powertools-lambda-python/issues/591)) +* **deps:** bump codecov/codecov-action from 2.0.1 to 2.0.2 ([#558](https://github.com/aws-powertools/powertools-lambda-python/issues/558)) +* **deps-dev:** bump mkdocs-material from 7.2.1 to 7.2.2 ([#582](https://github.com/aws-powertools/powertools-lambda-python/issues/582)) +* **deps-dev:** bump mkdocs-material from 7.2.2 to 7.2.3 ([#596](https://github.com/aws-powertools/powertools-lambda-python/issues/596)) +* **deps-dev:** bump pdoc3 from 0.9.2 to 0.10.0 ([#584](https://github.com/aws-powertools/powertools-lambda-python/issues/584)) +* **deps-dev:** bump isort from 5.9.2 to 5.9.3 ([#574](https://github.com/aws-powertools/powertools-lambda-python/issues/574)) +* **deps-dev:** bump mkdocs-material from 7.2.0 to 7.2.1 ([#566](https://github.com/aws-powertools/powertools-lambda-python/issues/566)) +* **deps-dev:** bump mkdocs-material from 7.1.11 to 7.2.0 ([#551](https://github.com/aws-powertools/powertools-lambda-python/issues/551)) +* **deps-dev:** bump flake8-black from 0.2.1 to 0.2.3 ([#541](https://github.com/aws-powertools/powertools-lambda-python/issues/541)) + + +<a name="v1.18.1"></a> +## [v1.18.1] - 2021-07-23 +## Bug Fixes + +* **api-gateway:** route regression non-word and unsafe URI chars ([#556](https://github.com/aws-powertools/powertools-lambda-python/issues/556)) + +## Maintenance + +* bump 1.18.1 + + +<a name="v1.18.0"></a> +## [v1.18.0] - 2021-07-20 +## Bug Fixes + +* **api-gateway:** non-greedy route pattern regex ([#533](https://github.com/aws-powertools/powertools-lambda-python/issues/533)) +* **api-gateway:** incorrect plain text mimetype [#506](https://github.com/aws-powertools/powertools-lambda-python/issues/506) +* **data-classes:** include milliseconds in scalar types ([#504](https://github.com/aws-powertools/powertools-lambda-python/issues/504)) +* **mypy:** fixes to resolve no implicit optional errors ([#521](https://github.com/aws-powertools/powertools-lambda-python/issues/521)) +* **parser:** Make ApiGateway version, authorizer fields optional ([#532](https://github.com/aws-powertools/powertools-lambda-python/issues/532)) +* **tracer:** mypy generic to preserve decorated method signature ([#529](https://github.com/aws-powertools/powertools-lambda-python/issues/529)) + +## Code Refactoring + +* **feature-toggles:** Code coverage and housekeeping ([#530](https://github.com/aws-powertools/powertools-lambda-python/issues/530)) + +## Documentation + +* **api-gateway:** document new HTTP service error exceptions ([#546](https://github.com/aws-powertools/powertools-lambda-python/issues/546)) +* **logger:** document new get_correlation_id method ([#545](https://github.com/aws-powertools/powertools-lambda-python/issues/545)) + +## Features + +* **api-gateway:** add debug mode ([#507](https://github.com/aws-powertools/powertools-lambda-python/issues/507)) +* **api-gateway:** add common service errors ([#506](https://github.com/aws-powertools/powertools-lambda-python/issues/506)) +* **event-handler:** Support AppSyncResolverEvent subclassing ([#526](https://github.com/aws-powertools/powertools-lambda-python/issues/526)) +* **feat-toggle:** New simple feature toggles rule engine (WIP) ([#494](https://github.com/aws-powertools/powertools-lambda-python/issues/494)) +* **logger:** add get_correlation_id method ([#516](https://github.com/aws-powertools/powertools-lambda-python/issues/516)) +* **mypy:** add mypy support to makefile ([#508](https://github.com/aws-powertools/powertools-lambda-python/issues/508)) + +## Maintenance + +* bump 1.18.0 ([#547](https://github.com/aws-powertools/powertools-lambda-python/issues/547)) +* **deps:** bump codecov/codecov-action from 1 to 2.0.1 ([#539](https://github.com/aws-powertools/powertools-lambda-python/issues/539)) +* **deps:** bump boto3 from 1.18.0 to 1.18.1 ([#528](https://github.com/aws-powertools/powertools-lambda-python/issues/528)) +* **deps:** bump boto3 from 1.17.110 to 1.18.0 ([#527](https://github.com/aws-powertools/powertools-lambda-python/issues/527)) +* **deps:** bump boto3 from 1.17.102 to 1.17.110 ([#523](https://github.com/aws-powertools/powertools-lambda-python/issues/523)) +* **deps-dev:** bump mkdocs-material from 7.1.10 to 7.1.11 ([#542](https://github.com/aws-powertools/powertools-lambda-python/issues/542)) +* **deps-dev:** bump mkdocs-material from 7.1.9 to 7.1.10 ([#522](https://github.com/aws-powertools/powertools-lambda-python/issues/522)) +* **deps-dev:** bump isort from 5.9.1 to 5.9.2 ([#514](https://github.com/aws-powertools/powertools-lambda-python/issues/514)) +* **event-handler:** adjusts exception docstrings to not confuse AppSync customers + + +<a name="v1.17.1"></a> +## [v1.17.1] - 2021-07-02 +## Bug Fixes + +* **validator:** handle built-in custom formats correctly ([#498](https://github.com/aws-powertools/powertools-lambda-python/issues/498)) + +## Documentation + +* add Layers example for Serverless framework & CDK ([#500](https://github.com/aws-powertools/powertools-lambda-python/issues/500)) +* enable dark mode switch ([#471](https://github.com/aws-powertools/powertools-lambda-python/issues/471)) +* **logger:** add FAQ for cross-account searches ([#501](https://github.com/aws-powertools/powertools-lambda-python/issues/501)) +* **tracer:** additional scenario when to disable auto-capture ([#499](https://github.com/aws-powertools/powertools-lambda-python/issues/499)) + +## Maintenance + +* bump 1.17.1 ([#502](https://github.com/aws-powertools/powertools-lambda-python/issues/502)) +* **deps:** bump boto3 from 1.17.101 to 1.17.102 ([#493](https://github.com/aws-powertools/powertools-lambda-python/issues/493)) +* **deps:** bump boto3 from 1.17.91 to 1.17.101 ([#490](https://github.com/aws-powertools/powertools-lambda-python/issues/490)) +* **deps:** bump email-validator from 1.1.2 to 1.1.3 ([#478](https://github.com/aws-powertools/powertools-lambda-python/issues/478)) +* **deps:** bump boto3 from 1.17.89 to 1.17.91 ([#473](https://github.com/aws-powertools/powertools-lambda-python/issues/473)) +* **deps-dev:** bump flake8-eradicate from 1.0.0 to 1.1.0 ([#492](https://github.com/aws-powertools/powertools-lambda-python/issues/492)) +* **deps-dev:** bump isort from 5.8.0 to 5.9.1 ([#487](https://github.com/aws-powertools/powertools-lambda-python/issues/487)) +* **deps-dev:** bump mkdocs-material from 7.1.7 to 7.1.9 ([#491](https://github.com/aws-powertools/powertools-lambda-python/issues/491)) + + +<a name="v1.17.0"></a> +## [v1.17.0] - 2021-06-08 +## Documentation + +* include new public roadmap ([#452](https://github.com/aws-powertools/powertools-lambda-python/issues/452)) +* **data_classes:** fix missing dynamodb stream get_type/value +* **idempotency:** remove old todo + +## Features + +* **data-classes:** add AttributeValueType to DynamoDBStreamEvent ([#462](https://github.com/aws-powertools/powertools-lambda-python/issues/462)) +* **data-classes:** decorator to instantiate data_classes and docs updates ([#442](https://github.com/aws-powertools/powertools-lambda-python/issues/442)) +* **logger:** add option to clear state per invocation ([#467](https://github.com/aws-powertools/powertools-lambda-python/issues/467)) +* **parser:** add support for API Gateway HTTP API [#434](https://github.com/aws-powertools/powertools-lambda-python/issues/434) ([#441](https://github.com/aws-powertools/powertools-lambda-python/issues/441)) + +## Maintenance + +* bump xenon from 0.7.1 to 0.7.3 ([#446](https://github.com/aws-powertools/powertools-lambda-python/issues/446)) +* fix changelog file redirection +* include dependencies label under maintenance +* ignore codecov upload +* reintroduce codecov token +* fix path for PR auto-labelling +* assited changelog pre-generation, auto-label PR ([#443](https://github.com/aws-powertools/powertools-lambda-python/issues/443)) +* enable dependabot for dep upgrades ([#444](https://github.com/aws-powertools/powertools-lambda-python/issues/444)) +* enable mergify ([#450](https://github.com/aws-powertools/powertools-lambda-python/issues/450)) +* dependabot/mergify guardrail for major versions +* fix dependabot commit messages prefix +* fix dependabot unique set config +* bump mkdocs-material from 7.1.5 to 7.1.6 ([#451](https://github.com/aws-powertools/powertools-lambda-python/issues/451)) +* bump boto3 from 1.17.78 to 1.17.84 ([#449](https://github.com/aws-powertools/powertools-lambda-python/issues/449)) +* update mergify to require approval on dependabot ([#456](https://github.com/aws-powertools/powertools-lambda-python/issues/456)) +* bump actions/setup-python from 1 to 2.2.2 ([#445](https://github.com/aws-powertools/powertools-lambda-python/issues/445)) +* trial boring cyborg automation +* **deps:** bump boto3 from 1.17.87 to 1.17.88 ([#463](https://github.com/aws-powertools/powertools-lambda-python/issues/463)) +* **deps:** bump boto3 from 1.17.88 to 1.17.89 ([#466](https://github.com/aws-powertools/powertools-lambda-python/issues/466)) +* **deps:** bump boto3 from 1.17.84 to 1.17.85 ([#455](https://github.com/aws-powertools/powertools-lambda-python/issues/455)) +* **deps:** bump boto3 from 1.17.85 to 1.17.86 ([#458](https://github.com/aws-powertools/powertools-lambda-python/issues/458)) +* **deps:** bump boto3 from 1.17.86 to 1.17.87 ([#459](https://github.com/aws-powertools/powertools-lambda-python/issues/459)) +* **deps-dev:** bump mkdocs-material from 7.1.6 to 7.1.7 ([#464](https://github.com/aws-powertools/powertools-lambda-python/issues/464)) +* **deps-dev:** bump pytest-cov from 2.12.0 to 2.12.1 ([#454](https://github.com/aws-powertools/powertools-lambda-python/issues/454)) +* **mergify:** use job name to match GH Actions +* **mergify:** disable check for matrix jobs + + +<a name="v1.16.1"></a> +## [v1.16.1] - 2021-05-23 +## Features + +* **parser:** security issue in Pydantic [#436](https://github.com/aws-powertools/powertools-lambda-python/issues/436) ([#437](https://github.com/aws-powertools/powertools-lambda-python/issues/437)) + +## Maintenance + +* bump to 1.16.1 + + +<a name="v1.16.0"></a> +## [v1.16.0] - 2021-05-17 +## Features + +* **data-classes:** decode base64 encoded body ([#425](https://github.com/aws-powertools/powertools-lambda-python/issues/425)) +* **data-classes:** support for code pipeline job event ([#416](https://github.com/aws-powertools/powertools-lambda-python/issues/416)) + +## Maintenance + +* bump to 1.16.0 + + +<a name="v1.15.1"></a> +## [v1.15.1] - 2021-05-13 +## Bug Fixes + +* **docs:** Use updated names for ProxyEventType ([#424](https://github.com/aws-powertools/powertools-lambda-python/issues/424)) + +## Documentation + +* update list of features +* **event_handler:** add missing note on trimmed responses + +## Maintenance + +* bump to 1.15.1 + + +<a name="v1.15.0"></a> +## [v1.15.0] - 2021-05-06 +## Bug Fixes + +* **deps:** Bump aws-xray-sdk from 2.6.0 to 2.8.0 ([#413](https://github.com/aws-powertools/powertools-lambda-python/issues/413)) +* **docs:** workflow to include api ref in latest alias ([#408](https://github.com/aws-powertools/powertools-lambda-python/issues/408)) +* **parser:** Improve types for parser.py ([#419](https://github.com/aws-powertools/powertools-lambda-python/issues/419)) +* **validator:** event type annotation as any in validate fn ([#405](https://github.com/aws-powertools/powertools-lambda-python/issues/405)) + +## Code Refactoring + +* simplify custom formatter for minor changes ([#417](https://github.com/aws-powertools/powertools-lambda-python/issues/417)) +* **event-handler:** api gateway handler review changes ([#420](https://github.com/aws-powertools/powertools-lambda-python/issues/420)) +* **event-handler:** Add ResponseBuilder and more docs ([#412](https://github.com/aws-powertools/powertools-lambda-python/issues/412)) +* **logger:** BYOFormatter and Handler, UTC support, and more ([#404](https://github.com/aws-powertools/powertools-lambda-python/issues/404)) + +## Documentation + +* **api_gateway:** new event handler for API Gateway and ALB ([#418](https://github.com/aws-powertools/powertools-lambda-python/issues/418)) +* **event_handler:** fix closing brackets in CORS sample +* **event_handler:** remove beta flag from new HTTP utility +* **idempotency:** remove beta flag +* **logger:** improvements extensibility & new features ([#415](https://github.com/aws-powertools/powertools-lambda-python/issues/415)) +* **parser:** fix table and heading syntax +* **tracer:** Fix line highlighting ([#395](https://github.com/aws-powertools/powertools-lambda-python/issues/395)) + +## Features + +* add support to persist default dimensions ([#410](https://github.com/aws-powertools/powertools-lambda-python/issues/410)) +* **event-handle:** allow for cors=None setting ([#421](https://github.com/aws-powertools/powertools-lambda-python/issues/421)) +* **event-handler:** add http ProxyEvent handler ([#369](https://github.com/aws-powertools/powertools-lambda-python/issues/369)) +* **parser:** Support for API GW v1 proxy schema & envelope ([#403](https://github.com/aws-powertools/powertools-lambda-python/issues/403)) + +## Maintenance + +* bump to 1.15.0 ([#422](https://github.com/aws-powertools/powertools-lambda-python/issues/422)) + + +<a name="v1.14.0"></a> +## [v1.14.0] - 2021-04-09 +## Bug Fixes + +* perf tests for Logger and fail str msgs +* downgrade poetry to 1.1.4 ([#385](https://github.com/aws-powertools/powertools-lambda-python/issues/385)) +* lock X-Ray SDK to 2.6.0 ([#384](https://github.com/aws-powertools/powertools-lambda-python/issues/384)) +* **data-classes:** Add missing operationName ([#373](https://github.com/aws-powertools/powertools-lambda-python/issues/373)) +* **idempotent:** Correctly raise IdempotencyKeyError ([#378](https://github.com/aws-powertools/powertools-lambda-python/issues/378)) +* **metrics:** AttributeError raised by MediaManager and Typing and docs ([#357](https://github.com/aws-powertools/powertools-lambda-python/issues/357)) +* **parser:** S3Model support empty keys ([#375](https://github.com/aws-powertools/powertools-lambda-python/issues/375)) +* **tracer:** Correct type hint for MyPy ([#365](https://github.com/aws-powertools/powertools-lambda-python/issues/365)) +* **workflow:** github actions depends on for release + +## Documentation + +* Fix doc links and line highlights ([#380](https://github.com/aws-powertools/powertools-lambda-python/issues/380)) +* fix extra key for versioning +* update mkdocs-material to 7.1.0 +* Correct link targets and line highlights ([#390](https://github.com/aws-powertools/powertools-lambda-python/issues/390)) +* introduce event handlers utility section ([#388](https://github.com/aws-powertools/powertools-lambda-python/issues/388)) +* enable versioning feature ([#374](https://github.com/aws-powertools/powertools-lambda-python/issues/374)) +* **idempotency:** add default configuration for those not using CFN ([#391](https://github.com/aws-powertools/powertools-lambda-python/issues/391)) +* **index:** fix link to event handler +* **logger:** add example on how to set UTC timestamp ([#392](https://github.com/aws-powertools/powertools-lambda-python/issues/392)) +* **validator:** include more complete examples & intro to JSON Schema ([#389](https://github.com/aws-powertools/powertools-lambda-python/issues/389)) + +## Features + +* **event-handler:** Add AppSync handler decorator ([#363](https://github.com/aws-powertools/powertools-lambda-python/issues/363)) +* **parameter:** add dynamodb_endpoint_url for local_testing ([#376](https://github.com/aws-powertools/powertools-lambda-python/issues/376)) +* **parser:** Add S3 Object Lambda Event ([#362](https://github.com/aws-powertools/powertools-lambda-python/issues/362)) + +## Maintenance + +* bump to 1.14.0 +* add approved by field in RFC template +* make RFC proposal more explicit +* update automated steps in release process + + +<a name="v1.13.0"></a> +## [v1.13.0] - 2021-03-23 +## Bug Fixes + +* **deps:** Bump dependencies and fix some of the dev tooling ([#354](https://github.com/aws-powertools/powertools-lambda-python/issues/354)) +* **lint:** Move `tests/THIRD-PARTY-LICENSES` to root ([#352](https://github.com/aws-powertools/powertools-lambda-python/issues/352)) + +## Features + +* **data-classes:** Add S3 Object Lambda Event ([#353](https://github.com/aws-powertools/powertools-lambda-python/issues/353)) + +## Maintenance + +* include internals in release template +* bump to 1.13.0 +* correct 3rd party license + + +<a name="v1.12.0"></a> +## [v1.12.0] - 2021-03-17 +## Bug Fixes + +* **idempotency:** TypeError when calling is_missing_idempotency_key with an int ([#315](https://github.com/aws-powertools/powertools-lambda-python/issues/315)) +* **idempotency:** Correctly handle save_inprogress errors ([#313](https://github.com/aws-powertools/powertools-lambda-python/issues/313)) + +## Code Refactoring + +* **parameters:** Consistently reference env ([#319](https://github.com/aws-powertools/powertools-lambda-python/issues/319)) + +## Documentation + +* surface new 1.12.0 features and enhancements ([#344](https://github.com/aws-powertools/powertools-lambda-python/issues/344)) +* Correct code examples ([#317](https://github.com/aws-powertools/powertools-lambda-python/issues/317)) +* **data-classes:** Add more cognito code examples ([#340](https://github.com/aws-powertools/powertools-lambda-python/issues/340)) +* **idempotency:** Correct examples and line highlights ([#312](https://github.com/aws-powertools/powertools-lambda-python/issues/312)) +* **metrics:** Corrections to the code examples ([#314](https://github.com/aws-powertools/powertools-lambda-python/issues/314)) +* **metrics:** remove minimum dimensions +* **metrics:** Correct code examples in markdown ([#316](https://github.com/aws-powertools/powertools-lambda-python/issues/316)) +* **tracer:** Fix Tracer typing hinting for Pycharm ([#345](https://github.com/aws-powertools/powertools-lambda-python/issues/345)) + +## Features + +* **data-classes:** Add appsync scalar_types_utils ([#339](https://github.com/aws-powertools/powertools-lambda-python/issues/339)) +* **data-classes:** AppSync Resolver Event ([#323](https://github.com/aws-powertools/powertools-lambda-python/issues/323)) +* **idempotent:** Include function name in the idempotent key ([#326](https://github.com/aws-powertools/powertools-lambda-python/issues/326)) +* **logging:** Add correlation_id support ([#321](https://github.com/aws-powertools/powertools-lambda-python/issues/321)) +* **logging:** Include exception_name ([#320](https://github.com/aws-powertools/powertools-lambda-python/issues/320)) +* **parameters:** Add force_fetch option ([#341](https://github.com/aws-powertools/powertools-lambda-python/issues/341)) + +## Maintenance + +* bump to 1.12.0 +* remove auto-label as restrictions prevent it from working +* increase perf SLA due to slow GitHub Actions machine +* add PR size labelling action # 2 +* add PR size labelling action +* add PR auto-label action +* remove gatsby mention as migrated completed + + +<a name="v1.11.0"></a> +## [v1.11.0] - 2021-03-05 +## Bug Fixes + +* import time latency by lazily loading high level modules ([#301](https://github.com/aws-powertools/powertools-lambda-python/issues/301)) +* correct behaviour to avoid caching "INPROGRESS" records ([#295](https://github.com/aws-powertools/powertools-lambda-python/issues/295)) +* **idempotency:** PR feedback on config and kwargs + +## Code Refactoring + +* **idempotent:** Change UX to use a config class for non-persistence related features ([#306](https://github.com/aws-powertools/powertools-lambda-python/issues/306)) +* **metrics:** optimize validation and serialization ([#307](https://github.com/aws-powertools/powertools-lambda-python/issues/307)) + +## Documentation + +* **batch:** add example on how to integrate with sentry.io ([#308](https://github.com/aws-powertools/powertools-lambda-python/issues/308)) +* **data-classes:** Correct import for DynamoDBRecordEventName ([#299](https://github.com/aws-powertools/powertools-lambda-python/issues/299)) +* **dataclasses:** new Connect Contact Flow ([#310](https://github.com/aws-powertools/powertools-lambda-python/issues/310)) +* **idempotency:** tidy up doc before release ([#309](https://github.com/aws-powertools/powertools-lambda-python/issues/309)) +* **idempotent:** Fix typos and code formatting ([#305](https://github.com/aws-powertools/powertools-lambda-python/issues/305)) + +## Features + +* Idempotency helper utility ([#245](https://github.com/aws-powertools/powertools-lambda-python/issues/245)) +* **data-classes:** Add connect contact flow event ([#304](https://github.com/aws-powertools/powertools-lambda-python/issues/304)) +* **idempotency:** Add raise_on_no_idempotency_key flag ([#297](https://github.com/aws-powertools/powertools-lambda-python/issues/297)) +* **idempotency:** Fix KeyError when local_cache is True and an error is raised in the lambda handler ([#300](https://github.com/aws-powertools/powertools-lambda-python/issues/300)) +* **idempotent:** Add support for jmespath_options ([#302](https://github.com/aws-powertools/powertools-lambda-python/issues/302)) + +## Maintenance + +* update changelog ([#311](https://github.com/aws-powertools/powertools-lambda-python/issues/311)) +* adjusts Metrics SLA for slow py36 interpreters +* remove unsuccessful labeler bot +* update labeler bot to sync upon PR changes +* attempt 1 to fix PR labeler + + +<a name="v1.10.5"></a> +## [v1.10.5] - 2021-02-17 +## Maintenance + +* version bump to 1.10.5 ([#292](https://github.com/aws-powertools/powertools-lambda-python/issues/292)) + + +<a name="v1.10.4"></a> +## [v1.10.4] - 2021-02-17 +## Bug Fixes + +* sync features in main page +* meta tags, and ext link to open in new tab + +## Documentation + +* **data-classes:** Fix anchor tags to be lower case ([#288](https://github.com/aws-powertools/powertools-lambda-python/issues/288)) + +## Maintenance + +* version bump to 1.10.4 ([#291](https://github.com/aws-powertools/powertools-lambda-python/issues/291)) +* add default runtime key +* Correct the docs location ([#289](https://github.com/aws-powertools/powertools-lambda-python/issues/289)) +* enable PR labeler workflow +* add auto-label for known files + +## Regression + +* search input size + + +<a name="v1.10.3"></a> +## [v1.10.3] - 2021-02-12 +## Bug Fixes + +* sfix typing hit for envelope parse model ([#286](https://github.com/aws-powertools/powertools-lambda-python/issues/286)) +* disable batching of X-Ray subsegments ([#284](https://github.com/aws-powertools/powertools-lambda-python/issues/284)) + +## Documentation + +* migrate documentation from Gatsby to MkDocs material ([#279](https://github.com/aws-powertools/powertools-lambda-python/issues/279)) + +## Maintenance + +* bump to 1.10.3 ([#287](https://github.com/aws-powertools/powertools-lambda-python/issues/287)) + + +<a name="v1.10.2"></a> +## [v1.10.2] - 2021-02-04 +## Bug Fixes + +* remove unnecessary typing-extensions for py3.8 ([#281](https://github.com/aws-powertools/powertools-lambda-python/issues/281)) +* batch processing exceptions ([#276](https://github.com/aws-powertools/powertools-lambda-python/issues/276)) + +## Documentation + +* **appconfig:** Use correct import for docstring ([#271](https://github.com/aws-powertools/powertools-lambda-python/issues/271)) + +## Maintenance + +* bump to 1.10.2 ([#282](https://github.com/aws-powertools/powertools-lambda-python/issues/282)) +* fix immer and socket.io CVEs ([#278](https://github.com/aws-powertools/powertools-lambda-python/issues/278)) +* typo in parser docs + + +<a name="v1.10.1"></a> +## [v1.10.1] - 2021-01-19 +## Features + +* add support for SNS->SQS protocol ([#272](https://github.com/aws-powertools/powertools-lambda-python/issues/272)) + +## Maintenance + +* bump to 1.10.1 ([#273](https://github.com/aws-powertools/powertools-lambda-python/issues/273)) + + +<a name="v1.10.0"></a> +## [v1.10.0] - 2021-01-18 +## Documentation + +* fix import ([#267](https://github.com/aws-powertools/powertools-lambda-python/issues/267)) +* add info about extras layer ([#260](https://github.com/aws-powertools/powertools-lambda-python/issues/260)) +* fix note whitespace +* add missing parser models ([#254](https://github.com/aws-powertools/powertools-lambda-python/issues/254)) + +## Features + +* toggle to disable log deduplication locally for pytest live log [#262](https://github.com/aws-powertools/powertools-lambda-python/issues/262) ([#268](https://github.com/aws-powertools/powertools-lambda-python/issues/268)) +* Add AppConfig parameter provider ([#236](https://github.com/aws-powertools/powertools-lambda-python/issues/236)) +* support extra parameter in Logger messages ([#257](https://github.com/aws-powertools/powertools-lambda-python/issues/257)) +* support custom formats in JSON Schema validation ([#247](https://github.com/aws-powertools/powertools-lambda-python/issues/247)) + +## Maintenance + +* bump to 1.10.0 ([#270](https://github.com/aws-powertools/powertools-lambda-python/issues/270)) +* move env names to constant file ([#264](https://github.com/aws-powertools/powertools-lambda-python/issues/264)) +* update stale bot +* general simplifications and cleanup ([#255](https://github.com/aws-powertools/powertools-lambda-python/issues/255)) +* hardcode axios transitive resolution ([#256](https://github.com/aws-powertools/powertools-lambda-python/issues/256)) + + +<a name="v1.9.1"></a> +## [v1.9.1] - 2020-12-21 +## Bug Fixes + +* ensures all Loggers have unique service names + +## Code Refactoring + +* convert dict into a literal dict object and re-use it + +## Documentation + +* add clarification to Tracer docs for how `capture_method` decorator can cause function responses to be read and serialized. + +## Features + +* **pep-561:** Create py.typed file and include into pyproject. + +## Maintenance + +* bump to 1.9.1 ([#252](https://github.com/aws-powertools/powertools-lambda-python/issues/252)) +* add changelog +* implement phony targets correctly +* **deps:** bump ini from 1.3.5 to 1.3.8 in /docs + +## Pull Requests + +* Merge pull request [#250](https://github.com/aws-powertools/powertools-lambda-python/issues/250) from heitorlessa/fix/[#249](https://github.com/aws-powertools/powertools-lambda-python/issues/249) +* Merge pull request [#235](https://github.com/aws-powertools/powertools-lambda-python/issues/235) from Nr18/phony +* Merge pull request [#244](https://github.com/aws-powertools/powertools-lambda-python/issues/244) from awslabs/docs/capture_method_clarification +* Merge pull request [#241](https://github.com/aws-powertools/powertools-lambda-python/issues/241) from awslabs/dependabot/npm_and_yarn/docs/ini-1.3.8 +* Merge pull request [#237](https://github.com/aws-powertools/powertools-lambda-python/issues/237) from gmcrocetti/pep-561 +* Merge pull request [#234](https://github.com/aws-powertools/powertools-lambda-python/issues/234) from Nr18/test-equal +* Merge pull request [#233](https://github.com/aws-powertools/powertools-lambda-python/issues/233) from GroovyDan/improv/add_equality_check_to_dict_wrapper +* Merge pull request [#232](https://github.com/aws-powertools/powertools-lambda-python/issues/232) from gyft/add-missing-tests + + +<a name="v1.9.0"></a> +## [v1.9.0] - 2020-12-04 +## Bug Fixes + +* s3 model import +* cloudwatch logs envelope typo + +## Documentation + +* add Kinesis Streams as a supported model & envelope +* add S3 as a supported model +* add CW Logs as a supported envelope +* add CW Logs as a supported model +* add Alb as a supported model +* shadow sidebar to remain expanded +* add source code link in nav bar +* fix broken link for github + +## Features + +* Add Kinesis lambda event support to Parser utility +* Add cloudwatch lambda event support to Parser utility +* Add alb lambda event support to Parser utility [#228](https://github.com/aws-powertools/powertools-lambda-python/issues/228) +* Add Kinesis lambda event support to Parser utility +* Add S3 lambda event support to Parser utility [#224](https://github.com/aws-powertools/powertools-lambda-python/issues/224) +* Add Ses lambda event support to Parser utility [#213](https://github.com/aws-powertools/powertools-lambda-python/issues/213) + +## Maintenance + + +## Pull Requests + +* Merge pull request [#227](https://github.com/aws-powertools/powertools-lambda-python/issues/227) from risenberg-cyberark/kinesis +* Merge pull request [#225](https://github.com/aws-powertools/powertools-lambda-python/issues/225) from risenberg-cyberark/s3 +* Merge pull request [#231](https://github.com/aws-powertools/powertools-lambda-python/issues/231) from risenberg-cyberark/cloudwatch +* Merge pull request [#229](https://github.com/aws-powertools/powertools-lambda-python/issues/229) from risenberg-cyberark/alb +* Merge pull request [#223](https://github.com/aws-powertools/powertools-lambda-python/issues/223) from heitorlessa/docs/add-source-code-link +* Merge pull request [#222](https://github.com/aws-powertools/powertools-lambda-python/issues/222) from awslabs/docs-fix-broken-link +* Merge pull request [#219](https://github.com/aws-powertools/powertools-lambda-python/issues/219) from igorlg/docs/logger-supress-clarify +* Merge pull request [#214](https://github.com/aws-powertools/powertools-lambda-python/issues/214) from risenberg-cyberark/ses + + +<a name="v1.8.0"></a> +## [v1.8.0] - 2020-11-20 +## Bug Fixes + +* replace now deprecated set-env with new GitHub Env file +* remove dummy heading to prevent htmlAst bug + +## Documentation + +* correct example usage of SES data class +* add faq section +* add minimal permission set for using layer + +## Features + +* include new replay-name field in parser and data_classes +* **data_classes:** API Gateway V2 IAM and Lambda + +## Maintenance + +* bump to 1.8.0 +* bump dependencies +* **docs:** Add some of the missing docstrings + +## Pull Requests + +* Merge pull request [#212](https://github.com/aws-powertools/powertools-lambda-python/issues/212) from heitorlessa/chore/bump-1.8.0 +* Merge pull request [#211](https://github.com/aws-powertools/powertools-lambda-python/issues/211) from heitorlessa/feat/eventbridge-replay-support +* Merge pull request [#209](https://github.com/aws-powertools/powertools-lambda-python/issues/209) from awslabs/docs/correct_ses_dataclass_example +* Merge pull request [#207](https://github.com/aws-powertools/powertools-lambda-python/issues/207) from risenberg-cyberark/sns +* Merge pull request [#205](https://github.com/aws-powertools/powertools-lambda-python/issues/205) from heitorlessa/chore/update-docs-dep +* Merge pull request [#202](https://github.com/aws-powertools/powertools-lambda-python/issues/202) from Nr18/logger-faq +* Merge pull request [#204](https://github.com/aws-powertools/powertools-lambda-python/issues/204) from am29d/docs/add-iam-permissions-for-layer +* Merge pull request [#201](https://github.com/aws-powertools/powertools-lambda-python/issues/201) from gyft/feat-data-classes-event-updates + + +<a name="v1.7.0"></a> +## [v1.7.0] - 2020-10-26 +## Bug Fixes + +* _parse return type +* high and security peer dependency vulnerabilities +* change to Yarn to support manual resolutions +* generic type to match ABC bound class +* debug logging in envelopes before each parsing +* remove malformed 3.1. sentence +* ensures parser can take json strings as input +* parse high level import +* code inspect issues +* unnecessary return; better error handling +* snake_case +* comment out validators [#118](https://github.com/aws-powertools/powertools-lambda-python/issues/118) +* CR fixes Merge branch 'develop' of https://github.com/awslabs/aws-lambda-powertools-python into pydantic +* reduce complexity of dynamo envelope +* poetry update + pydantic, typing_extensions as optional +* add only pydantic (+1 squashed commit) Squashed commits: [804f251] fix poetry.lock, revert changes +* Correct typo +* remove only dev extras +* remove jmespath extras in Make + +## Code Refactoring + +* pydantic as optional dependancy, remove lambdaContext +* change to advanced parser + +## Documentation + +* reorder parser's payload sample position +* add more info on conditional keys [#195](https://github.com/aws-powertools/powertools-lambda-python/issues/195) +* add a note that decorator will replace the event +* address Ran's feedback +* reorder data validation; improve envelopes section +* reorder extending models as parse fn wasn't introduced +* use yarn's resolution to fix incompatible dependency +* add cold start data +* add a FAQ section +* ensure examples can be copied/pasted as-is +* add extending built-in models +* add envelope section +* add data model validation section +* use non-hello world model to better exemplify parsing +* add 101 parsing events content +* initial structure for parser docs +* initial sketch of parser docs +* update examples in README + +## Features + +* experiment with codeQL over LGTM +* add standalone parse function +* Advanced parser utility (pydantic) +* RFC: Validate incoming and outgoing events utility [#95](https://github.com/aws-powertools/powertools-lambda-python/issues/95) +* **data_classes:** case insensitive header lookup +* **data_classes:** Cognito custom auth triggers + +## Maintenance + +* fix repository URL +* spacing +* typo in list +* typo on code generation tool +* remove flake8 polyfill as explicit dep +* explicit DynamoDB Stream schema naming +* lint +* kwarg over arg to ease refactoring +* remove test for commented code +* fix make build syntax for internal build whl +* upgrade docs dep +* remove dev deps from example project +* remove kitchen sink example +* upgrade gatsby +* upgrade amplify, antd, and gatsby plugins +* upgrade apollo-docs theme +* remove dev deps from example project +* remove kitchen sink example + +## Reverts +* fix: remove jmespath extras in Make +* fix: remove jmespath extras in Make + +## Pull Requests + +* Merge pull request [#200](https://github.com/aws-powertools/powertools-lambda-python/issues/200) from heitorlessa/chore/bump-1.7.0 +* Merge pull request [#199](https://github.com/aws-powertools/powertools-lambda-python/issues/199) from heitorlessa/docs/clarify-dynamic-log-keys +* Merge pull request [#198](https://github.com/aws-powertools/powertools-lambda-python/issues/198) from awslabs/improv/suppress-logger-propagation +* Merge pull request [#192](https://github.com/aws-powertools/powertools-lambda-python/issues/192) from heitorlessa/docs/parser +* Merge pull request [#196](https://github.com/aws-powertools/powertools-lambda-python/issues/196) from awslabs/dependabot/npm_and_yarn/docs/object-path-0.11.5 +* Merge pull request [#189](https://github.com/aws-powertools/powertools-lambda-python/issues/189) from heitorlessa/improv/parser[#118](https://github.com/aws-powertools/powertools-lambda-python/issues/118) +* Merge pull request [#186](https://github.com/aws-powertools/powertools-lambda-python/issues/186) from gyft/feat-case-insensitive-dict +* Merge pull request [#188](https://github.com/aws-powertools/powertools-lambda-python/issues/188) from gyft/tests-pydantic +* Merge pull request [#178](https://github.com/aws-powertools/powertools-lambda-python/issues/178) from gyft/cognito-custom-auth +* Merge pull request [#118](https://github.com/aws-powertools/powertools-lambda-python/issues/118) from risenberg-cyberark/pydantic +* Merge pull request [#181](https://github.com/aws-powertools/powertools-lambda-python/issues/181) from awslabs/fix/docs-sec-vuln +* Merge pull request [#180](https://github.com/aws-powertools/powertools-lambda-python/issues/180) from heitorlessa/chore/remove-example + + +<a name="v1.6.1"></a> +## [v1.6.1] - 2020-09-23 +## Maintenance + +* bump to 1.6.1 ([#177](https://github.com/aws-powertools/powertools-lambda-python/issues/177)) + + +<a name="v1.6.0"></a> +## [v1.6.0] - 2020-09-22 +## Bug Fixes + +* apply Tom's suggestion +* branding +* Correct description for data classes util +* duplicate features content +* navigation, branding +* remove DeleteMessageBatch call to SQS api if there are no messages to delete ([#170](https://github.com/aws-powertools/powertools-lambda-python/issues/170)) +* correct type hint Dict instead of dict + +## Code Refactoring + +* correct type hint + +## Documentation + +* fixed more typos, correct index reference to new util +* fix typo in DynamoDB example +* add docs for data classes utility +* improve wording on jmespath fns +* document validator utility + +## Features + +* add custom jmespath functions support +* emf multiple metric values ([#167](https://github.com/aws-powertools/powertools-lambda-python/issues/167)) +* add initial validator tests +* add cloudwatch_logs based on Bryan's feedback +* add powertools_base64 custom fn +* add built-in envelopes +* add jmespath as optional dependency +* add initial draft simple validator +* **trigger:** data class and helper functions for lambda trigger events ([#159](https://github.com/aws-powertools/powertools-lambda-python/issues/159)) + +## Maintenance + +* typo +* bump to 1.6.0 +* better type hinting +* update changelog +* fix docstring; import order + +## Pull Requests + +* Merge pull request [#175](https://github.com/aws-powertools/powertools-lambda-python/issues/175) from heitorlessa/chore/bump-1.6.0 +* Merge pull request [#171](https://github.com/aws-powertools/powertools-lambda-python/issues/171) from awslabs/docs/data_classes +* Merge pull request [#174](https://github.com/aws-powertools/powertools-lambda-python/issues/174) from heitorlessa/improv/docs-logger-metrics-testing +* Merge pull request [#168](https://github.com/aws-powertools/powertools-lambda-python/issues/168) from gyft/tests-missing +* Merge pull request [#153](https://github.com/aws-powertools/powertools-lambda-python/issues/153) from heitorlessa/feat/validator-utility + + +<a name="v1.5.0"></a> +## [v1.5.0] - 2020-09-04 +## Bug Fixes + +* throw exception by default if messages processing fails +* add sqs_batch_processor as its own method +* ensure debug log event has latest ctx +* update image with correct sample +* ensures xray_trace_id is refreshed +* typo in example +* include proposed suggestions +* **base-partial:** append record instead of entry +* **logging:** Don't include `json_default` in logs ([#132](https://github.com/aws-powertools/powertools-lambda-python/issues/132)) + +## Code Refactoring + +* changes partial_sqs middleware in favor of a generic interface always expecting a BatchProcessor +* replace LambdaEvent with Dict[str, Any] +* remove initial reference +* fix import issues and provide context in docblocks +* split properties and add docblocks +* split the objects into seperate files +* make requested changes +* use None instead of +* batch middleware +* remove references to BaseProcessor. Left BasePartialProcessor +* change return for failure/success handlers +* **sqs:** add module middlewares +* **sqs:** change methods to protected +* **tests:** update tests to new batch processor middleware +* **tests:** processor using default config + +## Documentation + +* address readability feedbacks +* add detail to batch processing +* simplify documentation more SQS specific focus Update for sqs_batch_processor interface +* rephrase the wording to make it more clear +* refactor example; improve docs about creating your own processor +* add newly created Slack Channel +* describe the typing utility +* add troubleshooting section +* add xray_trace_id key +* fix suggestions made by [@heitorlessa](https://github.com/heitorlessa) +* add description where to find the layer arn ([#145](https://github.com/aws-powertools/powertools-lambda-python/issues/145)) +* new section "Migrating from other Loggers" ([#148](https://github.com/aws-powertools/powertools-lambda-python/issues/148)) +* minor edit to letter case part 2 +* user specific documentation +* Fix doc for log sampling ([#135](https://github.com/aws-powertools/powertools-lambda-python/issues/135)) +* **partial-processor:** add simple docstrings to success/failure handlers +* **sqs:** docstrings for PartialSQS +* **sqs-base:** docstring for base class + +## Features + +* add xray_trace_id key when tracing is active [#137](https://github.com/aws-powertools/powertools-lambda-python/issues/137) +* initial implementation as the proposed gist is +* add sqs failure processors +* include base processors +* add batch module +* add package level import for batch utility +* **logger:** readable log_dict seq +* **logging:** suppress some log keys +* **logging:** allow for custom json order +* **parameters:** transform = "auto" ([#133](https://github.com/aws-powertools/powertools-lambda-python/issues/133)) +* **sqs:** add optional config parameter +* **sqs:** improve validation for queue_url + +## Maintenance + +* tiny changes for readability +* add debug logging for sqs batch processing +* remove middlewares module, moving decorator functionality to base and sqs +* add test for sqs_batch_processor interface +* add sqs_batch_processor decorator to simplify interface +* fix typos, docstrings and type hints ([#154](https://github.com/aws-powertools/powertools-lambda-python/issues/154)) +* doc typo +* **batch:** Housekeeping for recent changes ([#157](https://github.com/aws-powertools/powertools-lambda-python/issues/157)) + +## Pull Requests + +* Merge pull request [#149](https://github.com/aws-powertools/powertools-lambda-python/issues/149) from Nr18/static-types +* Merge pull request [#155](https://github.com/aws-powertools/powertools-lambda-python/issues/155) from awslabs/docs/batch_processing_util +* Merge pull request [#100](https://github.com/aws-powertools/powertools-lambda-python/issues/100) from gmcrocetti/partial-sqs-batch +* Merge pull request [#151](https://github.com/aws-powertools/powertools-lambda-python/issues/151) from Nr18/troubleshooting +* Merge pull request [#150](https://github.com/aws-powertools/powertools-lambda-python/issues/150) from heitorlessa/feat/logger-add-xray-trace-id +* Merge pull request [#140](https://github.com/aws-powertools/powertools-lambda-python/issues/140) from gyft/fix-log-key-order +* Merge pull request [#142](https://github.com/aws-powertools/powertools-lambda-python/issues/142) from gyft/fix-letter-case + + +<a name="v1.4.0"></a> +## [v1.4.0] - 2020-08-25 +## Bug Fixes + +* upgrade dot-prop, serialize-javascript +* remove actual response from debug logs +* naming and staticmethod consistency +* correct in_subsegment assertion +* update cold_start doc to reflect [#125](https://github.com/aws-powertools/powertools-lambda-python/issues/125) +* split ColdStart metric to its own EMF blob [#125](https://github.com/aws-powertools/powertools-lambda-python/issues/125) +* **ssm:** Make decrypt an explicit option and refactoring ([#123](https://github.com/aws-powertools/powertools-lambda-python/issues/123)) + +## Documentation + +* add Lambda Layer SAR App url and ARN +* move tenets; remove extra space +* use table for clarity +* add blog post, and quick example +* subtle rewording for better clarity +* fix typos, log_event & sampling wording +* make sensitive info more explicit with an example +* create Patching modules section; cleanup response wording +* move concurrent asynchronous under escape hatch +* grammar +* bring new feature upfront when returning sensitive info + +## Features + +* capture_response as metadata option [#127](https://github.com/aws-powertools/powertools-lambda-python/issues/127) + +## Maintenance + +* bump to 1.4.0 +* update internal docstrings for consistency +* update changelog to reflect new feature +* clarify changelog bugfix vs breaking change +* remove/correct unnecessary debug logs +* fix debug log adding unused obj +* grammar +* add metrics fix description +* correct typos + +## Pull Requests + +* Merge pull request [#129](https://github.com/aws-powertools/powertools-lambda-python/issues/129) from am29d/feat/lambda-layers +* Merge pull request [#130](https://github.com/aws-powertools/powertools-lambda-python/issues/130) from heitorlessa/docs/readability-improvements +* Merge pull request [#128](https://github.com/aws-powertools/powertools-lambda-python/issues/128) from heitorlessa/feat/tracer-disallow-response-metadata +* Merge pull request [#126](https://github.com/aws-powertools/powertools-lambda-python/issues/126) from heitorlessa/fix/metrics-cold-start-split + + +<a name="v1.3.1"></a> +## [v1.3.1] - 2020-08-22 +## Bug Fixes + +* **capture_method:** should yield inside with ([#124](https://github.com/aws-powertools/powertools-lambda-python/issues/124)) + +## Maintenance + +* version bump to 1.3.1 +* **deps:** bump prismjs from 1.20.0 to 1.21.0 in /docs +* **deps:** bump elliptic from 6.5.2 to 6.5.3 in /docs + +## Pull Requests + +* Merge pull request [#120](https://github.com/aws-powertools/powertools-lambda-python/issues/120) from awslabs/dependabot/npm_and_yarn/docs/elliptic-6.5.3 +* Merge pull request [#121](https://github.com/aws-powertools/powertools-lambda-python/issues/121) from awslabs/dependabot/npm_and_yarn/docs/prismjs-1.21.0 + + +<a name="v1.3.0"></a> +## [v1.3.0] - 2020-08-21 +## Features + +* add parameter utility ([#96](https://github.com/aws-powertools/powertools-lambda-python/issues/96)) + +## Maintenance + + + +<a name="v1.2.0"></a> +## [v1.2.0] - 2020-08-20 +## Features + +* add support for tracing of generators using capture_method decorator ([#113](https://github.com/aws-powertools/powertools-lambda-python/issues/113)) + +## Maintenance + + + +<a name="v1.1.3"></a> +## [v1.1.3] - 2020-08-18 +## Bug Fixes + +* remove root logger handler set by Lambda [#115](https://github.com/aws-powertools/powertools-lambda-python/issues/115) + +## Maintenance + +* bump to 1.1.3 + +## Pull Requests + +* Merge pull request [#117](https://github.com/aws-powertools/powertools-lambda-python/issues/117) from heitorlessa/chore/bump-1.1.3 +* Merge pull request [#116](https://github.com/aws-powertools/powertools-lambda-python/issues/116) from heitorlessa/fix/remove-root-logger-handler + + +<a name="v1.1.2"></a> +## [v1.1.2] - 2020-08-16 +## Bug Fixes + +* return subclass [#107](https://github.com/aws-powertools/powertools-lambda-python/issues/107) + +## Documentation + +* clarify auto_patch as per [#108](https://github.com/aws-powertools/powertools-lambda-python/issues/108) + +## Maintenance + +* suppress LGTM alert +* add autocomplete as unreleased +* remove unused stdout fixture +* update Tracer docs as per [#108](https://github.com/aws-powertools/powertools-lambda-python/issues/108) + +## Pull Requests + +* Merge pull request [#111](https://github.com/aws-powertools/powertools-lambda-python/issues/111) from heitorlessa/chore/bump-1.1.2 +* Merge pull request [#110](https://github.com/aws-powertools/powertools-lambda-python/issues/110) from heitorlessa/improv/logger-auto-complete +* Merge pull request [#109](https://github.com/aws-powertools/powertools-lambda-python/issues/109) from heitorlessa/docs/tracer-reuse + + +<a name="v1.1.1"></a> +## [v1.1.1] - 2020-08-14 +## Bug Fixes + +* regression 104 ([#105](https://github.com/aws-powertools/powertools-lambda-python/issues/105)) +* return log level int immediately +* add test covering logging constant + +## Maintenance + +* bump patch version +* fix unused fixture +* fix docstring on level [str,int] consistency +* fix test level typo +* trigger docs on new release ([#102](https://github.com/aws-powertools/powertools-lambda-python/issues/102)) ([#103](https://github.com/aws-powertools/powertools-lambda-python/issues/103)) +* trigger docs on new release ([#102](https://github.com/aws-powertools/powertools-lambda-python/issues/102)) +* trigger docs on new release + +## Regression + +* log level docstring as str + +## Pull Requests + +* Merge pull request [#106](https://github.com/aws-powertools/powertools-lambda-python/issues/106) from heitorlessa/fix/regression-104 + + +<a name="v1.1.0"></a> +## [v1.1.0] - 2020-08-14 +## Bug Fixes + +* auto-assigner filename as per docs + +## Features + +* add support for logger inheritance ([#99](https://github.com/aws-powertools/powertools-lambda-python/issues/99)) +* enable issue auto-assigner to core team + +## Maintenance + +* bump to 1.1.0 ([#101](https://github.com/aws-powertools/powertools-lambda-python/issues/101)) +* **deps:** bump lodash from 4.17.15 to 4.17.19 in /docs ([#93](https://github.com/aws-powertools/powertools-lambda-python/issues/93)) + + +<a name="v1.0.2"></a> +## [v1.0.2] - 2020-07-16 +## Maintenance + +* bump to 1.0.2 ([#90](https://github.com/aws-powertools/powertools-lambda-python/issues/90)) +* support aws-xray-sdk >=2.5.0 till <3.0.0 ([#89](https://github.com/aws-powertools/powertools-lambda-python/issues/89)) + + +<a name="v1.0.1"></a> +## [v1.0.1] - 2020-07-05 +## Bug Fixes + +* append structured logs when injecting lambda context ([#86](https://github.com/aws-powertools/powertools-lambda-python/issues/86)) + +## Documentation + +* add blog post in the readme + + +<a name="v1.0.0"></a> +## [v1.0.0] - 2020-06-18 +## Documentation + +* customize contributing guide ([#77](https://github.com/aws-powertools/powertools-lambda-python/issues/77)) + +## Features + +* docs anonymized page view ([#82](https://github.com/aws-powertools/powertools-lambda-python/issues/82)) +* add metrics metadata ([#81](https://github.com/aws-powertools/powertools-lambda-python/issues/81)) + +## Maintenance + +* bump to 1.0.0 GA ([#83](https://github.com/aws-powertools/powertools-lambda-python/issues/83)) +* add missing ':' and identation in examples +* cleanup tests ([#79](https://github.com/aws-powertools/powertools-lambda-python/issues/79)) +* remove deprecated code before GA ([#78](https://github.com/aws-powertools/powertools-lambda-python/issues/78)) +* move blockquotes as hidden comments + + +<a name="v0.11.0"></a> +## [v0.11.0] - 2020-06-10 +## Bug Fixes + +* default dimension creation now happens when metrics are serialized instead of on metrics constructor ([#74](https://github.com/aws-powertools/powertools-lambda-python/issues/74)) + +## Maintenance + +* update CHANGELOG + + +<a name="v0.10.1"></a> +## [v0.10.1] - 2020-06-10 +## Bug Fixes + +* default dimension creation now happens when metrics are serialized instead of on metrics constructor ([#74](https://github.com/aws-powertools/powertools-lambda-python/issues/74)) + +## Documentation + +* fix contrast on highlighted code text ([#73](https://github.com/aws-powertools/powertools-lambda-python/issues/73)) + +## Features + +* improve error handling for log_metrics decorator ([#71](https://github.com/aws-powertools/powertools-lambda-python/issues/71)) +* add high level imports ([#70](https://github.com/aws-powertools/powertools-lambda-python/issues/70)) + +## Maintenance + +* version bump 0.10.1 +* **deps:** bump graphql-playground-html from 1.6.19 to 1.6.25 in /docs + +## Pull Requests + +* Merge pull request [#72](https://github.com/aws-powertools/powertools-lambda-python/issues/72) from awslabs/dependabot/npm_and_yarn/docs/graphql-playground-html-1.6.25 + + +<a name="v0.10.0"></a> +## v0.10.0 - 2020-06-08 +## Bug Fixes + +* correct env var name for publish to pypi test ([#69](https://github.com/aws-powertools/powertools-lambda-python/issues/69)) +* release-drafter action syntax +* release-drafter label for new feature/major non-breaking changes +* cast dimension value to str to avoid issue where EMF silently fails ([#52](https://github.com/aws-powertools/powertools-lambda-python/issues/52)) +* ignore path that might seem a broken link [#49](https://github.com/aws-powertools/powertools-lambda-python/issues/49) +* open api ref in a new tab [#48](https://github.com/aws-powertools/powertools-lambda-python/issues/48) +* metrics not being flushed on every invocation ([#45](https://github.com/aws-powertools/powertools-lambda-python/issues/45)) +* [#35](https://github.com/aws-powertools/powertools-lambda-python/issues/35) duplicate changelog to project root +* [#24](https://github.com/aws-powertools/powertools-lambda-python/issues/24) correct example test and docs +* CI attempt 4 +* CI attempt 3 +* CI attempt 3 +* CI attempt 2 +* add missing single_metric example; test var name +* fix import of aws_lambda_logging to relative import +* **Makefile:** format before linting +* **make:** add twine as a dev dep +* **setup:** correct invalid license classifier +* **setup:** correct license to MIT-0 in meta + +## Documentation + +* build on master only +* clarify logger debug sampling message +* clean up readme in favour of docs website +* add install in main docs website +* add pypi badge + +## Features + +* add capture_cold_start_metric for log_metrics ([#67](https://github.com/aws-powertools/powertools-lambda-python/issues/67)) +* automate publishing to pypi ([#58](https://github.com/aws-powertools/powertools-lambda-python/issues/58)) +* add pre-commit hooks ([#64](https://github.com/aws-powertools/powertools-lambda-python/issues/64)) +* update Metrics interface to resemble tracer & logger: use "service" as its namespace. +* add codecov service ([#59](https://github.com/aws-powertools/powertools-lambda-python/issues/59)) +* add security and complexity baseline [#33](https://github.com/aws-powertools/powertools-lambda-python/issues/33) ([#57](https://github.com/aws-powertools/powertools-lambda-python/issues/57)) +* add pull request template [#33](https://github.com/aws-powertools/powertools-lambda-python/issues/33) +* add RFC template for proposals +* create issue templates +* readd release drafter action [#33](https://github.com/aws-powertools/powertools-lambda-python/issues/33) +* add release drafter ([#56](https://github.com/aws-powertools/powertools-lambda-python/issues/56)) +* add stale issues config [#33](https://github.com/aws-powertools/powertools-lambda-python/issues/33) ([#55](https://github.com/aws-powertools/powertools-lambda-python/issues/55)) +* enforce semantic PR titles ([#54](https://github.com/aws-powertools/powertools-lambda-python/issues/54)) +* add algolia search for docs and api ref ([#39](https://github.com/aws-powertools/powertools-lambda-python/issues/39)) +* add documentation website ([#37](https://github.com/aws-powertools/powertools-lambda-python/issues/37)) +* add docs to CI +* Add Python3.8 support +* **logger:** add log sampling +* **pypi:** add bumpversion, public release pypi +* **pyproject.toml:** move to poetry + +## Maintenance + +* version bump ([#68](https://github.com/aws-powertools/powertools-lambda-python/issues/68)) +* public beta version +* rename Makefile target docs-dev to docs-local ([#65](https://github.com/aws-powertools/powertools-lambda-python/issues/65)) +* correct docstring for log_metrics +* fix typo in metrics doc +* Correct test comment +* remove unused import +* formatting +* plat wheels are not needed +* reformat changelog to follow KeepAChangelog standard ([#50](https://github.com/aws-powertools/powertools-lambda-python/issues/50)) +* bump to release candidate +* renamed history to changelog dependabot +* grammar issues +* bump example to use 0.8.0 features +* clean up CI workflows +* fix github badge typo +* pypi monthly download badge +* lint +* bump 0.3.1 with logging patch +* bump history +* lint +* add Python 3.8 in badge as it's supported +* CI badge +* public beta version +* **deps:** bump bleach from 3.1.0 to 3.1.1 in /python +* **deps:** bump websocket-extensions from 0.1.3 to 0.1.4 in /docs ([#66](https://github.com/aws-powertools/powertools-lambda-python/issues/66)) + +## Pull Requests + +* Merge pull request [#60](https://github.com/aws-powertools/powertools-lambda-python/issues/60) from awslabs/improv/metrics_interface +* Merge pull request [#8](https://github.com/aws-powertools/powertools-lambda-python/issues/8) from awslabs/dependabot/pip/python/bleach-3.1.1 +* Merge pull request [#7](https://github.com/aws-powertools/powertools-lambda-python/issues/7) from danilohgds/sampling_feature +* Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38 + + +[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.39.1...HEAD +[v2.39.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.39.0...v2.39.1 +[v2.39.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.38.1...v2.39.0 +[v2.38.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.38.0...v2.38.1 +[v2.38.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.37.0...v2.38.0 +[v2.37.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.36.0...v2.37.0 +[v2.36.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.35.1...v2.36.0 +[v2.35.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.35.0...v2.35.1 +[v2.35.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.34.2...v2.35.0 +[v2.34.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.34.1...v2.34.2 +[v2.34.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.34.0...v2.34.1 +[v2.34.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.33.1...v2.34.0 +[v2.33.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.33.0...v2.33.1 +[v2.33.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.32.0...v2.33.0 +[v2.32.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.31.0...v2.32.0 +[v2.31.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.2...v2.31.0 +[v2.30.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.1...v2.30.2 +[v2.30.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.0...v2.30.1 +[v2.30.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.29.1...v2.30.0 +[v2.29.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.29.0...v2.29.1 +[v2.29.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.28.1...v2.29.0 +[v2.28.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.28.0...v2.28.1 +[v2.28.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.27.1...v2.28.0 +[v2.27.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.27.0...v2.27.1 +[v2.27.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.26.1...v2.27.0 +[v2.26.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.26.0...v2.26.1 +[v2.26.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.25.1...v2.26.0 +[v2.25.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.25.0...v2.25.1 +[v2.25.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.24.0...v2.25.0 +[v2.24.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.23.1...v2.24.0 +[v2.23.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.23.0...v2.23.1 +[v2.23.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.22.0...v2.23.0 +[v2.22.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.21.0...v2.22.0 +[v2.21.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.20.0...v2.21.0 +[v2.20.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.19.0...v2.20.0 +[v2.19.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.18.0...v2.19.0 +[v2.18.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.17.0...v2.18.0 +[v2.17.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.16.2...v2.17.0 +[v2.16.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.16.1...v2.16.2 +[v2.16.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.16.0...v2.16.1 +[v2.16.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.15.0...v2.16.0 +[v2.15.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.14.1...v2.15.0 +[v2.14.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.14.0...v2.14.1 +[v2.14.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.13.0...v2.14.0 +[v2.13.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.12.0...v2.13.0 +[v2.12.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.11.0...v2.12.0 +[v2.11.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.10.0...v2.11.0 +[v2.10.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.9.1...v2.10.0 +[v2.9.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.9.0...v2.9.1 +[v2.9.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.8.0...v2.9.0 +[v2.8.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.7.1...v2.8.0 +[v2.7.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.7.0...v2.7.1 +[v2.7.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.6.0...v2.7.0 +[v2.6.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.5.0...v2.6.0 +[v2.5.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.3.1...v2.4.0 +[v2.3.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.3.0...v2.3.1 +[v2.3.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.31.1...v2.0.0 +[v1.31.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.31.0...v1.31.1 +[v1.31.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.30.0...v1.31.0 +[v1.30.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.29.2...v1.30.0 +[v1.29.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.29.1...v1.29.2 +[v1.29.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.29.0...v1.29.1 +[v1.29.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.28.0...v1.29.0 +[v1.28.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.27.0...v1.28.0 +[v1.27.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.7...v1.27.0 +[v1.26.7]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.6...v1.26.7 +[v1.26.6]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.5...v1.26.6 +[v1.26.5]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.4...v1.26.5 +[v1.26.4]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.3...v1.26.4 +[v1.26.3]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.2...v1.26.3 +[v1.26.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.1...v1.26.2 +[v1.26.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.26.0...v1.26.1 +[v1.26.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.10...v1.26.0 +[v1.25.10]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.9...v1.25.10 +[v1.25.9]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.8...v1.25.9 +[v1.25.8]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.7...v1.25.8 +[v1.25.7]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.6...v1.25.7 +[v1.25.6]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.5...v1.25.6 +[v1.25.5]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.4...v1.25.5 +[v1.25.4]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.3...v1.25.4 +[v1.25.3]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.2...v1.25.3 +[v1.25.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.1...v1.25.2 +[v1.25.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.25.0...v1.25.1 +[v1.25.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.24.2...v1.25.0 +[v1.24.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.24.1...v1.24.2 +[v1.24.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.24.0...v1.24.1 +[v1.24.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.23.0...v1.24.0 +[v1.23.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.22.0...v1.23.0 +[v1.22.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.21.1...v1.22.0 +[v1.21.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.21.0...v1.21.1 +[v1.21.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.20.2...v1.21.0 +[v1.20.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.20.1...v1.20.2 +[v1.20.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.20.0...v1.20.1 +[v1.20.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.19.0...v1.20.0 +[v1.19.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.18.1...v1.19.0 +[v1.18.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.18.0...v1.18.1 +[v1.18.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.17.1...v1.18.0 +[v1.17.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.17.0...v1.17.1 +[v1.17.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.16.1...v1.17.0 +[v1.16.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.16.0...v1.16.1 +[v1.16.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.15.1...v1.16.0 +[v1.15.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.15.0...v1.15.1 +[v1.15.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.14.0...v1.15.0 +[v1.14.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.13.0...v1.14.0 +[v1.13.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.12.0...v1.13.0 +[v1.12.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.11.0...v1.12.0 +[v1.11.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.5...v1.11.0 +[v1.10.5]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.4...v1.10.5 +[v1.10.4]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.3...v1.10.4 +[v1.10.3]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.2...v1.10.3 +[v1.10.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.1...v1.10.2 +[v1.10.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.10.0...v1.10.1 +[v1.10.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.9.1...v1.10.0 +[v1.9.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.9.0...v1.9.1 +[v1.9.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.8.0...v1.9.0 +[v1.8.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.7.0...v1.8.0 +[v1.7.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.6.1...v1.7.0 +[v1.6.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.6.0...v1.6.1 +[v1.6.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.5.0...v1.6.0 +[v1.5.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.4.0...v1.5.0 +[v1.4.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.3.1...v1.4.0 +[v1.3.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.3.0...v1.3.1 +[v1.3.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.1.3...v1.2.0 +[v1.1.3]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.1.2...v1.1.3 +[v1.1.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.1.1...v1.1.2 +[v1.1.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.0.2...v1.1.0 +[v1.0.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v0.11.0...v1.0.0 +[v0.11.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v0.10.1...v0.11.0 +[v0.10.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v0.10.0...v0.10.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 914e0741d75..dc04db7ce4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,58 +1,182 @@ +<!-- markdownlint-disable MD043 MD041 --> +# Table of contents <!-- omit in toc --> + +- [Contributing Guidelines](#contributing-guidelines) + - [Reporting Bugs/Feature Requests](#reporting-bugsfeature-requests) + - [Contributing via Pull Requests](#contributing-via-pull-requests) + - [Dev setup](#dev-setup) + - [Local documentation](#local-documentation) + - [Conventions](#conventions) + - [General terminology and practices](#general-terminology-and-practices) + - [Testing definition](#testing-definition) + - [Finding contributions to work on](#finding-contributions-to-work-on) + - [Code of Conduct](#code-of-conduct) + - [Security issue notifications](#security-issue-notifications) + - [Troubleshooting](#troubleshooting) + - [API reference documentation](#api-reference-documentation) + - [Licensing](#licensing) + # Contributing Guidelines -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. +<!-- markdownlint-disable MD013 --> +Thank you for your interest in contributing to our project. Whether it's a [bug report](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=bug%2Ctriage&projects=&template=bug_report.yml&title=Bug%3A+TITLE), [new feature](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE), [correction](https://github.com/aws-powertools/powertools-lambda-python/issues/new/choose), or [additional documentation](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=documentation%2Ctriage&projects=&template=documentation_improvements.yml&title=Docs%3A+TITLE), we greatly value feedback and contributions from our community. +<!-- markdownlint-enable MD013 --> Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. - ## Reporting Bugs/Feature Requests -We welcome you to use the GitHub issue tracker to report bugs or suggest features. - -When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: - -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment +We welcome you to use the GitHub issue tracker to report bugs, suggest features, or documentation improvements. +<!-- markdownlint-disable MD013 --> +[When filing an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new/choose), please check [existing open](https://github.com/aws-powertools/powertools-lambda-python/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc), or [recently closed](https://github.com/aws-powertools/powertools-lambda-python/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed), issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. +<!-- markdownlint-enable MD013 --> ## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: - -1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. -To send us a pull request, please: +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +1. You are working against the latest source on the **develop** branch. +2. You check existing open, and recently merged pull requests to make sure someone else hasn't addressed the problem already. +3. You open an [issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new/choose) before you begin any implementation. We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. + +At a high level, these are the steps to get code merged in the repository - don't worry, nearly all of them are automated. + +```mermaid +timeline + title Code integration journey (CI) + Project setup <br> (make dev) : Code checkout + : Virtual environment + : Dependencies + : Git pre-commit hooks + : Local branch + : Local changes + : Local tests + + Pre-commit checks <br> (git commit) : Merge conflict check + : Trailing whitespaces + : TOML checks + : Code linting (standards) + : Markdown linting + : CloudFormation linting + : GitHub Actions linting + : Terraform linting + : Secrets linting + + Pre-Pull Request <br> (make pr) : Code linting + : Docs linting + : Static typing analysis + : Tests (unit|functional|perf|dependencies) + : Security baseline + : Complexity baseline + : +pre-commit checks + + Pull Request <br> (CI checks) : Semantic PR title check + : Related issue check + : Acknowledgment check + : Code coverage diff + : Contribution size check + : Contribution category check + : Dependency vulnerability check + : GitHub Actions security check + : +pre-pull request checks + + After merge <br> (CI checks) : End-to-end tests + : Longer SAST check + : Security posture check (scorecard) + : GitHub Actions security check + : Rebuild Changelog + : Deploy staging docs + : Update draft release +``` + +### Dev setup + +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/) + +Firstly, [fork the repository](https://github.com/aws-powertools/powertools-lambda-python/fork). + +To setup your development environment, we recommend using our pre-configured Cloud environment: <https://gitpod.io/#https://github.com/YOUR_USERNAME/aws-lambda-powertools-python>. Replace YOUR_USERNAME with your GitHub username or organization so the Cloud environment can target your fork accordingly. + +Alternatively, you can use `make dev` within your local virtual environment. + +To send us a pull request, please follow these steps: + +1. Create a new branch to focus on the specific change you are contributing e.g. `improv/logger-debug-sampling` +2. Run all tests, and code baseline checks: `make pr` + - Git hooks will run linting and formatting while `make pr` run deep checks that also run in the CI process +3. Commit to your fork using clear commit messages. +4. Send us a pull request with a [conventional semantic title](https://github.com/aws-powertools/powertools-lambda-python/pull/67), and answering any default questions in the pull request interface. +5. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +### Local documentation + +You might find useful to run both the documentation website and the API reference locally while contributing: + +- **API reference**: `make docs-api-local` +- **Docs website**: `make docs-local` + - If you prefer using Docker: `make docs-local-docker` + +## Conventions + +### General terminology and practices + +| Category | Convention | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Docstring** | We use a slight variation of Numpy convention with markdown to help generate more readable API references. | +| **Style guide** | We use black as well as [Ruff](https://beta.ruff.rs/docs/) to enforce beyond good practices [PEP8](https://pep8.org/). We use type annotations and enforce static type checking at CI (mypy). | +| **Core utilities** | Core utilities use a Class, always accept `service` as a constructor parameter, can work in isolation, and are also available in other languages implementation. | +| **Utilities** | Utilities are not as strict as core and focus on solving a developer experience problem while following the project [Tenets](https://docs.powertools.aws.dev/lambda/python/#tenets). | +| **Exceptions** | Specific exceptions live within utilities themselves and use `Error` suffix e.g. `MetricUnitError`. | +| **Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). We do not enforce conventional commits on contributors to lower the entry bar. Instead, we enforce a conventional PR title so our label automation and changelog are generated correctly. | +| **API documentation** | API reference docs are generated from docstrings which should have Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise. | +| **Documentation** | We treat it like a product. We sub-divide content aimed at getting started (80% of customers) vs advanced usage (20%). We also ensure customers know how to unit test their code when using our features. | + +### Testing definition + +We group tests in different categories + +| Test | When to write | Notes | Speed | +| ----------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| Unit tests | Verify the smallest possible unit works. | Networking access is prohibited. Prefer Functional tests given our complexity. | Lightning fast (nsec to ms) | +| Functional tests | Guarantee functionality works as expected. It's a subset of integration test covering multiple units. | No external dependency. Prefer Fake implementations (in-memory) over Mocks and Stubs. | Fast (ms to few seconds at worst) | +| Integration tests | Gain confidence that code works with one or more external dependencies. | No need for a Lambda function. Use our code base against an external dependency _e.g., fetch an existing SSM parameter_. | Moderate to slow (a few minutes) | +| End-to-end tests | Gain confidence that a Lambda function with our code operates as expected. | It simulates how customers configure, deploy, and run their Lambda function - Event Source configuration, IAM permissions, etc. | Slow (minutes) | +| Performance tests | Ensure critical operations won't increase latency and costs to customers. | CI arbitrary hardware can make it flaky. We'll resume writing perf test after our new Integ/End have significant coverage. | Fast to moderate (a few seconds to a few minutes) | + +**NOTE**: Functional tests are mandatory. We have plans to create a guide on how to create these different tests. Maintainers will help indicate whether additional tests are necessary and provide assistance as required. ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/help wanted/invalid/question/documentation), [looking at any 'help wanted' issues is a great place to start](https://github.com/orgs/aws-powertools/projects/3/views/5?query=is%3Aopen+sort%3Aupdated-desc). ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. - +<opensource-codeofconduct@amazon.com> with any additional questions or comments. ## Security issue notifications + If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. +## Troubleshooting + +### API reference documentation + +When you are working on the codebase and you use the local API reference documentation to preview your changes, you might see the following message: `Module aws_lambda_powertools not found`. + +This happens when: + +- You did not install the local dev environment yet + - You can install dev deps with `make dev` command +- The code in the repository is raising an exception while the `pdoc` is scanning the codebase + - Unfortunately, this exception is not shown to you, but if you run, `poetry run pdoc --pdf aws_lambda_powertools`, the exception is shown and you can prevent the exception from being raised + - Once resolved the documentation should load correctly again ## Licensing diff --git a/LICENSE b/LICENSE index fcc7fa6828c..fcdede53dc8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +MIT No Attribution + Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -12,4 +14,3 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000000..2fa7b2eed23 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,3 @@ +<!-- markdownlint-disable --> + +Maintainers' playbook moved: https://docs.powertools.aws.dev/lambda/python/latest/maintainers/ diff --git a/python/MANIFEST.in b/MANIFEST.in similarity index 76% rename from python/MANIFEST.in rename to MANIFEST.in index f3155af7064..370ff1a55e0 100644 --- a/python/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include LICENSE include README.md +include THIRD-PARTY-LICENSES recursive-exclude * __pycache__ recursive-exclude * *.py[co] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..114a817b1cd --- /dev/null +++ b/Makefile @@ -0,0 +1,119 @@ +.PHONY: target dev format lint test coverage-html pr build build-docs build-docs-api build-docs-website +.PHONY: docs-local docs-api-local security-baseline complexity-baseline release-prod release-test release + +target: + @$(MAKE) pr + +dev: + pip install --upgrade pip pre-commit poetry + poetry config --local virtualenvs.in-project true + @$(MAKE) dev-version-plugin + poetry install --extras "all redis datamasking" + pre-commit install + +dev-gitpod: + pip install --upgrade pip poetry + poetry install --extras "all redis datamasking" + pre-commit install + +format: + poetry run black aws_lambda_powertools tests examples + +lint: format + poetry run ruff check aws_lambda_powertools tests examples + +lint-docs: + docker run -v ${PWD}:/markdown 06kellyjac/markdownlint-cli "docs" + +lint-docs-fix: + docker run -v ${PWD}:/markdown 06kellyjac/markdownlint-cli --fix "docs" + +test: + poetry run pytest -m "not perf" --ignore tests/e2e --cov=aws_lambda_powertools --cov-report=xml + poetry run pytest --cache-clear tests/performance + +test-dependencies: + poetry run nox --error-on-external-run --reuse-venv=yes --non-interactive + +test-pydanticv2: + poetry run pytest -m "not perf" --ignore tests/e2e + +unit-test: + poetry run pytest tests/unit + +e2e-test: + poetry run pytest tests/e2e + +coverage-html: + poetry run pytest -m "not perf" --ignore tests/e2e --cov=aws_lambda_powertools --cov-report=html + +pre-commit: + pre-commit run --show-diff-on-failure + +pr: lint lint-docs mypy pre-commit test security-baseline complexity-baseline + +build: pr + poetry build + +release-docs: + @echo "Rebuilding docs" + rm -rf site api + @echo "Updating website docs" + poetry run mike deploy --push --update-aliases ${VERSION} ${ALIAS} + @echo "Building API docs" + @$(MAKE) build-docs-api VERSION=${VERSION} + +build-docs-api: + poetry run pdoc --html --output-dir ./api/ ./aws_lambda_powertools --force + mv -f ./api/aws_lambda_powertools/* ./api/ + rm -rf ./api/aws_lambda_powertools + mkdir ${VERSION} && cp -R api ${VERSION} + +docs-local: + poetry run mkdocs serve + +docs-local-docker: + docker build -t squidfunk/mkdocs-material ./docs/ + docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material + +docs-api-local: + poetry run pdoc --http : aws_lambda_powertools + +security-baseline: + poetry run bandit --baseline bandit.baseline -r aws_lambda_powertools + +complexity-baseline: + $(info Maintenability index) + poetry run radon mi aws_lambda_powertools + $(info Cyclomatic complexity index) + poetry run xenon --max-absolute C --max-modules A --max-average A aws_lambda_powertools --exclude aws_lambda_powertools/shared/json_encoder.py,aws_lambda_powertools/utilities/validation/base.py + +# +# Use `poetry version <major>/<minor></patch>` for version bump +# +release-prod: + poetry config pypi-token.pypi ${PYPI_TOKEN} + poetry publish -n + +release-test: + poetry config repositories.testpypi https://test.pypi.org/legacy + poetry config pypi-token.pypi ${PYPI_TEST_TOKEN} + poetry publish --repository testpypi -n + +release: pr + poetry build + $(MAKE) release-test + $(MAKE) release-prod + +changelog: + git fetch --tags origin + CURRENT_VERSION=$(shell git describe --abbrev=0 --tag) ;\ + echo "[+] Pre-generating CHANGELOG for tag: $$CURRENT_VERSION" ;\ + docker run -v "${PWD}":/workdir quay.io/git-chglog/git-chglog:0.15.1 > CHANGELOG.md + +mypy: + poetry run mypy --pretty aws_lambda_powertools examples + + +dev-version-plugin: + poetry self add git+https://github.com/monim67/poetry-bumpversion@315fe3324a699fa12ec20e202eb7375d4327d1c4 diff --git a/README.md b/README.md index 9db9bcbc68f..be449899676 100644 --- a/README.md +++ b/README.md @@ -1,198 +1,94 @@ -# Lambda Powertools +<!-- markdownlint-disable MD013 MD041 MD043 --> +# Powertools for AWS Lambda (Python) -![PackageStatus](https://img.shields.io/static/v1?label=status&message=beta&color=blueviolet?style=flat-square) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7&color=blue?style=flat-square&logo=python) +[![Build](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/quality_check.yml/badge.svg)](https://github.com/aws-powertools/powertools-lambda-python/actions/workflows/python_build.yml) +[![codecov.io](https://codecov.io/github/aws-powertools/powertools-lambda-python/branch/develop/graphs/badge.svg)](https://app.codecov.io/gh/aws-powertools/powertools-lambda-python) +![PythonSupport](https://img.shields.io/static/v1?label=python&message=%203.8|%203.9|%203.10|%203.11|%203.12&color=blue?style=flat-square&logo=python) ![PyPI version](https://badge.fury.io/py/aws-lambda-powertools.svg) ![PyPi monthly downloads](https://img.shields.io/pypi/dm/aws-lambda-powertools) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-python/badge)](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-python) [![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET?style=flat-square)](https://discord.gg/B8zZKbbyET) -A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier - Currently available for Python only and compatible with Python >=3.6. +Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda/python/latest/#features). -**Status**: Beta +> Also available in [Java](https://github.com/aws-powertools/powertools-lambda-java), [Typescript](https://github.com/aws-powertools/powertools-lambda-typescript), and [.NET](https://github.com/aws-powertools/powertools-lambda-dotnet). + +**[📜Documentation](https://docs.powertools.aws.dev/lambda/python/)** | **[🐍PyPi](https://pypi.org/project/aws-lambda-powertools/)** | **[Roadmap](https://docs.powertools.aws.dev/lambda/python/latest/roadmap/)** | **[Detailed blog post](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-lambda-powertools/)** + +![hero-image](https://user-images.githubusercontent.com/3340292/198254617-d0fdb672-86a6-4988-8a40-adf437135e0a.png) ## Features -**Tracing** +* **[Tracing](https://docs.powertools.aws.dev/lambda/python/latest/core/tracer/)** - Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions +* **[Logging](https://docs.powertools.aws.dev/lambda/python/latest/core/logger/)** - Structured logging made easier, and decorator to enrich structured logging with key Lambda context details +* **[Metrics](https://docs.powertools.aws.dev/lambda/python/latest/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) +* **[Event handler: AppSync](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/appsync/)** - AWS AppSync event handler for Lambda Direct Resolver and Amplify GraphQL Transformer function +* **[Event handler: API Gateway and ALB](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/)** - Amazon API Gateway REST/HTTP API and ALB event handler for Lambda functions invoked using Proxy integration +* **[Event handler: Agents for Amazon Bedrock](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/bedrock_agents/)** - Create Agents for Amazon Bedrock, automatically generating OpenAPI schemas +* **[Bring your own middleware](https://docs.powertools.aws.dev/lambda/python/latest/utilities/middleware_factory/)** - Decorator factory to create your own middleware to run logic before, and after each Lambda invocation +* **[Parameters utility](https://docs.powertools.aws.dev/lambda/python/latest/utilities/parameters/)** - Retrieve and cache parameter values from Parameter Store, Secrets Manager, or DynamoDB +* **[Batch processing](https://docs.powertools.aws.dev/lambda/python/latest/utilities/batch/)** - Handle partial failures for AWS SQS batch processing +* **[Typing](https://docs.powertools.aws.dev/lambda/python/latest/utilities/typing/)** - Static typing classes to speedup development in your IDE +* **[Validation](https://docs.powertools.aws.dev/lambda/python/latest/utilities/validation/)** - JSON Schema validator for inbound events and responses +* **[Event source data classes](https://docs.powertools.aws.dev/lambda/python/latest/utilities/data_classes/)** - Data classes describing the schema of common Lambda event triggers +* **[Parser](https://docs.powertools.aws.dev/lambda/python/latest/utilities/parser/)** - Data parsing and deep validation using Pydantic +* **[Idempotency](https://docs.powertools.aws.dev/lambda/python/latest/utilities/idempotency/)** - Convert your Lambda functions into idempotent operations which are safe to retry +* **[Data Masking](https://docs.powertools.aws.dev/lambda/python/latest/utilities/data_masking/)** - Protect confidential data with easy removal or encryption +* **[Feature Flags](https://docs.powertools.aws.dev/lambda/python/latest/utilities/feature_flags/)** - A simple rule engine to evaluate when one or multiple features should be enabled depending on the input +* **[Streaming](https://docs.powertools.aws.dev/lambda/python/latest/utilities/streaming/)** - Streams datasets larger than the available memory as streaming data. -> It currently uses AWS X-Ray +### Installation -* Decorators that capture cold start as annotation, and response and exceptions as metadata -* Run functions locally without code change to disable tracing -* Explicitly disable tracing via env var `POWERTOOLS_TRACE_DISABLED="true"` +With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip install aws-lambda-powertools`` -**Logging** +## Tutorial and Examples -* Decorators that capture key fields from Lambda context, cold start and structures logging output as JSON -* Optionally log Lambda request when instructed (disabled by default) - - Enable via `POWERTOOLS_LOGGER_LOG_EVENT="true"` or explicitly via decorator param -* Logs canonical custom metric line to logs that can be consumed asynchronously +* [Tutorial](https://docs.powertools.aws.dev/lambda/python/latest/tutorial) +* [Serverless Shopping cart](https://github.com/aws-samples/aws-serverless-shopping-cart) +* [Serverless Airline](https://github.com/aws-samples/aws-serverless-airline-booking) +* [Serverless E-commerce platform](https://github.com/aws-samples/aws-serverless-ecommerce-platform) +* [Serverless GraphQL Nanny Booking Api](https://github.com/trey-rosius/babysitter_api) -**Environment variables** used across suite of utilities +## How to support Powertools for AWS Lambda (Python)? -Environment variable | Description | Default | Utility -------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- -POWERTOOLS_SERVICE_NAME | Sets service name used for tracing namespace, metrics dimensions and structured logging | "service_undefined" | all -POWERTOOLS_TRACE_DISABLED | Disables tracing | "false" | tracing -POWERTOOLS_LOGGER_LOG_EVENT | Logs incoming event | "false" | logging -LOG_LEVEL | Sets logging level | "INFO" | logging +### Becoming a reference customer -## Usage +Knowing which companies are using this library is important to help prioritize the project internally. If your company is using Powertools for AWS Lambda (Python), you can request to have your name and logo added to the README file by raising a [Support Powertools for AWS Lambda (Python) (become a reference)](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=customer-reference&template=support_powertools.yml&title=%5BSupport+Lambda+Powertools%5D%3A+%3Cyour+organization+name%3E) issue. -### Installation +The following companies, among others, use Powertools: -With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip install aws-lambda-powertools`` +* [Brsk](https://www.brsk.co.uk/) +* [Capital One](https://www.capitalone.com/) +* [CPQi (Exadel Financial Services)](https://cpqi.com/) +* [CloudZero](https://www.cloudzero.com/) +* [CyberArk](https://www.cyberark.com/) +* [globaldatanet](https://globaldatanet.com/) +* [IMS](https://ims.tech/) +* [Jit Security](https://www.jit.io/) +* [Propellor.ai](https://www.propellor.ai/) +* [Recast](https://getrecast.com/) +* [TopSport](https://www.topsport.com.au/) +* [Transformity](https://transformity.tech/) +* [Trek10](https://www.trek10.com/) +* [Vertex Pharmaceuticals](https://www.vrtx.com/) + +### Sharing your work -### Tracing - -**Example SAM template using supported environment variables** - -```yaml -Globals: - Function: - Tracing: Active # can also be enabled per function - Environment: - Variables: - POWERTOOLS_SERVICE_NAME: "payment" - POWERTOOLS_TRACE_DISABLED: "false" -``` - -**Pseudo Python Lambda code** - -```python -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer() -# tracer = Tracer(service="payment") # can also be explicitly defined - -@tracer.capture_method -def collect_payment(charge_id): - # logic - ret = requests.post(PAYMENT_ENDPOINT) - # custom annotation - tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") - return ret - -@tracer.capture_lambda_handler -def handler(event, context) - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) - ... -``` - - -### Logging - -**Example SAM template using supported environment variables** - -```yaml -Globals: - Function: - Environment: - Variables: - POWERTOOLS_SERVICE_NAME: "payment" - LOG_LEVEL: "INFO" -``` - -**Pseudo Python Lambda code** - -```python -from aws_lambda_powertools.logging import logger_setup, logger_inject_lambda_context - -logger = logger_setup() -# logger_setup(service="payment") # also accept explicit service name -# logger_setup(level="INFO") # also accept explicit log level - -@logger_inject_lambda_context -def handler(event, context) - logger.info("Collecting payment") - ... - # You can log entire objects too - logger.info({ - "operation": "collect_payment", - "charge_id": event['charge_id'] - }) - ... -``` - -**Exerpt output in CloudWatch Logs** - -```json -{ - "timestamp":"2019-08-22 18:17:33,774", - "level":"INFO", - "location":"collect.handler:1", - "service":"payment", - "lambda_function_name":"test", - "lambda_function_memory_size":"128", - "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", - "cold_start": "true", - "message": "Collecting payment" -} - -{ - "timestamp":"2019-08-22 18:17:33,774", - "level":"INFO", - "location":"collect.handler:15", - "service":"payment", - "lambda_function_name":"test", - "lambda_function_memory_size":"128", - "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", - "cold_start": "true", - "message":{ - "operation":"collect_payment", - "charge_id": "ch_AZFlk2345C0" - } -} -``` - -#### Custom Metrics async - -> **NOTE**: This will **likely change after Beta** in light of [new Amazon CloudWatch embedded metric format](https://aws.amazon.com/about-aws/whats-new/2019/11/amazon-cloudwatch-launches-embedded-metric-format/), meaning we won't need an additional stack and interface could change. - -This feature requires [Custom Metrics SAR App](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:374852340823:applications~async-custom-metrics) in order to process canonical metric lines in CloudWatch Logs. - -If you're starting from scratch, you may want to see a working example, tune to your needs and deploy within your account - [Serverless Airline Log Processing Stack](https://github.com/aws-samples/aws-serverless-airline-booking/blob/develop/src/backend/log-processing/template.yaml) - -```python -from aws_lambda_powertools.logging import MetricUnit, log_metric - -def handler(event, context) - log_metric(name="SuccessfulPayment", unit=MetricUnit.Count, value=10, namespace="MyApplication") - - # Optional dimensions - log_metric(name="SuccessfulPayment", unit=MetricUnit.Count, value=10, namespace="MyApplication", customer_id="123-abc", charge_id="abc-123") - - # Explicit service name - log_metric(service="paymentTest", name="SuccessfulPayment", namespace="MyApplication".....) - ... -``` - -**Exerpt output in CloudWatch Logs** - -``` -MONITORING|10|Count|SuccessfulPayment|MyApplication|service="payment -MONITORING|10|Count|SuccessfulPayment|MyApplication|customer_id="123-abc",charge_id="abc-123",service="payment -MONITORING|10|Count|SuccessfulPayment|MyApplication|service="paymentTest -``` - - -## Beta - -This library may change its API/methods or environment variables as it receives feedback from customers. Currently looking for ideas in the following areas before making it stable: - -* **Should Tracer patch all possible imported libraries by default or only AWS SDKs?** - - Patching all libraries may have a small performance penalty (~50ms) at cold start - - Alternatively, we could patch only AWS SDK if available and to provide a param to patch multiple `Tracer(modules=("boto3", "requests"))` -* **Create a Tracer provider to support additional tracing** - - Either duck typing or ABC to allow additional tracing providers - -## TODO - -* [ ] Enable CI -* [ ] Add an example code using powertools -* [ ] Automate release and version bumping in CI +Share what you did with Powertools for AWS Lambda (Python) 💞💞. Blog post, workshops, presentation, sample apps and others. Check out what the community has already shared about Powertools for AWS Lambda (Python) [here](https://docs.powertools.aws.dev/lambda/python/latest/we_made_this/). + +### Using Lambda Layer or SAR + +This helps us understand who uses Powertools for AWS Lambda (Python) in a non-intrusive way, and helps us gain future investments for other Powertools for AWS Lambda languages. When [using Layers](https://docs.powertools.aws.dev/lambda/python/latest/#lambda-layer), you can add Powertools for AWS Lambda (Python) as a dev dependency (or as part of your virtual env) to not impact the development process. ## Credits * Structured logging initial implementation from [aws-lambda-logging](https://gitlab.com/hadrien/aws_lambda_logging) -* Powertools idea [DAZN Powertools](https://github.com/getndazn/dazn-lambda-powertools/) +* Powertools for AWS Lambda (Python) idea [DAZN Powertools](https://github.com/getndazn/dazn-lambda-powertools/) + +## Connect + +* **Powertools for AWS Lambda on Discord**: `#python` - **[Invite link](https://discord.gg/B8zZKbbyET)** +* **Email**: <aws-powertools-maintainers@amazon.com> + +## Security disclosures + +If you think you’ve found a potential security issue, please do not post it in the Issues. Instead, please follow the instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..885ffb18f2c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +<!-- markdownlint-disable MD043 --> + +## Reporting a vulnerability + +If you discover a potential security issue in this project, we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to <aws-security@amazon.com>. + +Please do **not** create a public GitHub issue. diff --git a/THIRD-PARTY-LICENSES b/THIRD-PARTY-LICENSES new file mode 100644 index 00000000000..f6d647c54a7 --- /dev/null +++ b/THIRD-PARTY-LICENSES @@ -0,0 +1,200 @@ +** FastAPI - https://github.com/tiangolo/fastapi/ - Used in the OpenAPI feature + + The MIT License (MIT) + + Copyright (c) 2018 Sebastián Ramírez + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +** Tensorflow - https://github.com/tensorflow/tensorflow/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/aws_lambda_powertools/__init__.py b/aws_lambda_powertools/__init__.py new file mode 100644 index 00000000000..14237bc7119 --- /dev/null +++ b/aws_lambda_powertools/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +"""Top-level package for Lambda Python Powertools.""" + +from pathlib import Path + +from aws_lambda_powertools.logging import Logger +from aws_lambda_powertools.metrics import Metrics, single_metric +from aws_lambda_powertools.package_logger import set_package_logger_handler +from aws_lambda_powertools.shared.user_agent import inject_user_agent +from aws_lambda_powertools.shared.version import VERSION +from aws_lambda_powertools.tracing import Tracer + +__version__ = VERSION +__author__ = """Amazon Web Services""" +__all__ = [ + "Logger", + "Metrics", + "single_metric", + "Tracer", +] + +PACKAGE_PATH = Path(__file__).parent + +set_package_logger_handler() + +inject_user_agent() diff --git a/aws_lambda_powertools/event_handler/__init__.py b/aws_lambda_powertools/event_handler/__init__.py new file mode 100644 index 00000000000..ffbb2abe4ae --- /dev/null +++ b/aws_lambda_powertools/event_handler/__init__.py @@ -0,0 +1,32 @@ +""" +Event handler decorators for common Lambda events +""" + +from aws_lambda_powertools.event_handler.api_gateway import ( + ALBResolver, + APIGatewayHttpResolver, + ApiGatewayResolver, + APIGatewayRestResolver, + CORSConfig, + Response, +) +from aws_lambda_powertools.event_handler.appsync import AppSyncResolver +from aws_lambda_powertools.event_handler.bedrock_agent import BedrockAgentResolver +from aws_lambda_powertools.event_handler.lambda_function_url import ( + LambdaFunctionUrlResolver, +) +from aws_lambda_powertools.event_handler.vpc_lattice import VPCLatticeResolver, VPCLatticeV2Resolver + +__all__ = [ + "AppSyncResolver", + "APIGatewayRestResolver", + "APIGatewayHttpResolver", + "ALBResolver", + "ApiGatewayResolver", + "BedrockAgentResolver", + "CORSConfig", + "LambdaFunctionUrlResolver", + "Response", + "VPCLatticeResolver", + "VPCLatticeV2Resolver", +] diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py new file mode 100644 index 00000000000..7c4d676931e --- /dev/null +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -0,0 +1,2654 @@ +import base64 +import json +import logging +import re +import traceback +import warnings +import zlib +from abc import ABC, abstractmethod +from enum import Enum +from functools import partial +from http import HTTPStatus +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generic, + List, + Mapping, + Match, + Optional, + Pattern, + Sequence, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +from aws_lambda_powertools.event_handler import content_types +from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError +from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION +from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError, SchemaValidationError +from aws_lambda_powertools.event_handler.openapi.types import ( + COMPONENT_REF_PREFIX, + METHODS_WITH_BODY, + OpenAPIResponse, + OpenAPIResponseContentModel, + OpenAPIResponseContentSchema, + validation_error_definition, + validation_error_response_definition, +) +from aws_lambda_powertools.event_handler.util import ( + _FrozenDict, + _FrozenListDict, + _validate_openapi_security_parameters, + extract_origin_header, +) +from aws_lambda_powertools.shared.cookies import Cookie +from aws_lambda_powertools.shared.functions import powertools_dev_is_set +from aws_lambda_powertools.shared.json_encoder import Encoder +from aws_lambda_powertools.shared.types import Literal +from aws_lambda_powertools.utilities.data_classes import ( + ALBEvent, + APIGatewayProxyEvent, + APIGatewayProxyEventV2, + BedrockAgentEvent, + LambdaFunctionUrlEvent, + VPCLatticeEvent, + VPCLatticeEventV2, +) +from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = logging.getLogger(__name__) + +_DYNAMIC_ROUTE_PATTERN = r"(<\w+>)" +_SAFE_URI = "-._~()'!*:@,;=+&$" # https://www.ietf.org/rfc/rfc3986.txt +# API GW/ALB decode non-safe URI chars; we must support them too +_UNSAFE_URI = r"%<> \[\]{}|^" +_NAMED_GROUP_BOUNDARY_PATTERN = rf"(?P\1[{_SAFE_URI}{_UNSAFE_URI}\\w]+)" +_DEFAULT_OPENAPI_RESPONSE_DESCRIPTION = "Successful Response" +_ROUTE_REGEX = "^{}$" + +ResponseEventT = TypeVar("ResponseEventT", bound=BaseProxyEvent) +ResponseT = TypeVar("ResponseT") + +if TYPE_CHECKING: + from aws_lambda_powertools.event_handler.openapi.compat import ( + JsonSchemaValue, + ModelField, + ) + from aws_lambda_powertools.event_handler.openapi.models import ( + Contact, + License, + OpenAPI, + SecurityScheme, + Server, + Tag, + ) + from aws_lambda_powertools.event_handler.openapi.params import Dependant + from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import ( + OAuth2Config, + ) + from aws_lambda_powertools.event_handler.openapi.types import ( + TypeModelOrEnum, + ) + + +class ProxyEventType(Enum): + """An enumerations of the supported proxy event types.""" + + APIGatewayProxyEvent = "APIGatewayProxyEvent" + APIGatewayProxyEventV2 = "APIGatewayProxyEventV2" + ALBEvent = "ALBEvent" + BedrockAgentEvent = "BedrockAgentEvent" + VPCLatticeEvent = "VPCLatticeEvent" + VPCLatticeEventV2 = "VPCLatticeEventV2" + LambdaFunctionUrlEvent = "LambdaFunctionUrlEvent" + + +class CORSConfig: + """CORS Config + + Examples + -------- + + Simple cors example using the default permissive cors, not this should only be used during early prototyping + + ```python + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + app = APIGatewayRestResolver() + + @app.get("/my/path", cors=True) + def with_cors(): + return {"message": "Foo"} + ``` + + Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors` + do not include any cors headers. + + ```python + from aws_lambda_powertools.event_handler.api_gateway import ( + APIGatewayRestResolver, CORSConfig + ) + + cors_config = CORSConfig( + allow_origin="https://wwww.example.com/", + extra_origins=["https://dev.example.com/"], + expose_headers=["x-exposed-response-header"], + allow_headers=["x-custom-request-header"], + max_age=100, + allow_credentials=True, + ) + app = APIGatewayRestResolver(cors=cors_config) + + @app.get("/my/path") + def with_cors(): + return {"message": "Foo"} + + @app.get("/another-one", cors=False) + def without_cors(): + return {"message": "Foo"} + ``` + """ + + _REQUIRED_HEADERS = ["Authorization", "Content-Type", "X-Amz-Date", "X-Api-Key", "X-Amz-Security-Token"] + + def __init__( + self, + allow_origin: str = "*", + extra_origins: Optional[List[str]] = None, + allow_headers: Optional[List[str]] = None, + expose_headers: Optional[List[str]] = None, + max_age: Optional[int] = None, + allow_credentials: bool = False, + ): + """ + Parameters + ---------- + allow_origin: str + The value of the `Access-Control-Allow-Origin` to send in the response. Defaults to "*", but should + only be used during development. + extra_origins: Optional[List[str]] + The list of additional allowed origins. + allow_headers: Optional[List[str]] + The list of additional allowed headers. This list is added to list of + built-in allowed headers: `Authorization`, `Content-Type`, `X-Amz-Date`, + `X-Api-Key`, `X-Amz-Security-Token`. + expose_headers: Optional[List[str]] + A list of values to return for the Access-Control-Expose-Headers + max_age: Optional[int] + The value for the `Access-Control-Max-Age` + allow_credentials: bool + A boolean value that sets the value of `Access-Control-Allow-Credentials` + """ + self._allowed_origins = [allow_origin] + if extra_origins: + self._allowed_origins.extend(extra_origins) + self.allow_headers = set(self._REQUIRED_HEADERS + (allow_headers or [])) + self.expose_headers = expose_headers or [] + self.max_age = max_age + self.allow_credentials = allow_credentials + + def to_dict(self, origin: Optional[str]) -> Dict[str, str]: + """Builds the configured Access-Control http headers""" + + # If there's no Origin, don't add any CORS headers + if not origin: + return {} + + # If the origin doesn't match any of the allowed origins, and we don't allow all origins ("*"), + # don't add any CORS headers + if origin not in self._allowed_origins and "*" not in self._allowed_origins: + return {} + + # The origin matched an allowed origin, so return the CORS headers + headers = { + "Access-Control-Allow-Origin": origin, + "Access-Control-Allow-Headers": CORSConfig.build_allow_methods(self.allow_headers), + } + + if self.expose_headers: + headers["Access-Control-Expose-Headers"] = ",".join(self.expose_headers) + if self.max_age is not None: + headers["Access-Control-Max-Age"] = str(self.max_age) + if self.allow_credentials is True: + headers["Access-Control-Allow-Credentials"] = "true" + return headers + + @staticmethod + def build_allow_methods(methods: Set[str]) -> str: + """Build sorted comma delimited methods for Access-Control-Allow-Methods header + + Parameters + ---------- + methods : set[str] + Set of HTTP Methods + + Returns + ------- + set[str] + Formatted string with all HTTP Methods allowed for CORS e.g., `GET, OPTIONS` + + """ + return ",".join(sorted(methods)) + + +class Response(Generic[ResponseT]): + """Response data class that provides greater control over what is returned from the proxy event""" + + def __init__( + self, + status_code: int, + content_type: Optional[str] = None, + body: Optional[ResponseT] = None, + headers: Optional[Mapping[str, Union[str, List[str]]]] = None, + cookies: Optional[List[Cookie]] = None, + compress: Optional[bool] = None, + ): + """ + + Parameters + ---------- + status_code: int + Http status code, example 200 + content_type: str + Optionally set the Content-Type header, example "application/json". Note this will be merged into any + provided http headers + body: Union[str, bytes, None] + Optionally set the response body. Note: bytes body will be automatically base64 encoded + headers: Mapping[str, Union[str, List[str]]] + Optionally set specific http headers. Setting "Content-Type" here would override the `content_type` value. + cookies: list[Cookie] + Optionally set cookies. + """ + self.status_code = status_code + self.body = body + self.base64_encoded = False + self.headers: Dict[str, Union[str, List[str]]] = dict(headers) if headers else {} + self.cookies = cookies or [] + self.compress = compress + self.content_type = content_type + if content_type: + self.headers.setdefault("Content-Type", content_type) + + def is_json(self) -> bool: + """ + Returns True if the response is JSON, based on the Content-Type. + """ + content_type = self.headers.get("Content-Type", "") + if isinstance(content_type, list): + content_type = content_type[0] + return content_type.startswith("application/json") + + +class Route: + """Internally used Route Configuration""" + + def __init__( + self, + method: str, + path: str, + rule: Pattern, + func: Callable, + cors: bool, + compress: bool, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: Optional[str] = None, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Response]]] = None, + ): + """ + + Parameters + ---------- + + method: str + The HTTP method, example "GET" + path: str + The path of the route + rule: Pattern + The route rule, example "/my/path" + func: Callable + The route handler function + cors: bool + Whether or not to enable CORS for this route + compress: bool + Whether or not to enable gzip compression for this route + cache_control: Optional[str] + The cache control header value, example "max-age=3600" + summary: Optional[str] + The OpenAPI summary for this route + description: Optional[str] + The OpenAPI description for this route + responses: Optional[Dict[int, OpenAPIResponse]] + The OpenAPI responses for this route + response_description: Optional[str] + The OpenAPI response description for this route + tags: Optional[List[str]] + The list of OpenAPI tags to be used for this route + operation_id: Optional[str] + The OpenAPI operationId for this route + include_in_schema: bool + Whether or not to include this route in the OpenAPI schema + security: List[Dict[str, List[str]]], optional + The OpenAPI security for this route + middlewares: Optional[List[Callable[..., Response]]] + The list of route middlewares to be called in order. + """ + self.method = method.upper() + self.path = "/" if path.strip() == "" else path + + # OpenAPI spec only understands paths with { }. So we'll have to convert Powertools' < >. + # https://swagger.io/specification/#path-templating + self.openapi_path = re.sub(r"<(.*?)>", lambda m: f"{{{''.join(m.group(1))}}}", self.path) + + self.rule = rule + self.func = func + self._middleware_stack = func + self.cors = cors + self.compress = compress + self.cache_control = cache_control + self.summary = summary + self.description = description + self.responses = responses + self.response_description = response_description + self.tags = tags or [] + self.include_in_schema = include_in_schema + self.security = security + self.middlewares = middlewares or [] + self.operation_id = operation_id or self._generate_operation_id() + + # _middleware_stack_built is used to ensure the middleware stack is only built once. + self._middleware_stack_built = False + + # _dependant is used to cache the dependant model for the handler function + self._dependant: Optional["Dependant"] = None + + # _body_field is used to cache the dependant model for the body field + self._body_field: Optional["ModelField"] = None + + def __call__( + self, + router_middlewares: List[Callable], + app: "ApiGatewayResolver", + route_arguments: Dict[str, str], + ) -> Union[Dict, Tuple, Response]: + """Calling the Router class instance will trigger the following actions: + 1. If Route Middleware stack has not been built, build it + 2. Call the Route Middleware stack wrapping the original function + handler with the app and route arguments. + + Parameters + ---------- + router_middlewares: List[Callable] + The list of Router Middlewares (assigned to ALL routes) + app: "ApiGatewayResolver" + The ApiGatewayResolver instance to pass into the middleware stack + route_arguments: Dict[str, str] + The route arguments to pass to the app function (extracted from the Api Gateway + Lambda Message structure from AWS) + + Returns + ------- + Union[Dict, Tuple, Response] + API Response object in ALL cases, except when the original API route + handler is called which may also return a Dict, Tuple, or Response. + """ + + # Save CPU cycles by building middleware stack once + if not self._middleware_stack_built: + self._build_middleware_stack(router_middlewares=router_middlewares) + + # If debug is turned on then output the middleware stack to the console + if app._debug: + print(f"\nProcessing Route:::{self.func.__name__} ({app.context['_path']})") + # Collect ALL middleware for debug printing - include internal _registered_api_adapter + all_middlewares = router_middlewares + self.middlewares + [_registered_api_adapter] + print("\nMiddleware Stack:") + print("=================") + print("\n".join(getattr(item, "__name__", "Unknown") for item in all_middlewares)) + print("=================") + + # Add Route Arguments to app context + app.append_context(_route_args=route_arguments) + + # Call the Middleware Wrapped _call_stack function handler with the app + return self._middleware_stack(app) + + def _build_middleware_stack(self, router_middlewares: List[Callable[..., Any]]) -> None: + """ + Builds the middleware stack for the handler by wrapping each + handler in an instance of MiddlewareWrapper which is used to contain the state + of each middleware step. + + Middleware is represented by a standard Python Callable construct. Any Middleware + handler wanting to short-circuit the middlware call chain can raise an exception + to force the Python call stack created by the handler call-chain to naturally un-wind. + + This becomes a simple concept for developers to understand and reason with - no additional + gymanstics other than plain old try ... except. + + Notes + ----- + The Route Middleware stack is processed in reverse order. This is so the stack of + middleware handlers is applied in the order of being added to the handler. + """ + all_middlewares = router_middlewares + self.middlewares + logger.debug(f"Building middleware stack: {all_middlewares}") + + # IMPORTANT: + # this must be the last middleware in the stack (tech debt for backward + # compatibility purposes) + # + # This adapter will: + # 1. Call the registered API passing only the expected route arguments extracted from the path + # and not the middleware. + # 2. Adapt the response type of the route handler (Union[Dict, Tuple, Response]) + # and normalise into a Response object so middleware will always have a constant signature + all_middlewares.append(_registered_api_adapter) + + # Wrap the original route handler function in the middleware handlers + # using the MiddlewareWrapper class callable construct in reverse order to + # ensure middleware is applied in the order the user defined. + # + # Start with the route function and wrap from last to the first Middleware handler. + for handler in reversed(all_middlewares): + self._middleware_stack = MiddlewareFrame(current_middleware=handler, next_middleware=self._middleware_stack) + + self._middleware_stack_built = True + + @property + def dependant(self) -> "Dependant": + if self._dependant is None: + from aws_lambda_powertools.event_handler.openapi.dependant import get_dependant + + self._dependant = get_dependant(path=self.openapi_path, call=self.func, responses=self.responses) + + return self._dependant + + @property + def body_field(self) -> Optional["ModelField"]: + if self._body_field is None: + from aws_lambda_powertools.event_handler.openapi.dependant import get_body_field + + self._body_field = get_body_field(dependant=self.dependant, name=self.operation_id) + + return self._body_field + + def _get_openapi_path( + self, + *, + dependant: "Dependant", + operation_ids: Set[str], + model_name_map: Dict["TypeModelOrEnum", str], + field_mapping: Dict[Tuple["ModelField", Literal["validation", "serialization"]], "JsonSchemaValue"], + ) -> Tuple[Dict[str, Any], Dict[str, Any]]: + """ + Returns the OpenAPI path and definitions for the route. + """ + from aws_lambda_powertools.event_handler.openapi.dependant import get_flat_params + + path = {} + definitions: Dict[str, Any] = {} + + # Gather all the route parameters + operation = self._openapi_operation_metadata(operation_ids=operation_ids) + parameters: List[Dict[str, Any]] = [] + all_route_params = get_flat_params(dependant) + operation_params = self._openapi_operation_parameters( + all_route_params=all_route_params, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + parameters.extend(operation_params) + + # Add security if present + if self.security: + operation["security"] = self.security + + # Add the parameters to the OpenAPI operation + if parameters: + all_parameters = {(param["in"], param["name"]): param for param in parameters} + required_parameters = {(param["in"], param["name"]): param for param in parameters if param.get("required")} + all_parameters.update(required_parameters) + operation["parameters"] = list(all_parameters.values()) + + # Add the request body to the OpenAPI operation, if applicable + if self.method.upper() in METHODS_WITH_BODY: + request_body_oai = self._openapi_operation_request_body( + body_field=self.body_field, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + if request_body_oai: + operation["requestBody"] = request_body_oai + + # Validation failure response (422) will always be part of the schema + operation_responses: Dict[int, OpenAPIResponse] = { + 422: { + "description": "Validation Error", + "content": { + "application/json": { + "schema": {"$ref": COMPONENT_REF_PREFIX + "HTTPValidationError"}, + }, + }, + }, + } + + # Add the response to the OpenAPI operation + if self.responses: + for status_code in list(self.responses): + response = self.responses[status_code] + + # Case 1: there is not 'content' key + if "content" not in response: + response["content"] = { + "application/json": self._openapi_operation_return( + param=dependant.return_param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ), + } + + # Case 2: there is a 'content' key + else: + # Need to iterate to transform any 'model' into a 'schema' + for content_type, payload in response["content"].items(): + new_payload: OpenAPIResponseContentSchema + + # Case 2.1: the 'content' has a model + if "model" in payload: + # Find the model in the dependant's extra models + return_field = next( + filter( + lambda model: model.type_ is cast(OpenAPIResponseContentModel, payload)["model"], + self.dependant.response_extra_models, + ), + ) + if not return_field: + raise AssertionError("Model declared in custom responses was not found") + + new_payload = self._openapi_operation_return( + param=return_field, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + # Case 2.2: the 'content' has a schema + else: + # Do nothing! We already have what we need! + new_payload = payload + + response["content"][content_type] = new_payload + + # Merge the user provided response with the default responses + operation_responses[status_code] = response + else: + # Set the default 200 response + response_schema = self._openapi_operation_return( + param=dependant.return_param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + # Add the response schema to the OpenAPI 200 response + operation_responses[200] = { + "description": self.response_description or _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + "content": {"application/json": response_schema}, + } + + operation["responses"] = operation_responses + path[self.method.lower()] = operation + + # Add the validation error schema to the definitions, but only if it hasn't been added yet + if "ValidationError" not in definitions: + definitions.update( + { + "ValidationError": validation_error_definition, + "HTTPValidationError": validation_error_response_definition, + }, + ) + + # Generate the response schema + return path, definitions + + def _openapi_operation_summary(self) -> str: + """ + Returns the OpenAPI operation summary. If the user has not provided a summary, we + generate one based on the route path and method. + """ + return self.summary or f"{self.method.upper()} {self.openapi_path}" + + def _openapi_operation_metadata(self, operation_ids: Set[str]) -> Dict[str, Any]: + """ + Returns the OpenAPI operation metadata. If the user has not provided a description, we + generate one based on the route path and method. + """ + operation: Dict[str, Any] = {} + + # Ensure tags is added to the operation + if self.tags: + operation["tags"] = self.tags + + # Ensure summary is added to the operation + operation["summary"] = self._openapi_operation_summary() + + # Ensure description is added to the operation + if self.description: + operation["description"] = self.description + + # Ensure operationId is unique + if self.operation_id in operation_ids: + message = f"Duplicate Operation ID {self.operation_id} for function {self.func.__name__}" + file_name = getattr(self.func, "__globals__", {}).get("__file__") + if file_name: + message += f" in {file_name}" + warnings.warn(message, stacklevel=1) + + # Adds the operation + operation_ids.add(self.operation_id) + operation["operationId"] = self.operation_id + + return operation + + @staticmethod + def _openapi_operation_request_body( + *, + body_field: Optional["ModelField"], + model_name_map: Dict["TypeModelOrEnum", str], + field_mapping: Dict[Tuple["ModelField", Literal["validation", "serialization"]], "JsonSchemaValue"], + ) -> Optional[Dict[str, Any]]: + """ + Returns the OpenAPI operation request body. + """ + from aws_lambda_powertools.event_handler.openapi.compat import ModelField, get_schema_from_model_field + from aws_lambda_powertools.event_handler.openapi.params import Body + + # Check that there is a body field and it's a Pydantic's model field + if not body_field: + return None + + if not isinstance(body_field, ModelField): + raise AssertionError(f"Expected ModelField, got {body_field}") + + # Generate the request body schema + body_schema = get_schema_from_model_field( + field=body_field, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + field_info = cast(Body, body_field.field_info) + request_media_type = field_info.media_type + required = body_field.required + request_body_oai: Dict[str, Any] = {} + if required: + request_body_oai["required"] = required + + if field_info.description: + request_body_oai["description"] = field_info.description + + # Generate the request body media type + request_media_content: Dict[str, Any] = {"schema": body_schema} + request_body_oai["content"] = {request_media_type: request_media_content} + return request_body_oai + + @staticmethod + def _openapi_operation_parameters( + *, + all_route_params: Sequence["ModelField"], + model_name_map: Dict["TypeModelOrEnum", str], + field_mapping: Dict[ + Tuple["ModelField", Literal["validation", "serialization"]], + "JsonSchemaValue", + ], + ) -> List[Dict[str, Any]]: + """ + Returns the OpenAPI operation parameters. + """ + from aws_lambda_powertools.event_handler.openapi.compat import ( + get_schema_from_model_field, + ) + from aws_lambda_powertools.event_handler.openapi.params import Param + + parameters = [] + parameter: Dict[str, Any] + for param in all_route_params: + field_info = param.field_info + field_info = cast(Param, field_info) + if not field_info.include_in_schema: + continue + + param_schema = get_schema_from_model_field( + field=param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + parameter = { + "name": param.alias, + "in": field_info.in_.value, + "required": param.required, + "schema": param_schema, + } + + if field_info.description: + parameter["description"] = field_info.description + + if field_info.deprecated: + parameter["deprecated"] = field_info.deprecated + + parameters.append(parameter) + + return parameters + + @staticmethod + def _openapi_operation_return( + *, + param: Optional["ModelField"], + model_name_map: Dict["TypeModelOrEnum", str], + field_mapping: Dict[ + Tuple["ModelField", Literal["validation", "serialization"]], + "JsonSchemaValue", + ], + ) -> OpenAPIResponseContentSchema: + """ + Returns the OpenAPI operation return. + """ + if param is None: + return {} + + from aws_lambda_powertools.event_handler.openapi.compat import ( + get_schema_from_model_field, + ) + + return_schema = get_schema_from_model_field( + field=param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + return {"schema": return_schema} + + def _generate_operation_id(self) -> str: + operation_id = self.func.__name__ + self.openapi_path + operation_id = re.sub(r"\W", "_", operation_id) + operation_id = operation_id + "_" + self.method.lower() + return operation_id + + +class ResponseBuilder(Generic[ResponseEventT]): + """Internally used Response builder""" + + def __init__( + self, + response: Response, + serializer: Callable[[Any], str] = partial(json.dumps, separators=(",", ":"), cls=Encoder), + route: Optional[Route] = None, + ): + self.response = response + self.serializer = serializer + self.route = route + + def _add_cors(self, event: ResponseEventT, cors: CORSConfig): + """Update headers to include the configured Access-Control headers""" + extracted_origin_header = extract_origin_header(event.resolved_headers_field) + self.response.headers.update(cors.to_dict(extracted_origin_header)) + + def _add_cache_control(self, cache_control: str): + """Set the specified cache control headers for 200 http responses. For non-200 `no-cache` is used.""" + cache_control = cache_control if self.response.status_code == 200 else "no-cache" + self.response.headers["Cache-Control"] = cache_control + + @staticmethod + def _has_compression_enabled( + route_compression: bool, + response_compression: Optional[bool], + event: ResponseEventT, + ) -> bool: + """ + Checks if compression is enabled. + + NOTE: Response compression takes precedence. + + Parameters + ---------- + route_compression: bool, optional + A boolean indicating whether compression is enabled or not in the route setting. + response_compression: bool, optional + A boolean indicating whether compression is enabled or not in the response setting. + event: ResponseEventT + The event object containing the request details. + + Returns + ------- + bool + True if compression is enabled and the "gzip" encoding is accepted, False otherwise. + """ + encoding: str = event.get_header_value( + name="accept-encoding", + default_value="", + case_sensitive=False, + ) # noqa: E501 + if "gzip" in encoding: + if response_compression is not None: + return response_compression # e.g., Response(compress=False/True)) + if route_compression: + return True # e.g., @app.get(compress=True) + + return False + + def _compress(self): + """Compress the response body, but only if `Accept-Encoding` headers includes gzip.""" + self.response.headers["Content-Encoding"] = "gzip" + if isinstance(self.response.body, str): + logger.debug("Converting string response to bytes before compressing it") + self.response.body = bytes(self.response.body, "utf-8") + gzip = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) + self.response.body = gzip.compress(self.response.body) + gzip.flush() + + def _route(self, event: ResponseEventT, cors: Optional[CORSConfig]): + """Optionally handle any of the route's configure response handling""" + if self.route is None: + return + if self.route.cors: + self._add_cors(event, cors or CORSConfig()) + if self.route.cache_control: + self._add_cache_control(self.route.cache_control) + if self._has_compression_enabled( + route_compression=self.route.compress, + response_compression=self.response.compress, + event=event, + ): + self._compress() + + def build(self, event: ResponseEventT, cors: Optional[CORSConfig] = None) -> Dict[str, Any]: + """Build the full response dict to be returned by the lambda""" + + # We only apply the serializer when the content type is JSON and the + # body is not a str, to avoid double encoding + if self.response.is_json() and not isinstance(self.response.body, str): + self.response.body = self.serializer(self.response.body) + + self._route(event, cors) + + if isinstance(self.response.body, bytes): + logger.debug("Encoding bytes response with base64") + self.response.base64_encoded = True + self.response.body = base64.b64encode(self.response.body).decode() + + return { + "statusCode": self.response.status_code, + "body": self.response.body, + "isBase64Encoded": self.response.base64_encoded, + **event.header_serializer().serialize(headers=self.response.headers, cookies=self.response.cookies), + } + + +class BaseRouter(ABC): + current_event: BaseProxyEvent + lambda_context: LambdaContext + context: dict + _router_middlewares: List[Callable] = [] + processed_stack_frames: List[str] = [] + + @abstractmethod + def route( + self, + rule: str, + method: Any, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + raise NotImplementedError() + + def use(self, middlewares: List[Callable[..., Response]]) -> None: + """ + Add one or more global middlewares that run before/after route specific middleware. + + NOTE: Middlewares are called in insertion order. + + Parameters + ---------- + middlewares: List[Callable[..., Response]] + List of global middlewares to be used + + Examples + -------- + + Add middlewares to be used for every request processed by the Router. + + ```python + from aws_lambda_powertools import Logger + from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response + from aws_lambda_powertools.event_handler.middlewares import NextMiddleware + + logger = Logger() + app = APIGatewayRestResolver() + + def log_request_response(app: APIGatewayRestResolver, next_middleware: NextMiddleware) -> Response: + logger.info("Incoming request", path=app.current_event.path, request=app.current_event.raw_event) + + result = next_middleware(app) + logger.info("Response received", response=result.__dict__) + + return result + + app.use(middlewares=[log_request_response]) + + + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + self._router_middlewares = self._router_middlewares + middlewares + + def get( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + """Get route decorator with GET `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.get("/get-call") + def simple_get(): + return {"message": "Foo"} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "GET", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def post( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + """Post route decorator with POST `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.post("/post-call") + def simple_post(): + post_data: dict = app.current_event.json_body + return {"message": post_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "POST", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def put( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + """Put route decorator with PUT `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.put("/put-call") + def simple_put(): + put_data: dict = app.current_event.json_body + return {"message": put_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "PUT", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def delete( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + """Delete route decorator with DELETE `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.delete("/delete-call") + def simple_delete(): + return {"message": "deleted"} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "DELETE", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def patch( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable]] = None, + ): + """Patch route decorator with PATCH `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.patch("/patch-call") + def simple_patch(): + patch_data: dict = app.current_event.json_body + patch_data["value"] = patched + + return {"message": patch_data} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "PATCH", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def head( + self, + rule: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable]] = None, + ): + """Head route decorator with HEAD `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.head("/head-call") + def simple_head(): + return Response(status_code=200, + content_type=content_types.APPLICATION_JSON, + headers={"Content-Length": "123"}) + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + return self.route( + rule, + "HEAD", + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + def _push_processed_stack_frame(self, frame: str): + """ + Add Current Middleware to the Middleware Stack Frames + The stack frames will be used when exceptions are thrown and Powertools + debug is enabled by developers. + """ + self.processed_stack_frames.append(frame) + + def _reset_processed_stack(self): + """Reset the Processed Stack Frames""" + self.processed_stack_frames.clear() + + def append_context(self, **additional_context): + """Append key=value data as routing context""" + self.context.update(**additional_context) + + def clear_context(self): + """Resets routing context""" + self.context.clear() + + +class MiddlewareFrame: + """ + Creates a Middle Stack Wrapper instance to be used as a "Frame" in the overall stack of + middleware functions. Each instance contains the current middleware and the next + middleware function to be called in the stack. + + In this way the middleware stack is constructed in a recursive fashion, with each middleware + calling the next as a simple function call. The actual Python call-stack will contain + each MiddlewareStackWrapper "Frame", meaning any Middleware function can cause the + entire Middleware call chain to be exited early (short-circuited) by raising an exception + or by simply returning early with a custom Response. The decision to short-circuit the middleware + chain is at the user's discretion but instantly available due to the Wrapped nature of the + callable constructs in the Middleware stack and each Middleware function having complete control over + whether the "Next" handler in the stack is called or not. + + Parameters + ---------- + current_middleware : Callable + The current middleware function to be called as a request is processed. + next_middleware : Callable + The next middleware in the middleware stack. + """ + + def __init__( + self, + current_middleware: Callable[..., Any], + next_middleware: Callable[..., Any], + ) -> None: + self.current_middleware: Callable[..., Any] = current_middleware + self.next_middleware: Callable[..., Any] = next_middleware + self._next_middleware_name = next_middleware.__name__ + + @property + def __name__(self) -> str: # noqa: A003 + """Current middleware name + + It ensures backward compatibility with view functions being callable. This + improves debugging since we need both current and next middlewares/callable names. + """ + return self.current_middleware.__name__ + + def __str__(self) -> str: + """Identify current middleware identity and call chain for debugging purposes.""" + middleware_name = self.__name__ + return f"[{middleware_name}] next call chain is {middleware_name} -> {self._next_middleware_name}" + + def __call__(self, app: "ApiGatewayResolver") -> Union[Dict, Tuple, Response]: + """ + Call the middleware Frame to process the request. + + Parameters + ---------- + app: BaseRouter + The router instance + + Returns + ------- + Union[Dict, Tuple, Response] + (tech-debt for backward compatibility). The response type should be a + Response object in all cases excepting when the original API route handler + is called which will return one of 3 outputs. + + """ + # Do debug printing and push processed stack frame AFTER calling middleware + # else the stack frame text of `current calling next` is confusing. + logger.debug("MiddlewareFrame: %s", self) + app._push_processed_stack_frame(str(self)) + + return self.current_middleware(app, self.next_middleware) + + +def _registered_api_adapter( + app: "ApiGatewayResolver", + next_middleware: Callable[..., Any], +) -> Union[Dict, Tuple, Response]: + """ + Calls the registered API using the "_route_args" from the Resolver context to ensure the last call + in the chain will match the API route function signature and ensure that Powertools passes the API + route handler the expected arguments. + + **IMPORTANT: This internal middleware ensures the actual API route is called with the correct call signature + and it MUST be the final frame in the middleware stack. This can only be removed when the API Route + function accepts `app: BaseRouter` as the first argument - which is the breaking change. + + Parameters + ---------- + app: ApiGatewayResolver + The API Gateway resolver + next_middleware: Callable[..., Any] + The function to handle the API + + Returns + ------- + Response + The API Response Object + + """ + route_args: Dict = app.context.get("_route_args", {}) + logger.debug(f"Calling API Route Handler: {route_args}") + return app._to_response(next_middleware(**route_args)) + + +class ApiGatewayResolver(BaseRouter): + """API Gateway and ALB proxy resolver + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + tracer = Tracer() + app = APIGatewayRestResolver() + + @app.get("/get-call") + def simple_get(): + return {"message": "Foo"} + + @app.post("/post-call") + def simple_post(): + post_data: dict = app.current_event.json_body + return {"message": post_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + + def __init__( + self, + proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, + cors: Optional[CORSConfig] = None, + debug: Optional[bool] = None, + serializer: Optional[Callable[[Dict], str]] = None, + strip_prefixes: Optional[List[Union[str, Pattern]]] = None, + enable_validation: bool = False, + ): + """ + Parameters + ---------- + proxy_type: ProxyEventType + Proxy request type, defaults to API Gateway V1 + cors: CORSConfig + Optionally configure and enabled CORS. Not each route will need to have to cors=True + debug: Optional[bool] + Enables debug mode, by default False. Can be also be enabled by "POWERTOOLS_DEV" + environment variable + serializer: Callable, optional + function to serialize `obj` to a JSON formatted `str`, by default json.dumps + strip_prefixes: List[Union[str, Pattern]], optional + optional list of prefixes to be removed from the request path before doing the routing. + This is often used with api gateways with multiple custom mappings. + Each prefix can be a static string or a compiled regex pattern + enable_validation: Optional[bool] + Enables validation of the request body against the route schema, by default False. + """ + self._proxy_type = proxy_type + self._dynamic_routes: List[Route] = [] + self._static_routes: List[Route] = [] + self._route_keys: List[str] = [] + self._exception_handlers: Dict[Type, Callable] = {} + self._cors = cors + self._cors_enabled: bool = cors is not None + self._cors_methods: Set[str] = {"OPTIONS"} + self._debug = self._has_debug(debug) + self._enable_validation = enable_validation + self._strip_prefixes = strip_prefixes + self.context: Dict = {} # early init as customers might add context before event resolution + self.processed_stack_frames = [] + self._response_builder_class = ResponseBuilder[BaseProxyEvent] + + # Allow for a custom serializer or a concise json serialization + self._serializer = serializer or partial(json.dumps, separators=(",", ":"), cls=Encoder) + + if self._enable_validation: + from aws_lambda_powertools.event_handler.middlewares.openapi_validation import OpenAPIValidationMiddleware + + # Note the serializer argument: only use custom serializer if provided by the caller + # Otherwise, fully rely on the internal Pydantic based mechanism to serialize responses for validation. + self.use([OpenAPIValidationMiddleware(validation_serializer=serializer)]) + + def get_openapi_schema( + self, + *, + title: str = "Powertools API", + version: str = DEFAULT_API_VERSION, + openapi_version: str = DEFAULT_OPENAPI_VERSION, + summary: Optional[str] = None, + description: Optional[str] = None, + tags: Optional[List[Union["Tag", str]]] = None, + servers: Optional[List["Server"]] = None, + terms_of_service: Optional[str] = None, + contact: Optional["Contact"] = None, + license_info: Optional["License"] = None, + security_schemes: Optional[Dict[str, "SecurityScheme"]] = None, + security: Optional[List[Dict[str, List[str]]]] = None, + ) -> "OpenAPI": + """ + Returns the OpenAPI schema as a pydantic model. + + Parameters + ---------- + title: str + The title of the application. + version: str + The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API + openapi_version: str, default = "3.0.0" + The version of the OpenAPI Specification (which the document uses). + summary: str, optional + A short summary of what the application does. + description: str, optional + A verbose explanation of the application behavior. + tags: List[Tag | str], optional + A list of tags used by the specification with additional metadata. + servers: List[Server], optional + An array of Server Objects, which provide connectivity information to a target server. + terms_of_service: str, optional + A URL to the Terms of Service for the API. MUST be in the format of a URL. + contact: Contact, optional + The contact information for the exposed API. + license_info: License, optional + The license information for the exposed API. + security_schemes: Dict[str, "SecurityScheme"]], optional + A declaration of the security schemes available to be used in the specification. + security: List[Dict[str, List[str]]], optional + A declaration of which security mechanisms are applied globally across the API. + + Returns + ------- + OpenAPI: pydantic model + The OpenAPI schema as a pydantic model. + """ + + from aws_lambda_powertools.event_handler.openapi.compat import ( + GenerateJsonSchema, + get_compat_model_name_map, + get_definitions, + ) + from aws_lambda_powertools.event_handler.openapi.models import OpenAPI, PathItem, Tag + from aws_lambda_powertools.event_handler.openapi.types import ( + COMPONENT_REF_TEMPLATE, + ) + + openapi_version = self._determine_openapi_version(openapi_version) + + # Start with the bare minimum required for a valid OpenAPI schema + info: Dict[str, Any] = {"title": title, "version": version} + + optional_fields = { + "summary": summary, + "description": description, + "termsOfService": terms_of_service, + "contact": contact, + "license": license_info, + } + + info.update({field: value for field, value in optional_fields.items() if value}) + + output: Dict[str, Any] = { + "openapi": openapi_version, + "info": info, + "servers": self._get_openapi_servers(servers), + "security": self._get_openapi_security(security, security_schemes), + } + + components: Dict[str, Dict[str, Any]] = {} + paths: Dict[str, Dict[str, Any]] = {} + operation_ids: Set[str] = set() + + all_routes = self._dynamic_routes + self._static_routes + all_fields = self._get_fields_from_routes(all_routes) + model_name_map = get_compat_model_name_map(all_fields) + + # Collect all models and definitions + schema_generator = GenerateJsonSchema(ref_template=COMPONENT_REF_TEMPLATE) + field_mapping, definitions = get_definitions( + fields=all_fields, + schema_generator=schema_generator, + model_name_map=model_name_map, + ) + + # Add routes to the OpenAPI schema + for route in all_routes: + + if route.security and not _validate_openapi_security_parameters( + security=route.security, + security_schemes=security_schemes, + ): + raise SchemaValidationError( + "Security configuration was not found in security_schemas or security_schema was not defined. " + "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes", + ) + + if not route.include_in_schema: + continue + + result = route._get_openapi_path( + dependant=route.dependant, + operation_ids=operation_ids, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + if result: + path, path_definitions = result + if path: + paths.setdefault(route.openapi_path, {}).update(path) + if path_definitions: + definitions.update(path_definitions) + + if definitions: + components["schemas"] = {k: definitions[k] for k in sorted(definitions)} + if security_schemes: + components["securitySchemes"] = security_schemes + if components: + output["components"] = components + if tags: + output["tags"] = [Tag(name=tag) if isinstance(tag, str) else tag for tag in tags] + + output["paths"] = {k: PathItem(**v) for k, v in paths.items()} + + return OpenAPI(**output) + + @staticmethod + def _get_openapi_servers(servers: Optional[List["Server"]]) -> List["Server"]: + from aws_lambda_powertools.event_handler.openapi.models import Server + + # If the 'servers' property is not provided or is an empty array, + # the default behavior is to return a Server Object with a URL value of "/". + return servers if servers else [Server(url="/")] + + @staticmethod + def _get_openapi_security( + security: Optional[List[Dict[str, List[str]]]], + security_schemes: Optional[Dict[str, "SecurityScheme"]], + ) -> Optional[List[Dict[str, List[str]]]]: + + if not security: + return None + + if not _validate_openapi_security_parameters(security=security, security_schemes=security_schemes): + raise SchemaValidationError( + "Security configuration was not found in security_schemas or security_schema was not defined. " + "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes", + ) + + return security + + @staticmethod + def _determine_openapi_version(openapi_version): + from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2 + + # Pydantic V2 has no support for OpenAPI schema 3.0 + if PYDANTIC_V2 and not openapi_version.startswith("3.1"): # pragma: no cover + warnings.warn( + "You are using Pydantic v2, which is incompatible with OpenAPI schema 3.0. Forcing OpenAPI 3.1", + stacklevel=2, + ) + openapi_version = "3.1.0" + elif not PYDANTIC_V2 and not openapi_version.startswith("3.0"): # pragma: no cover + warnings.warn( + "You are using Pydantic v1, which is incompatible with OpenAPI schema 3.1. Forcing OpenAPI 3.0", + stacklevel=2, + ) + openapi_version = "3.0.3" + return openapi_version + + def get_openapi_json_schema( + self, + *, + title: str = "Powertools API", + version: str = DEFAULT_API_VERSION, + openapi_version: str = DEFAULT_OPENAPI_VERSION, + summary: Optional[str] = None, + description: Optional[str] = None, + tags: Optional[List[Union["Tag", str]]] = None, + servers: Optional[List["Server"]] = None, + terms_of_service: Optional[str] = None, + contact: Optional["Contact"] = None, + license_info: Optional["License"] = None, + security_schemes: Optional[Dict[str, "SecurityScheme"]] = None, + security: Optional[List[Dict[str, List[str]]]] = None, + ) -> str: + """ + Returns the OpenAPI schema as a JSON serializable dict + + Parameters + ---------- + title: str + The title of the application. + version: str + The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API + openapi_version: str, default = "3.0.0" + The version of the OpenAPI Specification (which the document uses). + summary: str, optional + A short summary of what the application does. + description: str, optional + A verbose explanation of the application behavior. + tags: List[Tag, str], optional + A list of tags used by the specification with additional metadata. + servers: List[Server], optional + An array of Server Objects, which provide connectivity information to a target server. + terms_of_service: str, optional + A URL to the Terms of Service for the API. MUST be in the format of a URL. + contact: Contact, optional + The contact information for the exposed API. + license_info: License, optional + The license information for the exposed API. + security_schemes: Dict[str, "SecurityScheme"]], optional + A declaration of the security schemes available to be used in the specification. + security: List[Dict[str, List[str]]], optional + A declaration of which security mechanisms are applied globally across the API. + + Returns + ------- + str + The OpenAPI schema as a JSON serializable dict. + """ + from aws_lambda_powertools.event_handler.openapi.compat import model_json + + return model_json( + self.get_openapi_schema( + title=title, + version=version, + openapi_version=openapi_version, + summary=summary, + description=description, + tags=tags, + servers=servers, + terms_of_service=terms_of_service, + contact=contact, + license_info=license_info, + security_schemes=security_schemes, + security=security, + ), + by_alias=True, + exclude_none=True, + indent=2, + ) + + def enable_swagger( + self, + *, + path: str = "/swagger", + title: str = "Powertools for AWS Lambda (Python) API", + version: str = DEFAULT_API_VERSION, + openapi_version: str = DEFAULT_OPENAPI_VERSION, + summary: Optional[str] = None, + description: Optional[str] = None, + tags: Optional[List[Union["Tag", str]]] = None, + servers: Optional[List["Server"]] = None, + terms_of_service: Optional[str] = None, + contact: Optional["Contact"] = None, + license_info: Optional["License"] = None, + swagger_base_url: Optional[str] = None, + middlewares: Optional[List[Callable[..., Response]]] = None, + compress: bool = False, + security_schemes: Optional[Dict[str, "SecurityScheme"]] = None, + security: Optional[List[Dict[str, List[str]]]] = None, + oauth2_config: Optional["OAuth2Config"] = None, + persist_authorization: bool = False, + ): + """ + Returns the OpenAPI schema as a JSON serializable dict + + Parameters + ---------- + path: str, default = "/swagger" + The path to the swagger UI. + title: str + The title of the application. + version: str + The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API + openapi_version: str, default = "3.0.0" + The version of the OpenAPI Specification (which the document uses). + summary: str, optional + A short summary of what the application does. + description: str, optional + A verbose explanation of the application behavior. + tags: List[Tag, str], optional + A list of tags used by the specification with additional metadata. + servers: List[Server], optional + An array of Server Objects, which provide connectivity information to a target server. + terms_of_service: str, optional + A URL to the Terms of Service for the API. MUST be in the format of a URL. + contact: Contact, optional + The contact information for the exposed API. + license_info: License, optional + The license information for the exposed API. + swagger_base_url: str, optional + The base url for the swagger UI. If not provided, we will serve a recent version of the Swagger UI. + middlewares: List[Callable[..., Response]], optional + List of middlewares to be used for the swagger route. + compress: bool, default = False + Whether or not to enable gzip compression swagger route. + security_schemes: Dict[str, "SecurityScheme"], optional + A declaration of the security schemes available to be used in the specification. + security: List[Dict[str, List[str]]], optional + A declaration of which security mechanisms are applied globally across the API. + oauth2_config: OAuth2Config, optional + The OAuth2 configuration for the Swagger UI. + persist_authorization: bool, optional + Whether to persist authorization data on browser close/refresh. + """ + from aws_lambda_powertools.event_handler.openapi.compat import model_json + from aws_lambda_powertools.event_handler.openapi.models import Server + from aws_lambda_powertools.event_handler.openapi.swagger_ui import ( + generate_oauth2_redirect_html, + generate_swagger_html, + ) + + @self.get(path, middlewares=middlewares, include_in_schema=False, compress=compress) + def swagger_handler(): + query_params = self.current_event.query_string_parameters or {} + + # Check for query parameters; if "format" is specified as "oauth2-redirect", + # send the oauth2-redirect HTML stanza so OAuth2 can be used + # Source: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html + if query_params.get("format") == "oauth2-redirect": + return Response( + status_code=200, + content_type="text/html", + body=generate_oauth2_redirect_html(), + ) + + base_path = self._get_base_path() + + if swagger_base_url: + swagger_js = f"{swagger_base_url}/swagger-ui-bundle.min.js" + swagger_css = f"{swagger_base_url}/swagger-ui.min.css" + else: + # We now inject CSS and JS into the SwaggerUI file + swagger_js = Path.open( + Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui-bundle.min.js", + ).read() + swagger_css = Path.open(Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui.min.css").read() + + openapi_servers = servers or [Server(url=(base_path or "/"))] + + spec = self.get_openapi_schema( + title=title, + version=version, + openapi_version=openapi_version, + summary=summary, + description=description, + tags=tags, + servers=openapi_servers, + terms_of_service=terms_of_service, + contact=contact, + license_info=license_info, + security_schemes=security_schemes, + security=security, + ) + + # The .replace('</', '<\\/') part is necessary to prevent a potential issue where the JSON string contains + # </script> or similar tags. Escaping the forward slash in </ as <\/ ensures that the JSON does not + # inadvertently close the script tag, and the JSON remains a valid string within the JavaScript code. + escaped_spec = model_json( + spec, + by_alias=True, + exclude_none=True, + indent=2, + ).replace("</", "<\\/") + + # Check for query parameters; if "format" is specified as "json", + # respond with the JSON used in the OpenAPI spec + # Example: https://www.example.com/swagger?format=json + if query_params.get("format") == "json": + return Response( + status_code=200, + content_type="application/json", + body=escaped_spec, + ) + + body = generate_swagger_html( + escaped_spec, + f"{base_path}{path}", + swagger_js, + swagger_css, + swagger_base_url, + oauth2_config, + persist_authorization, + ) + + return Response( + status_code=200, + content_type="text/html", + body=body, + ) + + def route( + self, + rule: str, + method: Union[str, Union[List[str], Tuple[str]]], + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + """Route decorator includes parameter `method`""" + + def register_resolver(func: Callable): + methods = (method,) if isinstance(method, str) else method + logger.debug(f"Adding route using rule {rule} and methods: {','.join((m.upper() for m in methods))}") + + cors_enabled = self._cors_enabled if cors is None else cors + + for item in methods: + _route = Route( + item, + rule, + self._compile_regex(rule), + func, + cors_enabled, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # The more specific route wins. + # We store dynamic (/studies/{studyid}) and static routes (/studies/fetch) separately. + # Then attempt a match for static routes before dynamic routes. + # This ensures that the most specific route is prioritized and processed first (studies/fetch). + if _route.rule.groups > 0: + self._dynamic_routes.append(_route) + else: + self._static_routes.append(_route) + + self._create_route_key(item, rule) + + if cors_enabled: + logger.debug(f"Registering method {item.upper()} to Allow Methods in CORS") + self._cors_methods.add(item.upper()) + + return func + + return register_resolver + + def resolve(self, event, context) -> Dict[str, Any]: + """Resolves the response based on the provide event and decorator routes + + ## Internals + + Request processing chain is triggered by a Route object being called _(`_call_route` -> `__call__`)_: + + 1. **When a route is matched** + 1.1. Exception handlers _(if any exception bubbled up and caught)_ + 1.2. Global middlewares _(before, and after on the way back)_ + 1.3. Path level middleware _(before, and after on the way back)_ + 1.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter) + 1.5. Run actual route + 2. **When a route is NOT matched** + 2.1. Exception handlers _(if any exception bubbled up and caught)_ + 2.2. Global middlewares _(before, and after on the way back)_ + 2.3. Path level middleware _(before, and after on the way back)_ + 2.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter) + 2.5. Run 404 route handler + 3. **When a route is a pre-flight CORS (often not matched)** + 3.1. Exception handlers _(if any exception bubbled up and caught)_ + 3.2. Global middlewares _(before, and after on the way back)_ + 3.3. Path level middleware _(before, and after on the way back)_ + 3.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter) + 3.5. Return 204 with appropriate CORS headers + 4. **When a route is matched with Data Validation enabled** + 4.1. Exception handlers _(if any exception bubbled up and caught)_ + 4.2. Data Validation middleware _(before, and after on the way back)_ + 4.3. Global middlewares _(before, and after on the way back)_ + 4.4. Path level middleware _(before, and after on the way back)_ + 4.5. Middleware adapter to ensure Response is homogenous (_registered_api_adapter) + 4.6. Run actual route + + Parameters + ---------- + event: Dict[str, Any] + Event + context: LambdaContext + Lambda context + Returns + ------- + dict + Returns the dict response + """ + if isinstance(event, BaseProxyEvent): + warnings.warn( + "You don't need to serialize event to Event Source Data Class when using Event Handler; " + "see issue #1152", + stacklevel=2, + ) + event = event.raw_event + + if self._debug: + print(self._serializer(event)) + + # Populate router(s) dependencies without keeping a reference to each registered router + BaseRouter.current_event = self._to_proxy_event(event) + BaseRouter.lambda_context = context + + response = self._resolve().build(self.current_event, self._cors) + + # Debug print Processed Middlewares + if self._debug: + print("\nProcessed Middlewares:") + print("======================") + print("\n".join(self.processed_stack_frames)) + print("======================") + + self.clear_context() + + return response + + def __call__(self, event, context) -> Any: + return self.resolve(event, context) + + def _create_route_key(self, item: str, rule: str): + route_key = item + rule + if route_key in self._route_keys: + warnings.warn( + f"A route like this was already registered. method: '{item}' rule: '{rule}'", + stacklevel=2, + ) + self._route_keys.append(route_key) + + def _get_base_path(self) -> str: + raise NotImplementedError() + + @staticmethod + def _has_debug(debug: Optional[bool] = None) -> bool: + # It might have been explicitly switched off (debug=False) + if debug is not None: + return debug + + return powertools_dev_is_set() + + @staticmethod + def _compile_regex(rule: str, base_regex: str = _ROUTE_REGEX): + """Precompile regex pattern + + Logic + ----- + + 1. Find any dynamic routes defined as <pattern> + e.g. @app.get("/accounts/<account_id>") + 2. Create a new regex by substituting every dynamic route found as a named group (?P<group>), + and match whole words only (word boundary) instead of a greedy match + + non-greedy example with word boundary + + rule: '/accounts/<account_id>' + regex: r'/accounts/(?P<account_id>\\w+\\b)' + + value: /accounts/123/some_other_path + account_id: 123 + + greedy example without word boundary + + regex: r'/accounts/(?P<account_id>.+)' + + value: /accounts/123/some_other_path + account_id: 123/some_other_path + 3. Compiles a regex and include start (^) and end ($) in between for an exact match + + NOTE: See #520 for context + """ + rule_regex: str = re.sub(_DYNAMIC_ROUTE_PATTERN, _NAMED_GROUP_BOUNDARY_PATTERN, rule) + return re.compile(base_regex.format(rule_regex)) + + def _to_proxy_event(self, event: Dict) -> BaseProxyEvent: # noqa: PLR0911 # ignore many returns + """Convert the event dict to the corresponding data class""" + if self._proxy_type == ProxyEventType.APIGatewayProxyEvent: + logger.debug("Converting event to API Gateway REST API contract") + return APIGatewayProxyEvent(event) + if self._proxy_type == ProxyEventType.APIGatewayProxyEventV2: + logger.debug("Converting event to API Gateway HTTP API contract") + return APIGatewayProxyEventV2(event) + if self._proxy_type == ProxyEventType.BedrockAgentEvent: + logger.debug("Converting event to Bedrock Agent contract") + return BedrockAgentEvent(event) + if self._proxy_type == ProxyEventType.LambdaFunctionUrlEvent: + logger.debug("Converting event to Lambda Function URL contract") + return LambdaFunctionUrlEvent(event) + if self._proxy_type == ProxyEventType.VPCLatticeEvent: + logger.debug("Converting event to VPC Lattice contract") + return VPCLatticeEvent(event) + if self._proxy_type == ProxyEventType.VPCLatticeEventV2: + logger.debug("Converting event to VPC LatticeV2 contract") + return VPCLatticeEventV2(event) + logger.debug("Converting event to ALB contract") + return ALBEvent(event) + + def _resolve(self) -> ResponseBuilder: + """Resolves the response or return the not found response""" + method = self.current_event.http_method.upper() + path = self._remove_prefix(self.current_event.path) + + registered_routes = self._static_routes + self._dynamic_routes + + for route in registered_routes: + if method != route.method: + continue + match_results: Optional[Match] = route.rule.match(path) + if match_results: + logger.debug("Found a registered route. Calling function") + # Add matched Route reference into the Resolver context + self.append_context(_route=route, _path=path) + + route_keys = self._convert_matches_into_route_keys(match_results) + return self._call_route(route, route_keys) # pass fn args + + return self._handle_not_found(method=method, path=path) + + def _remove_prefix(self, path: str) -> str: + """Remove the configured prefix from the path""" + if not isinstance(self._strip_prefixes, list): + return path + + for prefix in self._strip_prefixes: + if isinstance(prefix, str): + if path == prefix: + return "/" + + if self._path_starts_with(path, prefix): + return path[len(prefix) :] + + if isinstance(prefix, Pattern): + path = re.sub(prefix, "", path) + + # When using regexes, we might get into a point where everything is removed + # from the string, so we check if it's empty and return /, since there's nothing + # else to strip anymore. + if not path: + return "/" + + return path + + def _convert_matches_into_route_keys(self, match: Match) -> Dict[str, str]: + """Converts the regex match into a dict of route keys""" + return match.groupdict() + + @staticmethod + def _path_starts_with(path: str, prefix: str): + """Returns true if the `path` starts with a prefix plus a `/`""" + if not isinstance(prefix, str) or prefix == "": + return False + + return path.startswith(prefix + "/") + + def _handle_not_found(self, method: str, path: str) -> ResponseBuilder: + """Called when no matching route was found and includes support for the cors preflight response""" + logger.debug(f"No match found for path {path} and method {method}") + + def not_found_handler(): + """Route handler for 404s + + It handles in the following order: + + 1. Pre-flight CORS requests (OPTIONS) + 2. Detects and calls custom HTTP 404 handler + 3. Returns standard 404 along with CORS headers + + Returns + ------- + Response + HTTP 404 response + """ + _headers: Dict[str, Any] = {} + + # Pre-flight request? Return immediately to avoid browser error + if self._cors and method == "OPTIONS": + logger.debug("Pre-flight request detected. Returning CORS with empty response") + _headers["Access-Control-Allow-Methods"] = CORSConfig.build_allow_methods(self._cors_methods) + + return Response(status_code=204, content_type=None, headers=_headers, body="") + + # Customer registered 404 route? Call it. + custom_not_found_handler = self._lookup_exception_handler(NotFoundError) + if custom_not_found_handler: + return custom_not_found_handler(NotFoundError()) + + # No CORS and no custom 404 fn? Default response + return Response( + status_code=HTTPStatus.NOT_FOUND.value, + content_type=content_types.APPLICATION_JSON, + headers=_headers, + body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"}, + ) + + # We create a route to trigger entire request chain (middleware+exception handlers) + route = Route( + rule=self._compile_regex(r".*"), + method=method, + path=path, + func=not_found_handler, + cors=self._cors_enabled, + compress=False, + ) + + # Add matched Route reference into the Resolver context + self.append_context(_route=route, _path=path) + + # Kick-off request chain: + # -> exception_handlers() + # --> middlewares() + # ---> not_found_route() + return self._call_route(route=route, route_arguments={}) + + def _call_route(self, route: Route, route_arguments: Dict[str, str]) -> ResponseBuilder: + """Actually call the matching route with any provided keyword arguments.""" + try: + # Reset Processed stack for Middleware (for debugging purposes) + self._reset_processed_stack() + + return self._response_builder_class( + response=self._to_response( + route(router_middlewares=self._router_middlewares, app=self, route_arguments=route_arguments), + ), + serializer=self._serializer, + route=route, + ) + except Exception as exc: + # If exception is handled then return the response builder to reduce noise + response_builder = self._call_exception_handler(exc, route) + if response_builder: + return response_builder + + logger.exception(exc) + if self._debug: + # If the user has turned on debug mode, + # we'll let the original exception propagate, so + # they get more information about what went wrong. + return self._response_builder_class( + response=Response( + status_code=500, + content_type=content_types.TEXT_PLAIN, + body="".join(traceback.format_exc()), + ), + serializer=self._serializer, + route=route, + ) + + raise + + def not_found(self, func: Optional[Callable] = None): + if func is None: + return self.exception_handler(NotFoundError) + return self.exception_handler(NotFoundError)(func) + + def exception_handler(self, exc_class: Union[Type[Exception], List[Type[Exception]]]): + def register_exception_handler(func: Callable): + if isinstance(exc_class, list): # pragma: no cover + for exp in exc_class: + self._exception_handlers[exp] = func + else: + self._exception_handlers[exc_class] = func + return func + + return register_exception_handler + + def _lookup_exception_handler(self, exp_type: Type) -> Optional[Callable]: + # Use "Method Resolution Order" to allow for matching against a base class + # of an exception + for cls in exp_type.__mro__: + if cls in self._exception_handlers: + return self._exception_handlers[cls] + return None + + def _call_exception_handler(self, exp: Exception, route: Route) -> Optional[ResponseBuilder]: + handler = self._lookup_exception_handler(type(exp)) + if handler: + try: + return self._response_builder_class(response=handler(exp), serializer=self._serializer, route=route) + except ServiceError as service_error: + exp = service_error + + if isinstance(exp, RequestValidationError): + # For security reasons, we hide msg details (don't leak Python, Pydantic or file names) + errors = [{"loc": e["loc"], "type": e["type"]} for e in exp.errors()] + + return self._response_builder_class( + response=Response( + status_code=HTTPStatus.UNPROCESSABLE_ENTITY, + content_type=content_types.APPLICATION_JSON, + body={"statusCode": HTTPStatus.UNPROCESSABLE_ENTITY, "detail": errors}, + ), + serializer=self._serializer, + route=route, + ) + + if isinstance(exp, ServiceError): + return self._response_builder_class( + response=Response( + status_code=exp.status_code, + content_type=content_types.APPLICATION_JSON, + body={"statusCode": exp.status_code, "message": exp.msg}, + ), + serializer=self._serializer, + route=route, + ) + + return None + + def _to_response(self, result: Union[Dict, Tuple, Response]) -> Response: + """Convert the route's result to a Response + + 3 main result types are supported: + + - Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to + application/json + - Tuple[dict, int]: Same dict handling as above but with the option of including a status code + - Response: returned as is, and allows for more flexibility + """ + status_code = HTTPStatus.OK + if isinstance(result, Response): + return result + elif isinstance(result, tuple) and len(result) == 2: + # Unpack result dict and status code from tuple + result, status_code = result + + logger.debug("Simple response detected, serializing return before constructing final response") + return Response( + status_code=status_code, + content_type=content_types.APPLICATION_JSON, + body=result, + ) + + def include_router(self, router: "Router", prefix: Optional[str] = None) -> None: + """Adds all routes and context defined in a router + + Parameters + ---------- + router : Router + The Router containing a list of routes to be registered after the existing routes + prefix : str, optional + An optional prefix to be added to the originally defined rule + """ + + # Add reference to parent ApiGatewayResolver to support use cases where people subclass it to add custom logic + router.api_resolver = self + + logger.debug("Merging App context with Router context") + self.context.update(**router.context) + + logger.debug("Appending Router middlewares into App middlewares.") + self._router_middlewares = self._router_middlewares + router._router_middlewares + + logger.debug("Appending Router exception_handler into App exception_handler.") + self._exception_handlers.update(router._exception_handlers) + + # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) + router.context = self.context + + # Iterate through the routes defined in the router to configure and apply middlewares for each route + for route, func in router._routes.items(): + new_route = route + + if prefix: + rule = route[0] + rule = prefix if rule == "/" else f"{prefix}{rule}" + new_route = (rule, *route[1:]) + + # Middlewares are stored by route separately - must grab them to include + # Middleware store the route without prefix, so we must not include prefix when grabbing + middlewares = router._routes_with_middleware.get(route) + + # Need to use "type: ignore" here since mypy does not like a named parameter after + # tuple expansion since may cause duplicate named parameters in the function signature. + # In this case this is not possible since the tuple expansion is from a hashable source + # and the `middlewares` List is a non-hashable structure so will never be included. + # Still need to ignore for mypy checks or will cause failures (false-positive) + self.route(*new_route, middlewares=middlewares)(func) # type: ignore + + @staticmethod + def _get_fields_from_routes(routes: Sequence[Route]) -> List["ModelField"]: + """ + Returns a list of fields from the routes + """ + + from aws_lambda_powertools.event_handler.openapi.compat import ModelField + from aws_lambda_powertools.event_handler.openapi.dependant import ( + get_flat_params, + ) + + body_fields_from_routes: List["ModelField"] = [] + responses_from_routes: List["ModelField"] = [] + request_fields_from_routes: List["ModelField"] = [] + + for route in routes: + if route.body_field: + if not isinstance(route.body_field, ModelField): + raise AssertionError("A request body myst be a Pydantic Field") + body_fields_from_routes.append(route.body_field) + + params = get_flat_params(route.dependant) + request_fields_from_routes.extend(params) + + if route.dependant.return_param: + responses_from_routes.append(route.dependant.return_param) + + if route.dependant.response_extra_models: + responses_from_routes.extend(route.dependant.response_extra_models) + + flat_models = list(responses_from_routes + request_fields_from_routes + body_fields_from_routes) + return flat_models + + +class Router(BaseRouter): + """Router helper class to allow splitting ApiGatewayResolver into multiple files""" + + def __init__(self): + self._routes: Dict[tuple, Callable] = {} + self._routes_with_middleware: Dict[tuple, List[Callable]] = {} + self.api_resolver: Optional[BaseRouter] = None + self.context = {} # early init as customers might add context before event resolution + self._exception_handlers: Dict[Type, Callable] = {} + + def route( + self, + rule: str, + method: Union[str, Union[List[str], Tuple[str]]], + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: Optional[str] = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + def register_route(func: Callable): + # All dict keys needs to be hashable. So we'll need to do some conversions: + methods = (method,) if isinstance(method, str) else tuple(method) + frozen_responses = _FrozenDict(responses) if responses else None + frozen_tags = frozenset(tags) if tags else None + frozen_security = _FrozenListDict(security) if security else None + + route_key = ( + rule, + methods, + cors, + compress, + cache_control, + summary, + description, + frozen_responses, + response_description, + frozen_tags, + operation_id, + include_in_schema, + frozen_security, + ) + + # Collate Middleware for routes + if middlewares is not None: + for handler in middlewares: + if self._routes_with_middleware.get(route_key) is None: + self._routes_with_middleware[route_key] = [handler] + else: + self._routes_with_middleware[route_key].append(handler) + else: + self._routes_with_middleware[route_key] = [] + + self._routes[route_key] = func + + return func + + return register_route + + def exception_handler(self, exc_class: Union[Type[Exception], List[Type[Exception]]]): + def register_exception_handler(func: Callable): + if isinstance(exc_class, list): + for exp in exc_class: + self._exception_handlers[exp] = func + else: + self._exception_handlers[exc_class] = func + return func + + return register_exception_handler + + +class APIGatewayRestResolver(ApiGatewayResolver): + current_event: APIGatewayProxyEvent + + def __init__( + self, + cors: Optional[CORSConfig] = None, + debug: Optional[bool] = None, + serializer: Optional[Callable[[Dict], str]] = None, + strip_prefixes: Optional[List[Union[str, Pattern]]] = None, + enable_validation: bool = False, + ): + """Amazon API Gateway REST and HTTP API v1 payload resolver""" + super().__init__( + ProxyEventType.APIGatewayProxyEvent, + cors, + debug, + serializer, + strip_prefixes, + enable_validation, + ) + + def _get_base_path(self) -> str: + # 3 different scenarios: + # + # 1. SAM local: even though a stage variable is sent to the Lambda function, it's not used in the path + # 2. API Gateway REST API: stage variable is used in the path + # 3. API Gateway REST Custom Domain: stage variable is not used in the path + # + # To solve the 3 scenarios, we try to match the beginning of the path with the stage variable + stage = self.current_event.request_context.stage + if stage and stage != "$default" and self.current_event.request_context.path.startswith(f"/{stage}"): + return f"/{stage}" + return "" + + # override route to ignore trailing "/" in routes for REST API + def route( + self, + rule: str, + method: Union[str, Union[List[str], Tuple[str]]], + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + security: Optional[List[Dict[str, List[str]]]] = None, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + # NOTE: see #1552 for more context. + return super().route( + rule.rstrip("/"), + method, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # Override _compile_regex to exclude trailing slashes for route resolution + @staticmethod + def _compile_regex(rule: str, base_regex: str = _ROUTE_REGEX): + return super(APIGatewayRestResolver, APIGatewayRestResolver)._compile_regex(rule, "^{}/*$") + + +class APIGatewayHttpResolver(ApiGatewayResolver): + current_event: APIGatewayProxyEventV2 + + def __init__( + self, + cors: Optional[CORSConfig] = None, + debug: Optional[bool] = None, + serializer: Optional[Callable[[Dict], str]] = None, + strip_prefixes: Optional[List[Union[str, Pattern]]] = None, + enable_validation: bool = False, + ): + """Amazon API Gateway HTTP API v2 payload resolver""" + super().__init__( + ProxyEventType.APIGatewayProxyEventV2, + cors, + debug, + serializer, + strip_prefixes, + enable_validation, + ) + + def _get_base_path(self) -> str: + # 3 different scenarios: + # + # 1. SAM local: even though a stage variable is sent to the Lambda function, it's not used in the path + # 2. API Gateway HTTP API: stage variable is used in the path + # 3. API Gateway HTTP Custom Domain: stage variable is not used in the path + # + # To solve the 3 scenarios, we try to match the beginning of the path with the stage variable + stage = self.current_event.request_context.stage + if stage and stage != "$default" and self.current_event.request_context.http.path.startswith(f"/{stage}"): + return f"/{stage}" + return "" + + +class ALBResolver(ApiGatewayResolver): + current_event: ALBEvent + + def __init__( + self, + cors: Optional[CORSConfig] = None, + debug: Optional[bool] = None, + serializer: Optional[Callable[[Dict], str]] = None, + strip_prefixes: Optional[List[Union[str, Pattern]]] = None, + enable_validation: bool = False, + ): + """Amazon Application Load Balancer (ALB) resolver""" + super().__init__(ProxyEventType.ALBEvent, cors, debug, serializer, strip_prefixes, enable_validation) + + def _get_base_path(self) -> str: + # ALB doesn't have a stage variable, so we just return an empty string + return "" diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py new file mode 100644 index 00000000000..fba5681ef6a --- /dev/null +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -0,0 +1,216 @@ +import logging +from typing import Any, Callable, Optional, Type, TypeVar + +from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = logging.getLogger(__name__) + +AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent) + + +class BaseRouter: + current_event: AppSyncResolverEventT # type: ignore[valid-type] + lambda_context: LambdaContext + context: dict + + def __init__(self): + self._resolvers: dict = {} + + def resolver(self, type_name: str = "*", field_name: Optional[str] = None): + """Registers the resolver for field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + """ + + def register_resolver(func): + logger.debug(f"Adding resolver `{func.__name__}` for field `{type_name}.{field_name}`") + self._resolvers[f"{type_name}.{field_name}"] = {"func": func} + return func + + return register_resolver + + def append_context(self, **additional_context): + """Append key=value data as routing context""" + self.context.update(**additional_context) + + def clear_context(self): + """Resets routing context""" + self.context.clear() + + +class AppSyncResolver(BaseRouter): + """ + AppSync resolver decorator + + Example + ------- + + **Sample usage** + + from aws_lambda_powertools.event_handler import AppSyncResolver + + app = AppSyncResolver() + + @app.resolver(type_name="Query", field_name="listLocations") + def list_locations(page: int = 0, size: int = 10) -> list: + # Your logic to fetch locations with arguments passed in + return [{"id": 100, "name": "Smooth Grooves"}] + + @app.resolver(type_name="Merchant", field_name="extraInfo") + def get_extra_info() -> dict: + # Can use "app.current_event.source" to filter within the parent context + account_type = app.current_event.source["accountType"] + method = "BTC" if account_type == "NEW" else "USD" + return {"preferredPaymentMethod": method} + + @app.resolver(field_name="commonField") + def common_field() -> str: + # Would match all fieldNames matching 'commonField' + return str(uuid.uuid4()) + """ + + def __init__(self): + super().__init__() + self.context = {} # early init as customers might add context before event resolution + + def resolve( + self, + event: dict, + context: LambdaContext, + data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, + ) -> Any: + """Resolve field_name + + Parameters + ---------- + event : dict + Lambda event + context : LambdaContext + Lambda context + data_model: + Your data data_model to decode AppSync event, by default AppSyncResolverEvent + + Example + ------- + + ```python + from aws_lambda_powertools.event_handler import AppSyncResolver + from aws_lambda_powertools.utilities.typing import LambdaContext + + @app.resolver(field_name="createSomething") + def create_something(id: str): # noqa AA03 VNE003 + return id + + def handler(event, context: LambdaContext): + return app.resolve(event, context) + ``` + + **Bringing custom models** + + ```python + from aws_lambda_powertools import Logger, Tracer + + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler import AppSyncResolver + + tracer = Tracer(service="sample_resolver") + logger = Logger(service="sample_resolver") + app = AppSyncResolver() + + + class MyCustomModel(AppSyncResolverEvent): + @property + def country_viewer(self) -> str: + return self.request_headers.get("cloudfront-viewer-country") + + + @app.resolver(field_name="listLocations") + @app.resolver(field_name="locations") + def get_locations(name: str, description: str = ""): + if app.current_event.country_viewer == "US": + ... + return name + description + + + @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPSYNC_RESOLVER) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context, data_model=MyCustomModel) + ``` + + Returns + ------- + Any + Returns the result of the resolver + + Raises + ------- + ValueError + If we could not find a field resolver + """ + # Maintenance: revisit generics/overload to fix [attr-defined] in mypy usage + BaseRouter.current_event = data_model(event) + BaseRouter.lambda_context = context + + resolver = self._get_resolver(BaseRouter.current_event.type_name, BaseRouter.current_event.field_name) + response = resolver(**BaseRouter.current_event.arguments) + self.clear_context() + + return response + + def _get_resolver(self, type_name: str, field_name: str) -> Callable: + """Get resolver for field_name + + Parameters + ---------- + type_name : str + Type name + field_name : str + Field name + + Returns + ------- + Callable + callable function and configuration + """ + full_name = f"{type_name}.{field_name}" + resolver = self._resolvers.get(full_name, self._resolvers.get(f"*.{field_name}")) + if not resolver: + raise ValueError(f"No resolver found for '{full_name}'") + return resolver["func"] + + def __call__( + self, + event: dict, + context: LambdaContext, + data_model: Type[AppSyncResolverEvent] = AppSyncResolverEvent, + ) -> Any: + """Implicit lambda handler which internally calls `resolve`""" + return self.resolve(event, context, data_model) + + def include_router(self, router: "Router") -> None: + """Adds all resolvers defined in a router + + Parameters + ---------- + router : Router + A router containing a dict of field resolvers + """ + # Merge app and router context + self.context.update(**router.context) + # use pointer to allow context clearance after event is processed e.g., resolve(evt, ctx) + router.context = self.context + + self._resolvers.update(router._resolvers) + + +class Router(BaseRouter): + def __init__(self): + super().__init__() + self.context = {} # early init as customers might add context before event resolution diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py new file mode 100644 index 00000000000..4d1a6096f32 --- /dev/null +++ b/aws_lambda_powertools/event_handler/bedrock_agent.py @@ -0,0 +1,270 @@ +from re import Match +from typing import Any, Callable, Dict, List, Optional + +from typing_extensions import override + +from aws_lambda_powertools.event_handler import ApiGatewayResolver +from aws_lambda_powertools.event_handler.api_gateway import ( + _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + ProxyEventType, + ResponseBuilder, +) +from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse +from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent + + +class BedrockResponseBuilder(ResponseBuilder): + """ + Bedrock Response Builder. This builds the response dict to be returned by Lambda when using Bedrock Agents. + + Since the payload format is different from the standard API Gateway Proxy event, we override the build method. + """ + + @override + def build(self, event: BedrockAgentEvent, *args) -> Dict[str, Any]: + """Build the full response dict to be returned by the lambda""" + self._route(event, None) + + body = self.response.body + if self.response.is_json() and not isinstance(self.response.body, str): + body = self.serializer(self.response.body) + + return { + "messageVersion": "1.0", + "response": { + "actionGroup": event.action_group, + "apiPath": event.api_path, + "httpMethod": event.http_method, + "httpStatusCode": self.response.status_code, + "responseBody": { + self.response.content_type: { + "body": body, + }, + }, + }, + } + + +class BedrockAgentResolver(ApiGatewayResolver): + """Bedrock Agent Resolver + + See https://aws.amazon.com/bedrock/agents/ for more information. + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import BedrockAgentResolver + + tracer = Tracer() + app = BedrockAgentResolver() + + @app.get("/claims") + def simple_get(): + return "You have 3 claims" + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + + """ + + current_event: BedrockAgentEvent + + def __init__(self, debug: bool = False, enable_validation: bool = True): + super().__init__( + proxy_type=ProxyEventType.BedrockAgentEvent, + cors=None, + debug=debug, + serializer=None, + strip_prefixes=None, + enable_validation=enable_validation, + ) + self._response_builder_class = BedrockResponseBuilder + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def get( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + security = None + + return super(BedrockAgentResolver, self).get( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def post( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + security = None + + return super().post( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def put( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + security = None + + return super().put( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def patch( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable]] = None, + ): + security = None + + return super().patch( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def delete( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + security = None + + return super().delete( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + security, + middlewares, + ) + + @override + def _convert_matches_into_route_keys(self, match: Match) -> Dict[str, str]: + # In Bedrock Agents, all the parameters come inside the "parameters" key, not on the apiPath + # So we have to search for route parameters in the parameters key + parameters: Dict[str, str] = {} + if match.groupdict() and self.current_event.parameters: + parameters = {parameter["name"]: parameter["value"] for parameter in self.current_event.parameters} + return parameters diff --git a/aws_lambda_powertools/event_handler/content_types.py b/aws_lambda_powertools/event_handler/content_types.py new file mode 100644 index 00000000000..0f55b1088ad --- /dev/null +++ b/aws_lambda_powertools/event_handler/content_types.py @@ -0,0 +1,5 @@ +# use mimetypes library to be certain, e.g., mimetypes.types_map[".json"] + +APPLICATION_JSON = "application/json" +TEXT_PLAIN = "text/plain" +TEXT_HTML = "text/html" diff --git a/aws_lambda_powertools/event_handler/exceptions.py b/aws_lambda_powertools/event_handler/exceptions.py new file mode 100644 index 00000000000..4a2838275b1 --- /dev/null +++ b/aws_lambda_powertools/event_handler/exceptions.py @@ -0,0 +1,45 @@ +from http import HTTPStatus + + +class ServiceError(Exception): + """API Gateway and ALB HTTP Service Error""" + + def __init__(self, status_code: int, msg: str): + """ + Parameters + ---------- + status_code: int + Http status code + msg: str + Error message + """ + self.status_code = status_code + self.msg = msg + + +class BadRequestError(ServiceError): + """API Gateway and ALB Bad Request Error (400)""" + + def __init__(self, msg: str): + super().__init__(HTTPStatus.BAD_REQUEST, msg) + + +class UnauthorizedError(ServiceError): + """API Gateway and ALB Unauthorized Error (401)""" + + def __init__(self, msg: str): + super().__init__(HTTPStatus.UNAUTHORIZED, msg) + + +class NotFoundError(ServiceError): + """API Gateway and ALB Not Found Error (404)""" + + def __init__(self, msg: str = "Not found"): + super().__init__(HTTPStatus.NOT_FOUND, msg) + + +class InternalServerError(ServiceError): + """API Gateway and ALB Not Found Internal Server Error (500)""" + + def __init__(self, message: str): + super().__init__(HTTPStatus.INTERNAL_SERVER_ERROR, message) diff --git a/aws_lambda_powertools/event_handler/lambda_function_url.py b/aws_lambda_powertools/event_handler/lambda_function_url.py new file mode 100644 index 00000000000..b69c8fc8087 --- /dev/null +++ b/aws_lambda_powertools/event_handler/lambda_function_url.py @@ -0,0 +1,70 @@ +from typing import Callable, Dict, List, Optional, Pattern, Union + +from aws_lambda_powertools.event_handler import CORSConfig +from aws_lambda_powertools.event_handler.api_gateway import ( + ApiGatewayResolver, + ProxyEventType, +) +from aws_lambda_powertools.utilities.data_classes import LambdaFunctionUrlEvent + + +class LambdaFunctionUrlResolver(ApiGatewayResolver): + """AWS Lambda Function URL resolver + + Notes: + ----- + Lambda Function URL follows the API Gateway HTTP APIs Payload Format Version 2.0. + + Documentation: + - https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html + - https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads + + Examples + -------- + Simple example integrating with Tracer + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler import LambdaFunctionUrlResolver + + tracer = Tracer() + app = LambdaFunctionUrlResolver() + + @app.get("/get-call") + def simple_get(): + return {"message": "Foo"} + + @app.post("/post-call") + def simple_post(): + post_data: dict = app.current_event.json_body + return {"message": post_data} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + """ + + current_event: LambdaFunctionUrlEvent + + def __init__( + self, + cors: Optional[CORSConfig] = None, + debug: Optional[bool] = None, + serializer: Optional[Callable[[Dict], str]] = None, + strip_prefixes: Optional[List[Union[str, Pattern]]] = None, + enable_validation: bool = False, + ): + super().__init__( + ProxyEventType.LambdaFunctionUrlEvent, + cors, + debug, + serializer, + strip_prefixes, + enable_validation, + ) + + def _get_base_path(self) -> str: + stage = self.current_event.request_context.stage + if stage and stage != "$default" and self.current_event.request_context.http.method.startswith(f"/{stage}"): + return f"/{stage}" + return "" diff --git a/aws_lambda_powertools/event_handler/middlewares/__init__.py b/aws_lambda_powertools/event_handler/middlewares/__init__.py new file mode 100644 index 00000000000..068ce9c04b7 --- /dev/null +++ b/aws_lambda_powertools/event_handler/middlewares/__init__.py @@ -0,0 +1,3 @@ +from aws_lambda_powertools.event_handler.middlewares.base import BaseMiddlewareHandler, NextMiddleware + +__all__ = ["BaseMiddlewareHandler", "NextMiddleware"] diff --git a/aws_lambda_powertools/event_handler/middlewares/base.py b/aws_lambda_powertools/event_handler/middlewares/base.py new file mode 100644 index 00000000000..fb4bf37cc74 --- /dev/null +++ b/aws_lambda_powertools/event_handler/middlewares/base.py @@ -0,0 +1,121 @@ +from abc import ABC, abstractmethod +from typing import Generic + +from aws_lambda_powertools.event_handler.api_gateway import Response +from aws_lambda_powertools.event_handler.types import EventHandlerInstance +from aws_lambda_powertools.shared.types import Protocol + + +class NextMiddleware(Protocol): + def __call__(self, app: EventHandlerInstance) -> Response: + """Protocol for callback regardless of next_middleware(app), get_response(app) etc""" + ... + + def __name__(self) -> str: # noqa A003 + """Protocol for name of the Middleware""" + ... + + +class BaseMiddlewareHandler(Generic[EventHandlerInstance], ABC): + """Base implementation for Middlewares to run code before and after in a chain. + + + This is the middleware handler function where middleware logic is implemented. + The next middleware handler is represented by `next_middleware`, returning a Response object. + + Examples + -------- + + **Correlation ID Middleware** + + ```python + import requests + + from aws_lambda_powertools import Logger + from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response + from aws_lambda_powertools.event_handler.middlewares import BaseMiddlewareHandler, NextMiddleware + + app = APIGatewayRestResolver() + logger = Logger() + + + class CorrelationIdMiddleware(BaseMiddlewareHandler): + def __init__(self, header: str): + super().__init__() + self.header = header + + def handler(self, app: APIGatewayRestResolver, next_middleware: NextMiddleware) -> Response: + # BEFORE logic + request_id = app.current_event.request_context.request_id + correlation_id = app.current_event.get_header_value( + name=self.header, + default_value=request_id, + ) + + # Call next middleware or route handler ('/todos') + response = next_middleware(app) + + # AFTER logic + response.headers[self.header] = correlation_id + + return response + + + @app.get("/todos", middlewares=[CorrelationIdMiddleware(header="x-correlation-id")]) + def get_todos(): + todos: requests.Response = requests.get("https://jsonplaceholder.typicode.com/todos") + todos.raise_for_status() + + # for brevity, we'll limit to the first 10 only + return {"todos": todos.json()[:10]} + + + @logger.inject_lambda_context + def lambda_handler(event, context): + return app.resolve(event, context) + + ``` + + """ + + @abstractmethod + def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) -> Response: + """ + The Middleware Handler + + Parameters + ---------- + app: EventHandlerInstance + An instance of an Event Handler that implements ApiGatewayResolver + next_middleware: NextMiddleware + The next middleware handler in the chain + + Returns + ------- + Response + The response from the next middleware handler in the chain + + """ + raise NotImplementedError() + + @property + def __name__(self) -> str: # noqa A003 + return str(self.__class__.__name__) + + def __call__(self, app: EventHandlerInstance, next_middleware: NextMiddleware) -> Response: + """ + The Middleware handler function. + + Parameters + ---------- + app: ApiGatewayResolver + An instance of an Event Handler that implements ApiGatewayResolver + next_middleware: NextMiddleware + The next middleware handler in the chain + + Returns + ------- + Response + The response from the next middleware handler in the chain + """ + return self.handler(app, next_middleware) diff --git a/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py new file mode 100644 index 00000000000..2eafb0d67bb --- /dev/null +++ b/aws_lambda_powertools/event_handler/middlewares/openapi_validation.py @@ -0,0 +1,437 @@ +import dataclasses +import json +import logging +from copy import deepcopy +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple + +from pydantic import BaseModel + +from aws_lambda_powertools.event_handler import Response +from aws_lambda_powertools.event_handler.api_gateway import Route +from aws_lambda_powertools.event_handler.middlewares import BaseMiddlewareHandler, NextMiddleware +from aws_lambda_powertools.event_handler.openapi.compat import ( + ModelField, + _model_dump, + _normalize_errors, + _regenerate_error_with_loc, + get_missing_field_error, +) +from aws_lambda_powertools.event_handler.openapi.dependant import is_scalar_field +from aws_lambda_powertools.event_handler.openapi.encoders import jsonable_encoder +from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError +from aws_lambda_powertools.event_handler.openapi.params import Param +from aws_lambda_powertools.event_handler.openapi.types import IncEx +from aws_lambda_powertools.event_handler.types import EventHandlerInstance + +logger = logging.getLogger(__name__) + + +class OpenAPIValidationMiddleware(BaseMiddlewareHandler): + """ + OpenAPIValidationMiddleware is a middleware that validates the request against the OpenAPI schema defined by the + Lambda handler. It also validates the response against the OpenAPI schema defined by the Lambda handler. It + should not be used directly, but rather through the `enable_validation` parameter of the `ApiGatewayResolver`. + + Examples + -------- + + ```python + from typing import List + + from pydantic import BaseModel + + from aws_lambda_powertools.event_handler.api_gateway import ( + APIGatewayRestResolver, + ) + + class Todo(BaseModel): + name: str + + app = APIGatewayRestResolver(enable_validation=True) + + @app.get("/todos") + def get_todos(): List[Todo]: + return [Todo(name="hello world")] + ``` + """ + + def __init__(self, validation_serializer: Optional[Callable[[Any], str]] = None): + """ + Initialize the OpenAPIValidationMiddleware. + + Parameters + ---------- + validation_serializer : Callable, optional + Optional serializer to use when serializing the response for validation. + Use it when you have a custom type that cannot be serialized by the default jsonable_encoder. + """ + self._validation_serializer = validation_serializer + + def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) -> Response: + logger.debug("OpenAPIValidationMiddleware handler") + + route: Route = app.context["_route"] + + values: Dict[str, Any] = {} + errors: List[Any] = [] + + # Process path values, which can be found on the route_args + path_values, path_errors = _request_params_to_args( + route.dependant.path_params, + app.context["_route_args"], + ) + + # Normalize query values before validate this + query_string = _normalize_multi_query_string_with_param( + app.current_event.resolved_query_string_parameters, + route.dependant.query_params, + ) + + # Process query values + query_values, query_errors = _request_params_to_args( + route.dependant.query_params, + query_string, + ) + + # Normalize header values before validate this + headers = _normalize_multi_header_values_with_param( + app.current_event.resolved_headers_field, + route.dependant.header_params, + ) + + # Process header values + header_values, header_errors = _request_params_to_args( + route.dependant.header_params, + headers, + ) + + values.update(path_values) + values.update(query_values) + values.update(header_values) + errors += path_errors + query_errors + header_errors + + # Process the request body, if it exists + if route.dependant.body_params: + (body_values, body_errors) = _request_body_to_args( + required_params=route.dependant.body_params, + received_body=self._get_body(app), + ) + values.update(body_values) + errors.extend(body_errors) + + if errors: + # Raise the validation errors + raise RequestValidationError(_normalize_errors(errors)) + else: + # Re-write the route_args with the validated values, and call the next middleware + app.context["_route_args"] = values + + # Call the handler by calling the next middleware + response = next_middleware(app) + + # Process the response + return self._handle_response(route=route, response=response) + + def _handle_response(self, *, route: Route, response: Response): + # Process the response body if it exists + if response.body: + # Validate and serialize the response, if it's JSON + if response.is_json(): + response.body = self._serialize_response( + field=route.dependant.return_param, + response_content=response.body, + ) + + return response + + def _serialize_response( + self, + *, + field: Optional[ModelField] = None, + response_content: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: + """ + Serialize the response content according to the field type. + """ + if field: + errors: List[Dict[str, Any]] = [] + # MAINTENANCE: remove this when we drop pydantic v1 + if not hasattr(field, "serializable"): + response_content = self._prepare_response_content( + response_content, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + value = _validate_field(field=field, value=response_content, loc=("response",), existing_errors=errors) + if errors: + raise RequestValidationError(errors=_normalize_errors(errors), body=response_content) + + if hasattr(field, "serialize"): + return field.serialize( + value, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + return jsonable_encoder( + value, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_serializer=self._validation_serializer, + ) + else: + # Just serialize the response content returned from the handler + return jsonable_encoder(response_content, custom_serializer=self._validation_serializer) + + def _prepare_response_content( + self, + res: Any, + *, + exclude_unset: bool, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: + """ + Prepares the response content for serialization. + """ + if isinstance(res, BaseModel): + return _model_dump( + res, + by_alias=True, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + elif isinstance(res, list): + return [ + self._prepare_response_content(item, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults) + for item in res + ] + elif isinstance(res, dict): + return { + k: self._prepare_response_content(v, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults) + for k, v in res.items() + } + elif dataclasses.is_dataclass(res): + return dataclasses.asdict(res) + return res + + def _get_body(self, app: EventHandlerInstance) -> Dict[str, Any]: + """ + Get the request body from the event, and parse it as JSON. + """ + + content_type_value = app.current_event.get_header_value("content-type") + if not content_type_value or content_type_value.strip().startswith("application/json"): + try: + return app.current_event.json_body + except json.JSONDecodeError as e: + raise RequestValidationError( + [ + { + "type": "json_invalid", + "loc": ("body", e.pos), + "msg": "JSON decode error", + "input": {}, + "ctx": {"error": e.msg}, + }, + ], + body=e.doc, + ) from e + else: + raise NotImplementedError("Only JSON body is supported") + + +def _request_params_to_args( + required_params: Sequence[ModelField], + received_params: Mapping[str, Any], +) -> Tuple[Dict[str, Any], List[Any]]: + """ + Convert the request params to a dictionary of values using validation, and returns a list of errors. + """ + values = {} + errors = [] + + for field in required_params: + field_info = field.field_info + + # To ensure early failure, we check if it's not an instance of Param. + if not isinstance(field_info, Param): + raise AssertionError(f"Expected Param field_info, got {field_info}") + + value = received_params.get(field.alias) + + loc = (field_info.in_.value, field.alias) + + # If we don't have a value, see if it's required or has a default + if value is None: + if field.required: + errors.append(get_missing_field_error(loc=loc)) + else: + values[field.name] = deepcopy(field.default) + continue + + # Finally, validate the value + values[field.name] = _validate_field(field=field, value=value, loc=loc, existing_errors=errors) + + return values, errors + + +def _request_body_to_args( + required_params: List[ModelField], + received_body: Optional[Dict[str, Any]], +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + """ + Convert the request body to a dictionary of values using validation, and returns a list of errors. + """ + values: Dict[str, Any] = {} + errors: List[Dict[str, Any]] = [] + + received_body, field_alias_omitted = _get_embed_body( + field=required_params[0], + required_params=required_params, + received_body=received_body, + ) + + for field in required_params: + # This sets the location to: + # { "user": { object } } if field.alias == user + # { { object } if field_alias is omitted + loc: Tuple[str, ...] = ("body", field.alias) + if field_alias_omitted: + loc = ("body",) + + value: Optional[Any] = None + + # Now that we know what to look for, try to get the value from the received body + if received_body is not None: + try: + value = received_body.get(field.alias) + except AttributeError: + errors.append(get_missing_field_error(loc)) + continue + + # Determine if the field is required + if value is None: + if field.required: + errors.append(get_missing_field_error(loc)) + else: + values[field.name] = deepcopy(field.default) + continue + + # MAINTENANCE: Handle byte and file fields + + # Finally, validate the value + values[field.name] = _validate_field(field=field, value=value, loc=loc, existing_errors=errors) + + return values, errors + + +def _validate_field( + *, + field: ModelField, + value: Any, + loc: Tuple[str, ...], + existing_errors: List[Dict[str, Any]], +): + """ + Validate a field, and append any errors to the existing_errors list. + """ + validated_value, errors = field.validate(value, value, loc=loc) + + if isinstance(errors, list): + processed_errors = _regenerate_error_with_loc(errors=errors, loc_prefix=()) + existing_errors.extend(processed_errors) + elif errors: + existing_errors.append(errors) + + return validated_value + + +def _get_embed_body( + *, + field: ModelField, + required_params: List[ModelField], + received_body: Optional[Dict[str, Any]], +) -> Tuple[Optional[Dict[str, Any]], bool]: + field_info = field.field_info + embed = getattr(field_info, "embed", None) + + # If the field is an embed, and the field alias is omitted, we need to wrap the received body in the field alias. + field_alias_omitted = len(required_params) == 1 and not embed + if field_alias_omitted: + received_body = {field.alias: received_body} + + return received_body, field_alias_omitted + + +def _normalize_multi_query_string_with_param( + query_string: Dict[str, List[str]], + params: Sequence[ModelField], +) -> Dict[str, Any]: + """ + Extract and normalize resolved_query_string_parameters + + Parameters + ---------- + query_string: Dict + A dictionary containing the initial query string parameters. + params: Sequence[ModelField] + A sequence of ModelField objects representing parameters. + + Returns + ------- + A dictionary containing the processed multi_query_string_parameters. + """ + resolved_query_string: Dict[str, Any] = query_string + for param in filter(is_scalar_field, params): + try: + # if the target parameter is a scalar, we keep the first value of the query string + # regardless if there are more in the payload + resolved_query_string[param.alias] = query_string[param.alias][0] + except KeyError: + pass + return resolved_query_string + + +def _normalize_multi_header_values_with_param(headers: Dict[str, Any], params: Sequence[ModelField]): + """ + Extract and normalize resolved_headers_field + + Parameters + ---------- + headers: Dict + A dictionary containing the initial header parameters. + params: Sequence[ModelField] + A sequence of ModelField objects representing parameters. + + Returns + ------- + A dictionary containing the processed headers. + """ + if headers: + for param in filter(is_scalar_field, params): + try: + if len(headers[param.alias]) == 1: + # if the target parameter is a scalar and the list contains only 1 element + # we keep the first value of the headers regardless if there are more in the payload + headers[param.alias] = headers[param.alias][0] + except KeyError: + pass + return headers diff --git a/aws_lambda_powertools/event_handler/middlewares/schema_validation.py b/aws_lambda_powertools/event_handler/middlewares/schema_validation.py new file mode 100644 index 00000000000..66be47a48f3 --- /dev/null +++ b/aws_lambda_powertools/event_handler/middlewares/schema_validation.py @@ -0,0 +1,124 @@ +import logging +from typing import Dict, Optional + +from aws_lambda_powertools.event_handler.api_gateway import Response +from aws_lambda_powertools.event_handler.exceptions import BadRequestError, InternalServerError +from aws_lambda_powertools.event_handler.middlewares import BaseMiddlewareHandler, NextMiddleware +from aws_lambda_powertools.event_handler.types import EventHandlerInstance +from aws_lambda_powertools.utilities.validation import validate +from aws_lambda_powertools.utilities.validation.exceptions import InvalidSchemaFormatError, SchemaValidationError + +logger = logging.getLogger(__name__) + + +class SchemaValidationMiddleware(BaseMiddlewareHandler): + """Middleware to validate API request and response against JSON Schema using the [Validation utility](https://docs.powertools.aws.dev/lambda/python/latest/utilities/validation/). + + Examples + -------- + **Validating incoming event** + + ```python + import requests + + from aws_lambda_powertools import Logger + from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response + from aws_lambda_powertools.event_handler.middlewares import BaseMiddlewareHandler, NextMiddleware + from aws_lambda_powertools.event_handler.middlewares.schema_validation import SchemaValidationMiddleware + + app = APIGatewayRestResolver() + logger = Logger() + json_schema_validation = SchemaValidationMiddleware(inbound_schema=INCOMING_JSON_SCHEMA) + + + @app.get("/todos", middlewares=[json_schema_validation]) + def get_todos(): + todos: requests.Response = requests.get("https://jsonplaceholder.typicode.com/todos") + todos.raise_for_status() + + # for brevity, we'll limit to the first 10 only + return {"todos": todos.json()[:10]} + + + @logger.inject_lambda_context + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ + + def __init__( + self, + inbound_schema: Dict, + inbound_formats: Optional[Dict] = None, + outbound_schema: Optional[Dict] = None, + outbound_formats: Optional[Dict] = None, + ): + """See [Validation utility](https://docs.powertools.aws.dev/lambda/python/latest/utilities/validation/) docs for examples on all parameters. + + Parameters + ---------- + inbound_schema : Dict + JSON Schema to validate incoming event + inbound_formats : Optional[Dict], optional + Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool, by default None + JSON Schema to validate outbound event, by default None + outbound_formats : Optional[Dict], optional + Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool, by default None + """ # noqa: E501 + super().__init__() + self.inbound_schema = inbound_schema + self.inbound_formats = inbound_formats + self.outbound_schema = outbound_schema + self.outbound_formats = outbound_formats + + def bad_response(self, error: SchemaValidationError) -> Response: + message: str = f"Bad Response: {error.message}" + logger.debug(message) + raise BadRequestError(message) + + def bad_request(self, error: SchemaValidationError) -> Response: + message: str = f"Bad Request: {error.message}" + logger.debug(message) + raise BadRequestError(message) + + def bad_config(self, error: InvalidSchemaFormatError) -> Response: + logger.debug(f"Invalid Schema Format: {error}") + raise InternalServerError("Internal Server Error") + + def handler(self, app: EventHandlerInstance, next_middleware: NextMiddleware) -> Response: + """Validates incoming JSON payload (body) against JSON Schema provided. + + Parameters + ---------- + app : EventHandlerInstance + An instance of an Event Handler + next_middleware : NextMiddleware + Callable to get response from the next middleware or route handler in the chain + + Returns + ------- + Response + It can return three types of response objects + + - Original response: Propagates HTTP response returned from the next middleware if validation succeeds + - HTTP 400: Payload or response failed JSON Schema validation + - HTTP 500: JSON Schema provided has incorrect format + """ + try: + validate(event=app.current_event.json_body, schema=self.inbound_schema, formats=self.inbound_formats) + except SchemaValidationError as error: + return self.bad_request(error) + except InvalidSchemaFormatError as error: + return self.bad_config(error) + + result = next_middleware(app) + + if self.outbound_formats is not None: + try: + validate(event=result.body, schema=self.inbound_schema, formats=self.inbound_formats) + except SchemaValidationError as error: + return self.bad_response(error) + except InvalidSchemaFormatError as error: + return self.bad_config(error) + + return result diff --git a/python/tests/functional/__init__.py b/aws_lambda_powertools/event_handler/openapi/__init__.py similarity index 100% rename from python/tests/functional/__init__.py rename to aws_lambda_powertools/event_handler/openapi/__init__.py diff --git a/aws_lambda_powertools/event_handler/openapi/compat.py b/aws_lambda_powertools/event_handler/openapi/compat.py new file mode 100644 index 00000000000..060886605ec --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/compat.py @@ -0,0 +1,497 @@ +# mypy: ignore-errors +# flake8: noqa +from collections import deque +from copy import copy + +# MAINTENANCE: remove when deprecating Pydantic v1. Mypy doesn't handle two different code paths that import different +# versions of a module, so we need to ignore errors here. + +from dataclasses import dataclass, is_dataclass +from enum import Enum +from typing import Any, Dict, List, Set, Tuple, Type, Union, FrozenSet, Deque, Sequence, Mapping + +from typing_extensions import Annotated, Literal, get_origin, get_args + +from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo + +from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2 +from aws_lambda_powertools.event_handler.openapi.types import ( + COMPONENT_REF_PREFIX, + ModelNameMap, + UnionType, +) + +sequence_annotation_to_type = { + Sequence: list, + List: list, + list: list, + Tuple: tuple, + tuple: tuple, + Set: set, + set: set, + FrozenSet: frozenset, + frozenset: frozenset, + Deque: deque, + deque: deque, +} + +sequence_types = tuple(sequence_annotation_to_type.keys()) + +RequestErrorModel: Type[BaseModel] = create_model("Request") + +if PYDANTIC_V2: # pragma: no cover # false positive; dropping in v3 + from pydantic import TypeAdapter, ValidationError + from pydantic._internal._typing_extra import eval_type_lenient + from pydantic.fields import FieldInfo + from pydantic._internal._utils import lenient_issubclass + from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue + from pydantic_core import PydanticUndefined, PydanticUndefinedType + + from aws_lambda_powertools.event_handler.openapi.types import IncEx + + Undefined = PydanticUndefined + Required = PydanticUndefined + UndefinedType = PydanticUndefinedType + + evaluate_forwardref = eval_type_lenient + + class ErrorWrapper(Exception): + pass + + @dataclass + class ModelField: + field_info: FieldInfo + name: str + mode: Literal["validation", "serialization"] = "validation" + + @property + def alias(self) -> str: + value = self.field_info.alias + return value if value is not None else self.name + + @property + def required(self) -> bool: + return self.field_info.is_required() + + @property + def default(self) -> Any: + return self.get_default() + + @property + def type_(self) -> Any: + return self.field_info.annotation + + def __post_init__(self) -> None: + self._type_adapter: TypeAdapter[Any] = TypeAdapter( + Annotated[self.field_info.annotation, self.field_info], + ) + + def get_default(self) -> Any: + if self.field_info.is_required(): + return Undefined + return self.field_info.get_default(call_default_factory=True) + + def serialize( + self, + value: Any, + *, + mode: Literal["json", "python"] = "json", + include: Union[IncEx, None] = None, + exclude: Union[IncEx, None] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: + return self._type_adapter.dump_python( + value, + mode=mode, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + def validate( + self, value: Any, values: Dict[str, Any] = {}, *, loc: Tuple[Union[int, str], ...] = () + ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: + try: + return (self._type_adapter.validate_python(value, from_attributes=True), None) + except ValidationError as exc: + return None, _regenerate_error_with_loc(errors=exc.errors(), loc_prefix=loc) + + def __hash__(self) -> int: + # Each ModelField is unique for our purposes + return id(self) + + def get_schema_from_model_field( + *, + field: ModelField, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + JsonSchemaValue, + ], + ) -> Dict[str, Any]: + json_schema = field_mapping[(field, field.mode)] + if "$ref" not in json_schema: + # MAINTENANCE: remove when deprecating Pydantic v1 + # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 + json_schema["title"] = field.field_info.title or field.alias.title().replace("_", " ") + return json_schema + + def get_definitions( + *, + fields: List[ModelField], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + ) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + Dict[str, Any], + ], + Dict[str, Dict[str, Any]], + ]: + inputs = [(field, field.mode, field._type_adapter.core_schema) for field in fields] + field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) + + return field_mapping, definitions + + def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: + return {} + + def get_annotation_from_field_info(annotation: Any, field_info: FieldInfo, field_name: str) -> Any: + return annotation + + def model_rebuild(model: Type[BaseModel]) -> None: + model.model_rebuild() + + def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + return type(field_info).from_annotation(annotation) + + def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + error = ValidationError.from_exception_data( + "Field required", [{"type": "missing", "loc": loc, "input": {}}] + ).errors()[0] + error["input"] = None + return error + + def is_scalar_field(field: ModelField) -> bool: + from aws_lambda_powertools.event_handler.openapi.params import Body + + return field_annotation_is_scalar(field.field_info.annotation) and not isinstance(field.field_info, Body) + + def is_scalar_sequence_field(field: ModelField) -> bool: + return field_annotation_is_scalar_sequence(field.field_info.annotation) + + def is_sequence_field(field: ModelField) -> bool: + return field_annotation_is_sequence(field.field_info.annotation) + + def is_bytes_field(field: ModelField) -> bool: + return is_bytes_or_nonable_bytes_annotation(field.type_) + + def is_bytes_sequence_field(field: ModelField) -> bool: + return is_bytes_sequence_annotation(field.type_) + + def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation + if not issubclass(origin_type, sequence_types): # type: ignore[arg-type] + raise AssertionError(f"Expected sequence type, got {origin_type}") + return sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] + + def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + return errors # type: ignore[return-value] + + def create_body_model(*, fields: Sequence[ModelField], model_name: str) -> Type[BaseModel]: + field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} + model: Type[BaseModel] = create_model(model_name, **field_params) + return model + + def _model_dump(model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any) -> Any: + return model.model_dump(mode=mode, **kwargs) + + def model_json(model: BaseModel, **kwargs: Any) -> Any: + return model.model_dump_json(**kwargs) + +else: + from pydantic import BaseModel, ValidationError + from pydantic.fields import ( + ModelField, + Required, + Undefined, + UndefinedType, + SHAPE_LIST, + SHAPE_SET, + SHAPE_FROZENSET, + SHAPE_TUPLE, + SHAPE_SEQUENCE, + SHAPE_TUPLE_ELLIPSIS, + SHAPE_SINGLETON, + ) + from pydantic.schema import ( + field_schema, + get_annotation_from_field_info, + get_flat_models_from_fields, + get_model_name_map, + model_process_schema, + ) + from pydantic.errors import MissingError + from pydantic.error_wrappers import ErrorWrapper + from pydantic.utils import lenient_issubclass + from pydantic.typing import evaluate_forwardref + + JsonSchemaValue = Dict[str, Any] + + sequence_shapes = [ + SHAPE_LIST, + SHAPE_SET, + SHAPE_FROZENSET, + SHAPE_TUPLE, + SHAPE_SEQUENCE, + SHAPE_TUPLE_ELLIPSIS, + ] + sequence_shape_to_type = { + SHAPE_LIST: list, + SHAPE_SET: set, + SHAPE_TUPLE: tuple, + SHAPE_SEQUENCE: list, + SHAPE_TUPLE_ELLIPSIS: list, + } + + @dataclass + class GenerateJsonSchema: + ref_template: str + + def get_schema_from_model_field( + *, + field: ModelField, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + JsonSchemaValue, + ], + ) -> Dict[str, Any]: + return field_schema( + field, + model_name_map=model_name_map, + ref_prefix=COMPONENT_REF_PREFIX, + )[0] + + def get_definitions( + *, + fields: List[ModelField], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + ) -> Tuple[ + Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[str, Dict[str, Any]], + ]: + models = get_flat_models_from_fields(fields, known_models=set()) + return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) + + def get_model_definitions( + *, + flat_models: Set[Union[Type[BaseModel], Type[Enum]]], + model_name_map: ModelNameMap, + ) -> Dict[str, Any]: + definitions: Dict[str, Dict[str, Any]] = {} + for model in flat_models: + m_schema, m_definitions, _ = model_process_schema( + model, + model_name_map=model_name_map, + ref_prefix=COMPONENT_REF_PREFIX, + ) + definitions.update(m_definitions) + model_name = model_name_map[model] + if "description" in m_schema: + m_schema["description"] = m_schema["description"].split("\f")[0] + definitions[model_name] = m_schema + return definitions + + def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: + models = get_flat_models_from_fields(fields, known_models=set()) + return get_model_name_map(models) + + def model_rebuild(model: Type[BaseModel]) -> None: + model.update_forward_refs() + + def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + return copy(field_info) + + def is_pv1_scalar_field(field: ModelField) -> bool: + from aws_lambda_powertools.event_handler.openapi.params import Body + + if not ( + field.shape == SHAPE_SINGLETON + and not lenient_issubclass(field.type_, BaseModel) + and not lenient_issubclass(field.type_, dict) + and not field_annotation_is_sequence(field.type_) + and not is_dataclass(field.type_) + and not isinstance(field.field_info, Body) + ): + return False + + if field.sub_fields: + if not all(is_pv1_scalar_sequence_field(f) for f in field.sub_fields): + return False + + return True + + def is_pv1_scalar_sequence_field(field: ModelField) -> bool: + if (field.shape in sequence_shapes) and not lenient_issubclass(field.type_, BaseModel): + if field.sub_fields is not None: + for sub_field in field.sub_fields: + if not is_pv1_scalar_field(sub_field): + return False + return True + if _annotation_is_sequence(field.type_): + return True + return False + + def is_scalar_field(field: ModelField) -> bool: + return is_pv1_scalar_field(field) + + def is_scalar_sequence_field(field: ModelField) -> bool: + return is_pv1_scalar_sequence_field(field) + + def is_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes or _annotation_is_sequence(field.type_) + + def is_bytes_field(field: ModelField) -> bool: + return lenient_issubclass(field.type_, bytes) + + def is_bytes_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) # type: ignore[attr-defined] + + def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + if lenient_issubclass(annotation, (str, bytes)): + return False + return lenient_issubclass(annotation, sequence_types) + + def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + missing_field_error = ErrorWrapper(MissingError(), loc=loc) + new_error = ValidationError([missing_field_error], RequestErrorModel) + return new_error.errors()[0] + + def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + use_errors: List[Any] = [] + for error in errors: + if isinstance(error, ErrorWrapper): + new_errors = ValidationError(errors=[error], model=RequestErrorModel).errors() # type: ignore[call-arg] + use_errors.extend(new_errors) + elif isinstance(error, list): + use_errors.extend(_normalize_errors(error)) + else: + use_errors.append(error) + return use_errors + + def create_body_model(*, fields: Sequence[ModelField], model_name: str) -> Type[BaseModel]: + body_model = create_model(model_name) + for f in fields: + body_model.__fields__[f.name] = f # type: ignore[index] + return body_model + + def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + return sequence_shape_to_type[field.shape](value) + + def _model_dump(model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any) -> Any: + return model.dict(**kwargs) + + def model_json(model: BaseModel, **kwargs: Any) -> Any: + return model.json(**kwargs) + + +# Common code for both versions + + +def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) + + return ( + _annotation_is_complex(annotation) + or _annotation_is_complex(origin) + or hasattr(origin, "__pydantic_core_schema__") + or hasattr(origin, "__get_pydantic_core_schema__") + ) + + +def field_annotation_is_scalar(annotation: Any) -> bool: + return annotation is Ellipsis or not field_annotation_is_complex(annotation) + + +def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + return _annotation_is_sequence(annotation) or _annotation_is_sequence(get_origin(annotation)) + + +def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one_scalar_sequence = False + for arg in get_args(annotation): + if field_annotation_is_scalar_sequence(arg): + at_least_one_scalar_sequence = True + continue + elif not field_annotation_is_scalar(arg): + return False + return at_least_one_scalar_sequence + return field_annotation_is_sequence(annotation) and all( + field_annotation_is_scalar(sub_annotation) for sub_annotation in get_args(annotation) + ) + + +def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool: + if lenient_issubclass(annotation, bytes): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, bytes): + return True + return False + + +def is_bytes_sequence_annotation(annotation: Any) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one = False + for arg in get_args(annotation): + if is_bytes_sequence_annotation(arg): + at_least_one = True + break + return at_least_one + return field_annotation_is_sequence(annotation) and all( + is_bytes_or_nonable_bytes_annotation(sub_annotation) for sub_annotation in get_args(annotation) + ) + + +def value_is_sequence(value: Any) -> bool: + return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type] + + +def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + return ( + lenient_issubclass(annotation, (BaseModel, Mapping)) # TODO: UploadFile + or _annotation_is_sequence(annotation) + or is_dataclass(annotation) + ) + + +def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + if lenient_issubclass(annotation, (str, bytes)): + return False + return lenient_issubclass(annotation, sequence_types) + + +def _regenerate_error_with_loc( + *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] +) -> List[Dict[str, Any]]: + updated_loc_errors: List[Any] = [ + {**err, "loc": loc_prefix + err.get("loc", ())} for err in _normalize_errors(errors) + ] + + return updated_loc_errors diff --git a/aws_lambda_powertools/event_handler/openapi/constants.py b/aws_lambda_powertools/event_handler/openapi/constants.py new file mode 100644 index 00000000000..dc326b68abb --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/constants.py @@ -0,0 +1,2 @@ +DEFAULT_API_VERSION = "1.0.0" +DEFAULT_OPENAPI_VERSION = "3.0.3" diff --git a/aws_lambda_powertools/event_handler/openapi/dependant.py b/aws_lambda_powertools/event_handler/openapi/dependant.py new file mode 100644 index 00000000000..abcb91e90dd --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/dependant.py @@ -0,0 +1,379 @@ +import inspect +import re +from typing import Any, Callable, Dict, ForwardRef, List, Optional, Set, Tuple, Type, cast + +from pydantic import BaseModel + +from aws_lambda_powertools.event_handler.openapi.compat import ( + ModelField, + create_body_model, + evaluate_forwardref, + is_scalar_field, + is_scalar_sequence_field, +) +from aws_lambda_powertools.event_handler.openapi.params import ( + Body, + Dependant, + Header, + Param, + ParamTypes, + Query, + _File, + _Form, + analyze_param, + create_response_field, + get_flat_dependant, +) +from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse, OpenAPIResponseContentModel + +""" +This turns the opaque function signature into typed, validated models. + +It relies on Pydantic's typing and validation to achieve this in a declarative way. +This enables traits like autocompletion, validation, and declarative structure vs imperative parsing. + +This code parses an OpenAPI operation handler function signature into Pydantic models. It uses inspect to get the +signature and regex to parse path parameters. Each parameter is analyzed to extract its type annotation and generate +a corresponding Pydantic field, which are added to a Dependant model. Return values are handled similarly. + +This modeling allows for type checking, automatic parameter name/location/type extraction, and input validation - +turning the opaque signature into validated models. It relies on Pydantic's typing and validation for a declarative +approach over imperative parsing, enabling autocompletion, validation and structure. +""" + + +def add_param_to_fields( + *, + field: ModelField, + dependant: Dependant, +) -> None: + """ + Adds a parameter to the list of parameters in the dependant model. + + Parameters + ---------- + field: ModelField + The field to add + dependant: Dependant + The dependant model to add the field to + + """ + field_info = cast(Param, field.field_info) + + # Dictionary to map ParamTypes to their corresponding lists in dependant + param_type_map = { + ParamTypes.path: dependant.path_params, + ParamTypes.query: dependant.query_params, + ParamTypes.header: dependant.header_params, + ParamTypes.cookie: dependant.cookie_params, + } + + # Check if field_info.in_ is a valid key in param_type_map and append the field to the corresponding list + # or raise an exception if it's not a valid key. + if field_info.in_ in param_type_map: + param_type_map[field_info.in_].append(field) + else: + raise AssertionError(f"Unsupported param type: {field_info.in_}") + + +def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: + """ + Evaluates a type annotation, which can be a string or a ForwardRef. + """ + if isinstance(annotation, str): + annotation = ForwardRef(annotation) + annotation = evaluate_forwardref(annotation, globalns, globalns) + return annotation + + +def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: + """ + Returns a typed signature for a callable, resolving forward references. + + Parameters + ---------- + call: Callable[..., Any] + The callable to get the signature for + + Returns + ------- + inspect.Signature + The typed signature + """ + signature = inspect.signature(call) + + # Gets the global namespace for the call. This is used to resolve forward references. + globalns = getattr(call, "__global__", {}) + + typed_params = [ + inspect.Parameter( + name=param.name, + kind=param.kind, + default=param.default, + annotation=get_typed_annotation(param.annotation, globalns), + ) + for param in signature.parameters.values() + ] + + # If the return annotation is not empty, add it to the signature. + if signature.return_annotation is not inspect.Signature.empty: + return_param = inspect.Parameter( + name="Return", + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=None, + annotation=get_typed_annotation(signature.return_annotation, globalns), + ) + return inspect.Signature(typed_params, return_annotation=return_param.annotation) + else: + return inspect.Signature(typed_params) + + +def get_path_param_names(path: str) -> Set[str]: + """ + Returns the path parameter names from a path template. Those are the strings between { and }. + + Parameters + ---------- + path: str + The path template + + Returns + ------- + Set[str] + The path parameter names + + """ + return set(re.findall("{(.*?)}", path)) + + +def get_dependant( + *, + path: str, + call: Callable[..., Any], + name: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, +) -> Dependant: + """ + Returns a dependant model for a handler function. A dependant model is a model that contains + the parameters and return value of a handler function. + + Parameters + ---------- + path: str + The path template + call: Callable[..., Any] + The handler function + name: str, optional + The name of the handler function + responses: List[Dict[int, OpenAPIResponse]], optional + The list of extra responses for the handler function + + Returns + ------- + Dependant + The dependant model for the handler function + """ + path_param_names = get_path_param_names(path) + endpoint_signature = get_typed_signature(call) + signature_params = endpoint_signature.parameters + + dependant = Dependant( + call=call, + name=name, + path=path, + ) + + # Add each parameter to the dependant model + for param_name, param in signature_params.items(): + # If the parameter is a path parameter, we need to set the in_ field to "path". + is_path_param = param_name in path_param_names + + # Analyze the parameter to get the Pydantic field. + param_field = analyze_param( + param_name=param_name, + annotation=param.annotation, + value=param.default, + is_path_param=is_path_param, + is_response_param=False, + ) + if param_field is None: + raise AssertionError(f"Parameter field is None for param: {param_name}") + + if is_body_param(param_field=param_field, is_path_param=is_path_param): + dependant.body_params.append(param_field) + else: + add_param_to_fields(field=param_field, dependant=dependant) + + _add_return_annotation(dependant, endpoint_signature) + _add_extra_responses(dependant, responses) + + return dependant + + +def _add_extra_responses(dependant: Dependant, responses: Optional[Dict[int, OpenAPIResponse]]): + # Also add the optional extra responses to the dependant model. + if not responses: + return + + for response in responses.values(): + for schema in response.get("content", {}).values(): + if "model" in schema: + response_field = analyze_param( + param_name="return", + annotation=cast(OpenAPIResponseContentModel, schema)["model"], + value=None, + is_path_param=False, + is_response_param=True, + ) + if response_field is None: + raise AssertionError("Response field is None for response model") + + dependant.response_extra_models.append(response_field) + + +def _add_return_annotation(dependant: Dependant, endpoint_signature: inspect.Signature): + # If the return annotation is not empty, add it to the dependant model. + return_annotation = endpoint_signature.return_annotation + if return_annotation is not inspect.Signature.empty: + param_field = analyze_param( + param_name="return", + annotation=return_annotation, + value=None, + is_path_param=False, + is_response_param=True, + ) + if param_field is None: + raise AssertionError("Param field is None for return annotation") + + dependant.return_param = param_field + + +def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool: + """ + Returns whether a parameter is a request body parameter, by checking if it is a scalar field or a body field. + + Parameters + ---------- + param_field: ModelField + The parameter field + is_path_param: bool + Whether the parameter is a path parameter + + Returns + ------- + bool + Whether the parameter is a request body parameter + """ + if is_path_param: + if not is_scalar_field(field=param_field): + raise AssertionError("Path params must be of one of the supported types") + return False + elif is_scalar_field(field=param_field): + return False + elif isinstance(param_field.field_info, (Query, Header)) and is_scalar_sequence_field(param_field): + return False + else: + if not isinstance(param_field.field_info, Body): + raise AssertionError(f"Param: {param_field.name} can only be a request body, use Body()") + return True + + +def get_flat_params(dependant: Dependant) -> List[ModelField]: + """ + Get a list of all the parameters from a Dependant object. + + Parameters + ---------- + dependant : Dependant + The Dependant object containing the parameters. + + Returns + ------- + List[ModelField] + A list of ModelField objects containing the flat parameters from the Dependant object. + + """ + flat_dependant = get_flat_dependant(dependant) + return ( + flat_dependant.path_params + + flat_dependant.query_params + + flat_dependant.header_params + + flat_dependant.cookie_params + ) + + +def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: + """ + Get the Body field for a given Dependant object. + """ + + flat_dependant = get_flat_dependant(dependant) + if not flat_dependant.body_params: + return None + + first_param = flat_dependant.body_params[0] + field_info = first_param.field_info + + # Handle the case where there is only one body parameter and it is embedded + embed = getattr(field_info, "embed", None) + body_param_names_set = {param.name for param in flat_dependant.body_params} + if len(body_param_names_set) == 1 and not embed: + return first_param + + # If one field requires to embed, all have to be embedded + for param in flat_dependant.body_params: + setattr(param.field_info, "embed", True) # noqa: B010 + + # Generate a custom body model for this endpoint + model_name = "Body_" + name + body_model = create_body_model(fields=flat_dependant.body_params, model_name=model_name) + + required = any(True for f in flat_dependant.body_params if f.required) + + body_field_info, body_field_info_kwargs = get_body_field_info( + body_model=body_model, + flat_dependant=flat_dependant, + required=required, + ) + + final_field = create_response_field( + name="body", + type_=body_model, + required=required, + alias="body", + field_info=body_field_info(**body_field_info_kwargs), + ) + return final_field + + +def get_body_field_info( + *, + body_model: Type[BaseModel], + flat_dependant: Dependant, + required: bool, +) -> Tuple[Type[Body], Dict[str, Any]]: + """ + Get the Body field info and kwargs for a given body model. + """ + + body_field_info_kwargs: Dict[str, Any] = {"annotation": body_model, "alias": "body"} + + if not required: + body_field_info_kwargs["default"] = None + + if any(isinstance(f.field_info, _File) for f in flat_dependant.body_params): + # MAINTENANCE: body_field_info: Type[Body] = _File + raise NotImplementedError("_File fields are not supported in request bodies") + elif any(isinstance(f.field_info, _Form) for f in flat_dependant.body_params): + # MAINTENANCE: body_field_info: Type[Body] = _Form + raise NotImplementedError("_Form fields are not supported in request bodies") + else: + body_field_info = Body + + body_param_media_types = [ + f.field_info.media_type for f in flat_dependant.body_params if isinstance(f.field_info, Body) + ] + if len(set(body_param_media_types)) == 1: + body_field_info_kwargs["media_type"] = body_param_media_types[0] + + return body_field_info, body_field_info_kwargs diff --git a/aws_lambda_powertools/event_handler/openapi/encoders.py b/aws_lambda_powertools/event_handler/openapi/encoders.py new file mode 100644 index 00000000000..520c0d71509 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/encoders.py @@ -0,0 +1,358 @@ +import dataclasses +import datetime +from collections import defaultdict, deque +from decimal import Decimal +from enum import Enum +from pathlib import Path, PurePath +from re import Pattern +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from uuid import UUID + +from pydantic import BaseModel +from pydantic.color import Color +from pydantic.types import SecretBytes, SecretStr + +from aws_lambda_powertools.event_handler.openapi.compat import _model_dump +from aws_lambda_powertools.event_handler.openapi.exceptions import SerializationError +from aws_lambda_powertools.event_handler.openapi.types import IncEx + +""" +This module contains the encoders used by jsonable_encoder to convert Python objects to JSON serializable data types. +""" + + +def jsonable_encoder( # noqa: PLR0911 + obj: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + custom_serializer: Optional[Callable[[Any], str]] = None, +) -> Any: + """ + JSON encodes an arbitrary Python object into JSON serializable data types. + + This is a modified version of fastapi.encoders.jsonable_encoder that supports + encoding of pydantic.BaseModel objects. + + Parameters + ---------- + obj : Any + The object to encode + include : Optional[IncEx], optional + A set or dictionary of strings that specifies which properties should be included, by default None, + meaning everything is included + exclude : Optional[IncEx], optional + A set or dictionary of strings that specifies which properties should be excluded, by default None, + meaning nothing is excluded + by_alias : bool, optional + Whether field aliases should be respected, by default True + exclude_unset : bool, optional + Whether fields that are not set should be excluded, by default False + exclude_defaults : bool, optional + Whether fields that are equal to their default value (as specified in the model) should be excluded, + by default False + exclude_none : bool, optional + Whether fields that are equal to None should be excluded, by default False + custom_serializer : Callable, optional + A custom serializer to use for encoding the object, when everything else fails. + + Returns + ------- + Any + The JSON serializable data types + """ + if include is not None and not isinstance(include, (set, dict)): + include = set(include) + if exclude is not None and not isinstance(exclude, (set, dict)): + exclude = set(exclude) + + try: + # Pydantic models + if isinstance(obj, BaseModel): + return _dump_base_model( + obj=obj, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + ) + + # Dataclasses + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) + return jsonable_encoder( + obj_dict, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + # Enums + if isinstance(obj, Enum): + return obj.value + + # Paths + if isinstance(obj, PurePath): + return str(obj) + + # Scalars + if isinstance(obj, (str, int, float, type(None))): + return obj + + # Dictionaries + if isinstance(obj, dict): + return _dump_dict( + obj=obj, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_none=exclude_none, + exclude_unset=exclude_unset, + ) + + # Sequences + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)): + return _dump_sequence( + obj=obj, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + exclude_unset=exclude_unset, + ) + + # Other types + if type(obj) in ENCODERS_BY_TYPE: + return ENCODERS_BY_TYPE[type(obj)](obj) + + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(obj, classes_tuple): + return encoder(obj) + + # Use custom serializer if present + if custom_serializer: + return custom_serializer(obj) + + # Default + return _dump_other( + obj=obj, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_none=exclude_none, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + ) + except ValueError as exc: + raise SerializationError( + f"Unable to serialize the object {obj} as it is not a supported type. Error details: {exc}", + "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#serializing-objects", + ) from exc + + +def _dump_base_model( + *, + obj: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_none: bool = False, + exclude_defaults: bool = False, +): + """ + Dump a BaseModel object to a dict, using the same parameters as jsonable_encoder + """ + obj_dict = _model_dump( + obj, + mode="json", + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + ) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + + return jsonable_encoder( + obj_dict, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + ) + + +def _dump_dict( + *, + obj: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_none: bool = False, +) -> Dict[str, Any]: + """ + Dump a dict to a dict, using the same parameters as jsonable_encoder + """ + encoded_dict = {} + allowed_keys = set(obj.keys()) + if include is not None: + allowed_keys &= set(include) + if exclude is not None: + allowed_keys -= set(exclude) + for key, value in obj.items(): + if ( + (not isinstance(key, str) or not key.startswith("_sa")) + and (value is not None or not exclude_none) + and key in allowed_keys + ): + encoded_key = jsonable_encoder( + key, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + ) + encoded_value = jsonable_encoder( + value, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + ) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + + +def _dump_sequence( + *, + obj: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_none: bool = False, + exclude_defaults: bool = False, +) -> List[Any]: + """ + Dump a sequence to a list, using the same parameters as jsonable_encoder + """ + encoded_list = [] + for item in obj: + encoded_list.append( + jsonable_encoder( + item, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ), + ) + return encoded_list + + +def _dump_other( + *, + obj: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_none: bool = False, + exclude_defaults: bool = False, +) -> Any: + """ + Dump an object to a hashable object, using the same parameters as jsonable_encoder + """ + try: + data = dict(obj) + except Exception as e: + errors: List[Exception] = [e] + try: + data = vars(obj) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder( + data, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + +def iso_format(o: Union[datetime.date, datetime.time]) -> str: + """ + ISO format for date and time + """ + return o.isoformat() + + +def decimal_encoder(dec_value: Decimal) -> Union[int, float]: + """ + Encodes a Decimal as int of there's no exponent, otherwise float + + This is useful when we use ConstrainedDecimal to represent Numeric(x,0) + where an integer (but not int typed) is used. Encoding this as a float + results in failed round-tripping between encode and parse. + + >>> decimal_encoder(Decimal("1.0")) + 1.0 + + >>> decimal_encoder(Decimal("1")) + 1 + """ + if dec_value.as_tuple().exponent >= 0: # type: ignore[operator] + return int(dec_value) + else: + return float(dec_value) + + +# Encoders for types that are not JSON serializable +ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { + bytes: lambda o: o.decode(), + Color: str, + datetime.date: iso_format, + datetime.datetime: iso_format, + datetime.time: iso_format, + datetime.timedelta: lambda td: td.total_seconds(), + Decimal: decimal_encoder, + Enum: lambda o: o.value, + frozenset: list, + deque: list, + GeneratorType: list, + Path: str, + Pattern: lambda o: o.pattern, + SecretBytes: str, + SecretStr: str, + set: list, + UUID: str, +} + + +# Generates a mapping of encoders to a tuple of classes that they can encode +def generate_encoders_by_class_tuples( + type_encoder_map: Dict[Any, Callable[[Any], Any]], +) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]: + encoders: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in type_encoder_map.items(): + encoders[encoder] += (type_,) + return encoders + + +# Mapping of encoders to a tuple of classes that they can encode +encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE) diff --git a/aws_lambda_powertools/event_handler/openapi/exceptions.py b/aws_lambda_powertools/event_handler/openapi/exceptions.py new file mode 100644 index 00000000000..e1ed33e67fd --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/exceptions.py @@ -0,0 +1,35 @@ +from typing import Any, Sequence + + +class ValidationException(Exception): + """ + Base exception for all validation errors + """ + + def __init__(self, errors: Sequence[Any]) -> None: + self._errors = errors + + def errors(self) -> Sequence[Any]: + return self._errors + + +class RequestValidationError(ValidationException): + """ + Raised when the request body does not match the OpenAPI schema + """ + + def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None: + super().__init__(errors) + self.body = body + + +class SerializationError(Exception): + """ + Base exception for all encoding errors + """ + + +class SchemaValidationError(ValidationException): + """ + Raised when the OpenAPI schema validation fails + """ diff --git a/aws_lambda_powertools/event_handler/openapi/models.py b/aws_lambda_powertools/event_handler/openapi/models.py new file mode 100644 index 00000000000..04345ddaad7 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/models.py @@ -0,0 +1,584 @@ +from enum import Enum +from typing import Any, Dict, List, Optional, Set, Union + +from pydantic import AnyUrl, BaseModel, Field + +from aws_lambda_powertools.event_handler.openapi.compat import model_rebuild +from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2 +from aws_lambda_powertools.shared.types import Annotated, Literal + +""" +The code defines Pydantic models for the various OpenAPI objects like OpenAPI, PathItem, Operation, Parameter etc. +These models can be used to parse OpenAPI JSON/YAML files into Python objects, or generate OpenAPI from Python data. +""" + + +# https://swagger.io/specification/#contact-object +class Contact(BaseModel): + name: Optional[str] = None + url: Optional[AnyUrl] = None + email: Optional[str] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#license-object +class License(BaseModel): + name: str + identifier: Optional[str] = None + url: Optional[AnyUrl] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#info-object +class Info(BaseModel): + title: str + description: Optional[str] = None + termsOfService: Optional[str] = None + contact: Optional[Contact] = None + license: Optional[License] = None # noqa: A003 + version: str + + if PYDANTIC_V2: + summary: Optional[str] = None + model_config = {"extra": "ignore"} + + else: + + class Config: + extra = "ignore" + + +# https://swagger.io/specification/#server-variable-object +class ServerVariable(BaseModel): + enum: Annotated[Optional[List[str]], Field(min_length=1)] = None + default: str + description: Optional[str] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#server-object +class Server(BaseModel): + url: Union[AnyUrl, str] + description: Optional[str] = None + variables: Optional[Dict[str, ServerVariable]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#reference-object +class Reference(BaseModel): + ref: str = Field(alias="$ref") + + +# https://swagger.io/specification/#discriminator-object +class Discriminator(BaseModel): + propertyName: str + mapping: Optional[Dict[str, str]] = None + + +# https://swagger.io/specification/#xml-object +class XML(BaseModel): + name: Optional[str] = None + namespace: Optional[str] = None + prefix: Optional[str] = None + attribute: Optional[bool] = None + wrapped: Optional[bool] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#external-documentation-object +class ExternalDocumentation(BaseModel): + description: Optional[str] = None + url: AnyUrl + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#schema-object +class Schema(BaseModel): + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu + # Core Vocabulary + schema_: Optional[str] = Field(default=None, alias="$schema") + vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") + id: Optional[str] = Field(default=None, alias="$id") # noqa: A003 + anchor: Optional[str] = Field(default=None, alias="$anchor") + dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") + ref: Optional[str] = Field(default=None, alias="$ref") + dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") + defs: Optional[Dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") + comment: Optional[str] = Field(default=None, alias="$comment") + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s + # A Vocabulary for Applying Subschemas + allOf: Optional[List["SchemaOrBool"]] = None + anyOf: Optional[List["SchemaOrBool"]] = None + oneOf: Optional[List["SchemaOrBool"]] = None + not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") + if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") + then: Optional["SchemaOrBool"] = None + else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") + dependentSchemas: Optional[Dict[str, "SchemaOrBool"]] = None + prefixItems: Optional[List["SchemaOrBool"]] = None + # MAINTENANCE: uncomment and remove below when deprecating Pydantic v1 + # MAINTENANCE: It generates a list of schemas for tuples, before prefixItems was available + # MAINTENANCE: items: Optional["SchemaOrBool"] = None + items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None + contains: Optional["SchemaOrBool"] = None + properties: Optional[Dict[str, "SchemaOrBool"]] = None + patternProperties: Optional[Dict[str, "SchemaOrBool"]] = None + additionalProperties: Optional["SchemaOrBool"] = None + propertyNames: Optional["SchemaOrBool"] = None + unevaluatedItems: Optional["SchemaOrBool"] = None + unevaluatedProperties: Optional["SchemaOrBool"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural + # A Vocabulary for Structural Validation + type: Optional[str] = None # noqa: A003 + enum: Optional[List[Any]] = None + const: Optional[Any] = None + multipleOf: Optional[float] = Field(default=None, gt=0) + maximum: Optional[float] = None + exclusiveMaximum: Optional[float] = None + minimum: Optional[float] = None + exclusiveMinimum: Optional[float] = None + maxLength: Optional[int] = Field(default=None, ge=0) + minLength: Optional[int] = Field(default=None, ge=0) + pattern: Optional[str] = None + maxItems: Optional[int] = Field(default=None, ge=0) + minItems: Optional[int] = Field(default=None, ge=0) + uniqueItems: Optional[bool] = None + maxContains: Optional[int] = Field(default=None, ge=0) + minContains: Optional[int] = Field(default=None, ge=0) + maxProperties: Optional[int] = Field(default=None, ge=0) + minProperties: Optional[int] = Field(default=None, ge=0) + required: Optional[List[str]] = None + dependentRequired: Optional[Dict[str, Set[str]]] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c + # Vocabularies for Semantic Content With "format" + format: Optional[str] = None # noqa: A003 + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten + # A Vocabulary for the Contents of String-Encoded Data + contentEncoding: Optional[str] = None + contentMediaType: Optional[str] = None + contentSchema: Optional["SchemaOrBool"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta + # A Vocabulary for Basic Meta-Data Annotations + title: Optional[str] = None + description: Optional[str] = None + default: Optional[Any] = None + deprecated: Optional[bool] = None + readOnly: Optional[bool] = None + writeOnly: Optional[bool] = None + examples: Optional[List["Example"]] = None + # Ref: OpenAPI 3.0.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schema-object + # Schema Object + discriminator: Optional[Discriminator] = None + xml: Optional[XML] = None + externalDocs: Optional[ExternalDocumentation] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents +# A JSON Schema MUST be an object or a boolean. +SchemaOrBool = Union[Schema, bool] + + +# https://swagger.io/specification/#example-object +class Example(BaseModel): + summary: Optional[str] = None + description: Optional[str] = None + value: Optional[Any] = None + externalValue: Optional[AnyUrl] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class ParameterInType(Enum): + query = "query" + header = "header" + path = "path" + cookie = "cookie" + + +# https://swagger.io/specification/#encoding-object +class Encoding(BaseModel): + contentType: Optional[str] = None + headers: Optional[Dict[str, Union["Header", Reference]]] = None + style: Optional[str] = None + explode: Optional[bool] = None + allowReserved: Optional[bool] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#media-type-object +class MediaType(BaseModel): + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") + examples: Optional[Dict[str, Union[Example, Reference]]] = None + encoding: Optional[Dict[str, Encoding]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#parameter-object +class ParameterBase(BaseModel): + description: Optional[str] = None + required: Optional[bool] = None + deprecated: Optional[bool] = None + # Serialization rules for simple scenarios + style: Optional[str] = None + explode: Optional[bool] = None + allowReserved: Optional[bool] = None + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") + examples: Optional[Dict[str, Union[Example, Reference]]] = None + # Serialization rules for more complex scenarios + content: Optional[Dict[str, MediaType]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class Parameter(ParameterBase): + name: str + in_: ParameterInType = Field(alias="in") + + +class Header(ParameterBase): + pass + + +# https://swagger.io/specification/#request-body-object +class RequestBody(BaseModel): + description: Optional[str] = None + content: Dict[str, MediaType] + required: Optional[bool] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#link-object +class Link(BaseModel): + operationRef: Optional[str] = None + operationId: Optional[str] = None + parameters: Optional[Dict[str, Union[Any, str]]] = None + requestBody: Optional[Union[Any, str]] = None + description: Optional[str] = None + server: Optional[Server] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#response-object +class Response(BaseModel): + description: str + headers: Optional[Dict[str, Union[Header, Reference]]] = None + content: Optional[Dict[str, MediaType]] = None + links: Optional[Dict[str, Union[Link, Reference]]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#tag-object +class Tag(BaseModel): + name: str + description: Optional[str] = None + externalDocs: Optional[ExternalDocumentation] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#operation-object +class Operation(BaseModel): + tags: Optional[List[str]] = None + summary: Optional[str] = None + description: Optional[str] = None + externalDocs: Optional[ExternalDocumentation] = None + operationId: Optional[str] = None + parameters: Optional[List[Union[Parameter, Reference]]] = None + requestBody: Optional[Union[RequestBody, Reference]] = None + # Using Any for Specification Extensions + responses: Optional[Dict[int, Union[Response, Any]]] = None + callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None + deprecated: Optional[bool] = None + security: Optional[List[Dict[str, List[str]]]] = None + servers: Optional[List[Server]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#path-item-object +class PathItem(BaseModel): + ref: Optional[str] = Field(default=None, alias="$ref") + summary: Optional[str] = None + description: Optional[str] = None + get: Optional[Operation] = None + put: Optional[Operation] = None + post: Optional[Operation] = None + delete: Optional[Operation] = None + options: Optional[Operation] = None + head: Optional[Operation] = None + patch: Optional[Operation] = None + trace: Optional[Operation] = None + servers: Optional[List[Server]] = None + parameters: Optional[List[Union[Parameter, Reference]]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#security-scheme-object +class SecuritySchemeType(Enum): + apiKey = "apiKey" + http = "http" + oauth2 = "oauth2" + openIdConnect = "openIdConnect" + + +class SecurityBase(BaseModel): + type_: SecuritySchemeType = Field(alias="type") + description: Optional[str] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow", "populate_by_name": True} + + else: + + class Config: + extra = "allow" + allow_population_by_field_name = True + + +class APIKeyIn(Enum): + query = "query" + header = "header" + cookie = "cookie" + + +class APIKey(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type") + in_: APIKeyIn = Field(alias="in") + name: str + + +class HTTPBase(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type") + scheme: str + + +class HTTPBearer(HTTPBase): + scheme: Literal["bearer"] = "bearer" + bearerFormat: Optional[str] = None + + +class OAuthFlow(BaseModel): + refreshUrl: Optional[str] = None + scopes: Dict[str, str] = {} + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class OAuthFlowImplicit(OAuthFlow): + authorizationUrl: str + + +class OAuthFlowPassword(OAuthFlow): + tokenUrl: str + + +class OAuthFlowClientCredentials(OAuthFlow): + tokenUrl: str + + +class OAuthFlowAuthorizationCode(OAuthFlow): + authorizationUrl: str + tokenUrl: str + + +class OAuthFlows(BaseModel): + implicit: Optional[OAuthFlowImplicit] = None + password: Optional[OAuthFlowPassword] = None + clientCredentials: Optional[OAuthFlowClientCredentials] = None + authorizationCode: Optional[OAuthFlowAuthorizationCode] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class OAuth2(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type") + flows: OAuthFlows + + +class OpenIdConnect(SecurityBase): + type_: SecuritySchemeType = Field( + default=SecuritySchemeType.openIdConnect, + alias="type", + ) + openIdConnectUrl: str + + +SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer] + + +# https://swagger.io/specification/#components-object +class Components(BaseModel): + schemas: Optional[Dict[str, Union[Schema, Reference]]] = None + responses: Optional[Dict[str, Union[Response, Reference]]] = None + parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None + examples: Optional[Dict[str, Union[Example, Reference]]] = None + requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None + headers: Optional[Dict[str, Union[Header, Reference]]] = None + securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None + links: Optional[Dict[str, Union[Link, Reference]]] = None + # Using Any for Specification Extensions + callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None + pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +# https://swagger.io/specification/#openapi-object +class OpenAPI(BaseModel): + openapi: str + info: Info + jsonSchemaDialect: Optional[str] = None + servers: Optional[List[Server]] = None + # Using Any for Specification Extensions + paths: Optional[Dict[str, Union[PathItem, Any]]] = None + webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None + components: Optional[Components] = None + security: Optional[List[Dict[str, List[str]]]] = None + tags: Optional[List[Tag]] = None + externalDocs: Optional[ExternalDocumentation] = None + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +model_rebuild(Schema) +model_rebuild(Operation) +model_rebuild(Encoding) diff --git a/aws_lambda_powertools/event_handler/openapi/params.py b/aws_lambda_powertools/event_handler/openapi/params.py new file mode 100644 index 00000000000..d5665a48d30 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/params.py @@ -0,0 +1,1105 @@ +import inspect +from enum import Enum +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union + +from pydantic import BaseConfig +from pydantic.fields import FieldInfo + +from aws_lambda_powertools.event_handler import Response +from aws_lambda_powertools.event_handler.openapi.compat import ( + ModelField, + Required, + Undefined, + UndefinedType, + copy_field_info, + field_annotation_is_scalar, + get_annotation_from_field_info, +) +from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2 +from aws_lambda_powertools.event_handler.openapi.types import CacheKey +from aws_lambda_powertools.shared.types import Annotated, Literal, get_args, get_origin + +""" +This turns the low-level function signature into typed, validated Pydantic models for consumption. +""" + + +class ParamTypes(Enum): + query = "query" + header = "header" + path = "path" + cookie = "cookie" + + +# MAINTENANCE: update when deprecating Pydantic v1, remove this alias +_Unset: Any = Undefined + + +class Dependant: + """ + A class used internally to represent a dependency between path operation decorators and the path operation function. + """ + + def __init__( + self, + *, + path_params: Optional[List[ModelField]] = None, + query_params: Optional[List[ModelField]] = None, + header_params: Optional[List[ModelField]] = None, + cookie_params: Optional[List[ModelField]] = None, + body_params: Optional[List[ModelField]] = None, + return_param: Optional[ModelField] = None, + response_extra_models: Optional[List[ModelField]] = None, + name: Optional[str] = None, + call: Optional[Callable[..., Any]] = None, + request_param_name: Optional[str] = None, + websocket_param_name: Optional[str] = None, + http_connection_param_name: Optional[str] = None, + response_param_name: Optional[str] = None, + background_tasks_param_name: Optional[str] = None, + path: Optional[str] = None, + ) -> None: + self.path_params = path_params or [] + self.query_params = query_params or [] + self.header_params = header_params or [] + self.cookie_params = cookie_params or [] + self.body_params = body_params or [] + self.return_param = return_param or None + self.response_extra_models = response_extra_models or [] + self.request_param_name = request_param_name + self.websocket_param_name = websocket_param_name + self.http_connection_param_name = http_connection_param_name + self.response_param_name = response_param_name + self.background_tasks_param_name = background_tasks_param_name + self.name = name + self.call = call + # Store the path to be able to re-generate a dependable from it in overrides + self.path = path + # Save the cache key at creation to optimize performance + self.cache_key: CacheKey = self.call + + +class Param(FieldInfo): + """ + A class used internally to represent a parameter in a path operation. + """ + + in_: ParamTypes + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # MAINTENANCE: validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + """ + Constructs a new Param. + + Parameters + ---------- + default: Any + The default value of the parameter + default_factory: Callable[[], Any], optional + Callable that will be called when a default value is needed for this field + annotation: Any, optional + The type annotation of the parameter + alias: str, optional + The public name of the field + alias_priority: int, optional + Priority of the alias. This affects whether an alias generator is used + validation_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for validation only + serialization_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for serialization only + title: str, optional + The title of the parameter + description: str, optional + The description of the parameter + gt: float, optional + Only applies to numbers, required the field to be "greater than" + ge: float, optional + Only applies to numbers, required the field to be "greater than or equal" + lt: float, optional + Only applies to numbers, required the field to be "less than" + le: float, optional + Only applies to numbers, required the field to be "less than or equal" + min_length: int, optional + Only applies to strings, required the field to have a minimum length + max_length: int, optional + Only applies to strings, required the field to have a maximum length + pattern: str, optional + Only applies to strings, requires the field match against a regular expression pattern string + discriminator: str, optional + Parameter field name for discriminating the type in a tagged union + strict: bool, optional + Enables Pydantic's strict mode for the field + multiple_of: float, optional + Only applies to numbers, requires the field to be a multiple of the given value + allow_inf_nan: bool, optional + Only applies to numbers, requires the field to allow infinity and NaN values + max_digits: int, optional + Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal. + decimal_places: int, optional + Only applies to Decimals, requires the field to have at most a number of decimal places + examples: List[Any], optional + A list of examples for the parameter + deprecated: bool, optional + If `True`, the parameter will be marked as deprecated + include_in_schema: bool, optional + If `False`, the parameter will be excluded from the generated OpenAPI schema + json_schema_extra: Dict[str, Any], optional + Extra values to include in the generated OpenAPI schema + """ + self.deprecated = deprecated + self.include_in_schema = include_in_schema + + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_V2: + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + "pattern": pattern, + }, + ) + else: + kwargs["regex"] = pattern + kwargs.update(**current_json_schema_extra) + + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class Path(Param): + """ + A class used internally to represent a path parameter in a path operation. + """ + + in_ = ParamTypes.path + + def __init__( + self, + default: Any = ..., + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # MAINTENANCE: validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + """ + Constructs a new Path param. + + Parameters + ---------- + default: Any + The default value of the parameter + default_factory: Callable[[], Any], optional + Callable that will be called when a default value is needed for this field + annotation: Any, optional + The type annotation of the parameter + alias: str, optional + The public name of the field + alias_priority: int, optional + Priority of the alias. This affects whether an alias generator is used + validation_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for validation only + serialization_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for serialization only + title: str, optional + The title of the parameter + description: str, optional + The description of the parameter + gt: float, optional + Only applies to numbers, required the field to be "greater than" + ge: float, optional + Only applies to numbers, required the field to be "greater than or equal" + lt: float, optional + Only applies to numbers, required the field to be "less than" + le: float, optional + Only applies to numbers, required the field to be "less than or equal" + min_length: int, optional + Only applies to strings, required the field to have a minimum length + max_length: int, optional + Only applies to strings, required the field to have a maximum length + pattern: str, optional + Only applies to strings, requires the field match against a regular expression pattern string + discriminator: str, optional + Parameter field name for discriminating the type in a tagged union + strict: bool, optional + Enables Pydantic's strict mode for the field + multiple_of: float, optional + Only applies to numbers, requires the field to be a multiple of the given value + allow_inf_nan: bool, optional + Only applies to numbers, requires the field to allow infinity and NaN values + max_digits: int, optional + Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal. + decimal_places: int, optional + Only applies to Decimals, requires the field to have at most a number of decimal places + examples: List[Any], optional + A list of examples for the parameter + deprecated: bool, optional + If `True`, the parameter will be marked as deprecated + include_in_schema: bool, optional + If `False`, the parameter will be excluded from the generated OpenAPI schema + json_schema_extra: Dict[str, Any], optional + Extra values to include in the generated OpenAPI schema + """ + if default is not ...: + raise AssertionError("Path parameters cannot have a default value") + + super(Path, self).__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + examples=examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Query(Param): + """ + A class used internally to represent a query parameter in a path operation. + """ + + in_ = ParamTypes.query + + def __init__( + self, + default: Any = _Unset, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + """ + Constructs a new Query param. + + Parameters + ---------- + default: Any + The default value of the parameter + default_factory: Callable[[], Any], optional + Callable that will be called when a default value is needed for this field + annotation: Any, optional + The type annotation of the parameter + alias: str, optional + The public name of the field + alias_priority: int, optional + Priority of the alias. This affects whether an alias generator is used + validation_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for validation only + serialization_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for serialization only + title: str, optional + The title of the parameter + description: str, optional + The description of the parameter + gt: float, optional + Only applies to numbers, required the field to be "greater than" + ge: float, optional + Only applies to numbers, required the field to be "greater than or equal" + lt: float, optional + Only applies to numbers, required the field to be "less than" + le: float, optional + Only applies to numbers, required the field to be "less than or equal" + min_length: int, optional + Only applies to strings, required the field to have a minimum length + max_length: int, optional + Only applies to strings, required the field to have a maximum length + pattern: str, optional + Only applies to strings, requires the field match against a regular expression pattern string + discriminator: str, optional + Parameter field name for discriminating the type in a tagged union + strict: bool, optional + Enables Pydantic's strict mode for the field + multiple_of: float, optional + Only applies to numbers, requires the field to be a multiple of the given value + allow_inf_nan: bool, optional + Only applies to numbers, requires the field to allow infinity and NaN values + max_digits: int, optional + Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal. + decimal_places: int, optional + Only applies to Decimals, requires the field to have at most a number of decimal places + examples: List[Any], optional + A list of examples for the parameter + deprecated: bool, optional + If `True`, the parameter will be marked as deprecated + include_in_schema: bool, optional + If `False`, the parameter will be excluded from the generated OpenAPI schema + json_schema_extra: Dict[str, Any], optional + Extra values to include in the generated OpenAPI schema + """ + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + examples=examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Header(Param): + """ + A class used internally to represent a header parameter in a path operation. + """ + + in_ = ParamTypes.header + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + convert_underscores: bool = True, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + """ + Constructs a new Query param. + + Parameters + ---------- + default: Any + The default value of the parameter + default_factory: Callable[[], Any], optional + Callable that will be called when a default value is needed for this field + annotation: Any, optional + The type annotation of the parameter + alias: str, optional + The public name of the field + alias_priority: int, optional + Priority of the alias. This affects whether an alias generator is used + validation_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for validation only + serialization_alias: str | AliasPath | AliasChoices | None, optional + Alias to be used for serialization only + convert_underscores: bool + If true convert "_" to "-" + See RFC: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-name-registry + title: str, optional + The title of the parameter + description: str, optional + The description of the parameter + gt: float, optional + Only applies to numbers, required the field to be "greater than" + ge: float, optional + Only applies to numbers, required the field to be "greater than or equal" + lt: float, optional + Only applies to numbers, required the field to be "less than" + le: float, optional + Only applies to numbers, required the field to be "less than or equal" + min_length: int, optional + Only applies to strings, required the field to have a minimum length + max_length: int, optional + Only applies to strings, required the field to have a maximum length + pattern: str, optional + Only applies to strings, requires the field match against a regular expression pattern string + discriminator: str, optional + Parameter field name for discriminating the type in a tagged union + strict: bool, optional + Enables Pydantic's strict mode for the field + multiple_of: float, optional + Only applies to numbers, requires the field to be a multiple of the given value + allow_inf_nan: bool, optional + Only applies to numbers, requires the field to allow infinity and NaN values + max_digits: int, optional + Only applies to Decimals, requires the field to have a maxmium number of digits within the decimal. + decimal_places: int, optional + Only applies to Decimals, requires the field to have at most a number of decimal places + examples: List[Any], optional + A list of examples for the parameter + deprecated: bool, optional + If `True`, the parameter will be marked as deprecated + include_in_schema: bool, optional + If `False`, the parameter will be excluded from the generated OpenAPI schema + json_schema_extra: Dict[str, Any], optional + Extra values to include in the generated OpenAPI schema + """ + self.convert_underscores = convert_underscores + self._alias = alias + + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=self._alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + examples=examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + @property + def alias(self): + return self._alias + + @alias.setter + def alias(self, value: Optional[str] = None): + if value is not None: + # Headers are case-insensitive according to RFC 7540 (HTTP/2), so we lower the parameter name + # This ensures that customers can access headers with any casing, as per the RFC guidelines. + # Reference: https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2 + self._alias = value.lower() + + +class Body(FieldInfo): + """ + A class used internally to represent a body parameter in a path operation. + """ + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + embed: bool = False, + media_type: str = "application/json", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + self.embed = embed + self.media_type = media_type + self.deprecated = deprecated + self.include_in_schema = include_in_schema + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_V2: + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + "pattern": pattern, + }, + ) + else: + kwargs["regex"] = pattern + kwargs.update(**current_json_schema_extra) + + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class _Form(Body): + """ + A class used internally to represent a form parameter in a path operation. + """ + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "application/x-www-form-urlencoded", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + embed=True, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + examples=examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class _File(_Form): + """ + A class used internally to represent a file parameter in a path operation. + """ + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "multipart/form-data", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # MAINTENANCE: update when deprecating Pydantic v1, import these types + # str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + deprecated: Optional[bool] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + examples=examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def get_flat_dependant( + dependant: Dependant, + visited: Optional[List[CacheKey]] = None, +) -> Dependant: + """ + Flatten a recursive Dependant model structure. + + This function recursively concatenates the parameter fields of a Dependant model and its dependencies into a flat + Dependant structure. This is useful for scenarios like parameter validation where the nested structure is not + relevant. + + Parameters + ---------- + dependant: Dependant + The dependant model to flatten + skip_repeats: bool + If True, child Dependents already visited will be skipped to avoid duplicates + visited: List[CacheKey], optional + Keeps track of visited Dependents to avoid infinite recursion. Defaults to empty list. + + Returns + ------- + Dependant + The flattened Dependant model + """ + if visited is None: + visited = [] + visited.append(dependant.cache_key) + + return Dependant( + path_params=dependant.path_params.copy(), + query_params=dependant.query_params.copy(), + header_params=dependant.header_params.copy(), + cookie_params=dependant.cookie_params.copy(), + body_params=dependant.body_params.copy(), + path=dependant.path, + ) + + +def analyze_param( + *, + param_name: str, + annotation: Any, + value: Any, + is_path_param: bool, + is_response_param: bool, +) -> Optional[ModelField]: + """ + Analyze a parameter annotation and value to determine the type and default value of the parameter. + + Parameters + ---------- + param_name: str + The name of the parameter + annotation + The annotation of the parameter + value + The value of the parameter + is_path_param + Whether the parameter is a path parameter + is_response_param + Whether the parameter is the return annotation + + Returns + ------- + Optional[ModelField] + The type annotation and the Pydantic field representing the parameter + """ + field_info, type_annotation = get_field_info_and_type_annotation(annotation, value, is_path_param) + + # If the value is a FieldInfo, we use it as the FieldInfo for the parameter + if isinstance(value, FieldInfo): + if field_info is not None: + raise AssertionError("Cannot use a FieldInfo as a parameter annotation and pass a FieldInfo as a value") + field_info = value + + if PYDANTIC_V2: + field_info.annotation = type_annotation # type: ignore[attr-defined,unused-ignore] + + # If we didn't determine the FieldInfo yet, we create a default one + if field_info is None: + default_value = value if value is not inspect.Signature.empty else Required + + # Check if the parameter is part of the path. Otherwise, defaults to query. + if is_path_param: + field_info = Path(annotation=type_annotation) + elif not field_annotation_is_scalar(annotation=type_annotation): + field_info = Body(annotation=type_annotation, default=default_value) + else: + field_info = Query(annotation=type_annotation, default=default_value) + + # When we have a response field, we need to set the default value to Required + if is_response_param: + field_info.default = Required + + field = _create_model_field(field_info, type_annotation, param_name, is_path_param) + return field + + +def get_field_info_and_type_annotation(annotation, value, is_path_param: bool) -> Tuple[Optional[FieldInfo], Any]: + """ + Get the FieldInfo and type annotation from an annotation and value. + """ + field_info: Optional[FieldInfo] = None + type_annotation: Any = Any + + if annotation is not inspect.Signature.empty: + # If the annotation is an Annotated type, we need to extract the type annotation and the FieldInfo + if get_origin(annotation) is Annotated: + field_info, type_annotation = get_field_info_annotated_type(annotation, value, is_path_param) + # If the annotation is a Response type, we recursively call this function with the inner type + elif get_origin(annotation) is Response: + field_info, type_annotation = get_field_info_response_type(annotation, value) + # If the annotation is not an Annotated type, we use it as the type annotation + else: + type_annotation = annotation + + return field_info, type_annotation + + +def get_field_info_response_type(annotation, value) -> Tuple[Optional[FieldInfo], Any]: + # Example: get_args(Response[inner_type]) == (inner_type,) # noqa: ERA001 + (inner_type,) = get_args(annotation) + + # Recursively resolve the inner type + return get_field_info_and_type_annotation(inner_type, value, False) + + +def get_field_info_annotated_type(annotation, value, is_path_param: bool) -> Tuple[Optional[FieldInfo], Any]: + """ + Get the FieldInfo and type annotation from an Annotated type. + """ + field_info: Optional[FieldInfo] = None + annotated_args = get_args(annotation) + type_annotation = annotated_args[0] + powertools_annotations = [arg for arg in annotated_args[1:] if isinstance(arg, FieldInfo)] + + if len(powertools_annotations) > 1: + raise AssertionError("Only one FieldInfo can be used per parameter") + + powertools_annotation = next(iter(powertools_annotations), None) + + if isinstance(powertools_annotation, FieldInfo): + # Copy `field_info` because we mutate `field_info.default` later + field_info = copy_field_info( + field_info=powertools_annotation, + annotation=annotation, + ) + if field_info.default not in [Undefined, Required]: + raise AssertionError("FieldInfo needs to have a default value of Undefined or Required") + + if value is not inspect.Signature.empty: + if is_path_param: + raise AssertionError("Cannot use a FieldInfo as a path parameter and pass a value") + field_info.default = value + else: + field_info.default = Required + + return field_info, type_annotation + + +def create_response_field( + name: str, + type_: Type[Any], + default: Optional[Any] = Undefined, + required: Union[bool, UndefinedType] = Undefined, + model_config: Type[BaseConfig] = BaseConfig, + field_info: Optional[FieldInfo] = None, + alias: Optional[str] = None, + mode: Literal["validation", "serialization"] = "validation", +) -> ModelField: + """ + Create a new response field. Raises if type_ is invalid. + """ + if PYDANTIC_V2: + field_info = field_info or FieldInfo( + annotation=type_, + default=default, + alias=alias, + ) + else: + field_info = field_info or FieldInfo() + kwargs = {"name": name, "field_info": field_info} + + if PYDANTIC_V2: + kwargs.update({"mode": mode}) + else: + kwargs.update( + { + "type_": type_, + "class_validators": {}, + "default": default, + "required": required, + "model_config": model_config, + "alias": alias, + }, + ) + return ModelField(**kwargs) # type: ignore[arg-type] + + +def _create_model_field( + field_info: Optional[FieldInfo], + type_annotation: Any, + param_name: str, + is_path_param: bool, +) -> Optional[ModelField]: + """ + Create a new ModelField from a FieldInfo and type annotation. + """ + if field_info is None: + return None + + if is_path_param: + if not isinstance(field_info, Path): + raise AssertionError("Path parameters must be of type Path") + elif isinstance(field_info, Param) and getattr(field_info, "in_", None) is None: + field_info.in_ = ParamTypes.query + + # If the field_info is a Param, we use the `in_` attribute to determine the type annotation + use_annotation = get_annotation_from_field_info(type_annotation, field_info, param_name) + + # If the field doesn't have a defined alias, we use the param name + if not field_info.alias and getattr(field_info, "convert_underscores", None): + alias = param_name.replace("_", "-") + else: + alias = field_info.alias or param_name + field_info.alias = alias + + return create_response_field( + name=param_name, + type_=use_annotation, + default=field_info.default, + alias=alias, + required=field_info.default in (Required, Undefined), + field_info=field_info, + ) diff --git a/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py b/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py new file mode 100644 index 00000000000..225f7e88096 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/pydantic_loader.py @@ -0,0 +1,6 @@ +try: + from pydantic.version import VERSION as PYDANTIC_VERSION + + PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") +except ImportError: + PYDANTIC_V2 = False # pragma: no cover # false positive; dropping in v3 diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py new file mode 100644 index 00000000000..bc6eda8abb3 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/__init__.py @@ -0,0 +1,13 @@ +from aws_lambda_powertools.event_handler.openapi.swagger_ui.html import ( + generate_swagger_html, +) +from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import ( + OAuth2Config, + generate_oauth2_redirect_html, +) + +__all__ = [ + "generate_swagger_html", + "generate_oauth2_redirect_html", + "OAuth2Config", +] diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py new file mode 100644 index 00000000000..85e041f2f56 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py @@ -0,0 +1,101 @@ +from typing import Optional + +from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import OAuth2Config + + +def generate_swagger_html( + spec: str, + path: str, + swagger_js: str, + swagger_css: str, + swagger_base_url: str, + oauth2_config: Optional[OAuth2Config], + persist_authorization: bool = False, +) -> str: + """ + Generate Swagger UI HTML page + + Parameters + ---------- + spec: str + The OpenAPI spec + path: str + The path to the Swagger documentation + swagger_js: str + Swagger UI JavaScript source code or URL + swagger_css: str + Swagger UI CSS source code or URL + swagger_base_url: str + The base URL for Swagger UI + oauth2_config: OAuth2Config, optional + The OAuth2 configuration. + persist_authorization: bool, optional + Whether to persist authorization data on browser close/refresh. + """ + + # If Swagger base URL is present, generate HTML content with linked CSS and JavaScript files + # If no Swagger base URL is provided, include CSS and JavaScript directly in the HTML + if swagger_base_url: + swagger_css_content = f"<link rel='stylesheet' type='text/css' href='{swagger_css}'>" + swagger_js_content = f"<script src='{swagger_js}'></script>" + else: + swagger_css_content = f"<style>{swagger_css}</style>" + swagger_js_content = f"<script>{swagger_js}</script>" + + # Prepare oauth2 config + oauth2_content = ( + f"ui.initOAuth({oauth2_config.json(exclude_none=True, exclude_unset=True)});" if oauth2_config else "" + ) + + return f""" +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Swagger UI + + {swagger_css_content} + + + +
+ Loading... +
+ + +{swagger_js_content} + + + + """.strip() diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py new file mode 100644 index 00000000000..29250ae0056 --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/oauth2.py @@ -0,0 +1,158 @@ +# ruff: noqa: E501 +import warnings +from typing import Dict, Optional, Sequence + +from pydantic import BaseModel, Field, validator + +from aws_lambda_powertools.event_handler.openapi.pydantic_loader import PYDANTIC_V2 +from aws_lambda_powertools.shared.functions import powertools_dev_is_set + + +# Based on https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/ +class OAuth2Config(BaseModel): + """ + OAuth2 configuration for Swagger UI + """ + + # The client ID for the OAuth2 application + clientId: Optional[str] = Field(alias="client_id", default=None) + + # The client secret for the OAuth2 application. This is sensitive information and requires the explicit presence + # of the POWERTOOLS_DEV environment variable. + clientSecret: Optional[str] = Field(alias="client_secret", default=None) + + # The realm in which the OAuth2 application is registered. Optional. + realm: Optional[str] = Field(default=None) + + # The name of the OAuth2 application + appName: str = Field(alias="app_name") + + # The scopes that the OAuth2 application requires. Defaults to an empty list. + scopes: Sequence[str] = Field(default=[]) + + # Additional query string parameters to be included in the OAuth2 request. Defaults to an empty dictionary. + additionalQueryStringParams: Dict[str, str] = Field(alias="additional_query_string_params", default={}) + + # Whether to use basic authentication with the access code grant type. Defaults to False. + useBasicAuthenticationWithAccessCodeGrant: bool = Field( + alias="use_basic_authentication_with_access_code_grant", + default=False, + ) + + # Whether to use PKCE with the authorization code grant type. Defaults to False. + usePkceWithAuthorizationCodeGrant: bool = Field(alias="use_pkce_with_authorization_code_grant", default=False) + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + else: + + class Config: + extra = "allow" + allow_population_by_field_name = True + + @validator("clientSecret", always=True) + def client_secret_only_on_dev(cls, v: Optional[str]) -> Optional[str]: + if not v: + return None + + if not powertools_dev_is_set(): + raise ValueError( + "cannot use client_secret without POWERTOOLS_DEV mode. See " + "https://docs.powertools.aws.dev/lambda/python/latest/#optimizing-for-non-production-environments", + ) + else: + warnings.warn( + "OAuth2Config is using client_secret and POWERTOOLS_DEV is set. This reveals sensitive information. " + "DO NOT USE THIS OUTSIDE LOCAL DEVELOPMENT", + stacklevel=2, + ) + return v + + +def generate_oauth2_redirect_html() -> str: + """ + Generates the HTML content for the OAuth2 redirect page. + + Source: https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html + """ + return """ + + + + Swagger UI: OAuth2 Redirect + + + + + + """.strip() diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui-bundle.min.js b/aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui-bundle.min.js new file mode 100644 index 00000000000..bc1d328090c --- /dev/null +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/swagger-ui-bundle.min.js @@ -0,0 +1,12 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,()=>{var s,i,n={69119:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BLANK_URL=t.relativeFirstCharacters=t.urlSchemeRegex=t.ctrlCharactersRegex=t.htmlCtrlEntityRegex=t.htmlEntitiesRegex=t.invalidProtocolRegex=void 0,t.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im,t.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g,t.htmlCtrlEntityRegex=/&(newline|tab);/gi,t.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,t.urlSchemeRegex=/^.+(:|:)/gim,t.relativeFirstCharacters=[".","/"],t.BLANK_URL="about:blank"},16750:(e,t,n)=>{"use strict";t.J=void 0;var r=n(69119);t.J=function(e){if(!e)return r.BLANK_URL;e=e.replace(r.ctrlCharactersRegex,"").replace(r.htmlEntitiesRegex,function(e,t){return String.fromCharCode(t)}).replace(r.htmlCtrlEntityRegex,"").replace(r.ctrlCharactersRegex,"").trim();if(!e)return r.BLANK_URL;if(-1{"use strict";t.byteLength=function(e){var e=u(e),t=e[0],e=e[1];return 3*(t+e)/4-e},t.toByteArray=function(e){for(var t,n=u(e),r=n[0],n=n[1],o=new c(3*(r+n)/4-n),s=0,i=0>16&255,o[s++]=t>>8&255,o[s++]=255&t;return 2===n&&(t=l[e.charCodeAt(a)]<<2|l[e.charCodeAt(a+1)]>>4,o[s++]=255&t),1===n&&(t=l[e.charCodeAt(a)]<<10|l[e.charCodeAt(a+1)]<<4|l[e.charCodeAt(a+2)]>>2,o[s++]=t>>8&255,o[s++]=255&t),o},t.fromByteArray=function(e){for(var t,n=e.length,r=n%3,o=[],s=0,i=n-r;s>18&63]+a[r>>12&63]+a[r>>6&63]+a[63&r]);return o.join("")}(e,s,i>2]+a[t<<4&63]+"==")):2==r&&(t=(e[n-2]<<8)+e[n-1],o.push(a[t>>10]+a[t>>4&63]+a[t<<2&63]+"=")),o.join("")};for(var a=[],l=[],c="undefined"!=typeof Uint8Array?Uint8Array:Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=0;r<64;++r)a[r]=n[r],l[n.charCodeAt(r)]=r;function u(e){var t=e.length;if(0{"use strict";const g=e(67526),s=e(251),t="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null,r=(n.Buffer=u,n.SlowBuffer=function(e){return u.alloc(+(e=+e!=e?0:e))},n.INSPECT_MAX_BYTES=50,2147483647);function c(e){if(e>r)throw new RangeError('The value "'+e+'" is invalid for option "size"');e=new Uint8Array(e);return Object.setPrototypeOf(e,u.prototype),e}function u(e,t,n){if("number"!=typeof e)return o(e,t,n);if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return a(e)}function o(t,n,e){if("string"==typeof t){var r=t,o=n;if(!u.isEncoding(o="string"==typeof o&&""!==o?o:"utf8"))throw new TypeError("Unknown encoding: "+o);var s=0|f(r,o);let e=c(s);return r=e.write(r,o),e=r!==s?e.slice(0,r):e}if(ArrayBuffer.isView(t))return T(o=t,Uint8Array)?h((s=new Uint8Array(o)).buffer,s.byteOffset,s.byteLength):p(o);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(T(t,ArrayBuffer)||t&&T(t.buffer,ArrayBuffer))return h(t,n,e);if("undefined"!=typeof SharedArrayBuffer&&(T(t,SharedArrayBuffer)||t&&T(t.buffer,SharedArrayBuffer)))return h(t,n,e);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');r=t.valueOf&&t.valueOf();if(null!=r&&r!==t)return u.from(r,n,e);var i,a,l=u.isBuffer(i=t)?(0!==(a=c(l=0|d(i.length))).length&&i.copy(a,0,0,l),a):void 0!==i.length?"number"!=typeof i.length||R(i.length)?c(0):p(i):"Buffer"===i.type&&Array.isArray(i.data)?p(i.data):void 0;if(l)return l;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return u.from(t[Symbol.toPrimitive]("string"),n,e);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function i(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function a(e){return i(e),c(e<0?0:0|d(e))}function p(t){const n=t.length<0?0:0|d(t.length),r=c(n);for(let e=0;e=r)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+r.toString(16)+" bytes");return 0|e}function f(e,t){if(u.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||T(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var n=e.length,r=2>>1;case"base64":return $(e).length;default:if(o)return r?-1:N(e).length;t=(""+t).toLowerCase(),o=!0}}function F(e,n,r){let t=!1;if((n=void 0===n||n<0?0:n)>this.length)return"";if((r=void 0===r||r>this.length?this.length:r)<=0)return"";if((r>>>=0)<=(n>>>=0))return"";for(e=e||"utf8";;)switch(e){case"hex":{var o=this;var s=n;var i=r;var a=o.length;(!s||s<0)&&(s=0),(!i||i<0||a=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=u.from(t,r)),u.isBuffer(t))return 0===t.length?-1:y(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?(o?Uint8Array.prototype.indexOf:Uint8Array.prototype.lastIndexOf).call(e,t,n):y(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function y(n,r,t,e,o){let s,i=1,a=n.length,l=r.length;if(void 0!==e&&("ucs2"===(e=String(e).toLowerCase())||"ucs-2"===e||"utf16le"===e||"utf-16le"===e)){if(n.length<2||r.length<2)return-1;i=2,a/=2,l/=2,t/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){let e=-1;for(s=t;sa&&(t=a-l),s=t;0<=s;s--){let t=!0;for(let e=0;e>>10&1023|55296),o=56320|1023&o),n.push(o),l+=s}{var r=n,o=r.length;if(o<=b)return String.fromCharCode.apply(String,r);let e="",t=0;for(;tr.length?(e=u.isBuffer(e)?e:u.from(e)).copy(r,o):Uint8Array.prototype.set.call(r,e,o);else{if(!u.isBuffer(e))throw new TypeError('"list" argument must be an Array of Buffers');e.copy(r,o)}o+=e.length}return r},u.byteLength=f,u.prototype._isBuffer=!0,u.prototype.swap16=function(){var t=this.length;if(t%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let e=0;et&&(e+=" ... "),""},t&&(u.prototype[t]=u.prototype.inspect),u.prototype.compare=function(e,t,n,r,o){if(T(e,Uint8Array)&&(e=u.from(e,e.offset,e.byteLength)),!u.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),(t=void 0===t?0:t)<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(o<=r&&n<=t)return 0;if(o<=r)return-1;if(n<=t)return 1;if(this===e)return 0;let s=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0);var a=Math.min(s,i),l=this.slice(r,o),c=e.slice(t,n);for(let e=0;e>>=0,isFinite(r)?(r>>>=0,void 0===e&&(e="utf8")):(e=r,r=void 0)}var o,s,i,a,l,c,u=this.length-n;if((void 0===r||uthis.length)throw new RangeError("Attempt to write outside buffer bounds");e=e||"utf8";let p=!1;for(;;)switch(e){case"hex":{var h=this;var d=t;var f=n;var m=r;f=Number(f)||0;var g=h.length-f,g=((!m||(m=Number(m))>g)&&(m=g),d.length);let e;for(g/2>8,s.push(r%256),s.push(o);return s}(t,(o=this).length-s),o,s,i);default:if(p)throw new TypeError("Unknown encoding: "+e);e=(""+e).toLowerCase(),p=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const b=4096;function w(e,t,n){if(e%1!=0||e<0)throw new RangeError("offset is not uint");if(ne.length)throw new RangeError("Index out of range")}function S(e,t,n,r,o){L(t,r,o,e,n,7);r=Number(t&BigInt(4294967295)),e[n++]=r,e[n++]=r>>=8,e[n++]=r>>=8,e[n++]=r>>=8,o=Number(t>>BigInt(32)&BigInt(4294967295));return e[n++]=o,e[n++]=o>>=8,e[n++]=o>>=8,e[n++]=o>>=8,n}function x(e,t,n,r,o){L(t,r,o,e,n,7);r=Number(t&BigInt(4294967295)),e[n+7]=r,e[n+6]=r>>=8,e[n+5]=r>>=8,e[n+4]=r>>=8,o=Number(t>>BigInt(32)&BigInt(4294967295));return e[n+3]=o,e[n+2]=o>>=8,e[n+1]=o>>=8,e[n]=o>>=8,n+8}function _(e,t,n,r){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function k(e,t,n,r,o){return t=+t,n>>>=0,o||_(e,0,n,4),s.write(e,t,n,r,23,4),n+4}function A(e,t,n,r,o){return t=+t,n>>>=0,o||_(e,0,n,8),s.write(e,t,n,r,52,8),n+8}u.prototype.slice=function(e,t){var n=this.length,n=((e=~~e)<0?(e+=n)<0&&(e=0):n>>=0,t>>>=0,n||w(e,t,this.length);let r=this[e],o=1,s=0;for(;++s>>=0,t>>>=0,n||w(e,t,this.length);let r=this[e+--t],o=1;for(;0>>=0,t||w(e,1,this.length),this[e]},u.prototype.readUint16LE=u.prototype.readUInt16LE=function(e,t){return e>>>=0,t||w(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUint16BE=u.prototype.readUInt16BE=function(e,t){return e>>>=0,t||w(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUint32LE=u.prototype.readUInt32LE=function(e,t){return e>>>=0,t||w(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUint32BE=u.prototype.readUInt32BE=function(e,t){return e>>>=0,t||w(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readBigUInt64LE=M(function(e){j(e>>>=0,"offset");var t=this[e],n=this[e+7],t=(void 0!==t&&void 0!==n||P(e,this.length-8),t+256*this[++e]+65536*this[++e]+this[++e]*2**24),e=this[++e]+256*this[++e]+65536*this[++e]+n*2**24;return BigInt(t)+(BigInt(e)<>>=0,"offset");var t=this[e],n=this[e+7],t=(void 0!==t&&void 0!==n||P(e,this.length-8),t*2**24+65536*this[++e]+256*this[++e]+this[++e]),e=this[++e]*2**24+65536*this[++e]+256*this[++e]+n;return(BigInt(t)<>>=0,t>>>=0,n||w(e,t,this.length);let r=this[e],o=1,s=0;for(;++s=o&&(r-=Math.pow(2,8*t)),r},u.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||w(e,t,this.length);let r=t,o=1,s=this[e+--r];for(;0=o&&(s-=Math.pow(2,8*t)),s},u.prototype.readInt8=function(e,t){return e>>>=0,t||w(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){e>>>=0,t||w(e,2,this.length);t=this[e]|this[e+1]<<8;return 32768&t?4294901760|t:t},u.prototype.readInt16BE=function(e,t){e>>>=0,t||w(e,2,this.length);t=this[e+1]|this[e]<<8;return 32768&t?4294901760|t:t},u.prototype.readInt32LE=function(e,t){return e>>>=0,t||w(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return e>>>=0,t||w(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readBigInt64LE=M(function(e){j(e>>>=0,"offset");var t=this[e],n=this[e+7],n=(void 0!==t&&void 0!==n||P(e,this.length-8),this[e+4]+256*this[e+5]+65536*this[e+6]+(n<<24));return(BigInt(n)<>>=0,"offset");var t=this[e],n=this[e+7],t=(void 0!==t&&void 0!==n||P(e,this.length-8),(t<<24)+65536*this[++e]+256*this[++e]+this[++e]);return(BigInt(t)<>>=0,t||w(e,4,this.length),s.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return e>>>=0,t||w(e,4,this.length),s.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return e>>>=0,t||w(e,8,this.length),s.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return e>>>=0,t||w(e,8,this.length),s.read(this,e,!1,52,8)},u.prototype.writeUintLE=u.prototype.writeUIntLE=function(e,t,n,r){e=+e,t>>>=0,n>>>=0,r||E(this,e,t,n,Math.pow(2,8*n)-1,0);let o=1,s=0;for(this[t]=255&e;++s>>=0,n>>>=0,r||E(this,e,t,n,Math.pow(2,8*n)-1,0);let o=n-1,s=1;for(this[t+o]=255&e;0<=--o&&(s*=256);)this[t+o]=e/s&255;return t+n},u.prototype.writeUint8=u.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,1,255,0),this[t]=255&e,t+1},u.prototype.writeUint16LE=u.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},u.prototype.writeUint16BE=u.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},u.prototype.writeUint32LE=u.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},u.prototype.writeUint32BE=u.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},u.prototype.writeBigUInt64LE=M(function(e,t=0){return S(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))}),u.prototype.writeBigUInt64BE=M(function(e,t=0){return x(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))}),u.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);E(this,e,t,n,r-1,-r)}let o=0,s=1,i=0;for(this[t]=255&e;++o>0)-i&255;return t+n},u.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);E(this,e,t,n,r-1,-r)}let o=n-1,s=1,i=0;for(this[t+o]=255&e;0<=--o&&(s*=256);)e<0&&0===i&&0!==this[t+o+1]&&(i=1),this[t+o]=(e/s>>0)-i&255;return t+n},u.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,1,127,-128),this[t]=255&(e=e<0?255+e+1:e),t+1},u.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},u.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},u.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},u.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||E(this,e,t,4,2147483647,-2147483648),this[t]=(e=e<0?4294967295+e+1:e)>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},u.prototype.writeBigInt64LE=M(function(e,t=0){return S(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))}),u.prototype.writeBigInt64BE=M(function(e,t=0){return x(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))}),u.prototype.writeFloatLE=function(e,t,n){return k(this,e,t,!0,n)},u.prototype.writeFloatBE=function(e,t,n){return k(this,e,t,!1,n)},u.prototype.writeDoubleLE=function(e,t,n){return A(this,e,t,!0,n)},u.prototype.writeDoubleBE=function(e,t,n){return A(this,e,t,!1,n)},u.prototype.copy=function(e,t,n,r){if(!u.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n=n||0,r||0===r||(r=this.length),t>=e.length&&(t=e.length),(r=0=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length);var o=(r=e.length-t>>=0,n=void 0===n?this.length:n>>>0,"number"==typeof(e=e||0))for(o=t;o=4+r;n-=3)t="_"+e.slice(n-3,n)+t;return""+e.slice(0,n)+t}function L(e,t,n,r,o,s){if(n= 0${r} and < 2${r} ** `+8*(s+1)+r:`>= -(2${r} ** ${8*(s+1)-1}${r}) and < 2 ** `+(8*(s+1)-1)+r:`>= ${t}${r} and <= `+n+r;throw new O.ERR_OUT_OF_RANGE("value",t,e)}n=r,t=s,j(e=o,"offset"),void 0!==n[e]&&void 0!==n[e+t]||P(e,n.length-(t+1))}function j(e,t){if("number"!=typeof e)throw new O.ERR_INVALID_ARG_TYPE(t,"number",e)}function P(e,t,n){if(Math.floor(e)!==e)throw j(e,n),new O.ERR_OUT_OF_RANGE(n||"offset","an integer",e);if(t<0)throw new O.ERR_BUFFER_OUT_OF_BOUNDS;throw new O.ERR_OUT_OF_RANGE(n||"offset",`>= ${n?1:0} and <= `+t,e)}C("ERR_BUFFER_OUT_OF_BOUNDS",function(e){return e?e+" is outside of buffer bounds":"Attempt to access memory outside buffer bounds"},RangeError),C("ERR_INVALID_ARG_TYPE",function(e,t){return`The "${e}" argument must be of type number. Received type `+typeof t},TypeError),C("ERR_OUT_OF_RANGE",function(e,t,n){let r=`The value of "${e}" is out of range.`,o=n;return Number.isInteger(n)&&Math.abs(n)>2**32?o=B(String(n)):"bigint"==typeof n&&(o=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(o=B(o)),o+="n"),r+=` It must be ${t}. Received `+o},RangeError);const q=/[^+/0-9A-Za-z-_]/g;function N(t,n){let r;n=n||1/0;var o=t.length;let s=null;const i=[];for(let e=0;e>6|192,63&r|128)}else if(r<65536){if((n-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((n-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function $(e){return g.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(q,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function I(e,t,n,r){let o;for(o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function T(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function R(e){return e!=e}const z=function(){const n="0123456789abcdef",r=new Array(256);for(let t=0;t<16;++t){var o=16*t;for(let e=0;e<16;++e)r[o+e]=n[t]+n[e]}return r}();function M(e){return"undefined"==typeof BigInt?U:e}function U(){throw new Error("BigInt not supported")}},38075:(e,t,n)=>{"use strict";var r=n(70453),o=n(10487),s=o(r("String.prototype.indexOf"));e.exports=function(e,t){t=r(e,!!t);return"function"==typeof t&&-1{"use strict";var r=n(66743),o=n(70453),s=n(96897),i=o("%TypeError%"),a=o("%Function.prototype.apply%"),l=o("%Function.prototype.call%"),c=o("%Reflect.apply%",!0)||r.call(l,a),n=o("%Object.defineProperty%",!0),u=o("%Math.max%");if(n)try{n({},"a",{value:1})}catch(e){n=null}e.exports=function(e){if("function"!=typeof e)throw new i("a function is required");var t=c(r,l,arguments);return s(t,1+u(0,e.length-(arguments.length-1)),!0)};function p(){return c(r,a,arguments)}n?n(e.exports,"apply",{value:p}):e.exports.apply=p},17965:(e,t,n)=>{"use strict";var p=n(16426),h={"text/plain":"Text","text/html":"Url",default:"Text"};e.exports=function(n,r){var t,e,o,s,i,a,l,c=!1,u=(r=r||{}).debug||!1;try{if(e=p(),o=document.createRange(),s=document.getSelection(),(i=document.createElement("span")).textContent=n,i.ariaHidden="true",i.style.all="unset",i.style.position="fixed",i.style.top=0,i.style.clip="rect(0, 0, 0, 0)",i.style.whiteSpace="pre",i.style.webkitUserSelect="text",i.style.MozUserSelect="text",i.style.msUserSelect="text",i.style.userSelect="text",i.addEventListener("copy",function(e){var t;e.stopPropagation(),r.format&&(e.preventDefault(),void 0===e.clipboardData?(u&&console.warn("unable to use e.clipboardData"),u&&console.warn("trying IE specific stuff"),window.clipboardData.clearData(),t=h[r.format]||h.default,window.clipboardData.setData(t,n)):(e.clipboardData.clearData(),e.clipboardData.setData(r.format,n))),r.onCopy&&(e.preventDefault(),r.onCopy(e.clipboardData))}),document.body.appendChild(i),o.selectNodeContents(i),s.addRange(o),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");c=!0}catch(e){u&&console.error("unable to copy using execCommand: ",e),u&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(r.format||"text",n),r.onCopy&&r.onCopy(window.clipboardData),c=!0}catch(e){u&&console.error("unable to copy using clipboardData: ",e),u&&console.error("falling back to prompt"),a="message"in r?r.message:"Copy to clipboard: #{key}, Enter",l=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C",t=a.replace(/#{\s*key\s*}/g,l),window.prompt(t,n)}}finally{s&&("function"==typeof s.removeRange?s.removeRange(o):s.removeAllRanges()),i&&document.body.removeChild(i),e()}return c}},2205:function(e,t,n){n=void 0!==n.g?n.g:this;e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;function t(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,n=String(e),r=n.length,o=-1,s="",i=n.charCodeAt(0);++o{"use strict";var r=n(48287).Buffer;function s(e){return e instanceof r||e instanceof Date||e instanceof RegExp}function i(e){var t;if(e instanceof r)return t=r.alloc?r.alloc(e.length):new r(e.length),e.copy(t),t;if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function a(e,t){return"__proto__"===t?void 0:e[t]}var l=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var n,r,o=arguments[0];return Array.prototype.slice.call(arguments,1).forEach(function(t){"object"!=typeof t||null===t||Array.isArray(t)||Object.keys(t).forEach(function(e){return r=a(o,e),(n=a(t,e))===o?void 0:"object"!=typeof n||null===n?void(o[e]=n):Array.isArray(n)?void(o[e]=function n(e){var r=[];return e.forEach(function(e,t){"object"==typeof e&&null!==e?Array.isArray(e)?r[t]=n(e):s(e)?r[t]=i(e):r[t]=l({},e):r[t]=e}),r}(n)):s(n)?void(o[e]=i(n)):"object"!=typeof r||null===r||Array.isArray(r)?void(o[e]=l({},n)):void(o[e]=l(r,n))})}),o}},14744:e=>{"use strict";function o(e){return!(!(t=e)||"object"!=typeof t||(t=e,"[object RegExp]"===(e=Object.prototype.toString.call(t))||"[object Date]"===e||t.$$typeof===n));var t}var n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function a(e,t){return!1!==t.clone&&t.isMergeableObject(e)?c(Array.isArray(e)?[]:{},e,t):e}function s(e,t,n){return e.concat(t).map(function(e){return a(e,n)})}function t(e){return Object.keys(e).concat((t=e,Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(t).filter(function(e){return Object.propertyIsEnumerable.call(t,e)}):[]));var t}function l(e,t){try{return t in e}catch(e){return!1}}function i(r,o,s){var i={};return s.isMergeableObject(r)&&t(r).forEach(function(e){i[e]=a(r[e],s)}),t(o).forEach(function(e){var t,n;(!l(t=r,n=e)||Object.hasOwnProperty.call(t,n)&&Object.propertyIsEnumerable.call(t,n))&&(l(r,e)&&s.isMergeableObject(o[e])?i[e]=function(e,t){if(!t.customMerge)return c;t=t.customMerge(e);return"function"==typeof t?t:c}(e,s)(r[e],o[e],s):i[e]=a(o[e],s))}),i}function c(e,t,n){(n=n||{}).arrayMerge=n.arrayMerge||s,n.isMergeableObject=n.isMergeableObject||o,n.cloneUnlessOtherwiseSpecified=a;var r=Array.isArray(t);return r===Array.isArray(e)?r?n.arrayMerge(e,t,n):i(e,t,n):a(t,n)}c.all=function(e,n){if(Array.isArray(e))return e.reduce(function(e,t){return c(e,t,n)},{});throw new Error("first argument should be an array")},e.exports=c},30041:(e,t,n)=>{"use strict";var r=n(30592)(),o=n(70453),l=r&&o("%Object.defineProperty%",!0);if(l)try{l({},"a",{value:1})}catch(e){l=!1}var c=o("%SyntaxError%"),u=o("%TypeError%"),p=n(75795);e.exports=function(e,t,n){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new u("`obj` must be an object or a function`");if("string"!=typeof t&&"symbol"!=typeof t)throw new u("`property` must be a string or a symbol`");if(3/gm),p=e(/\${[\w\W]*}/gm),h=e(/^data-[\-\w.\u00B7-\uFFFF]/),d=e(/^aria-[\-\w]+$/),ht=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),f=e(/^(?:\w+script|data):/i),m=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),dt=e(/^html$/i);var ft=Object.freeze({__proto__:null,MUSTACHE_EXPR:t,ERB_EXPR:n,TMPLIT_EXPR:p,DATA_ATTR:h,ARIA_ATTR:d,IS_ALLOWED_URI:ht,IS_SCRIPT_OR_DATA:f,ATTR_WHITESPACE:m,DOCTYPE_NAME:dt});return function D(e){e=0D(e);if(c.version="3.0.9",c.removed=[],!e||!e.document||9!==e.document.nodeType)return c.isSupported=!1,c;let s=e.document;const l=s,F=l.currentScript,{DocumentFragment:B,HTMLTemplateElement:L,Node:u,Element:q,NodeFilter:t,NamedNodeMap:$=e.NamedNodeMap||e.MozNamedAttrMap,HTMLFormElement:z,DOMParser:U,trustedTypes:p}=e,n=q.prototype,V=nt(n,"cloneNode"),K=nt(n,"nextSibling"),W=nt(n,"childNodes"),o=nt(n,"parentNode");if("function"==typeof L){const ze=s.createElement("template");ze.content&&ze.content.ownerDocument&&(s=ze.content.ownerDocument)}let h,d="";const{implementation:i,createNodeIterator:J,createDocumentFragment:H,getElementsByTagName:G}=s,Y=l.importNode;let r={};c.isSupported="function"==typeof ze&&"function"==typeof o&&i&&void 0!==i.createHTMLDocument;const{MUSTACHE_EXPR:f,ERB_EXPR:m,TMPLIT_EXPR:X,DATA_ATTR:Q,ARIA_ATTR:Z,IS_SCRIPT_OR_DATA:ee,ATTR_WHITESPACE:te}=ft;let ne=ft.IS_ALLOWED_URI,g=null;const re=et({},[...rt,...ot,...st,...it,...at]);let y=null;const oe=et({},[...lt,...ct,...ut,...pt]);let a=Object.seal(Ve(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),v=null,se=null,ie=!0,ae=!0,le=!1,ce=!0,b=!1,w=!1,ue=!1,pe=!1,E=!1,S=!1,x=!1,he=!0,de=!1;const fe="user-content-";let me=!0,_=!1,k={},A=null;const ge=et({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let ye=null;const ve=et({},["audio","video","img","source","image","track"]);let be=null;const we=et({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),O="http://www.w3.org/1998/Math/MathML",C="http://www.w3.org/2000/svg",j="http://www.w3.org/1999/xhtml";let P=j,Ee,Se=null;const xe=et({},[O,C,j],Ge);let N=null;const _e=["application/xhtml+xml","text/html"];let I=null,T=null;function ke(e){return e instanceof RegExp||e instanceof Function}function Ae(){let e=0e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+t+" could not be created."),null}}(p,F):h)&&"string"==typeof d&&(d=h.createHTML(""));Ue&&Ue(e),T=e}}function R(t){Je(c.removed,{element:t});try{t.parentNode.removeChild(t)}catch(e){t.remove()}}function Oe(e){let t=null,n=null;if(pe)e=""+e;else{const t=mt(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===N&&P===j&&(e=''+e+"");var r=h?h.createHTML(e):e;if(P===j)try{t=(new U).parseFromString(r,N)}catch(e){}if(!t||!t.documentElement){t=i.createDocument(P,"template",null);try{t.documentElement.innerHTML=Ee?d:r}catch(e){}}const o=t.body||t.documentElement;return e&&n&&o.insertBefore(s.createTextNode(n),o.childNodes[0]||null),P===j?G.call(t,w?"html":"body")[0]:w?t.documentElement:o}function Ce(e){return J.call(e.ownerDocument||e,e,t.SHOW_ELEMENT|t.SHOW_COMMENT|t.SHOW_TEXT,null)}function je(e){return"function"==typeof u&&e instanceof u}function Pe(t){let n=null;if(M("beforeSanitizeElements",t,null),(e=t)instanceof z&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof $)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes))return R(t),1;var e;const r=I(t.nodeName);if(M("uponSanitizeElement",t,{tagName:r,allowedTags:g}),t.hasChildNodes()&&!je(t.firstElementChild)&&Qe(/<[/\w]/g,t.innerHTML)&&Qe(/<[/\w]/g,t.textContent))return R(t),1;if(g[r]&&!v[r])return t instanceof q&&!function(e){let t=o(e);t&&t.tagName||(t={namespaceURI:P,tagName:"template"});var n=He(e.tagName),r=He(t.tagName);return Se[e.namespaceURI]&&(e.namespaceURI===C?t.namespaceURI===j?"svg"===n:t.namespaceURI===O?"svg"===n&&("annotation-xml"===r||Me[r]):Boolean(Be[n]):e.namespaceURI===O?t.namespaceURI===j?"math"===n:t.namespaceURI===C?"math"===n&&De[r]:Boolean(Le[n]):e.namespaceURI===j?(t.namespaceURI!==C||De[r])&&(t.namespaceURI!==O||Me[r])&&!Le[n]&&(Fe[n]||!Be[n]):"application/xhtml+xml"===N&&Se[e.namespaceURI])}(t)||("noscript"===r||"noembed"===r||"noframes"===r)&&Qe(/<\/no(script|embed|frames)/i,t.innerHTML)?(R(t),!0):(b&&3===t.nodeType&&(n=t.textContent,Ke([f,m,X],e=>{n=Ye(n,e," ")}),t.textContent!==n&&(Je(c.removed,{element:t.cloneNode()}),t.textContent=n)),M("afterSanitizeElements",t,null),!1);if(!v[r]&&Ne(r)){if(a.tagNameCheck instanceof RegExp&&Qe(a.tagNameCheck,r))return;if(a.tagNameCheck instanceof Function&&a.tagNameCheck(r))return}if(me&&!A[r]){const n=o(t)||t.parentNode,r=W(t)||t.childNodes;if(r&&n)for(let e=r.length-1;0<=e;--e)n.insertBefore(V(r[e],!0),K(t))}return R(t),1}function Ne(e){return"annotation-xml"!==e&&0/i,t))qe(o,n);else if(b&&Ke([f,m,X],e=>{t=Ye(t,e," ")}),i=I(n.nodeName),$e(i,a,t)){if(!de||"id"!==a&&"name"!==a||(qe(o,n),t=fe+t),h&&"object"==typeof p&&"function"==typeof p.getAttributeType&&!s)switch(p.getAttributeType(i,a)){case"TrustedHTML":t=h.createHTML(t);break;case"TrustedScriptURL":t=h.createScriptURL(t)}try{s?n.setAttributeNS(s,o,t):n.setAttribute(o,t),We(c.removed)}catch(n){}}}M("afterSanitizeAttributes",n,null)}}function Te(e){var t;const n=Ce(e);for(M("beforeSanitizeShadowDOM",e,null);t=n.nextNode();)M("uponSanitizeShadowNode",t,null),Pe(t)||(t.content instanceof B&&Te(t.content),Ie(t));M("afterSanitizeShadowDOM",e,null)}const Re=s.createElement("form"),Me=et({},["mi","mo","mn","ms","mtext"]),De=et({},["foreignobject","desc","title","annotation-xml"]),Fe=et({},["title","style","font","a","script"]),Be=et({},[...ot,...st,...vt]),Le=et({},[...it,...bt]),qe=function(e,t){try{Je(c.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){Je(c.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!y[e])if(E||S)try{R(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},M=function(e,t,n){r[e]&&Ke(r[e],e=>{e.call(c,t,n,T)})},$e=function(e,t,n){if(he&&("id"===t||"name"===t)&&(n in s||n in Re))return!1;if((!ae||se[t]||!Qe(Q,t))&&(!ie||!Qe(Z,t)))if(!y[t]||se[t]){if(!(Ne(e)&&(a.tagNameCheck instanceof RegExp&&Qe(a.tagNameCheck,e)||a.tagNameCheck instanceof Function&&a.tagNameCheck(e))&&(a.attributeNameCheck instanceof RegExp&&Qe(a.attributeNameCheck,t)||a.attributeNameCheck instanceof Function&&a.attributeNameCheck(t))||"is"===t&&a.allowCustomizedBuiltInElements&&(a.tagNameCheck instanceof RegExp&&Qe(a.tagNameCheck,n)||a.tagNameCheck instanceof Function&&a.tagNameCheck(n))))return!1}else if(!be[t]&&!Qe(ne,Ye(n,te,""))&&("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==gt(n,"data:")||!ye[e])&&(!le||Qe(ee,Ye(n,te,"")))&&n)return!1;return!0};return c.sanitize=function(e){let t=1\n"+a),b&&Ke([f,m,X],e=>{a=Ye(a,e," ")}),h&&x?h.createHTML(a):a},c.setConfig=function(){Ae(0{"use strict";class s{constructor(e,t){this.low=e,this.high=t,this.length=1+t-e}overlaps(e){return!(this.highe.high)}touches(e){return!(this.high+1e.high)}add(e){return new s(Math.min(this.low,e.low),Math.max(this.high,e.high))}subtract(e){return e.low<=this.low&&e.high>=this.high?[]:e.low>this.low&&e.highe+t.length,0)}add(e,t){var n=e=>{for(var t=0;t{for(var t=0;t{for(var t=0;t{for(var n=t.low;n<=t.high;)e.push(n),n++;return e},[])}subranges(){return this.ranges.map(e=>({low:e.low,high:e.high,length:1+e.high-e.low}))}}},37007:e=>{"use strict";var t="object"==typeof Reflect?Reflect:null,l=t&&"function"==typeof t.apply?t.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)},n=t&&"function"==typeof t.ownKeys?t.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)},r=Number.isNaN||function(e){return e!=e};function o(){o.init.call(this)}e.exports=o,e.exports.once=function(a,l){return new Promise(function(e,t){function n(e){a.removeListener(l,r),t(e)}function r(){"function"==typeof a.removeListener&&a.removeListener("error",n),e([].slice.call(arguments))}var o,s,i;f(a,l,r,{once:!0}),"error"!==l&&(s=n,i={once:!0},"function"==typeof(o=a).on&&f(o,"error",s,i))})},(o.EventEmitter=o).prototype._events=void 0,o.prototype._eventsCount=0,o.prototype._maxListeners=void 0;var s=10;function c(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function i(e){return void 0===e._maxListeners?o.defaultMaxListeners:e._maxListeners}function a(e,t,n,r){var o,s;return c(n),void 0===(o=e._events)?(o=e._events=Object.create(null),e._eventsCount=0):(void 0!==o.newListener&&(e.emit("newListener",t,n.listener||n),o=e._events),s=o[t]),void 0===s?(s=o[t]=n,++e._eventsCount):("function"==typeof s?s=o[t]=r?[n,s]:[s,n]:r?s.unshift(n):s.push(n),0<(o=i(e))&&s.length>o&&!s.warned&&(s.warned=!0,(r=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit")).name="MaxListenersExceededWarning",r.emitter=e,r.type=t,r.count=s.length,n=r,console&&console.warn&&console.warn(n))),e}function u(e,t,n){e={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},t=function(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}.bind(e);return t.listener=n,e.wrapFn=t}function p(e,t,n){e=e._events;if(void 0===e)return[];e=e[t];{if(void 0===e)return[];if("function"==typeof e)return n?[e.listener||e]:[e];if(n){for(var r=e,o=new Array(r.length),s=0;s{"use strict";var r=n(26311),n=o(Error);function o(t){return e.displayName=t.displayName||t.name,e;function e(e){return e=e&&r.apply(null,arguments),new t(e)}}(e.exports=n).eval=o(EvalError),n.range=o(RangeError),n.reference=o(ReferenceError),n.syntax=o(SyntaxError),n.type=o(TypeError),n.uri=o(URIError),n.create=o},26311:e=>{function n(t){function e(){return i[s++]}for(var n,r,o,s=1,i=[].slice.call(arguments),a=0,l=t.length,c="",u=!1,p=!1;a{"use strict";function l(e,t){for(var n=[],r=0;r{"use strict";n=n(89353);e.exports=Function.prototype.bind||n},70453:(e,t,n)=>{"use strict";var r,h=SyntaxError,o=Function,d=TypeError,s=function(e){try{return o('"use strict"; return ('+e+").constructor;")()}catch(e){}},f=Object.getOwnPropertyDescriptor;if(f)try{f({},"")}catch(e){f=null}function i(){throw new d}var a=f?function(){try{return i}catch(e){try{return f(arguments,"callee").get}catch(e){return i}}}():i,l=n(64039)(),c=n(80024)(),u=Object.getPrototypeOf||(c?function(e){return e.__proto__}:null),m={},c="undefined"!=typeof Uint8Array&&u?u(Uint8Array):r,g={"%AggregateError%":"undefined"==typeof AggregateError?r:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?r:ArrayBuffer,"%ArrayIteratorPrototype%":l&&u?u([][Symbol.iterator]()):r,"%AsyncFromSyncIteratorPrototype%":r,"%AsyncFunction%":m,"%AsyncGenerator%":m,"%AsyncGeneratorFunction%":m,"%AsyncIteratorPrototype%":m,"%Atomics%":"undefined"==typeof Atomics?r:Atomics,"%BigInt%":"undefined"==typeof BigInt?r:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?r:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?r:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?r:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?r:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?r:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?r:FinalizationRegistry,"%Function%":o,"%GeneratorFunction%":m,"%Int8Array%":"undefined"==typeof Int8Array?r:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?r:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?r:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":l&&u?u(u([][Symbol.iterator]())):r,"%JSON%":"object"==typeof JSON?JSON:r,"%Map%":"undefined"==typeof Map?r:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&l&&u?u((new Map)[Symbol.iterator]()):r,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?r:Promise,"%Proxy%":"undefined"==typeof Proxy?r:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?r:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?r:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&l&&u?u((new Set)[Symbol.iterator]()):r,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?r:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":l&&u?u(""[Symbol.iterator]()):r,"%Symbol%":l?Symbol:r,"%SyntaxError%":h,"%ThrowTypeError%":a,"%TypedArray%":c,"%TypeError%":d,"%Uint8Array%":"undefined"==typeof Uint8Array?r:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?r:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?r:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?r:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?r:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?r:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?r:WeakSet};if(u)try{null.error}catch(e){l=u(u(e));g["%Error.prototype%"]=l}function y(e){var t,n;return"%AsyncFunction%"===e?t=s("async function () {}"):"%GeneratorFunction%"===e?t=s("function* () {}"):"%AsyncGeneratorFunction%"===e?t=s("async function* () {}"):"%AsyncGenerator%"===e?(n=y("%AsyncGeneratorFunction%"))&&(t=n.prototype):"%AsyncIteratorPrototype%"===e&&(n=y("%AsyncGenerator%"))&&u&&(t=u(n.prototype)),g[e]=t}var v={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},a=n(66743),b=n(9957),w=a.call(Function.call,Array.prototype.concat),E=a.call(Function.apply,Array.prototype.splice),S=a.call(Function.call,String.prototype.replace),x=a.call(Function.call,String.prototype.slice),_=a.call(Function.call,RegExp.prototype.exec),k=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,A=/\\(\\)?/g;e.exports=function(e,t){if("string"!=typeof e||0===e.length)throw new d("intrinsic name must be a non-empty string");if(1=n.length?(l=!!(p=f(s,c)))&&"get"in p&&!("originalValue"in p.get)?p.get:s[c]:(l=b(s,c),s[c]),l&&!i&&(g[u]=s)}}return s}},75795:(e,t,n)=>{"use strict";n=n(70453)("%Object.getOwnPropertyDescriptor%",!0);if(n)try{n([],"length")}catch(e){n=null}e.exports=n},30592:(e,t,n)=>{"use strict";function r(){if(o)try{return o({},"a",{value:1}),!0}catch(e){return!1}return!1}var o=n(70453)("%Object.defineProperty%",!0);r.hasArrayLengthDefineBug=function(){if(!r())return null;try{return 1!==o([],"length",{value:1}).length}catch(e){return!0}},e.exports=r},80024:e=>{"use strict";var t={foo:{}},n=Object;e.exports=function(){return{__proto__:t}.foo===t.foo&&!({__proto__:null}instanceof n)}},64039:(e,t,n)=>{"use strict";var r="undefined"!=typeof Symbol&&Symbol,o=n(41333);e.exports=function(){return"function"==typeof r&&"function"==typeof Symbol&&"symbol"==typeof r("foo")&&"symbol"==typeof Symbol("bar")&&o()}},41333:e=>{"use strict";e.exports=function(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var e={},t=Symbol("test"),n=Object(t);if("string"==typeof t)return!1;if("[object Symbol]"!==Object.prototype.toString.call(t))return!1;if("[object Symbol]"!==Object.prototype.toString.call(n))return!1;for(t in e[t]=42,e)return!1;if("function"==typeof Object.keys&&0!==Object.keys(e).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(e).length)return!1;n=Object.getOwnPropertySymbols(e);if(1!==n.length||n[0]!==t)return!1;if(!Object.prototype.propertyIsEnumerable.call(e,t))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){n=Object.getOwnPropertyDescriptor(e,t);if(42!==n.value||!0!==n.enumerable)return!1}return!0}},9957:(e,t,n)=>{"use strict";var r=Function.prototype.call,o=Object.prototype.hasOwnProperty,n=n(66743);e.exports=n.call(r,o)},45981:e=>{function n(t){return t instanceof Map?t.clear=t.delete=t.set=function(){throw new Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=function(){throw new Error("set is read-only")}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach(function(e){e=t[e];"object"!=typeof e||Object.isFrozen(e)||n(e)}),t}var g=n;g.default=n;class P{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function u(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function l(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t];return t.forEach(function(e){for(const t in e)n[t]=e[t]}),n}const r=e=>!!e.kind;class t{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=u(e)}openNode(t){if(r(t)){let e=t.kind;t.sublanguage||(e=""+this.classPrefix+e),this.span(e)}}closeNode(e){r(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class o{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){e={kind:e,children:[]};this.add(e),this.stack.push(e)}closeNode(){if(1this._walk(t,e)),t.closeNode(e)),t}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{o._collapse(e)}))}}class y extends o{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new t(this,this.options).value()}finalize(){return!0}}function c(e){return e?"string"==typeof e?e:e.source:null}function s(e,t,n={}){const r=l({className:"comment",begin:e,end:t,contains:[]},n);return r.contains.push(w),r.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),r}const p=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,i="[a-zA-Z]\\w*",a="[a-zA-Z_]\\w*",h="\\b\\d+(\\.\\d+)?",d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",f="\\b(0b[01]+)",m={begin:"\\\\[\\s\\S]",relevance:0},v={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[m]},b={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[m]},w={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},E=s("//","$"),S=s("/\\*","\\*/"),x=s("#","$"),C={className:"number",begin:h,relevance:0},j={className:"number",begin:d,relevance:0},B={className:"number",begin:f,relevance:0},L={className:"number",begin:h+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},q={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[m,{begin:/\[/,end:/\]/,relevance:0,contains:[m]}]}]},$={className:"title",begin:i,relevance:0},z={className:"title",begin:a,relevance:0},U={begin:"\\.\\s*"+a,relevance:0};var N=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:i,UNDERSCORE_IDENT_RE:a,NUMBER_RE:h,C_NUMBER_RE:d,BINARY_NUMBER_RE:f,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{var t=/^#![ ]*\//;return e.binary&&(e.begin=[t,/.*\b/,e.binary,/\b.*/].map(e=>c(e)).join("")),l({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:m,APOS_STRING_MODE:v,QUOTE_STRING_MODE:b,PHRASAL_WORDS_MODE:w,COMMENT:s,C_LINE_COMMENT_MODE:E,C_BLOCK_COMMENT_MODE:S,HASH_COMMENT_MODE:x,NUMBER_MODE:C,C_NUMBER_MODE:j,BINARY_NUMBER_MODE:B,CSS_NUMBER_MODE:L,REGEXP_MODE:q,TITLE_MODE:$,UNDERSCORE_TITLE_MODE:z,METHOD_GUARD:U,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}});function V(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}const K=["of","and","for","in","not","or","if","then","parent","list","value"],W="keyword";function _(t,r,e=W){const o={};return"string"==typeof t?n(e,t.split(" ")):Array.isArray(t)?n(e,t):Object.keys(t).forEach(function(e){Object.assign(o,_(t[e],r,e))}),o;function n(n,e){(e=r?e.map(e=>e.toLowerCase()):e).forEach(function(e){var t,e=e.split("|");o[e[0]]=[n,(t=e[0],(e=e[1])?Number(e):function(e){return K.includes(e.toLowerCase())}(t)?0:1)]})}}function J(s,{}){function i(e,t){return new RegExp(c(e),"m"+(s.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=new RegExp(e.toString()+"|").exec("").length-1+1}compile(){0===this.regexes.length&&(this.exec=()=>null);var e=this.regexes.map(e=>e[1]);this.matcherRe=i(function(e,t="|"){let o=0;return e.map(e=>{var t=o+=1;let n=c(e),r="";for(;0`(${e})`).join(t)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;var e=t.findIndex((e,t)=>0n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition()&&(!n||n.index!==this.lastIndex)){const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=l(s.classNameAliases||{}),function t(n,r){const o=n;if(n.isCompiled)return o;[function(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}].forEach(e=>e(n,r)),s.compilerExtensions.forEach(e=>e(n,r)),n.__beforeBegin=null,[function(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=V,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))},function(e,t){Array.isArray(e.illegal)&&(e.illegal=([...e]=[...e.illegal],"("+e.map(e=>c(e)).join("|")+")"))},function(e,t){void 0===e.relevance&&(e.relevance=1)}].forEach(e=>e(n,r)),n.isCompiled=!0;let e=null;if("object"==typeof n.keywords&&(e=n.keywords.$pattern,delete n.keywords.$pattern),n.keywords&&(n.keywords=_(n.keywords,s.case_insensitive)),n.lexemes&&e)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return e=e||n.lexemes||/\w+/,o.keywordPatternRe=i(e,!0),r&&(n.begin||(n.begin=/\B|\b/),o.beginRe=i(n.begin),n.endSameAsBegin&&(n.end=n.begin),n.end||n.endsWithParent||(n.end=/\B|\b/),n.end&&(o.endRe=i(n.end)),o.terminatorEnd=c(n.end)||"",n.endsWithParent&&r.terminatorEnd&&(o.terminatorEnd+=(n.end?"|":"")+r.terminatorEnd)),n.illegal&&(o.illegalRe=i(n.illegal)),n.contains||(n.contains=[]),n.contains=[].concat(...n.contains.map(function(e){return(t="self"===e?n:e).variants&&!t.cachedVariants&&(t.cachedVariants=t.variants.map(function(e){return l(t,{variants:null},e)})),t.cachedVariants||(function e(t){return!!t&&(t.endsWithParent||e(t.starts))}(t)?l(t,{starts:t.starts?l(t.starts):null}):Object.isFrozen(t)?l(t):t);var t})),n.contains.forEach(function(e){t(e,o)}),n.starts&&t(n.starts,r),o.matcher=function(e){const t=new a;return e.contains.forEach(e=>t.addRule(e.begin,{rule:e,type:"begin"})),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t}(o),o}(s)}function H(t){const n={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!t.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,u(this.code);let e={};return this.autoDetect?(e=t.highlightAuto(this.code),this.detectedLanguage=e.language):(e=t.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),e.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:n,VuePlugin:{install(e){e.component("highlightjs",n)}}}}const G={"after:highlightElement":({el:e,result:t,text:n})=>{e=A(e);if(e.length){const r=document.createElement("div");r.innerHTML=t.value,t.value=function(t,e,n){let r=0,o="";const s=[];function i(){return t.length&&e.length?t[0].offset!==e[0].offset?t[0].offset"}function l(e){o+=""}function c(e){("start"===e.event?a:l)(e.node)}for(;t.length||e.length;){let e=i();if(o+=u(n.substring(r,e[0].offset)),r=e[0].offset,e===t){for(s.reverse().forEach(l);c(e.splice(0,1)[0]),(e=i())===t&&e.length&&e[0].offset===r;);s.reverse().forEach(a)}else"start"===e[0].event?s.push(e[0].node):s.pop(),c(e.splice(0,1)[0])}return o+u(n.substr(r))}(e,A(r),n)}}};function k(e){return e.nodeName.toLowerCase()}function A(e){const o=[];return function t(n,r){for(let e=n.firstChild;e;e=e.nextSibling)3===e.nodeType?r+=e.nodeValue.length:1===e.nodeType&&(o.push({event:"start",offset:r,node:e}),r=t(e,r),k(e).match(/br|hr|img|input/)||o.push({event:"stop",offset:r,node:e}));return r}(e,0),o}const O={},I=e=>{console.error(e)},T=(e,...t)=>{console.log("WARN: "+e,...t)},R=(e,t)=>{O[e+"/"+t]||(console.log(`Deprecated as of ${e}. `+t),O[e+"/"+t]=!0)},M=u,D=l,F=Symbol("nomatch");var Y=function(r){const S=Object.create(null),s=Object.create(null),x=[];let _=!0;const t=/(^(<[^>]+>|\t|)+|\n)/gm,k="Could not find the language '{}', did you forget to load/include a language module?",l={disableAutodetect:!0,name:"Plain text",contains:[]};let A={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:y};function i(e){return A.noHighlightRe.test(e)}function a(e,t,n,r){let o="",s="";"object"==typeof t?(o=e,n=t.ignoreIllegals,s=t.language,r=void 0):(R("10.7.0","highlight(lang, code, ...args) has been deprecated."),R("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),s=e,o=t);e={code:o,language:s};m("before:highlight",e);const i=e.result||O(e.language,e.code,n,r);return i.code=e.code,m("after:highlight",i),i}function O(r,o,s,e){function i(){(null!=d.subLanguage?function(){if(""!==g){let e=null;if("string"==typeof d.subLanguage){if(!S[d.subLanguage])return m.addText(g);e=O(d.subLanguage,g,!0,f[d.subLanguage]),f[d.subLanguage]=e.top}else e=C(g,d.subLanguage.length?d.subLanguage:null);0")+'"');throw r.mode=d,r}if("end"===t.type){const r=c(t);if(r!==F)return r}if("illegal"===t.type&&""===n)return 1;if(1e53*t.index)throw new Error("potential infinite loop, way more iterations than matches");return g+=n,n.length}const p=j(r);if(!p)throw I(k.replace("{}",r)),new Error('Unknown language: "'+r+'"');var n=J(p,{plugins:x});let h="",d=e||n;const f={},m=new A.__emitter(A);{const E=[];for(let e=d;e!==p;e=e.parent)e.className&&E.unshift(e.className);E.forEach(e=>m.openNode(e))}let g="",y=0,v=0,b=0,w=!1;try{for(d.matcher.considerAll();;){b++,w?w=!1:d.matcher.considerAll(),d.matcher.lastIndex=v;const r=d.matcher.exec(o);if(!r)break;const S=t(o.substring(v,r.index),r);v=r.index+S}return t(o.substr(v)),m.closeAllNodes(),m.finalize(),h=m.toHTML(),{relevance:Math.floor(y),value:h,language:r,illegal:!1,emitter:m,top:d}}catch(e){if(e.message&&e.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:e.message,context:o.slice(v-100,v+100),mode:e.mode},sofar:h,relevance:0,value:M(o),emitter:m};if(_)return{illegal:!1,relevance:0,value:M(o),emitter:m,language:r,top:d,errorRaised:e};throw e}}function C(t,e){e=e||A.languages||Object.keys(S);const n=function(e){const t={relevance:0,emitter:new A.__emitter(A),value:M(e),illegal:!1,top:l};return t.emitter.addText(e),t}(t),r=e.filter(j).filter(f).map(e=>O(e,t,!1)),o=(r.unshift(n),r.sort((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(j(e.language).supersetOf===t.language)return 1;if(j(t.language).supersetOf===e.language)return-1}return 0})),[s,i]=o,a=s;return a.second_best=i,a}const e={"before:highlightElement":({el:e})=>{A.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{A.useBR&&(e.value=e.value.replace(/\n/g,"
"))}},n=/^(<[^>]+>|\t)+/gm,o={"after:highlightElement":({result:e})=>{A.tabReplace&&(e.value=e.value.replace(n,e=>e.replace(/\t/g,A.tabReplace)))}};function c(e){var t,n,r,o=function(e){let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";var n=A.languageDetectRe.exec(t);if(n){const t=j(n[1]);return t||(T(k.replace("{}",n[1])),T("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find(e=>i(e)||j(e))}(e);i(o)||(m("before:highlightElement",{el:e,language:o}),n=e.textContent,m("after:highlightElement",{el:e,result:t=o?a(n,{language:o,ignoreIllegals:!0}):C(n),text:n}),e.innerHTML=t.value,n=e,o=o,r=t.language,o=o?s[o]:r,n.classList.add("hljs"),o&&n.classList.add(o),e.result={language:t.language,re:t.relevance,relavance:t.relevance},t.second_best&&(e.second_best={language:t.second_best.language,re:t.second_best.relevance,relavance:t.second_best.relevance}))}const u=()=>{u.called||(u.called=!0,R("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead."),document.querySelectorAll("pre code").forEach(c))};let p=!1;function h(){"loading"===document.readyState?p=!0:document.querySelectorAll("pre code").forEach(c)}function j(e){return e=(e||"").toLowerCase(),S[e]||S[s[e]]}function d(e,{languageName:t}){(e="string"==typeof e?[e]:e).forEach(e=>{s[e.toLowerCase()]=t})}function f(e){e=j(e);return e&&!e.disableAutodetect}function m(e,t){const n=e;x.forEach(function(e){e[n]&&e[n](t)})}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",function(){p&&h()},!1),Object.assign(r,{highlight:a,highlightAuto:C,highlightAll:h,fixMarkup:function(e){return R("10.2.0","fixMarkup will be removed entirely in v11.0"),R("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),e=e,A.tabReplace||A.useBR?e.replace(t,e=>"\n"===e?A.useBR?"
":e:A.tabReplace?e.replace(/\t/g,A.tabReplace):e):e},highlightElement:c,highlightBlock:function(e){return R("10.7.0","highlightBlock will be removed entirely in v12.0"),R("10.7.0","Please use highlightElement now."),c(e)},configure:function(e){e.useBR&&(R("10.3.0","'useBR' will be removed entirely in v11.0"),R("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),A=D(A,e)},initHighlighting:u,initHighlightingOnLoad:function(){R("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),p=!0},registerLanguage:function(t,e){let n=null;try{n=e(r)}catch(e){if(I("Language definition for '{}' could not be registered.".replace("{}",t)),!_)throw e;I(e),n=l}n.name||(n.name=t),(S[t]=n).rawDefinition=e.bind(null,r),n.aliases&&d(n.aliases,{languageName:t})},unregisterLanguage:function(e){delete S[e];for(const t of Object.keys(s))s[t]===e&&delete s[t]},listLanguages:function(){return Object.keys(S)},getLanguage:j,registerAliases:d,requireLanguage:function(e){R("10.4.0","requireLanguage will be removed entirely in v11."),R("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");var t=j(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:f,inherit:D,addPlugin:function(e){var t;(t=e)["before:highlightBlock"]&&!t["before:highlightElement"]&&(t["before:highlightElement"]=e=>{t["before:highlightBlock"](Object.assign({block:e.el},e))}),t["after:highlightBlock"]&&!t["after:highlightElement"]&&(t["after:highlightElement"]=e=>{t["after:highlightBlock"](Object.assign({block:e.el},e))}),x.push(e)},vuePlugin:H(r).VuePlugin}),r.debugMode=function(){_=!1},r.safeMode=function(){_=!0},r.versionString="10.7.3";for(const r in N)"object"==typeof N[r]&&g(N[r]);return Object.assign(r,N),r.addPlugin(e),r.addPlugin(G),r.addPlugin(o),r}({});e.exports=Y},35344:e=>{e.exports=function(e){var t={},n={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{className:"variable",variants:[{begin:[/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])"].map(e=>{return e?"string"==typeof e?e:e.source:null}).join("")},n]});const r={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},o={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},s={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,t,r]};r.contains.push(s);var n={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t]},i=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),a={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[i,e.SHEBANG(),a,n,e.HASH_COMMENT_MODE,o,s,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},t]}}},73402:e=>{e.exports=function(e){var t="HTTP/(2|1\\.[01])",n={className:"attribute",begin:["^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"].map(e=>{return e?"string"==typeof e?e:e.source:null}).join(""),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},r=[n,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+t+" \\d{3})",end:/$/,contains:[{className:"meta",begin:t},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:r}},{begin:"(?=^[A-Z]+ (.*?) "+t+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:t},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:r}},e.inherit(n,{relevance:0})]}}},95089:e=>{const b="[A-Za-z$_][0-9A-Za-z$_]*",w=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],E=["true","false","null","undefined","NaN","Infinity"],S=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function x(e){return _("(?=",e,")")}function _(...e){return e.map(e=>{return e?"string"==typeof e?e:e.source:null}).join("")}e.exports=function(e){const t=b,n=/<[A-Za-z0-9\\._:-]+/,r=/\/[A-Za-z0-9\\._:-]+>|\/>/,o=(e,t)=>{var n=e[0].length+e.index,r=e.input[n];"<"!==r?">"===r&&([r,e]=[e,{after:n}["after"]],n="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:y}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:n,"on:begin":o,end:r}],subLanguage:"xml",contains:[{begin:n,end:r,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:s,contains:["self",e.inherit(e.TITLE_MODE,{begin:t}),v],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[v,e.inherit(e.TITLE_MODE,{begin:t})]},{variants:[{begin:"\\."+t},{begin:"\\$"+t}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:t}),"self",v]},{begin:"(get|set)\\s+(?="+t+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:t}),{begin:/\(\)/},v]},{begin:/\$[(.]/}]}}},65772:e=>{e.exports=function(e){const t={literal:"true false null"},n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],r=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],o={end:",",endsWithParent:!0,excludeEnd:!0,contains:r,keywords:t},s={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(o,{begin:/:/})].concat(n),illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(o)],illegal:"\\S"};return r.push(s,i),n.forEach(function(e){r.push(e)}),{name:"JSON",contains:r,keywords:t,illegal:"\\S"}}},26571:e=>{e.exports=function(e){const t={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},n={begin:"`[\\s\\S]",relevance:0},r={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},o={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[n,r,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},s={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},i=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),a={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},c={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[r]}]},u={begin:/using\s/,end:/$/,returnBegin:!0,contains:[o,s,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},h={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]},d=[h,i,n,e.NUMBER_MODE,o,s,a,r,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],f={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",d,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return h.contains.unshift(f),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:t,contains:d.concat(l,c,u,p,f)}}},17285:e=>{function l(e){return e?"string"==typeof e?e:e.source:null}function c(e){return u("(?=",e,")")}function u(...e){return e.map(e=>l(e)).join("")}e.exports=function(e){var t=u(/[A-Z_]/,u("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),n={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},r={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},o=e.inherit(r,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),a={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[r,i,s,o,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[r,o,i,s]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[a],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[a],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:u(//,/>/,/\s/].map(e=>l(e)).join("|")+")"))),end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:a}]},{className:"tag",begin:u(/<\//,c(u(t,/>/))),contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533:e=>{e.exports=function(e){var t="true false yes no null",n="[\\w#;/?:@&=+$,.~*'()[\\]]+",r={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},o=e.inherit(r,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},n=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+n},{className:"type",begin:"!<"+n+">"},{className:"type",begin:"!"+n},{className:"type",begin:"!!"+n},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},{begin:/\{/,end:/\}/,contains:[s],illegal:"\\n",relevance:0},{begin:"\\[",end:"\\]",contains:[s],illegal:"\\n",relevance:0},r],t=[...n];return t.pop(),t.push(o),s.contains=t,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:n}}},251:(e,t)=>{t.read=function(e,t,n,r,o){var s,i,a=8*o-r-1,l=(1<>1,u=-7,p=n?o-1:0,h=n?-1:1,o=e[t+p];for(p+=h,s=o&(1<<-u)-1,o>>=-u,u+=a;0>=-u,u+=r;0>1,p=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,h=r?0:s-1,d=r?1:-1,s=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,i=c):(i=Math.floor(Math.log(t)/Math.LN2),t*(r=Math.pow(2,-i))<1&&(i--,r*=2),2<=(t+=1<=i+u?p/r:p*Math.pow(2,1-u))*r&&(i++,r/=2),c<=i+u?(a=0,i=c):1<=i+u?(a=(t*r-1)*Math.pow(2,o),i+=u):(a=t*Math.pow(2,u-1)*Math.pow(2,o),i=0));8<=o;e[n+h]=255&a,h+=d,a/=256,o-=8);for(i=i<>>0;if(""+n!==t||4294967295==n)return NaN;t=n}return t<0?H(e)+t:t}function Y(){return!0}function X(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&n<=t)}function Q(e,t){return ee(e,t,0)}function Z(e,t){return ee(e,t,t)}function ee(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var te=0,h=1,ne=2,re="function"==typeof Symbol&&Symbol.iterator,oe="@@iterator",se=re||oe;function d(e){this.next=e}function f(e,t,n,r){e=0===e?t:1===e?n:[t,n];return r?r.value=e:r={value:e,done:!1},r}function m(){return{value:void 0,done:!0}}function ie(e){return ce(e)}function ae(e){return e&&"function"==typeof e.next}function le(e){var t=ce(e);return t&&t.call(e)}function ce(e){e=e&&(re&&e[re]||e[oe]);if("function"==typeof e)return e}function ue(e){return e&&"number"==typeof e.length}function n(e){{if(null==e)return Se();if(u(e))return e.toSeq();var t=ke(e)||"object"==typeof e&&new ve(e);if(t)return t;throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e)}}function pe(e){return null==e?Se().toKeyedSeq():u(e)?c(e)?e.toSeq():e.fromEntrySeq():xe(e)}function g(e){return null==e?Se():u(e)?c(e)?e.entrySeq():e.toIndexedSeq():_e(e)}function he(e){return(null==e?Se():u(e)?c(e)?e.entrySeq():e:_e(e)).toSetSeq()}d.prototype.toString=function(){return"[Iterator]"},d.KEYS=te,d.VALUES=h,d.ENTRIES=ne,d.prototype.inspect=d.prototype.toSource=function(){return this.toString()},d.prototype[se]=function(){return this},e(n,l),n.of=function(){return n(arguments)},n.prototype.toSeq=function(){return this},n.prototype.toString=function(){return this.__toString("Seq {","}")},n.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},n.prototype.__iterate=function(e,t){return Ae(this,e,t,!0)},n.prototype.__iterator=function(e,t){return Oe(this,e,t,!0)},e(pe,n),pe.prototype.toKeyedSeq=function(){return this},e(g,n),g.of=function(){return g(arguments)},g.prototype.toIndexedSeq=function(){return this},g.prototype.toString=function(){return this.__toString("Seq [","]")},g.prototype.__iterate=function(e,t){return Ae(this,e,t,!1)},g.prototype.__iterator=function(e,t){return Oe(this,e,t,!1)},e(he,n),he.of=function(){return he(arguments)},he.prototype.toSetSeq=function(){return this},n.isSeq=Ee,n.Keyed=pe,n.Set=he,n.Indexed=g;var de,fe,me,ge="@@__IMMUTABLE_SEQ__@@";function ye(e){this._array=e,this.size=e.length}function ve(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function be(e){this._iterable=e,this.size=e.length||e.size}function we(e){this._iterator=e,this._iteratorCache=[]}function Ee(e){return!(!e||!e[ge])}function Se(){return de=de||new ye([])}function xe(e){var t=Array.isArray(e)?new ye(e).fromEntrySeq():ae(e)?new we(e).fromEntrySeq():ie(e)?new be(e).fromEntrySeq():"object"==typeof e?new ve(e):void 0;if(t)return t;throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e)}function _e(e){var t=ke(e);if(t)return t;throw new TypeError("Expected Array or iterable object of values: "+e)}function ke(e){return ue(e)?new ye(e):ae(e)?new we(e):ie(e)?new be(e):void 0}function Ae(e,t,n,r){var o=e._cache;if(o){for(var s=o.length-1,i=0;i<=s;i++){var a=o[n?s-i:i];if(!1===t(a[1],r?a[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function Oe(e,t,n,r){var o,s,i=e._cache;return i?(o=i.length-1,s=0,new d(function(){var e=i[n?o-s:s];return s++>o?m():f(t,r?e[0]:s-1,e[1])})):e.__iteratorUncached(t,n)}function Ce(e,t){return t?function n(r,o,e,t){return Array.isArray(o)?r.call(t,e,g(o).map(function(e,t){return n(r,e,t,o)})):Pe(o)?r.call(t,e,pe(o).map(function(e,t){return n(r,e,t,o)})):o}(t,e,"",{"":e}):je(e)}function je(e){return Array.isArray(e)?g(e).map(je).toList():Pe(e)?pe(e).map(je).toMap():e}function Pe(e){return e&&(e.constructor===Object||void 0===e.constructor)}function v(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function Ne(n,e){if(n===e)return!0;if(!u(e)||void 0!==n.size&&void 0!==e.size&&n.size!==e.size||void 0!==n.__hash&&void 0!==e.__hash&&n.__hash!==e.__hash||c(n)!==c(e)||p(n)!==p(e)||B(n)!==B(e))return!1;if(0===n.size&&0===e.size)return!0;var r,o=!F(n);if(B(n))return r=n.entries(),e.every(function(e,t){var n=r.next().value;return n&&v(n[1],e)&&(o||v(n[0],t))})&&r.next().done;var s=!1,i=(void 0===n.size&&(void 0===e.size?"function"==typeof n.cacheResult&&n.cacheResult():(s=!0,t=n,n=e,e=t)),!0),t=e.__iterate(function(e,t){if(o?!n.has(e):s?!v(e,n.get(t,x)):!v(n.get(t,x),e))return i=!1});return i&&n.size===t}function s(e,t){if(!(this instanceof s))return new s(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(fe)return fe;fe=this}}function Ie(e,t){if(!e)throw new Error(t)}function i(e,t,n){if(!(this instanceof i))return new i(e,t,n);if(Ie(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),ts?m():f(t,e,r[e])})},ve.prototype[z]=!0,e(be,g),be.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n,r=le(this._iterable),o=0;if(ae(r))for(;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},be.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=le(this._iterable);if(!ae(n))return new d(m);var r=0;return new d(function(){var e=n.next();return e.done?e:f(t,r++,e.value)})},e(we,g),we.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n=this._iterator,r=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[o]=e.value}return f(t,o,r[o++])})},e(s,g),s.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},s.prototype.get=function(e,t){return this.has(e)?this._value:t},s.prototype.includes=function(e){return v(this._value,e)},s.prototype.slice=function(e,t){var n=this.size;return X(e,t,n)?this:new s(this._value,Z(t,n)-Q(e,n))},s.prototype.reverse=function(){return this},s.prototype.indexOf=function(e){return v(this._value,e)?0:-1},s.prototype.lastIndexOf=function(e){return v(this._value,e)?this.size:-1},s.prototype.__iterate=function(e,t){for(var n=0;n>>16)*r+n*(t>>>16)<<16>>>0)|0};function Be(e){return e>>>1&1073741824|3221225471&e}function _(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t,n,r=typeof e;if("number"==r){if(e!=e||e===1/0)return 0;var o=0|e;for(o!==e&&(o^=4294967295*e);4294967295We?(void 0===(t=Ge[s=e])&&(t=Le(s),He===Je&&(He=0,Ge={}),He++,Ge[s]=t),t):Le(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"==r){var s=e;if(Ue&&void 0!==(n=ze.get(s)))return n;if(void 0!==(n=s[Ke]))return n;if(!$e){if(void 0!==(n=s.propertyIsEnumerable&&s.propertyIsEnumerable[Ke]))return n;if(void 0!==(n=function(e){if(e&&0=n.length)throw new Error("Missing value for key: "+n[t]);e.set(n[t],n[t+1])}})},k.prototype.toString=function(){return this.__toString("Map {","}")},k.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},k.prototype.set=function(e,t){return ct(this,e,t)},k.prototype.setIn=function(e,t){return this.updateIn(e,x,function(){return t})},k.prototype.remove=function(e){return ct(this,e,x)},k.prototype.deleteIn=function(e){return this.updateIn(e,function(){return x})},k.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},k.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);e=function e(t,n,r,o){var s=t===x,i=n.next();if(i.done)return(l=o(a=s?r:t))===a?t:l;Ie(s||t&&t.set,"invalid keyPath");var a=i.value,l=s?x:t.get(a,x),i=e(l,n,r,o);return i===l?t:i===x?t.remove(a):(s?lt():t).set(a,i)}(this,cn(e),t,n);return e===x?void 0:e},k.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):lt()},k.prototype.merge=function(){return dt(this,void 0,arguments)},k.prototype.mergeWith=function(e){return dt(this,e,r.call(arguments,1))},k.prototype.mergeIn=function(e){var t=r.call(arguments,1);return this.updateIn(e,lt(),function(e){return"function"==typeof e.merge?e.merge.apply(e,t):t[t.length-1]})},k.prototype.mergeDeep=function(){return dt(this,ft,arguments)},k.prototype.mergeDeepWith=function(e){return dt(this,mt(e),r.call(arguments,1))},k.prototype.mergeDeepIn=function(e){var t=r.call(arguments,1);return this.updateIn(e,lt(),function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,t):t[t.length-1]})},k.prototype.sort=function(e){return j(Qt(this,e))},k.prototype.sortBy=function(e,t){return j(Qt(this,t,e))},k.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},k.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new W)},k.prototype.asImmutable=function(){return this.__ensureOwner()},k.prototype.wasAltered=function(){return this.__altered},k.prototype.__iterator=function(e,t){return new ot(this,e,t)},k.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate(function(e){return r++,t(e[1],e[0],n)},e),r},k.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?at(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},k.isMap=Ye;var Xe,Qe="@@__IMMUTABLE_MAP__@@",A=k.prototype;function Ze(e,t){this.ownerID=e,this.entries=t}function et(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function tt(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function nt(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function rt(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function ot(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&it(e._root)}function st(e,t){return f(e,t[0],t[1])}function it(e,t){return{node:e,index:0,__prev:t}}function at(e,t,n,r){var o=Object.create(A);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function lt(){return Xe=Xe||at(0)}function ct(e,t,n){if(e._root){var r=K(U),o=K(V),s=ut(e._root,e.__ownerID,0,void 0,t,n,r,o);if(!o.value)return e;o=e.size+(r.value?n===x?-1:1:0)}else{if(n===x)return e;o=1,s=new Ze(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=s,e.__hash=void 0,e.__altered=!0,e):s?at(o,s):lt()}function ut(e,t,n,r,o,s,i,a){return e?e.update(t,n,r,o,s,i,a):s===x?e:(y(a),y(i),new rt(t,r,[o,s]))}function pt(e){return e.constructor===rt||e.constructor===nt}function ht(e,t,n,r,o){if(e.keyHash===r)return new nt(t,r,[e.entry,o]);var s=(0===n?e.keyHash:e.keyHash>>>n)&S,i=(0===n?r:r>>>n)&S;return new et(t,1<>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,127&(e+=e>>8)+(e>>16)}function vt(e,t,n,r){r=r?e:J(e);return r[t]=n,r}A[Qe]=!0,A[t]=A.remove,A.removeIn=A.deleteIn,Ze.prototype.get=function(e,t,n,r){for(var o=this.entries,s=0,i=o.length;s=bt){for(var h=e,d=l,i=r,s=o,f=new rt(h=h||new W,_(i),[i,s]),m=0;m>>e)&S),s=this.bitmap;return 0==(s&o)?r:this.nodes[yt(s&o-1)].get(e+w,t,n,r)},et.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=_(r));var a=(0===t?n:n>>>t)&S,l=1<=wt){for(var n=e,f=h,m=c,r=a,o=t,g=0,y=new Array(E),v=0;0!==m;v++,m>>>=1)y[v]=1&m?f[g++]:void 0;return y[r]=o,new tt(n,g+1,y)}if(u&&!t&&2===h.length&&pt(h[1^p]))return h[1^p];if(u&&t&&1===h.length&&pt(t))return t;s=e&&e===this.ownerID,i=u?t?c:c^l:c|l,d=u?t?vt(h,p,t,s):function(e,t,n){var r=e.length-1;if(n&&t===r)return e.pop(),e;for(var o=new Array(r),s=0,i=0;i>>e)&S];return o?o.get(e+w,t,n,r):r},tt.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=_(r));var a=(0===t?n:n>>>t)&S,l=o===x,c=this.nodes,u=c[a];if(l&&!u)return this;l=ut(u,e,t+w,n,r,o,s,i);if(l===u)return this;t=this.count;if(u){if(!l&&--t=n.size||r<0)return n.withMutations(function(e){r<0?Rt(e,r).set(0,o):Rt(e,0,r+1).set(r,o)});r+=n._origin;var e=n._tail,t=n._root,s=K(V);return r>=Dt(n._capacity)?e=Nt(e,n.__ownerID,0,r,o,s):t=Nt(t,n.__ownerID,n._level,r,o,s),s.value?n.__ownerID?(n._root=t,n._tail=e,n.__hash=void 0,n.__altered=!0,n):jt(n._origin,n._capacity,n._level,t,e):n},O.prototype.remove=function(e){return this.has(e)?0===e?this.shift():e===this.size-1?this.pop():this.splice(e,1):this},O.prototype.insert=function(e,t){return this.splice(e,0,t)},O.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=w,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):Pt()},O.prototype.push=function(){var n=arguments,r=this.size;return this.withMutations(function(e){Rt(e,0,r+n.length);for(var t=0;t>>t&S;if(r>=this.array.length)return new _t([],e);var o,s=0==r;if(0>>t&S;if(o>=this.array.length)return this;if(0>r,E<(l=1+(g-o>>r))&&(l=E),function(){for(;;){if(s){var e=s();if(e!==Ot)return e;s=null}if(a===l)return Ot;e=f?--l:a++;s=b(i&&i[e],r-w,o+(e<>>n&S,c=e&&l=Dt(e._capacity))return e._tail;if(t<1<>>r&S],r-=w;return n}}function Rt(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new W,o=e._origin,s=e._capacity,i=o+t,t=void 0===n?s:n<0?s+n:o+n;if(i===o&&t===s)return e;if(t<=i)return e.clear();for(var a=e._level,l=e._root,c=0;i+c<0;)l=new _t(l&&l.array.length?[void 0,l]:[],r),c+=1<<(a+=w);c&&(i+=c,o+=c,t+=c,s+=c);for(var u=Dt(s),p=Dt(t);1<>>f&S,d=d.array[m]=It(d.array[m],r);d.array[u>>>w&S]=n}if(t>>a&S;if(g!=p>>>a&S)break;g&&(c+=(1<o&&(o=a.size),u(i)||(a=a.map(function(e){return Ce(e)})),r.push(a)}return gt(e=o>e.size?e.setSize(o):e,t,r)}function Dt(e){return e>>w<=E&&i.size>=2*s.size?(r=(o=i.filter(function(e,t){return void 0!==e&&a!==t})).toKeyedSeq().map(function(e){return e[0]}).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=s.remove(t),o=a===i.size-1?i.pop():i.set(a,void 0))}else if(l){if(n===i.get(a)[1])return e;r=s,o=i.set(a,[t,n])}else r=s.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Bt(r,o)}function $t(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function zt(e){this._iter=e,this.size=e.size}function Ut(e){this._iter=e,this.size=e.size}function Vt(e){this._iter=e,this.size=e.size}function Kt(o){var e=sn(o);return e._iter=o,e.size=o.size,e.flip=function(){return o},e.reverse=function(){var e=o.reverse.apply(this);return e.flip=function(){return o.reverse()},e},e.has=function(e){return o.includes(e)},e.includes=function(e){return o.has(e)},e.cacheResult=an,e.__iterateUncached=function(n,e){var r=this;return o.__iterate(function(e,t){return!1!==n(t,e,r)},e)},e.__iteratorUncached=function(e,t){var n;return e===ne?(n=o.__iterator(e,t),new d(function(){var e,t=n.next();return t.done||(e=t.value[0],t.value[0]=t.value[1],t.value[1]=e),t})):o.__iterator(e===h?te:h,t)},e}function Wt(s,i,a){var e=sn(s);return e.size=s.size,e.has=function(e){return s.has(e)},e.get=function(e,t){var n=s.get(e,x);return n===x?t:i.call(a,n,e,s)},e.__iterateUncached=function(r,e){var o=this;return s.__iterate(function(e,t,n){return!1!==r(i.call(a,e,t,n),t,o)},e)},e.__iteratorUncached=function(r,e){var o=s.__iterator(ne,e);return new d(function(){var e=o.next();if(e.done)return e;var t=e.value,n=t[0];return f(r,n,i.call(a,t[1],n,s),e)})},e}function Jt(o,n){var e=sn(o);return e._iter=o,e.size=o.size,e.reverse=function(){return o},o.flip&&(e.flip=function(){var e=Kt(o);return e.reverse=function(){return o.flip()},e}),e.get=function(e,t){return o.get(n?e:-1-e,t)},e.has=function(e){return o.has(n?e:-1-e)},e.includes=function(e){return o.includes(e)},e.cacheResult=an,e.__iterate=function(n,e){var r=this;return o.__iterate(function(e,t){return n(e,t,r)},!e)},e.__iterator=function(e,t){return o.__iterator(e,!t)},e}function Ht(i,a,l,c){var e=sn(i);return c&&(e.has=function(e){var t=i.get(e,x);return t!==x&&!!a.call(l,t,e,i)},e.get=function(e,t){var n=i.get(e,x);return n!==x&&a.call(l,n,e,i)?n:t}),e.__iterateUncached=function(r,e){var o=this,s=0;return i.__iterate(function(e,t,n){if(a.call(l,e,t,n))return s++,r(e,c?t:s-1,o)},e),s},e.__iteratorUncached=function(r,e){var o=i.__iterator(ne,e),s=0;return new d(function(){for(;;){var e=o.next();if(e.done)return e;var t=e.value,n=t[0],t=t[1];if(a.call(l,t,n,i))return f(r,c?n:s++,t,e)}})},e}function Gt(a,e,t,l){var n=a.size;if(void 0!==e&&(e|=0),void 0!==t&&(t===1/0?t=n:t|=0),X(e,t,n))return a;var c=Q(e,n),n=Z(t,n);if(c!=c||n!=n)return Gt(a.toSeq().cacheResult(),e,t,l);var u,e=n-c,t=(e==e&&(u=e<0?0:e),sn(a));return t.size=0===u?u:a.size&&u||void 0,!l&&Ee(a)&&0<=u&&(t.get=function(e,t){return 0<=(e=G(this,e))&&eu)return m();var e=n.next();return l||t===h?e:f(t,o-1,t===te?void 0:e.value[1],e)})},t}function Yt(t,c,u,p){var e=sn(t);return e.__iterateUncached=function(r,e){var o=this;if(e)return this.cacheResult().__iterate(r,e);var s=!0,i=0;return t.__iterate(function(e,t,n){if(!s||!(s=c.call(u,e,t,n)))return i++,r(e,p?t:i-1,o)}),i},e.__iteratorUncached=function(o,e){var s=this;if(e)return this.cacheResult().__iterator(o,e);var i=t.__iterator(ne,e),a=!0,l=0;return new d(function(){var e;do{if((e=i.next()).done)return p||o===h?e:f(o,l++,o===te?void 0:e.value[1],e);var t=e.value,n=t[0],r=t[1]}while(a=a&&c.call(u,r,n,s));return o===ne?e:f(o,n,r,e)})},e}function Xt(e,l,c){var t=sn(e);return t.__iterateUncached=function(s,t){var i=0,a=!1;return function n(e,r){var o=this;e.__iterate(function(e,t){return(!l||r>>-15,461845907),t=Fe(t<<13|t>>>-13,5),t=Fe((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=Be((t=Fe(t^t>>>13,3266489909))^t>>>16)}(e.__iterate(n?t?function(e,t){r=31*r+Ln(_(e),_(t))|0}:function(e,t){r=r+Ln(_(e),_(t))|0}:t?function(e){r=31*r+_(e)|0}:function(e){r=r+_(e)|0}),r)}(this))}});var t=l.prototype,Nn=(t[L]=!0,t[se]=t.values,t.__toJS=t.toArray,t.__toStringMapper=Dn,t.inspect=t.toSource=function(){return this.toString()},t.chain=t.flatMap,t.contains=t.includes,Pn(a,{flip:function(){return P(this,Kt(this))},mapEntries:function(n,r){var o=this,s=0;return P(this,this.toSeq().map(function(e,t){return n.call(r,[t,e],s++,o)}).fromEntrySeq())},mapKeys:function(n,r){var o=this;return P(this,this.toSeq().flip().map(function(e,t){return n.call(r,e,t,o)}).flip())}}),a.prototype);function In(e,t){return t}function Tn(e,t){return[t,e]}function Rn(e){return function(){return!e.apply(this,arguments)}}function Mn(e){return function(){return-e.apply(this,arguments)}}function Dn(e){return"string"==typeof e?JSON.stringify(e):String(e)}function Fn(){return J(arguments)}function Bn(e,t){return e>2)|0}return Nn[q]=!0,Nn[se]=t.entries,Nn.__toJS=t.toObject,Nn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+Dn(e)},Pn(D,{toKeyedSeq:function(){return new $t(this,!1)},filter:function(e,t){return P(this,Ht(this,e,t,!1))},findIndex:function(e,t){e=this.findEntry(e,t);return e?e[0]:-1},indexOf:function(e){e=this.keyOf(e);return void 0===e?-1:e},lastIndexOf:function(e){e=this.lastKeyOf(e);return void 0===e?-1:e},reverse:function(){return P(this,Jt(this,!1))},slice:function(e,t){return P(this,Gt(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=Q(e,e<0?this.count():this.size);var r=this.slice(0,e);return P(this,1===n?r:r.concat(J(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){e=this.findLastEntry(e,t);return e?e[0]:-1},first:function(){return this.get(0)},flatten:function(e){return P(this,Xt(this,e,!1))},get:function(n,e){return(n=G(this,n))<0||this.size===1/0||void 0!==this.size&&n>this.size?e:this.find(function(e,t){return t===n},void 0,e)},has:function(e){return 0<=(e=G(this,e))&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){var n;t&&(e.super_=t,(n=function(){}).prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e)}},5419:e=>{e.exports=function(e,t,n,r){var o,s,r=new Blob(void 0!==r?[r,e]:[e],{type:n||"application/octet-stream"});void 0!==window.navigator.msSaveBlob?window.navigator.msSaveBlob(r,t):(o=(window.URL&&window.URL.createObjectURL?window.URL:window.webkitURL).createObjectURL(r),(s=document.createElement("a")).style.display="none",s.href=o,s.setAttribute("download",t),void 0===s.download&&s.setAttribute("target","_blank"),document.body.appendChild(s),s.click(),setTimeout(function(){document.body.removeChild(s),window.URL.revokeObjectURL(o)},200))}},20181:(e,t,n)=>{function v(){return c.Date.now()}var r=/^\s+|\s+$/g,o=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,i=/^0o[0-7]+$/i,a=parseInt,n="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,l="object"==typeof self&&self&&self.Object===Object&&self,c=n||l||Function("return this")(),u=Object.prototype.toString,b=Math.max,w=Math.min;function E(e){var t=typeof e;return e&&("object"==t||"function"==t)}function S(e){if("number"==typeof e)return e;if("symbol"==typeof(n=e)||(t=n)&&"object"==typeof t&&"[object Symbol]"==u.call(n))return NaN;var t;if("string"!=typeof(e=E(e)?E(t="function"==typeof e.valueOf?e.valueOf():e)?t+"":t:e))return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||i.test(e)?a(e.slice(2),n?2:8):o.test(e)?NaN:+e}e.exports=function(r,n,e){var o,s,i,a,l,c,u=0,p=!1,h=!1,t=!0;if("function"!=typeof r)throw new TypeError("Expected a function");function d(e){var t=o,n=s;return o=s=void 0,u=e,a=r.apply(n,t)}function f(e){var t=e-c;return void 0===c||n<=t||t<0||h&&i<=e-u}function m(){var e,t=v();if(f(t))return g(t);l=setTimeout(m,(e=n-((t=t)-c),h?w(e,i-(t-u)):e))}function g(e){return l=void 0,t&&o?d(e):(o=s=void 0,a)}function y(){var e=v(),t=f(e);if(o=arguments,s=this,c=e,t){if(void 0===l)return u=e=c,l=setTimeout(m,n),p?d(e):a;if(h)return l=setTimeout(m,n),d(c)}return void 0===l&&(l=setTimeout(m,n)),a}return n=S(n)||0,E(e)&&(p=!!e.leading,i=(h="maxWait"in e)?b(S(e.maxWait)||0,n):i,t="trailing"in e?!!e.trailing:t),y.cancel=function(){void 0!==l&&clearTimeout(l),o=c=s=l=void(u=0)},y.flush=function(){return void 0===l?a:g(v())},y}},55580:(e,t,n)=>{n=n(56110)(n(9325),"DataView");e.exports=n},21549:(e,t,n)=>{var r=n(22032),o=n(63862),s=n(66721),i=n(12749),n=n(35749);function a(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(39344),n=n(94033);function o(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}o.prototype=r(n.prototype),e.exports=o.prototype.constructor=o},80079:(e,t,n)=>{var r=n(63702),o=n(70080),s=n(24739),i=n(48655),n=n(31175);function a(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(39344),n=n(94033);function o(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}o.prototype=r(n.prototype),e.exports=o.prototype.constructor=o},68223:(e,t,n)=>{n=n(56110)(n(9325),"Map");e.exports=n},53661:(e,t,n)=>{var r=n(63040),o=n(17670),s=n(90289),i=n(4509),n=n(72949);function a(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{n=n(56110)(n(9325),"Promise");e.exports=n},76545:(e,t,n)=>{n=n(56110)(n(9325),"Set");e.exports=n},38859:(e,t,n)=>{var r=n(53661),o=n(31380),n=n(51459);function s(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t{var r=n(80079),o=n(51420),s=n(90938),i=n(63605),a=n(29817),n=n(80945);function l(e){e=this.__data__=new r(e);this.size=e.size}l.prototype.clear=o,l.prototype.delete=s,l.prototype.get=i,l.prototype.has=a,l.prototype.set=n,e.exports=l},51873:(e,t,n)=>{n=n(9325).Symbol;e.exports=n},37828:(e,t,n)=>{n=n(9325).Uint8Array;e.exports=n},28303:(e,t,n)=>{n=n(56110)(n(9325),"WeakMap");e.exports=n},91033:e=>{e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},83729:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n{var r=n(96131);e.exports=function(e,t){return!(null==e||!e.length)&&-1{var u=n(78096),p=n(72428),h=n(56449),d=n(3656),f=n(30361),m=n(37167),g=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n,r=h(e),o=!r&&p(e),s=!r&&!o&&d(e),i=!r&&!o&&!s&&m(e),a=r||o||s||i,l=a?u(e.length,String):[],c=l.length;for(n in e)!t&&!g.call(e,n)||a&&("length"==n||s&&("offset"==n||"parent"==n)||i&&("buffer"==n||"byteLength"==n||"byteOffset"==n)||f(n,c))||l.push(n);return l}},34932:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n{e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++n{e.exports=function(e,t,n,r){var o=-1,s=null==e?0:e.length;for(r&&s&&(n=e[++o]);++o{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e){return e.split("")}},1733:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},87805:(e,t,n)=>{var r=n(43360),o=n(75288);e.exports=function(e,t,n){(void 0===n||o(e[t],n))&&(void 0!==n||t in e)||r(e,t,n)}},16547:(e,t,n)=>{var o=n(43360),s=n(75288),i=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var r=e[t];i.call(e,t)&&s(r,n)&&(void 0!==n||t in e)||o(e,t,n)}},26025:(e,t,n)=>{var r=n(75288);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},74733:(e,t,n)=>{var r=n(21791),o=n(95950);e.exports=function(e,t){return e&&r(t,o(t),e)}},43838:(e,t,n)=>{var r=n(21791),o=n(37241);e.exports=function(e,t){return e&&r(t,o(t),e)}},43360:(e,t,n)=>{var r=n(93243);e.exports=function(e,t,n){"__proto__"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},9999:(e,t,n)=>{var f=n(37217),m=n(83729),g=n(16547),y=n(74733),v=n(43838),b=n(93290),w=n(23007),E=n(92271),S=n(48948),x=n(50002),_=n(83349),k=n(5861),A=n(76189),O=n(77199),C=n(35529),j=n(56449),P=n(3656),N=n(87730),I=n(23805),T=n(38440),R=n(95950),M=n(37241),D="[object Arguments]",F="[object Function]",B="[object Object]",L={};L[D]=L["[object Array]"]=L["[object ArrayBuffer]"]=L["[object DataView]"]=L["[object Boolean]"]=L["[object Date]"]=L["[object Float32Array]"]=L["[object Float64Array]"]=L["[object Int8Array]"]=L["[object Int16Array]"]=L["[object Int32Array]"]=L["[object Map]"]=L["[object Number]"]=L[B]=L["[object RegExp]"]=L["[object Set]"]=L["[object String]"]=L["[object Symbol]"]=L["[object Uint8Array]"]=L["[object Uint8ClampedArray]"]=L["[object Uint16Array]"]=L["[object Uint32Array]"]=!0,L["[object Error]"]=L[F]=L["[object WeakMap]"]=!1,e.exports=function n(r,o,s,e,t,i){var a,l=1&o,c=2&o,u=4&o;if(void 0!==(a=s?t?s(r,e,t,i):s(r):a))return a;if(!I(r))return r;e=j(r);if(e){if(a=A(r),!l)return w(r,a)}else{var p=k(r),h=p==F||"[object GeneratorFunction]"==p;if(P(r))return b(r,l);if(p==B||p==D||h&&!t){if(a=c||h?{}:C(r),!l)return c?S(r,v(a,r)):E(r,y(a,r))}else{if(!L[p])return t?r:{};a=O(r,p,l)}}h=(i=i||new f).get(r);if(h)return h;i.set(r,a),T(r)?r.forEach(function(e){a.add(n(e,o,s,e,r,i))}):N(r)&&r.forEach(function(e,t){a.set(t,n(e,o,s,t,r,i))});var d=e?void 0:(u?c?_:x:c?M:R)(r);return m(d||r,function(e,t){d&&(e=r[t=e]),g(a,t,n(e,o,s,t,r,i))}),a}},39344:(e,t,n)=>{var r=n(23805),o=Object.create;function s(){}e.exports=function(e){if(!r(e))return{};if(o)return o(e);s.prototype=e;e=new s;return s.prototype=void 0,e}},80909:(e,t,n)=>{var r=n(30641),n=n(38329)(r);e.exports=n},2523:e=>{e.exports=function(e,t,n,r){for(var o=e.length,s=n+(r?1:-1);r?s--:++s{var c=n(14528),u=n(45891);e.exports=function e(t,n,r,o,s){var i=-1,a=t.length;for(r=r||u,s=s||[];++i{n=n(83221)();e.exports=n},30641:(e,t,n)=>{var r=n(86649),o=n(95950);e.exports=function(e,t){return e&&r(e,t,o)}},47422:(e,t,n)=>{var o=n(31769),s=n(77797);e.exports=function(e,t){for(var n=0,r=(t=o(t,e)).length;null!=e&&n{var r=n(14528),o=n(56449);e.exports=function(e,t,n){t=t(e);return o(e)?t:r(t,n(e))}},72552:(e,t,n)=>{var r=n(51873),o=n(659),s=n(59350),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":(i&&i in Object(e)?o:s)(e)}},28077:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},96131:(e,t,n)=>{var r=n(2523),o=n(85463),s=n(76959);e.exports=function(e,t,n){return t==t?s(e,t,n):r(e,o,n)}},27534:(e,t,n)=>{var r=n(72552),o=n(40346);e.exports=function(e){return o(e)&&"[object Arguments]"==r(e)}},60270:(e,t,n)=>{var i=n(87068),a=n(40346);e.exports=function e(t,n,r,o,s){return t===n||(null==t||null==n||!a(t)&&!a(n)?t!=t&&n!=n:i(t,n,r,o,e,s))}},87068:(e,t,n)=>{var p=n(37217),h=n(25911),d=n(21986),f=n(50689),m=n(5861),g=n(56449),y=n(3656),v=n(37167),b="[object Arguments]",w="[object Array]",E="[object Object]",S=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,r,o,s){var i=g(e),a=g(t),l=i?w:m(e),a=a?w:m(t),c=(l=l==b?E:l)==E,u=(a=a==b?E:a)==E,a=l==a;if(a&&y(e)){if(!y(t))return!1;c=!(i=!0)}if(a&&!c)return s=s||new p,i||v(e)?h(e,t,n,r,o,s):d(e,t,l,n,r,o,s);if(!(1&n)){i=c&&S.call(e,"__wrapped__"),l=u&&S.call(t,"__wrapped__");if(i||l)return o(i?e.value():e,l?t.value():t,n,r,s=s||new p)}return!!a&&(s=s||new p,f(e,t,n,r,o,s))}},29172:(e,t,n)=>{var r=n(5861),o=n(40346);e.exports=function(e){return o(e)&&"[object Map]"==r(e)}},41799:(e,t,n)=>{var d=n(37217),f=n(60270);e.exports=function(e,t,n,r){var o=n.length,s=o,i=!r;if(null==e)return!s;for(e=Object(e);o--;){var a=n[o];if(i&&a[2]?a[1]!==e[a[0]]:!(a[0]in e))return!1}for(;++o{e.exports=function(e){return e!=e}},45083:(e,t,n)=>{var r=n(1882),o=n(87296),s=n(23805),i=n(47473),a=/^\[object .+?Constructor\]$/,n=Function.prototype,l=Object.prototype,n=n.toString,l=l.hasOwnProperty,c=RegExp("^"+n.call(l).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!s(e)||o(e))&&(r(e)?c:a).test(i(e))}},16038:(e,t,n)=>{var r=n(5861),o=n(40346);e.exports=function(e){return o(e)&&"[object Set]"==r(e)}},4901:(e,t,n)=>{var r=n(72552),o=n(30294),s=n(40346),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return s(e)&&o(e.length)&&!!i[r(e)]}},15389:(e,t,n)=>{var r=n(93663),o=n(87978),s=n(83488),i=n(56449),a=n(50583);e.exports=function(e){return"function"==typeof e?e:null==e?s:"object"==typeof e?i(e)?o(e[0],e[1]):r(e):a(e)}},88984:(e,t,n)=>{var r=n(55527),o=n(3650),s=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t,n=[];for(t in Object(e))s.call(e,t)&&"constructor"!=t&&n.push(t);return n}},72903:(e,t,n)=>{var o=n(23805),s=n(55527),i=n(90181),a=Object.prototype.hasOwnProperty;e.exports=function(e){if(!o(e))return i(e);var t,n=s(e),r=[];for(t in e)("constructor"!=t||!n&&a.call(e,t))&&r.push(t);return r}},94033:e=>{e.exports=function(){}},93663:(e,t,n)=>{var r=n(41799),o=n(10776),s=n(67197);e.exports=function(t){var n=o(t);return 1==n.length&&n[0][2]?s(n[0][0],n[0][1]):function(e){return e===t||r(e,t,n)}}},87978:(e,t,n)=>{var o=n(60270),s=n(58156),i=n(80631),a=n(28586),l=n(30756),c=n(67197),u=n(77797);e.exports=function(n,r){return a(n)&&l(r)?c(u(n),r):function(e){var t=s(e,n);return void 0===t&&t===r?i(e,n):o(r,t,3)}}},85250:(e,t,n)=>{var c=n(37217),u=n(87805),p=n(86649),h=n(42824),d=n(23805),f=n(37241),m=n(14974);e.exports=function r(o,s,i,a,l){o!==s&&p(s,function(e,t){var n;l=l||new c,d(e)?h(o,s,t,i,r,a,l):(n=a?a(m(o,t),e,t+"",o,s,l):void 0,u(o,t,n=void 0===n?e:n))},f)}},42824:(e,t,n)=>{var d=n(87805),f=n(93290),m=n(71961),g=n(23007),y=n(35529),v=n(72428),b=n(56449),w=n(83693),E=n(3656),S=n(1882),x=n(23805),_=n(11331),k=n(37167),A=n(14974),O=n(69884);e.exports=function(e,t,n,r,o,s,i){var a,l,c,u=A(e,n),p=A(t,n),h=i.get(p);h?d(e,n,h):((t=void 0===(h=s?s(u,p,n+"",e,t,i):void 0))&&(l=!(a=b(p))&&E(p),c=!a&&!l&&k(p),h=p,a||l||c?h=b(u)?u:w(u)?g(u):l?f(p,!(t=!1)):c?m(p,!(t=!1)):[]:_(p)||v(p)?v(h=u)?h=O(u):x(u)&&!S(u)||(h=y(p)):t=!1),t&&(i.set(p,h),o(h,p,r,s,i),i.delete(p)),d(e,n,h))}},47237:e=>{e.exports=function(t){return function(e){return null==e?void 0:e[t]}}},17255:(e,t,n)=>{var r=n(47422);e.exports=function(t){return function(e){return r(e,t)}}},54552:e=>{e.exports=function(t){return function(e){return null==t?void 0:t[e]}}},85558:e=>{e.exports=function(e,r,o,s,t){return t(e,function(e,t,n){o=s?(s=!1,e):r(o,e,t,n)}),o}},69302:(e,t,n)=>{var r=n(83488),o=n(56757),s=n(32865);e.exports=function(e,t){return s(o(e,t,r),e+"")}},73170:(e,t,n)=>{var p=n(16547),h=n(31769),d=n(30361),f=n(23805),m=n(77797);e.exports=function(e,t,n,r){if(!f(e))return e;for(var o=-1,s=(t=h(t,e)).length,i=s-1,a=e;null!=a&&++o{var r=n(83488),o=n(48152);e.exports=o?function(e,t){return o.set(e,t),e}:r},19570:(e,t,n)=>{var r=n(37334),o=n(93243),n=n(83488);e.exports=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:n},25160:e=>{e.exports=function(e,t,n){var r=-1,o=e.length;(n=o>>0,t>>>=0;for(var s=Array(o);++r{var s=n(80909);e.exports=function(e,r){var o;return s(e,function(e,t,n){return!(o=r(e,t,n))}),!!o}},78096:e=>{e.exports=function(e,t){for(var n=-1,r=Array(e);++n{var r=n(51873),o=n(34932),s=n(56449),i=n(44394),n=r?r.prototype:void 0,a=n?n.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(s(t))return o(t,e)+"";if(i(t))return a?a.call(t):"";var n=t+"";return"0"==n&&1/t==-1/0?"-0":n}},54128:(e,t,n)=>{var r=n(31800),o=/^\s+/;e.exports=function(e){return e&&e.slice(0,r(e)+1).replace(o,"")}},27301:e=>{e.exports=function(t){return function(e){return t(e)}}},19931:(e,t,n)=>{var r=n(31769),o=n(68090),s=n(68969),i=n(77797);e.exports=function(e,t){return t=r(t,e),null==(e=s(e,t))||delete e[i(o(t))]}},51234:e=>{e.exports=function(e,t,n){for(var r=-1,o=e.length,s=t.length,i={};++r{e.exports=function(e,t){return e.has(t)}},31769:(e,t,n)=>{var r=n(56449),o=n(28586),s=n(61802),i=n(13222);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:s(i(e))}},28754:(e,t,n)=>{var o=n(25160);e.exports=function(e,t,n){var r=e.length;return n=void 0===n?r:n,!t&&r<=n?e:o(e,t,n)}},49653:(e,t,n)=>{var r=n(37828);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},93290:(e,t,n)=>{e=n.nmd(e);var n=n(9325),t=t&&!t.nodeType&&t,r=t&&e&&!e.nodeType&&e,r=r&&r.exports===t?n.Buffer:void 0,o=r?r.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();t=e.length,t=o?o(t):new e.constructor(t);return e.copy(t),t}},76169:(e,t,n)=>{var r=n(49653);e.exports=function(e,t){t=t?r(e.buffer):e.buffer;return new e.constructor(t,e.byteOffset,e.byteLength)}},73201:e=>{var n=/\w*$/;e.exports=function(e){var t=new e.constructor(e.source,n.exec(e));return t.lastIndex=e.lastIndex,t}},93736:(e,t,n)=>{var n=n(51873),n=n?n.prototype:void 0,r=n?n.valueOf:void 0;e.exports=function(e){return r?Object(r.call(e)):{}}},71961:(e,t,n)=>{var r=n(49653);e.exports=function(e,t){t=t?r(e.buffer):e.buffer;return new e.constructor(t,e.byteOffset,e.length)}},91596:e=>{var h=Math.max;e.exports=function(e,t,n,r){for(var o=-1,s=e.length,i=n.length,a=-1,l=t.length,c=h(s-i,0),u=Array(l+c),p=!r;++a{var f=Math.max;e.exports=function(e,t,n,r){for(var o=-1,s=e.length,i=-1,a=n.length,l=-1,c=t.length,u=f(s-a,0),p=Array(u+c),h=!r;++o{e.exports=function(e,t){var n=-1,r=e.length;for(t=t||Array(r);++n{var c=n(16547),u=n(43360);e.exports=function(e,t,n,r){var o=!n;n=n||{};for(var s=-1,i=t.length;++s{var r=n(21791),o=n(4664);e.exports=function(e,t){return r(e,o(e),t)}},48948:(e,t,n)=>{var r=n(21791),o=n(86375);e.exports=function(e,t){return r(e,o(e),t)}},55481:(e,t,n)=>{n=n(9325)["__core-js_shared__"];e.exports=n},58523:e=>{e.exports=function(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}},20999:(e,t,n)=>{var r=n(69302),l=n(36800);e.exports=function(a){return r(function(e,t){var n=-1,r=t.length,o=1{var a=n(64894);e.exports=function(s,i){return function(e,t){if(null==e)return e;if(!a(e))return s(e,t);for(var n=e.length,r=i?n:-1,o=Object(e);(i?r--:++r{e.exports=function(l){return function(e,t,n){for(var r=-1,o=Object(e),s=n(e),i=s.length;i--;){var a=s[l?i:++r];if(!1===t(o[a],a,o))break}return e}}},11842:(e,t,n)=>{var s=n(82819),i=n(9325);e.exports=function(t,e,n){var r=1&e,o=s(t);return function e(){return(this&&this!==i&&this instanceof e?o:t).apply(r?n:this,arguments)}}},12507:(e,t,n)=>{var o=n(28754),s=n(49698),i=n(63912),a=n(13222);e.exports=function(r){return function(e){e=a(e);var t=s(e)?i(e):void 0,n=t?t[0]:e.charAt(0),t=t?o(t,1).join(""):e.slice(1);return n[r]()+t}}},45539:(e,t,n)=>{var r=n(40882),o=n(50828),s=n(66645),i=RegExp("['’]","g");e.exports=function(t){return function(e){return r(s(o(e).replace(i,"")),t,"")}}},82819:(e,t,n)=>{var o=n(39344),s=n(23805);e.exports=function(r){return function(){var e=arguments;switch(e.length){case 0:return new r;case 1:return new r(e[0]);case 2:return new r(e[0],e[1]);case 3:return new r(e[0],e[1],e[2]);case 4:return new r(e[0],e[1],e[2],e[3]);case 5:return new r(e[0],e[1],e[2],e[3],e[4]);case 6:return new r(e[0],e[1],e[2],e[3],e[4],e[5]);case 7:return new r(e[0],e[1],e[2],e[3],e[4],e[5],e[6])}var t=o(r.prototype),n=r.apply(t,e);return s(n)?n:t}}},77078:(e,t,n)=>{var c=n(91033),r=n(82819),u=n(37471),p=n(18073),h=n(11287),d=n(36306),f=n(9325);e.exports=function(s,i,a){var l=r(s);return function e(){for(var t=arguments.length,n=Array(t),r=t,o=h(e);r--;)n[r]=arguments[r];o=t<3&&n[0]!==o&&n[t-1]!==o?[]:d(n,o);return(t-=o.length){var i=n(15389),a=n(64894),l=n(95950);e.exports=function(s){return function(e,t,n){var r,o=Object(e),t=(a(e)||(r=i(t,3),e=l(e),t=function(e){return r(o[e],e,o)}),s(e,t,n));return-1{var x=n(91596),_=n(53320),k=n(58523),A=n(82819),O=n(18073),C=n(11287),j=n(68294),P=n(36306),N=n(9325);e.exports=function i(a,l,c,u,p,h,d,f,m,g){var y=128&l,v=1&l,b=2&l,w=24&l,E=512&l,S=b?void 0:A(a);return function e(){for(var t=arguments.length,n=Array(t),r=t;r--;)n[r]=arguments[r];if(w&&(o=C(e),s=k(n,o)),u&&(n=x(n,u,p,w)),h&&(n=_(n,h,d,w)),t-=s,w&&t{var h=n(91033),r=n(82819),d=n(9325);e.exports=function(a,e,l,c){var u=1&e,p=r(a);return function e(){for(var t=-1,n=arguments.length,r=-1,o=c.length,s=Array(o+n),i=this&&this!==d&&this instanceof e?p:a;++r{var p=n(85087),h=n(54641),d=n(70981);e.exports=function(e,t,n,r,o,s,i,a,l,c){var u=8&t,o=(4&(t=(t|(u?32:64))&~(u?64:32))||(t&=-4),[e,t,o,u?s:void 0,u?i:void 0,u?void 0:s,u?void 0:i,a,l,c]),s=n.apply(void 0,o);return p(e)&&h(s,o),s.placeholder=r,d(s,e,t)}},66977:(e,t,n)=>{var d=n(68882),f=n(11842),m=n(77078),g=n(37471),y=n(24168),v=n(37381),b=n(3209),w=n(54641),E=n(70981),S=n(61489),x=Math.max;e.exports=function(e,t,n,r,o,s,i,a){var l=2&t;if(!l&&"function"!=typeof e)throw new TypeError("Expected a function");var c,u=r?r.length:0,p=(u||(t&=-97,r=o=void 0),i=void 0===i?i:x(S(i),0),a=void 0===a?a:S(a),u-=o?o.length:0,64&t&&(h=r,c=o,r=o=void 0),l?void 0:v(e)),h=[e,t,n,r,o,h,c,s,i,a];return p&&b(h,p),e=h[0],t=h[1],n=h[2],r=h[3],o=h[4],!(a=h[9]=void 0===h[9]?l?0:e.length:x(h[9]-u,0))&&24&t&&(t&=-25),c=t&&1!=t?8==t||16==t?m(e,t,a):32!=t&&33!=t||o.length?g.apply(void 0,h):y(e,t,n,r):f(e,t,n),E((p?d:w)(c,h),e,t)}},53138:(e,t,n)=>{var r=n(11331);e.exports=function(e){return r(e)?void 0:e}},24647:(e,t,n)=>{n=n(54552)({"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"});e.exports=n},93243:(e,t,n)=>{var r=n(56110),n=function(){try{var e=r(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=n},25911:(e,t,n)=>{var g=n(38859),y=n(14248),v=n(19219);e.exports=function(e,t,n,r,o,s){var i=1&n,a=e.length,l=t.length;if(a!=l&&!(i&&a{var r=n(51873),c=n(37828),u=n(75288),p=n(25911),h=n(20317),d=n(84247),n=r?r.prototype:void 0,f=n?n.valueOf:void 0;e.exports=function(e,t,n,r,o,s,i){switch(n){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!s(new c(e),new c(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return u(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var a=h;case"[object Set]":a=a||d;if(e.size!=t.size&&!(1&r))return!1;var l=i.get(e);if(l)return l==t;r|=2,i.set(e,t);l=p(a(e),a(t),r,o,s,i);return i.delete(e),l;case"[object Symbol]":if(f)return f.call(e)==f.call(t)}return!1}},50689:(e,t,n)=>{var v=n(50002),b=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,r,o,s){var i=1&n,a=v(e),l=a.length;if(l!=v(t).length&&!i)return!1;for(var c=l;c--;){var u=a[c];if(!(i?u in t:b.call(t,u)))return!1}var p=s.get(e),h=s.get(t);if(p&&h)return p==t&&h==e;var d=!0;s.set(e,t),s.set(t,e);for(var f=i;++c{var r=n(35970),o=n(56757),s=n(32865);e.exports=function(e){return s(o(e,void 0,r),e+"")}},34840:(e,t,n)=>{n="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=n},50002:(e,t,n)=>{var r=n(82199),o=n(4664),s=n(95950);e.exports=function(e){return r(e,s,o)}},83349:(e,t,n)=>{var r=n(82199),o=n(86375),s=n(37241);e.exports=function(e){return r(e,s,o)}},37381:(e,t,n)=>{var r=n(48152),n=n(63950);e.exports=r?function(e){return r.get(e)}:n},62284:(e,t,n)=>{var i=n(84629),a=Object.prototype.hasOwnProperty;e.exports=function(e){for(var t=e.name+"",n=i[t],r=a.call(i,t)?n.length:0;r--;){var o=n[r],s=o.func;if(null==s||s==e)return o.name}return t}},11287:e=>{e.exports=function(e){return e.placeholder}},12651:(e,t,n)=>{var r=n(74218);e.exports=function(e,t){e=e.__data__;return r(t)?e["string"==typeof t?"string":"hash"]:e.map}},10776:(e,t,n)=>{var s=n(30756),i=n(95950);e.exports=function(e){for(var t=i(e),n=t.length;n--;){var r=t[n],o=e[r];t[n]=[r,o,s(o)]}return t}},56110:(e,t,n)=>{var r=n(45083),o=n(10392);e.exports=function(e,t){e=o(e,t);return r(e)?e:void 0}},28879:(e,t,n)=>{n=n(74335)(Object.getPrototypeOf,Object);e.exports=n},659:(e,t,n)=>{var n=n(51873),r=Object.prototype,s=r.hasOwnProperty,i=r.toString,a=n?n.toStringTag:void 0;e.exports=function(e){var t=s.call(e,a),n=e[a];try{var r=!(e[a]=void 0)}catch(e){}var o=i.call(e);return r&&(t?e[a]=n:delete e[a]),o}},4664:(e,t,n)=>{var r=n(79770),n=n(63345),o=Object.prototype.propertyIsEnumerable,s=Object.getOwnPropertySymbols;e.exports=s?function(t){return null==t?[]:(t=Object(t),r(s(t),function(e){return o.call(t,e)}))}:n},86375:(e,t,n)=>{var r=n(14528),o=n(28879),s=n(4664),n=n(63345),n=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)r(t,s(e)),e=o(e);return t}:n;e.exports=n},5861:(e,t,n)=>{var r=n(55580),o=n(68223),s=n(32804),i=n(76545),a=n(28303),l=n(72552),c=n(47473),u="[object Map]",p="[object Promise]",h="[object Set]",d="[object WeakMap]",f="[object DataView]",m=c(r),g=c(o),y=c(s),v=c(i),b=c(a),n=l;(r&&n(new r(new ArrayBuffer(1)))!=f||o&&n(new o)!=u||s&&n(s.resolve())!=p||i&&n(new i)!=h||a&&n(new a)!=d)&&(n=function(e){var t=l(e),e="[object Object]"==t?e.constructor:void 0,e=e?c(e):"";if(e)switch(e){case m:return f;case g:return u;case y:return p;case v:return h;case b:return d}return t}),e.exports=n},10392:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},75251:e=>{var t=/\{\n\/\* \[wrapped with (.+)\] \*/,n=/,? & /;e.exports=function(e){e=e.match(t);return e?e[1].split(n):[]}},49326:(e,t,n)=>{var a=n(31769),l=n(72428),c=n(56449),u=n(30361),p=n(30294),h=n(77797);e.exports=function(e,t,n){for(var r=-1,o=(t=a(t,e)).length,s=!1;++r{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},45434:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},22032:(e,t,n)=>{var r=n(81042);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},63862:e=>{e.exports=function(e){e=this.has(e)&&delete this.__data__[e];return this.size-=e?1:0,e}},66721:(e,t,n)=>{var r=n(81042),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t,n=this.__data__;return r?"__lodash_hash_undefined__"===(t=n[e])?void 0:t:o.call(n,e)?n[e]:void 0}},12749:(e,t,n)=>{var r=n(81042),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},35749:(e,t,n)=>{var r=n(81042);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?"__lodash_hash_undefined__":t,this}},76189:e=>{var r=Object.prototype.hasOwnProperty;e.exports=function(e){var t=e.length,n=new e.constructor(t);return t&&"string"==typeof e[0]&&r.call(e,"index")&&(n.index=e.index,n.input=e.input),n}},77199:(e,t,n)=>{var o=n(49653),s=n(76169),i=n(73201),a=n(93736),l=n(71961);e.exports=function(e,t,n){var r=e.constructor;switch(t){case"[object ArrayBuffer]":return o(e);case"[object Boolean]":case"[object Date]":return new r(+e);case"[object DataView]":return s(e,n);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return l(e,n);case"[object Map]":case"[object Set]":return new r;case"[object Number]":case"[object String]":return new r(e);case"[object RegExp]":return i(e);case"[object Symbol]":return a(e)}}},35529:(e,t,n)=>{var r=n(39344),o=n(28879),s=n(55527);e.exports=function(e){return"function"!=typeof e.constructor||s(e)?{}:r(o(e))}},62060:e=>{var o=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;e.exports=function(e,t){var n=t.length;if(!n)return e;var r=n-1;return t[r]=(1{var r=n(51873),o=n(72428),s=n(56449),i=r?r.isConcatSpreadable:void 0;e.exports=function(e){return s(e)||o(e)||!!(i&&e&&e[i])}},30361:e=>{var r=/^(?:0|[1-9]\d*)$/;e.exports=function(e,t){var n=typeof e;return!!(t=null==t?9007199254740991:t)&&("number"==n||"symbol"!=n&&r.test(e))&&-1{var o=n(75288),s=n(64894),i=n(30361),a=n(23805);e.exports=function(e,t,n){if(!a(n))return!1;var r=typeof t;return!!("number"==r?s(n)&&i(t,n.length):"string"==r&&t in n)&&o(n[t],e)}},28586:(e,t,n)=>{var r=n(56449),o=n(44394),s=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||i.test(e)||!s.test(e)||null!=t&&e in Object(t)}},74218:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},85087:(e,t,n)=>{var r=n(30980),o=n(37381),s=n(62284),i=n(53758);e.exports=function(e){var t=s(e),n=i[t];if("function"!=typeof n||!(t in r.prototype))return!1;if(e===n)return!0;t=o(n);return!!t&&e===t[0]}},87296:(e,t,n)=>{var n=n(55481),r=(n=/[^.]+$/.exec(n&&n.keys&&n.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"";e.exports=function(e){return!!r&&r in e}},55527:e=>{var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}},30756:(e,t,n)=>{var r=n(23805);e.exports=function(e){return e==e&&!r(e)}},63702:e=>{e.exports=function(){this.__data__=[],this.size=0}},70080:(e,t,n)=>{var r=n(26025),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,e=r(t,e);return!(e<0||(e==t.length-1?t.pop():o.call(t,e,1),--this.size,0))}},24739:(e,t,n)=>{var r=n(26025);e.exports=function(e){var t=this.__data__,e=r(t,e);return e<0?void 0:t[e][1]}},48655:(e,t,n)=>{var r=n(26025);e.exports=function(e){return-1{var o=n(26025);e.exports=function(e,t){var n=this.__data__,r=o(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}},63040:(e,t,n)=>{var r=n(21549),o=n(80079),s=n(68223);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(s||o),string:new r}}},17670:(e,t,n)=>{var r=n(12651);e.exports=function(e){e=r(this,e).delete(e);return this.size-=e?1:0,e}},90289:(e,t,n)=>{var r=n(12651);e.exports=function(e){return r(this,e).get(e)}},4509:(e,t,n)=>{var r=n(12651);e.exports=function(e){return r(this,e).has(e)}},72949:(e,t,n)=>{var o=n(12651);e.exports=function(e,t){var n=o(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this}},20317:e=>{e.exports=function(e){var n=-1,r=Array(e.size);return e.forEach(function(e,t){r[++n]=[t,e]}),r}},67197:e=>{e.exports=function(t,n){return function(e){return null!=e&&e[t]===n&&(void 0!==n||t in Object(e))}}},62224:(e,t,n)=>{var r=n(50104);e.exports=function(e){var e=r(e,function(e){return 500===t.size&&t.clear(),e}),t=e.cache;return e}},3209:(e,t,n)=>{var a=n(91596),l=n(53320),c=n(36306),u="__lodash_placeholder__",p=Math.min;e.exports=function(e,t){var n=e[1],r=t[1],o=n|r,s=128==r&&8==n||128==r&&256==n&&e[7].length<=t[8]||384==r&&t[7].length<=t[8]&&8==n;if(!(o<131)&&!s)return e;1&r&&(e[2]=t[2],o|=1&n?0:4);var i,s=t[3];return s&&(i=e[3],e[3]=i?a(i,s,t[4]):s,e[4]=i?c(e[3],u):t[4]),(s=t[5])&&(i=e[5],e[5]=i?l(i,s,t[6]):s,e[6]=i?c(e[5],u):t[6]),(s=t[7])&&(e[7]=s),128&r&&(e[8]=null==e[8]?t[8]:p(e[8],t[8])),null==e[9]&&(e[9]=t[9]),e[0]=t[0],e[1]=o,e}},48152:(e,t,n)=>{n=n(28303),n=n&&new n;e.exports=n},81042:(e,t,n)=>{n=n(56110)(Object,"create");e.exports=n},3650:(e,t,n)=>{n=n(74335)(Object.keys,Object);e.exports=n},90181:e=>{e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},86009:(e,t,n)=>{e=n.nmd(e);var n=n(34840),t=t&&!t.nodeType&&t,r=t&&e&&!e.nodeType&&e,o=r&&r.exports===t&&n.process,t=function(){try{return r&&r.require&&r.require("util").types||o&&o.binding&&o.binding("util")}catch(e){}}();e.exports=t},59350:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},74335:e=>{e.exports=function(t,n){return function(e){return t(n(e))}}},56757:(e,t,n)=>{var l=n(91033),c=Math.max;e.exports=function(s,i,a){return i=c(void 0===i?s.length-1:i,0),function(){for(var e=arguments,t=-1,n=c(e.length-i,0),r=Array(n);++t{var r=n(47422),o=n(25160);e.exports=function(e,t){return t.length<2?e:r(e,o(t,0,-1))}},84629:e=>{e.exports={}},68294:(e,t,n)=>{var i=n(23007),a=n(30361),l=Math.min;e.exports=function(e,t){for(var n=e.length,r=l(t.length,n),o=i(e);r--;){var s=t[r];e[r]=a(s,n)?o[s]:void 0}return e}},36306:e=>{var a="__lodash_placeholder__";e.exports=function(e,t){for(var n=-1,r=e.length,o=0,s=[];++n{var n=n(34840),r="object"==typeof self&&self&&self.Object===Object&&self,n=n||r||Function("return this")();e.exports=n},14974:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},31380:e=>{e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},51459:e=>{e.exports=function(e){return this.__data__.has(e)}},54641:(e,t,n)=>{var r=n(68882),n=n(51811)(r);e.exports=n},84247:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach(function(e){n[++t]=e}),n}},32865:(e,t,n)=>{var r=n(19570),n=n(51811)(r);e.exports=n},70981:(e,t,n)=>{var r=n(75251),o=n(62060),s=n(32865),i=n(75948);e.exports=function(e,t,n){t+="";return s(e,o(t,i(r(t),n)))}},51811:e=>{var s=Date.now;e.exports=function(n){var r=0,o=0;return function(){var e=s(),t=16-(e-o);if(o=e,0{var r=n(80079);e.exports=function(){this.__data__=new r,this.size=0}},90938:e=>{e.exports=function(e){var t=this.__data__,e=t.delete(e);return this.size=t.size,e}},63605:e=>{e.exports=function(e){return this.__data__.get(e)}},29817:e=>{e.exports=function(e){return this.__data__.has(e)}},80945:(e,t,n)=>{var o=n(80079),s=n(68223),i=n(53661);e.exports=function(e,t){var n=this.__data__;if(n instanceof o){var r=n.__data__;if(!s||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new i(r)}return n.set(e,t),this.size=n.size,this}},76959:e=>{e.exports=function(e,t,n){for(var r=n-1,o=e.length;++r{var r=n(61074),o=n(49698),s=n(42054);e.exports=function(e){return(o(e)?s:r)(e)}},61802:(e,t,n)=>{var n=n(62224),r=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,s=/\\(\\)?/g,n=n(function(e){var o=[];return 46===e.charCodeAt(0)&&o.push(""),e.replace(r,function(e,t,n,r){o.push(n?r.replace(s,"$1"):t||e)}),o});e.exports=n},77797:(e,t,n)=>{var r=n(44394);e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-1/0?"-0":t}},47473:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},31800:e=>{var n=/\s/;e.exports=function(e){for(var t=e.length;t--&&n.test(e.charAt(t)););return t}},42054:e=>{var t="\\ud800-\\udfff",n="["+t+"]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",o="\\ud83c[\\udffb-\\udfff]",t="[^"+t+"]",s="(?:\\ud83c[\\udde6-\\uddff]){2}",i="[\\ud800-\\udbff][\\udc00-\\udfff]",a="(?:"+r+"|"+o+")?",l="[\\ufe0e\\ufe0f]?",l=l+a+"(?:\\u200d(?:"+[t,s,i].join("|")+")"+l+a+")*",a="(?:"+[t+r+"?",r,s,i,n].join("|")+")",c=RegExp(o+"(?="+o+")|"+a+l,"g");e.exports=function(e){return e.match(c)||[]}},22225:e=>{var t="\\ud800-\\udfff",n="\\u2700-\\u27bf",r="a-z\\xdf-\\xf6\\xf8-\\xff",o="A-Z\\xc0-\\xd6\\xd8-\\xde",s="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",i="["+s+"]",a="["+n+"]",l="["+r+"]",s="[^"+t+s+"\\d+"+n+r+o+"]",n="(?:\\ud83c[\\udde6-\\uddff]){2}",r="[\\ud800-\\udbff][\\udc00-\\udfff]",o="["+o+"]",c="(?:"+l+"|"+s+")",s="(?:"+o+"|"+s+")",u="(?:['’](?:d|ll|m|re|s|t|ve))?",p="(?:['’](?:D|LL|M|RE|S|T|VE))?",h="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",d="[\\ufe0e\\ufe0f]?",t=d+h+"(?:\\u200d(?:"+["[^"+t+"]",n,r].join("|")+")"+d+h+")*",d="(?:"+[a,n,r].join("|")+")"+t,f=RegExp([o+"?"+l+"+"+u+"(?="+[i,o,"$"].join("|")+")",s+"+"+p+"(?="+[i,o+c,"$"].join("|")+")",o+"?"+c+"+"+u,o+"+"+p,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",d].join("|"),"g");e.exports=function(e){return e.match(f)||[]}},75948:(e,t,n)=>{var o=n(83729),s=n(15325),i=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];e.exports=function(n,r){return o(i,function(e){var t="_."+e[0];r&e[1]&&!s(n,t)&&n.push(t)}),n.sort()}},80257:(e,t,n)=>{var r=n(30980),o=n(56017),s=n(23007);e.exports=function(e){if(e instanceof r)return e.clone();var t=new o(e.__wrapped__,e.__chain__);return t.__actions__=s(e.__actions__),t.__index__=e.__index__,t.__values__=e.__values__,t}},64626:(e,t,n)=>{var r=n(66977);e.exports=function(e,t,n){return t=n?void 0:t,t=e&&null==t?e.length:t,r(e,128,void 0,void 0,void 0,void 0,t)}},84058:(e,t,n)=>{var r=n(14792),n=n(45539)(function(e,t,n){return t=t.toLowerCase(),e+(n?r(t):t)});e.exports=n},14792:(e,t,n)=>{var r=n(13222),o=n(55808);e.exports=function(e){return o(r(e).toLowerCase())}},32629:(e,t,n)=>{var r=n(9999);e.exports=function(e){return r(e,4)}},37334:e=>{e.exports=function(e){return function(){return e}}},49747:(e,t,n)=>{var r=n(66977);function o(e,t,n){e=r(e,8,void 0,void 0,void 0,void 0,void 0,t=n?void 0:t);return e.placeholder=o.placeholder,e}o.placeholder={},e.exports=o},38221:(e,t,n)=>{var v=n(23805),b=n(10124),w=n(99374),E=Math.max,S=Math.min;e.exports=function(r,n,e){var o,s,i,a,l,c,u=0,p=!1,h=!1,t=!0;if("function"!=typeof r)throw new TypeError("Expected a function");function d(e){var t=o,n=s;return o=s=void 0,u=e,a=r.apply(n,t)}function f(e){var t=e-c;return void 0===c||n<=t||t<0||h&&i<=e-u}function m(){var e,t=b();if(f(t))return g(t);l=setTimeout(m,(e=n-((t=t)-c),h?S(e,i-(t-u)):e))}function g(e){return l=void 0,t&&o?d(e):(o=s=void 0,a)}function y(){var e=b(),t=f(e);if(o=arguments,s=this,c=e,t){if(void 0===l)return u=e=c,l=setTimeout(m,n),p?d(e):a;if(h)return clearTimeout(l),l=setTimeout(m,n),d(c)}return void 0===l&&(l=setTimeout(m,n)),a}return n=w(n)||0,v(e)&&(p=!!e.leading,i=(h="maxWait"in e)?E(w(e.maxWait)||0,n):i,t="trailing"in e?!!e.trailing:t),y.cancel=function(){void 0!==l&&clearTimeout(l),o=c=s=l=void(u=0)},y.flush=function(){return void 0===l?a:g(b())},y}},50828:(e,t,n)=>{var r=n(24647),o=n(13222),s=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=o(e))&&e.replace(s,r).replace(i,"")}},75288:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},7309:(e,t,n)=>{n=n(62006)(n(24713));e.exports=n},24713:(e,t,n)=>{var o=n(2523),s=n(15389),i=n(61489),a=Math.max;e.exports=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;n=null==n?0:i(n);return n<0&&(n=a(r+n,0)),o(e,s(t,3),n)}},35970:(e,t,n)=>{var r=n(83120);e.exports=function(e){return null!=e&&e.length?r(e,1):[]}},73424:(e,t,n)=>{var B=n(16962),L=n(2874),q=Array.prototype.push;function $(n,e){return 2==e?function(e,t){return n(e,t)}:function(e){return n(e)}}function z(e){for(var t=e?e.length:0,n=Array(t);t--;)n[t]=e[t];return n}function U(r,o){return function(){var e=arguments.length;if(e){for(var t=Array(e);e--;)t[e]=arguments[e];var n=t[0]=o.apply(void 0,t);return r.apply(void 0,t),n}}}e.exports=function a(n,e,t,l){var c="function"==typeof e,r=e===Object(e);if(r&&(l=t,t=e,e=void 0),null==t)throw new TypeError;var u={cap:!("cap"in(l=l||{}))||l.cap,curry:!("curry"in l)||l.curry,fixed:!("fixed"in l)||l.fixed,immutable:!("immutable"in l)||l.immutable,rearg:!("rearg"in l)||l.rearg},o=c?t:L,p="curry"in l&&l.curry,h="fixed"in l&&l.fixed,s="rearg"in l&&l.rearg,d=c?t.runInContext():void 0,f=c?t:{ary:n.ary,assign:n.assign,clone:n.clone,curry:n.curry,forEach:n.forEach,isArray:n.isArray,isError:n.isError,isFunction:n.isFunction,isWeakMap:n.isWeakMap,iteratee:n.iteratee,keys:n.keys,rearg:n.rearg,toInteger:n.toInteger,toPath:n.toPath},m=f.ary,g=f.assign,y=f.clone,v=f.curry,b=f.forEach,i=f.isArray,w=f.isError,E=f.isFunction,S=f.isWeakMap,x=f.keys,_=f.rearg,k=f.toInteger,A=f.toPath,O=x(B.aryMethod),C={castArray:function(t){return function(){var e=arguments[0];return i(e)?t(z(e)):t.apply(void 0,arguments)}},iteratee:function(r){return function(){var e=arguments[1],t=r(arguments[0],e),n=t.length;return u.cap&&"number"==typeof e?(e=2{s.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},s.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},s.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},s.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},s.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},s.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},s.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},s.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},s.realToAlias=function(){var e,t=Object.prototype.hasOwnProperty,n=s.aliasToReal,r={};for(e in n){var o=n[e];t.call(r,o)?r[o].push(e):r[o]=[e]}return r}(),s.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},s.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},s.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934:(e,t,n)=>{e.exports={ary:n(64626),assign:n(74733),clone:n(32629),curry:n(49747),forEach:n(83729),isArray:n(56449),isError:n(23546),isFunction:n(1882),isWeakMap:n(47886),iteratee:n(33855),keys:n(88984),rearg:n(84195),toInteger:n(61489),toPath:n(42072)}},56367:(e,t,n)=>{e.exports=n(77731)},79920:(e,t,n)=>{var r=n(73424),o=n(47934);e.exports=function(e,t,n){return r(o,e,t,n)}},2874:e=>{e.exports={}},77731:(e,t,n)=>{var r=n(79920)("set",n(63560));r.placeholder=n(2874),e.exports=r},58156:(e,t,n)=>{var r=n(47422);e.exports=function(e,t,n){e=null==e?void 0:r(e,t);return void 0===e?n:e}},80631:(e,t,n)=>{var r=n(28077),o=n(49326);e.exports=function(e,t){return null!=e&&o(e,t,r)}},83488:e=>{e.exports=function(e){return e}},72428:(e,t,n)=>{var r=n(27534),o=n(40346),n=Object.prototype,s=n.hasOwnProperty,i=n.propertyIsEnumerable,n=r(function(){return arguments}())?r:function(e){return o(e)&&s.call(e,"callee")&&!i.call(e,"callee")};e.exports=n},56449:e=>{var t=Array.isArray;e.exports=t},64894:(e,t,n)=>{var r=n(1882),o=n(30294);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},83693:(e,t,n)=>{var r=n(64894),o=n(40346);e.exports=function(e){return o(e)&&r(e)}},53812:(e,t,n)=>{var r=n(72552),o=n(40346);e.exports=function(e){return!0===e||!1===e||o(e)&&"[object Boolean]"==r(e)}},3656:(e,t,n)=>{e=n.nmd(e);var r=n(9325),n=n(89935),t=t&&!t.nodeType&&t,o=t&&e&&!e.nodeType&&e,o=o&&o.exports===t?r.Buffer:void 0,t=(o?o.isBuffer:void 0)||n;e.exports=t},62193:(e,t,n)=>{var r=n(88984),o=n(5861),s=n(72428),i=n(56449),a=n(64894),l=n(3656),c=n(55527),u=n(37167),p=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(a(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||l(e)||u(e)||s(e)))return!e.length;var t,n=o(e);if("[object Map]"==n||"[object Set]"==n)return!e.size;if(c(e))return!r(e).length;for(t in e)if(p.call(e,t))return!1;return!0}},2404:(e,t,n)=>{var r=n(60270);e.exports=function(e,t){return r(e,t)}},23546:(e,t,n)=>{var r=n(72552),o=n(40346),s=n(11331);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Error]"==t||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!s(e)}},1882:(e,t,n)=>{var r=n(72552),o=n(23805);e.exports=function(e){if(!o(e))return!1;e=r(e);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},30294:e=>{e.exports=function(e){return"number"==typeof e&&-1{var r=n(29172),o=n(27301),n=n(86009),n=n&&n.isMap,o=n?o(n):r;e.exports=o},5187:e=>{e.exports=function(e){return null===e}},98023:(e,t,n)=>{var r=n(72552),o=n(40346);e.exports=function(e){return"number"==typeof e||o(e)&&"[object Number]"==r(e)}},23805:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},40346:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},11331:(e,t,n)=>{var r=n(72552),o=n(28879),s=n(40346),n=Function.prototype,i=Object.prototype,a=n.toString,l=i.hasOwnProperty,c=a.call(Object);e.exports=function(e){if(!s(e)||"[object Object]"!=r(e))return!1;e=o(e);if(null===e)return!0;e=l.call(e,"constructor")&&e.constructor;return"function"==typeof e&&e instanceof e&&a.call(e)==c}},38440:(e,t,n)=>{var r=n(16038),o=n(27301),n=n(86009),n=n&&n.isSet,o=n?o(n):r;e.exports=o},85015:(e,t,n)=>{var r=n(72552),o=n(56449),s=n(40346);e.exports=function(e){return"string"==typeof e||!o(e)&&s(e)&&"[object String]"==r(e)}},44394:(e,t,n)=>{var r=n(72552),o=n(40346);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},37167:(e,t,n)=>{var r=n(4901),o=n(27301),n=n(86009),n=n&&n.isTypedArray,o=n?o(n):r;e.exports=o},47886:(e,t,n)=>{var r=n(5861),o=n(40346);e.exports=function(e){return o(e)&&"[object WeakMap]"==r(e)}},33855:(e,t,n)=>{var r=n(9999),o=n(15389);e.exports=function(e){return o("function"==typeof e?e:r(e,1))}},95950:(e,t,n)=>{var r=n(70695),o=n(88984),s=n(64894);e.exports=function(e){return(s(e)?r:o)(e)}},37241:(e,t,n)=>{var r=n(70695),o=n(72903),s=n(64894);e.exports=function(e){return s(e)?r(e,!0):o(e)}},68090:e=>{e.exports=function(e){var t=null==e?0:e.length;return t?e[t-1]:void 0}},50104:(e,t,n)=>{var i=n(53661);function a(r,o){if("function"!=typeof r||null!=o&&"function"!=typeof o)throw new TypeError("Expected a function");function s(){var e=arguments,t=o?o.apply(this,e):e[0],n=s.cache;return n.has(t)?n.get(t):(e=r.apply(this,e),s.cache=n.set(t,e)||n,e)}return s.cache=new(a.Cache||i),s}a.Cache=i,e.exports=a},55364:(e,t,n)=>{var r=n(85250),n=n(20999)(function(e,t,n){r(e,t,n)});e.exports=n},6048:e=>{e.exports=function(t){if("function"!=typeof t)throw new TypeError("Expected a function");return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}},63950:e=>{e.exports=function(){}},10124:(e,t,n)=>{var r=n(9325);e.exports=function(){return r.Date.now()}},90179:(e,t,n)=>{var s=n(34932),i=n(9999),a=n(19931),l=n(31769),c=n(21791),u=n(53138),r=n(38816),p=n(83349),n=r(function(t,e){var n={};if(null==t)return n;var r=!1;e=s(e,function(e){return e=l(e,t),r=r||1{var r=n(47237),o=n(17255),s=n(28586),i=n(77797);e.exports=function(e){return s(e)?r(i(e)):o(e)}},84195:(e,t,n)=>{var r=n(66977),n=n(38816)(function(e,t){return r(e,256,void 0,void 0,void 0,t)});e.exports=n},40860:(e,t,n)=>{var s=n(40882),i=n(80909),a=n(15389),l=n(85558),c=n(56449);e.exports=function(e,t,n){var r=c(e)?s:l,o=arguments.length<3;return r(e,a(t,4),n,o,i)}},63560:(e,t,n)=>{var r=n(73170);e.exports=function(e,t,n){return null==e?e:r(e,t,n)}},42426:(e,t,n)=>{var o=n(14248),s=n(15389),i=n(90916),a=n(56449),l=n(36800);e.exports=function(e,t,n){var r=a(e)?o:i;return n&&l(e,t,n)&&(t=void 0),r(e,s(t,3))}},63345:e=>{e.exports=function(){return[]}},89935:e=>{e.exports=function(){return!1}},17400:(e,t,n)=>{var r=n(99374);e.exports=function(e){return e?(e=r(e))===1/0||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},61489:(e,t,n)=>{var r=n(17400);e.exports=function(e){var e=r(e),t=e%1;return e==e?t?e-t:e:0}},80218:(e,t,n)=>{var r=n(13222);e.exports=function(e){return r(e).toLowerCase()}},99374:(e,t,n)=>{var r=n(54128),o=n(23805),s=n(44394),i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(s(e))return NaN;if(o(e)&&(t="function"==typeof e.valueOf?e.valueOf():e,e=o(t)?t+"":t),"string"!=typeof e)return 0===e?e:+e;e=r(e);var t=a.test(e);return t||l.test(e)?c(e.slice(2),t?2:8):i.test(e)?NaN:+e}},42072:(e,t,n)=>{var r=n(34932),o=n(23007),s=n(56449),i=n(44394),a=n(61802),l=n(77797),c=n(13222);e.exports=function(e){return s(e)?r(e,l):i(e)?[e]:o(a(c(e)))}},69884:(e,t,n)=>{var r=n(21791),o=n(37241);e.exports=function(e){return r(e,o(e))}},13222:(e,t,n)=>{var r=n(77556);e.exports=function(e){return null==e?"":r(e)}},55808:(e,t,n)=>{n=n(12507)("toUpperCase");e.exports=n},66645:(e,t,n)=>{var r=n(1733),o=n(45434),s=n(13222),i=n(22225);e.exports=function(e,t,n){return e=s(e),void 0===(t=n?void 0:t)?(o(e)?i:r)(e):e.match(t)||[]}},53758:(e,t,n)=>{var r=n(30980),o=n(56017),s=n(94033),i=n(56449),a=n(40346),l=n(80257),c=Object.prototype.hasOwnProperty;function u(e){if(a(e)&&!i(e)&&!(e instanceof r)){if(e instanceof o)return e;if(c.call(e,"__wrapped__"))return l(e)}return new o(e)}u.prototype=s.prototype,e.exports=u.prototype.constructor=u},47248:(e,t,n)=>{var r=n(16547),o=n(51234);e.exports=function(e,t){return o(e||[],t||[],r)}},43768:(e,t,n)=>{"use strict";var u=n(45981),p=n(85587),o=(t.highlight=h,t.highlightAuto=function(e,t){var n,r,o,s,i=t||{},a=i.subset||u.listLanguages(),i=i.prefix,l=a.length,c=-1;if(null==i&&0,"string"!=typeof e)throw p("Expected `string` for value, got `%s`",e);for(r={relevance:0,language:null,value:[]},n={relevance:0,language:null,value:[]};++cr.relevance&&(r=o),o.relevance>n.relevance&&(r=n,n=o));return r.language&&(n.secondBest=r),n},t.registerLanguage=function(e,t){u.registerLanguage(e,t)},t.listLanguages=function(){return u.listLanguages()},t.registerAlias=function(e,t){var n,r=e;for(n in t&&((r={})[e]=t),r)u.registerAliases(r[n],{languageName:n})},s.prototype.addText=function(e){var t,n=this.stack;""!==e&&((t=(n=n[n.length-1]).children[n.children.length-1])&&"text"===t.type?t.value+=e:n.children.push({type:"text",value:e}))},s.prototype.addKeyword=function(e,t){this.openNode(t),this.addText(e),this.closeNode()},s.prototype.addSublanguage=function(e,t){var n=this.stack,n=n[n.length-1],e=e.rootNode.children;n.children=n.children.concat(t?{type:"element",tagName:"span",properties:{className:[t]},children:e}:e)},s.prototype.openNode=function(e){var t=this.stack,e={type:"element",tagName:"span",properties:{className:[this.options.classPrefix+e]},children:[]};t[t.length-1].children.push(e),t.push(e)},s.prototype.closeNode=function(){this.stack.pop()},s.prototype.closeAllNodes=r,s.prototype.finalize=r,s.prototype.toHTML=function(){return""},"hljs-");function h(e,t,n){var r=u.configure({}),n=(n||{}).prefix;if("string"!=typeof e)throw p("Expected `string` for name, got `%s`",e);if(!u.getLanguage(e))throw p("Unknown language: `%s` is not registered",e);if("string"!=typeof t)throw p("Expected `string` for value, got `%s`",t);if(u.configure({__emitter:s,classPrefix:n=null==n?o:n}),n=u.highlight(t,{language:e,ignoreIllegals:!0}),u.configure(r||{}),n.errorRaised)throw n.errorRaised;return{relevance:n.relevance,language:n.language,value:n.emitter.rootNode.children}}function s(e){this.options=e,this.rootNode={children:[]},this.stack=[this.rootNode]}function r(){}},92340:(e,t,n)=>{const r=n(6048);function o(t){return"string"==typeof t?e=>e.element===t:t.constructor&&t.extend?e=>e instanceof t:t}class s{constructor(e){this.elements=e||[]}toValue(){return this.elements.map(e=>e.toValue())}map(e,t){return this.elements.map(e,t)}flatMap(e,t){return this.map(e,t).reduce((e,t)=>e.concat(t),[])}compactMap(t,n){const r=[];return this.forEach(e=>{e=t.bind(n)(e);e&&r.push(e)}),r}filter(e,t){return e=o(e),new s(this.elements.filter(e,t))}reject(e,t){return e=o(e),new s(this.elements.filter(r(e),t))}find(e,t){return e=o(e),this.elements.find(e,t)}forEach(e,t){this.elements.forEach(e,t)}reduce(e,t){return this.elements.reduce(e,t)}includes(t){return this.elements.some(e=>e.equals(t))}shift(){return this.elements.shift()}unshift(e){this.elements.unshift(this.refract(e))}push(e){return this.elements.push(this.refract(e)),this}add(e){this.push(e)}get(e){return this.elements[e]}getValue(e){const t=this.elements[e];if(t)return t.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(s.prototype[Symbol.iterator]=function(){return this.elements[Symbol.iterator]()}),e.exports=s},55973:e=>{e.exports=class t{constructor(e,t){this.key=e,this.value=t}clone(){const e=new t;return this.key&&(e.key=this.key.clone()),this.value&&(e.value=this.value.clone()),e}}},3110:(e,t,n)=>{const r=n(5187),o=n(85015),s=n(98023),i=n(53812),a=n(23805),l=n(85105),c=n(86804);class u{constructor(e){this.elementMap={},this.elementDetection=[],this.Element=c.Element,this.KeyValuePair=c.KeyValuePair,e&&e.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(e){return e.namespace&&e.namespace({base:this}),e.load&&e.load({base:this}),this}useDefault(){return this.register("null",c.NullElement).register("string",c.StringElement).register("number",c.NumberElement).register("boolean",c.BooleanElement).register("array",c.ArrayElement).register("object",c.ObjectElement).register("member",c.MemberElement).register("ref",c.RefElement).register("link",c.LinkElement),this.detect(r,c.NullElement,!1).detect(o,c.StringElement,!1).detect(s,c.NumberElement,!1).detect(i,c.BooleanElement,!1).detect(Array.isArray,c.ArrayElement,!1).detect(a,c.ObjectElement,!1),this}register(e,t){return this._elements=void 0,this.elementMap[e]=t,this}unregister(e){return this._elements=void 0,delete this.elementMap[e],this}detect(e,t,n){return void 0===n||n?this.elementDetection.unshift([e,t]):this.elementDetection.push([e,t]),this}toElement(t){if(t instanceof this.Element)return t;let n;for(let e=0;e{var t=e[0].toUpperCase()+e.substr(1);this._elements[t]=this.elementMap[e]})),this._elements}get serialiser(){return new l(this)}}l.prototype.Namespace=u,e.exports=u},10866:(e,t,n)=>{const r=n(6048),o=n(92340);class s extends o{map(t,n){return this.elements.map(e=>t.bind(n)(e.value,e.key,e))}filter(t,n){return new s(this.elements.filter(e=>t.bind(n)(e.value,e.key,e)))}reject(e,t){return this.filter(r(e.bind(t)))}forEach(n,r){return this.elements.forEach((e,t)=>{n.bind(r)(e.value,e.key,e,t)})}keys(){return this.map((e,t)=>t.toValue())}values(){return this.map(e=>e.toValue())}}e.exports=s},86804:(e,t,n)=>{const r=n(10316),o=n(41067),s=n(71167),i=n(40239),a=n(12242),l=n(6233),c=n(87726),u=n(61045),p=n(86303),h=n(14540),d=n(92340),f=n(10866),m=n(55973);function g(e){return e instanceof r?e:"string"==typeof e?new s(e):"number"==typeof e?new i(e):"boolean"==typeof e?new a(e):null===e?new o:Array.isArray(e)?new l(e.map(g)):"object"==typeof e?new u(e):e}r.prototype.ObjectElement=u,r.prototype.RefElement=h,r.prototype.MemberElement=c,r.prototype.refract=g,d.prototype.refract=g,e.exports={Element:r,NullElement:o,StringElement:s,NumberElement:i,BooleanElement:a,ArrayElement:l,MemberElement:c,ObjectElement:u,LinkElement:p,RefElement:h,refract:g,ArraySlice:d,ObjectSlice:f,KeyValuePair:m}},86303:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e||[],t,n),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(e){this.attributes.set("relation",e)}get href(){return this.attributes.get("href")}set href(e){this.attributes.set("href",e)}}},14540:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e||[],t,n),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(e){this.attributes.set("path",e)}}},34035:(e,t,n)=>{var r=n(3110),o=n(86804);t.g$=r,t.KeyValuePair=n(55973),t.G6=o.ArraySlice,t.ot=o.ObjectSlice,t.Hg=o.Element,t.Om=o.StringElement,t.kT=o.NumberElement,t.bd=o.BooleanElement,t.Os=o.NullElement,t.wE=o.ArrayElement,t.Sh=o.ObjectElement,t.Pr=o.MemberElement,t.sI=o.RefElement,t.Ft=o.LinkElement,t.e=o.refract,n(85105),n(75147)},6233:(e,t,n)=>{const r=n(6048),o=n(10316),s=n(92340);class i extends o{constructor(e,t,n){super(e||[],t,n),this.element="array"}primitive(){return"array"}get(e){return this.content[e]}getValue(e){const t=this.get(e);if(t)return t.toValue()}getIndex(e){return this.content[e]}set(e,t){return this.content[e]=this.refract(t),this}remove(e){e=this.content.splice(e,1);return e.length?e[0]:null}map(e,t){return this.content.map(e,t)}flatMap(e,t){return this.map(e,t).reduce((e,t)=>e.concat(t),[])}compactMap(t,n){const r=[];return this.forEach(e=>{e=t.bind(n)(e);e&&r.push(e)}),r}filter(e,t){return new s(this.content.filter(e,t))}reject(e,t){return this.filter(r(e),t)}reduce(t,e){let n,r;r=void 0!==e?(n=0,this.refract(e)):(n=1,"object"===this.primitive()?this.first.value:this.first);for(let e=n;e{n.bind(r)(e,this.refract(t))})}shift(){return this.content.shift()}unshift(e){this.content.unshift(this.refract(e))}push(e){return this.content.push(this.refract(e)),this}add(e){this.push(e)}findElements(r,e){const t=e||{},o=!!t.recursive,s=void 0===t.results?[]:t.results;return this.forEach((e,t,n)=>{o&&void 0!==e.findElements&&e.findElements(r,{results:s,recursive:o}),r(e,t,n)&&s.push(e)}),s}find(e){return new s(this.findElements(e,{recursive:!0}))}findByElement(t){return this.find(e=>e.element===t)}findByClass(t){return this.find(e=>e.classes.includes(t))}getById(t){return this.find(e=>e.id.toValue()===t).first}includes(t){return this.content.some(e=>e.equals(t))}contains(e){return this.includes(e)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(e){return new this.constructor(this.content.concat(e.content))}"fantasy-land/concat"(e){return this.concat(e)}"fantasy-land/map"(e){return new this.constructor(this.map(e))}"fantasy-land/chain"(t){return this.map(e=>t(e),this).reduce((e,t)=>e.concat(t),this.empty())}"fantasy-land/filter"(e){return new this.constructor(this.content.filter(e))}"fantasy-land/reduce"(e,t){return this.content.reduce(e,t)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}i["fantasy-land/empty"]=i.empty=function(){return new this},"undefined"!=typeof Symbol&&(i.prototype[Symbol.iterator]=function(){return this.content[Symbol.iterator]()}),e.exports=i},12242:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e,t,n),this.element="boolean"}primitive(){return"boolean"}}},10316:(e,t,n)=>{const r=n(2404),a=n(55973),l=n(92340);e.exports=class o{constructor(e,t,n){t&&(this.meta=t),n&&(this.attributes=n),this.content=e}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this).meta.freeze(),this._attributes&&(this.attributes.parent=this).attributes.freeze(),this.children.forEach(e=>{e.parent=this,e.freeze()},this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const e=new this.constructor;return e.element=this.element,this.meta.length&&(e._meta=this.meta.clone()),this.attributes.length&&(e._attributes=this.attributes.clone()),this.content?this.content.clone?e.content=this.content.clone():Array.isArray(this.content)?e.content=this.content.map(e=>e.clone()):e.content=this.content:e.content=this.content,e}toValue(){return this.content instanceof o?this.content.toValue():this.content instanceof a?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map(e=>e.toValue(),this):this.content}toRef(e){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const t=new this.RefElement(this.id.toValue());return e&&(t.path=e),t}findRecursive(...r){if(1(e.push(t),e),i=(e,t)=>{t.element===o&&e.push(t);const n=t.findRecursive(o);return n&&n.reduce(s,e),t.content instanceof a&&(t.content.key&&i(e,t.content.key),t.content.value&&i(e,t.content.value)),e};return this.content&&(this.content.element&&i(e,this.content),Array.isArray(this.content)&&this.content.reduce(i,e)),e=r.isEmpty?e:e.filter(e=>{let t=e.parents.map(e=>e.element);for(const e in r){var n=r[e];if(-1===(n=t.indexOf(n)))return!1;t=t.splice(0,n)}return!0})}set(e){return this.content=e,this}equals(e){return r(this.toValue(),e)}getMetaProperty(e,t){if(!this.meta.hasKey(e)){if(this.isFrozen){const e=this.refract(t);return e.freeze(),e}this.meta.set(e,t)}return this.meta.get(e)}setMetaProperty(e,t){this.meta.set(e,t)}get element(){return this._storedElement||"element"}set element(e){this._storedElement=e}get content(){return this._content}set content(t){if(t instanceof o)this._content=t;else if(t instanceof l)this.content=t.elements;else if("string"==typeof t||"number"==typeof t||"boolean"==typeof t||"null"===t||null==t)this._content=t;else if(t instanceof a)this._content=t;else if(Array.isArray(t))this._content=t.map(this.refract);else{if("object"!=typeof t)throw new Error("Cannot set content to given value");this._content=Object.keys(t).map(e=>new this.MemberElement(e,t[e]))}}get meta(){if(!this._meta){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._meta=new this.ObjectElement}return this._meta}set meta(e){e instanceof this.ObjectElement?this._meta=e:this.meta.set(e||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._attributes=new this.ObjectElement}return this._attributes}set attributes(e){e instanceof this.ObjectElement?this._attributes=e:this.attributes.set(e||{})}get id(){return this.getMetaProperty("id","")}set id(e){this.setMetaProperty("id",e)}get classes(){return this.getMetaProperty("classes",[])}set classes(e){this.setMetaProperty("classes",e)}get title(){return this.getMetaProperty("title","")}set title(e){this.setMetaProperty("title",e)}get description(){return this.getMetaProperty("description","")}set description(e){this.setMetaProperty("description",e)}get links(){return this.getMetaProperty("links",[])}set links(e){this.setMetaProperty("links",e)}get isFrozen(){return Object.isFrozen(this)}get parents(){let e=this.parent;const t=new l;for(;e;)t.push(e),e=e.parent;return t}get children(){if(Array.isArray(this.content))return new l(this.content);if(this.content instanceof a){const e=new l([this.content.key]);return this.content.value&&e.push(this.content.value),e}return this.content instanceof o?new l([this.content]):new l}get recursiveChildren(){const t=new l;return this.children.forEach(e=>{t.push(e),e.recursiveChildren.forEach(e=>{t.push(e)})}),t}}},87726:(e,t,n)=>{const o=n(55973),r=n(10316);e.exports=class extends r{constructor(e,t,n,r){super(new o,n,r),this.element="member",this.key=e,this.value=t}get key(){return this.content.key}set key(e){this.content.key=this.refract(e)}get value(){return this.content.value}set value(e){this.content.value=this.refract(e)}}},41067:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e||null,t,n),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e,t,n),this.element="number"}primitive(){return"number"}}},61045:(e,t,n)=>{const r=n(6048),o=n(23805),s=n(6233),i=n(87726),a=n(10866);e.exports=class extends s{constructor(e,t,n){super(e||[],t,n),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce((e,t)=>(e[t.key.toValue()]=t.value?t.value.toValue():void 0,e),{})}get(e){e=this.getMember(e);if(e)return e.value}getMember(t){if(void 0!==t)return this.content.find(e=>e.key.toValue()===t)}remove(t){let n=null;return this.content=this.content.filter(e=>e.key.toValue()!==t||(n=e,!1)),n}getKey(e){e=this.getMember(e);if(e)return e.key}set(t,e){if(o(t))return Object.keys(t).forEach(e=>{this.set(e,t[e])}),this;const n=t,r=this.getMember(n);return r?r.value=e:this.content.push(new i(n,e)),this}keys(){return this.content.map(e=>e.key.toValue())}values(){return this.content.map(e=>e.value.toValue())}hasKey(t){return this.content.some(e=>e.key.equals(t))}items(){return this.content.map(e=>[e.key.toValue(),e.value.toValue()])}map(t,n){return this.content.map(e=>t.bind(n)(e.value,e.key,e))}compactMap(r,o){const s=[];return this.forEach((e,t,n)=>{e=r.bind(o)(e,t,n);e&&s.push(e)}),s}filter(e,t){return new a(this.content).filter(e,t)}reject(e,t){return this.filter(r(e),t)}forEach(t,n){return this.content.forEach(e=>t.bind(n)(e.value,e.key,e))}}},71167:(e,t,n)=>{n=n(10316);e.exports=class extends n{constructor(e,t,n){super(e,t,n),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147:(e,t,n)=>{n=n(85105);e.exports=class extends n{serialise(t){if(!(t instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${t}\` is not an Element instance`);let n;t._attributes&&t.attributes.get("variable")&&(n=t.attributes.get("variable"));const r={element:t.element};t._meta&&0{e.content&&e.content.element&&e.content.attributes.remove("typeAttributes")}),e.content&&0!==n.length&&o.unshift(e.content),(o=o.map(e=>e instanceof this.namespace.elements.Array?[e]:new this.namespace.elements.Array([e.content]))).length&&t.set("samples",o),0{const t=e.clone();return t.attributes.remove("typeAttributes"),this.serialise(t)})}if(e.content){const n=e.content.clone();return n.attributes.remove("typeAttributes"),[this.serialise(n)]}return[]}deserialise(t){if("string"==typeof t)return new this.namespace.elements.String(t);if("number"==typeof t)return new this.namespace.elements.Number(t);if("boolean"==typeof t)return new this.namespace.elements.Boolean(t);if(null===t)return new this.namespace.elements.Null;if(Array.isArray(t))return new this.namespace.elements.Array(t.map(this.deserialise,this));const r=this.namespace.getElementClass(t.element),o=new r,s=(o.element!==t.element&&(o.element=t.element),t.meta&&this.deserialiseObject(t.meta,o.meta),t.attributes&&this.deserialiseObject(t.attributes,o.attributes),this.deserialiseContent(t.content));if(void 0===s&&null!==o.content||(o.content=s),"enum"===o.element){o.content&&o.attributes.set("enumerations",o.content);let n=o.attributes.get("samples");if(o.attributes.remove("samples"),n){const s=n;n=new this.namespace.elements.Array,s.forEach(e=>{e.forEach(e=>{const t=new r(e);t.element=o.element,n.push(t)})});t=n.shift();o.content=t?t.content:void 0,o.attributes.set("samples",n)}else o.content=void 0;let e=o.attributes.get("default");if(e&&0this.shouldRefract(e)||"default"===t?this.serialise(e):"array"===e.element||"object"===e.element||"enum"===e.element?e.children.map(e=>this.serialise(e)):e.toValue()):"object"===e.element?(e.content||[]).map(this.serialise,this):e.toValue()}serialiseEnum(e){return e.children.map(e=>this.serialise(e))}serialiseObject(e){const n={};return e.forEach((e,t)=>{e&&(t=t.toValue(),n[t]=this.convertKeyToRefract(t,e))}),n}deserialiseObject(t,n){Object.keys(t).forEach(e=>{n.set(e,this.deserialise(t[e]))})}}},85105:e=>{e.exports=class{constructor(e){this.namespace=e||new this.Namespace}serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);const t={element:e.element};e._meta&&0{e&&(n[t.toValue()]=this.serialise(e))}),0!==Object.keys(n).length)return n}deserialiseObject(t,n){Object.keys(t).forEach(e=>{n.set(e,this.deserialise(t[e]))})}}},58859:(n,r,w)=>{var e="function"==typeof Map&&Map.prototype,t=Object.getOwnPropertyDescriptor&&e?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,E=e&&t&&"function"==typeof t.get?t.get:null,S=e&&Map.prototype.forEach,t="function"==typeof Set&&Set.prototype,e=Object.getOwnPropertyDescriptor&&t?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,x=t&&e&&"function"==typeof e.get?e.get:null,_=t&&Set.prototype.forEach,k="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,A="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,O="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,X=Boolean.prototype.valueOf,s=Object.prototype.toString,Q=Function.prototype.toString,Z=String.prototype.match,C=String.prototype.slice,j=String.prototype.replace,i=String.prototype.toUpperCase,P=String.prototype.toLowerCase,u=RegExp.prototype.test,N=Array.prototype.concat,I=Array.prototype.join,ee=Array.prototype.slice,o=Math.floor,T="function"==typeof BigInt?BigInt.prototype.valueOf:null,p=Object.getOwnPropertySymbols,R="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,M="function"==typeof Symbol&&"object"==typeof Symbol.iterator,D="function"==typeof Symbol&&Symbol.toStringTag&&(Symbol.toStringTag,1)?Symbol.toStringTag:null,F=Object.prototype.propertyIsEnumerable,B=("function"==typeof Reflect?Reflect:Object).getPrototypeOf||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function L(e,t){if(e===1/0||e===-1/0||e!=e||e&&-1e3 0, or `null`');if(K(i,"numericSeparator")&&"boolean"!=typeof i.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var t=i.numericSeparator;if(void 0===n)return"undefined";if(null===n)return"null";if("boolean"==typeof n)return n?"true":"false";if("string"==typeof n)return function e(t,n){{var r;if(t.length>n.maxStringLength)return r=t.length-n.maxStringLength,r="... "+r+" more character"+(1"}if(U(n)){if(0===n.length)return"[]";var b=Y(n,m);return a&&!function(e){for(var t=0;t "+m(e,n))}),oe("Map",E.call(n),u,a)):function(e){if(x&&e&&"object"==typeof e)try{x.call(e);try{E.call(e)}catch(e){return 1}return e instanceof Set}catch(e){}}(n)?(p=[],_&&_.call(n,function(e){p.push(m(e,n))}),oe("Set",x.call(n),p,a)):function(e){if(k&&e&&"object"==typeof e)try{k.call(e,k);try{A.call(e,A)}catch(e){return 1}return e instanceof WeakMap}catch(e){}}(n)?H("WeakMap"):function(e){if(A&&e&&"object"==typeof e)try{A.call(e,A);try{k.call(e,k)}catch(e){return 1}return e instanceof WeakSet}catch(e){}}(n)?H("WeakSet"):function(e){if(O&&e&&"object"==typeof e)try{return O.call(e),1}catch(e){}}(n)?H("WeakRef"):"[object Number]"!==W(h=n)||D&&"object"==typeof h&&D in h?function(e){if(e&&"object"==typeof e&&T)try{return T.call(e),1}catch(e){}}(n)?J(m(T.call(n))):"[object Boolean]"!==W(t=n)||D&&"object"==typeof t&&D in t?"[object String]"!==W(e=n)||D&&"object"==typeof e&&D in e?"undefined"!=typeof window&&n===window?"{ [object Window] }":n===w.g?"{ [object globalThis] }":("[object Date]"!==W(t=n)||D&&"object"==typeof t&&D in t)&&!V(n)?(e=Y(n,m),t=B?B(n)===Object.prototype:n instanceof Object||n.constructor===Object,d=n instanceof Object?"":"null prototype",f=!t&&D&&Object(n)===n&&D in n?C.call(W(n),8,-1):d?"Object":"",t=(!t&&"function"==typeof n.constructor&&n.constructor.name?n.constructor.name+" ":"")+(f||d?"["+I.call(N.call([],f||[],d||[]),": ")+"] ":""),0===e.length?t+"{}":a?t+"{"+G(e,a)+"}":t+"{ "+I.call(e,", ")+" }"):String(n):J(m(String(n))):J(X.call(n)):J(m(Number(n)))};var a=Object.prototype.hasOwnProperty||function(e){return e in this};function K(e,t){return a.call(e,t)}function W(e){return s.call(e)}function ne(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n{var n,r,e=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function i(t){if(n===setTimeout)return setTimeout(t,0);if((n===o||!n)&&setTimeout)return(n=setTimeout)(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}var a,l=[],c=!1,u=-1;function p(){c&&a&&(c=!1,a.length?l=a.concat(l):u=-1,l.length&&h())}function h(){if(!c){var e=i(p);c=!0;for(var t=l.length;t;){for(a=l,l=[];++u{"use strict";var i=n(6925);function r(){}function o(){}o.resetWarningCache=r,e.exports=function(){function e(e,t,n,r,o,s){if(s!==i)throw(s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")).name="Invariant Violation",s}function t(){return e}var n={array:e.isRequired=e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:r};return n.PropTypes=n}},5556:(e,t,n)=>{e.exports=n(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},74765:e=>{"use strict";var t=String.prototype.replace,n=/%20/g;e.exports={default:"RFC3986",formatters:{RFC1738:function(e){return t.call(e,n,"+")},RFC3986:function(e){return String(e)}},RFC1738:"RFC1738",RFC3986:"RFC3986"}},55373:(e,t,n)=>{"use strict";var r=n(98636),o=n(62642),n=n(74765);e.exports={formats:n,parse:o,stringify:r}},62642:(e,t,n)=>{"use strict";function y(e,t){return e&&"string"==typeof e&&t.comma&&-1{"use strict";function I(e,t){r.apply(e,D(t)?t:[t])}function T(e,t,n,r,o,s,i,a,l,c,u,p,h,d,f,m){for(var g=e,y=m,v=0,b=!1;void 0!==(y=y.get(L))&&!b;){var w=y.get(e);if(v+=1,void 0!==w){if(w===v)throw new RangeError("Cyclic object value");b=!0}void 0===y.get(L)&&(v=0)}if("function"==typeof a?g=a(t,g):g instanceof Date?g=u(g):"comma"===n&&D(g)&&(g=M.maybeMap(g,function(e){return e instanceof Date?u(e):e})),null===g){if(o)return i&&!d?i(t,B.encoder,f,"key",p):t;g=""}if("string"==typeof(E=g)||"number"==typeof E||"boolean"==typeof E||"symbol"==typeof E||"bigint"==typeof E||M.isBuffer(g)){if(i){var E=d?t:i(t,B.encoder,f,"key",p);if("comma"===n&&d){for(var S=F.call(String(g),","),x="",_=0;_{"use strict";function a(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r>6]+u[128|63&l]:l<55296||57344<=l?i+=u[224|l>>12]+u[128|l>>6&63]+u[128|63&l]:(a+=1,l=65536+((1023&l)<<10|1023&s.charCodeAt(a)),i+=u[240|l>>18]+u[128|l>>12&63]+u[128|l>>6&63]+u[128|63&l])}return i},isBuffer:function(e){return!(!e||"object"!=typeof e||!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e)))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(m(e)){for(var n=[],r=0;r{"use strict";var s=Object.prototype.hasOwnProperty;function i(e){try{return decodeURIComponent(e.replace(/\+/g," "))}catch(e){return null}}function a(e){try{return encodeURIComponent(e)}catch(e){return null}}t.stringify=function(e,t){var n,r,o=[];for(r in"string"!=typeof(t=t||"")&&(t="?"),e)if(s.call(e,r)){if((n=e[r])||null!=n&&!isNaN(n)||(n=""),r=a(r),n=a(n),null===r||null===n)continue;o.push(r+"="+n)}return o.length?t+o.join("&"):""},t.parse=function(e){for(var t=/([^=?#&]+)=?([^&]*)/g,n={};o=t.exec(e);){var r=i(o[1]),o=i(o[2]);null===r||null===o||r in n||(n[r]=o)}return n}},41859:(e,t,n)=>{const i=n(27096),a=n(78004),l=i.types;e.exports=class r{constructor(e,t){if(this._setDefaults(e),e instanceof RegExp)this.ignoreCase=e.ignoreCase,this.multiline=e.multiline,e=e.source;else{if("string"!=typeof e)throw new Error("Expected a regexp or string");this.ignoreCase=t&&-1!==t.indexOf("i"),this.multiline=t&&-1!==t.indexOf("m")}this.tokens=i(e)}_setDefaults(e){this.max=null!=e.max?e.max:null!=r.prototype.max?r.prototype.max:100,this.defaultRange=e.defaultRange||this.defaultRange.clone(),e.randInt&&(this.randInt=e.randInt)}gen(){return this._gen(this.tokens,[])}_gen(e,t){var n,r,o,s,i;switch(e.type){case l.ROOT:case l.GROUP:if(e.followedBy||e.notFollowedBy)return"";for(e.remember&&void 0===e.groupNumber&&(e.groupNumber=t.push(null)-1),r="",s=0,i=(n=e.options?this._randSelect(e.options):e.stack).length;s{"use strict";var o=n(65606),s=n(92861).Buffer,i=n.g.crypto||n.g.msCrypto;i&&i.getRandomValues?e.exports=function(e,t){if(4294967295{"use strict";function s(e){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var l=r(n(96540)),c=r(n(17965)),u=["text","onCopy","options","children"];function r(e){return e&&e.__esModule?e:{default:e}}function o(t,e){var n,r=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)),r}function p(t){for(var e=1;e{"use strict";n=n(25264).CopyToClipboard;n.CopyToClipboard=n,e.exports=n},81214:(e,t,n)=>{"use strict";function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.DebounceInput=void 0;var l=r(n(96540)),c=r(n(20181)),u=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function r(e){return e&&e.__esModule?e:{default:e}}function o(t,e){var n,r=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)),r}function p(t){for(var e=1;e=r?o.notify(t):n.length>e.length&&o.notify(p(p({},t),{},{target:p(p({},t.target),{},{value:""})}))})}),g(f(o),"onKeyDown",function(e){"Enter"===e.key&&o.forceNotify(e);var t=o.props.onKeyDown;t&&(e.persist(),t(e))}),g(f(o),"onBlur",function(e){o.forceNotify(e);var t=o.props.onBlur;t&&(e.persist(),t(e))}),g(f(o),"createNotifier",function(e){var t;e<0?o.notify=function(){return null}:0===e?o.notify=o.doNotify:(t=(0,c.default)(function(e){o.isDebouncing=!1,o.doNotify(e)},e),o.notify=function(e){o.isDebouncing=!0,t(e)},o.flush=function(){return t.flush()},o.cancel=function(){o.isDebouncing=!1,t.cancel()})}),g(f(o),"doNotify",function(){o.props.onChange.apply(void 0,arguments)}),g(f(o),"forceNotify",function(e){var t,n=o.props.debounceTimeout;!o.isDebouncing&&0=t?o.doNotify(e):o.doNotify(p(p({},e),{},{target:p(p({},e.target),{},{value:n})})))}),o.isDebouncing=!1,o.state={value:void 0===e.value||null===e.value?"":e.value};e=o.props.debounceTimeout;return o.createNotifier(e),o}return t=i,(e=[{key:"componentDidUpdate",value:function(e){var t,n,r,o;this.isDebouncing||(t=(n=this.props).value,n=n.debounceTimeout,r=e.debounceTimeout,e=e.value,o=this.state.value,void 0!==t&&e!==t&&o!==t&&this.setState({value:t}),n!==r&&this.createNotifier(n))}},{key:"componentWillUnmount",value:function(){this.flush&&this.flush()}},{key:"render",value:function(){var e=this.props,t=e.element,n=(e.onChange,e.value,e.minLength,e.debounceTimeout,e.forceNotifyByEnter),r=e.forceNotifyOnBlur,o=e.onKeyDown,s=e.onBlur,i=e.inputRef,e=function(e,t){if(null==e)return{};var n,r=function(e,t){if(null==e)return{};for(var n,r={},o=Object.keys(e),s=0;s{"use strict";n=n(81214).DebounceInput;n.DebounceInput=n,e.exports=n},22551:(l,f,e)=>{"use strict";var O=e(96540),n=e(69982);function q(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n