diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8a2d4b1..437c7ac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: - package-ecosystem: "pip" # See documentation for possible values directories: - "unicorn_contracts" # Location of package manifests - - "unicorn_properties" + - "unicorn_approvals" - "unicorn_web" schedule: interval: "weekly" diff --git a/.github/workflows/auto_assign.yml b/.github/workflows/auto_assign.yml index 5728867..093e194 100644 --- a/.github/workflows/auto_assign.yml +++ b/.github/workflows/auto_assign.yml @@ -7,4 +7,4 @@ jobs: add-reviews: runs-on: ubuntu-latest steps: - - uses: kentaro-m/auto-assign-action@v1.2.5 \ No newline at end of file + - uses: kentaro-m/auto-assign-action@v2.0.0 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9f3a07f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,80 @@ +name: Build Python Services + +on: + push: + branches: [develop, main] + paths: + - 'unicorn_contracts/**' + - 'unicorn_approvals/**' + - 'unicorn_web/**' + pull_request: + branches: [develop, main] + paths: + - 'unicorn_contracts/**' + - 'unicorn_approvals/**' + - 'unicorn_web/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + service: [unicorn_contracts, unicorn_approvals, unicorn_web] + include: + - service: unicorn_contracts + display_name: Unicorn Contracts Service + - service: unicorn_approvals + display_name: Unicorn Approvals Service + - service: unicorn_web + display_name: Unicorn Web Service + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.7.8 + + - name: Install AWS SAM CLI + uses: aws-actions/setup-sam@v2 + + - name: Install cfn-lint and plugins + run: | + pip install cfn-lint + pip install cfn-lint-serverless + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod a+x /usr/local/bin/yq + + - name: Initialize dependencies for ${{ matrix.display_name }} + run: make ci_init + working-directory: ./${{ matrix.service }} + + - name: Build ${{ matrix.display_name }} + run: | + # Use uv run to ensure all commands run in the virtual environment + uv run make build + working-directory: ./${{ matrix.service }} + env: + DOCKER_OPTS: --use-container + + - name: Upload build artifacts for ${{ matrix.display_name }} + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.service }}-build-artifacts + path: ${{ matrix.service }}/.aws-sam/ + retention-days: 7 + + - name: Clean up ${{ matrix.display_name }} + run: uv run make clean + working-directory: ./${{ matrix.service }} + if: always() \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 08ca221..5d1886f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,18 +25,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) # - name: Autobuild - # uses: github/codeql-action/autobuild@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + # uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -49,4 +49,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2ca79b6fa8d3ec278944088b4aa5f46912db5d63 #v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/label_pr_on_title.yml b/.github/workflows/label_pr_on_title.yml index 3815a49..e6ce47d 100644 --- a/.github/workflows/label_pr_on_title.yml +++ b/.github/workflows/label_pr_on_title.yml @@ -22,9 +22,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Label PR based on title" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_TITLE: ${{ needs.get_pr_details.outputs.prTitle }} diff --git a/.github/workflows/on_label_added.yml b/.github/workflows/on_label_added.yml index e9180d8..ab04444 100644 --- a/.github/workflows/on_label_added.yml +++ b/.github/workflows/on_label_added.yml @@ -23,10 +23,10 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Maintenance: Persist state per PR as an artifact to avoid spam on label add - name: "Suggest split large Pull Request" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} diff --git a/.github/workflows/on_merged_pr.yml b/.github/workflows/on_merged_pr.yml index cd97e1c..2bce046 100644 --- a/.github/workflows/on_merged_pr.yml +++ b/.github/workflows/on_merged_pr.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest if: needs.get_pr_details.outputs.prIsMerged == 'true' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Label PR related issue for release" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} diff --git a/.github/workflows/on_opened_pr.yml b/.github/workflows/on_opened_pr.yml index 043ff96..9712a3f 100644 --- a/.github/workflows/on_opened_pr.yml +++ b/.github/workflows/on_opened_pr.yml @@ -19,9 +19,9 @@ jobs: needs: get_pr_details runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Ensure related issue is present" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} @@ -36,9 +36,9 @@ jobs: needs: get_pr_details runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Ensure acknowledgement section is present" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml index 44f445a..7ef50e4 100644 --- a/.github/workflows/record_pr.yml +++ b/.github/workflows/record_pr.yml @@ -9,14 +9,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Extract PR details" - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const script = require('.github/scripts/save_pr_details.js') await script({github, context, core}) - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: pr path: pr.txt diff --git a/.github/workflows/reusable_export_pr_details.yml b/.github/workflows/reusable_export_pr_details.yml index a30b158..0b9956c 100644 --- a/.github/workflows/reusable_export_pr_details.yml +++ b/.github/workflows/reusable_export_pr_details.yml @@ -59,9 +59,9 @@ jobs: prIsMerged: ${{ steps.prIsMerged.outputs.prIsMerged }} steps: - name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Download previously saved PR" - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: WORKFLOW_ID: ${{ inputs.record_pr_workflow_id }} # For security, we only download artifacts tied to the successful PR recording workflow diff --git a/.github/workflows/reusable_unit_tests.yml b/.github/workflows/reusable_unit_tests.yml index 26974fd..ff944b7 100644 --- a/.github/workflows/reusable_unit_tests.yml +++ b/.github/workflows/reusable_unit_tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: { python-version: 3.12 } diff --git a/.github/workflows/services_unit_tests.yml b/.github/workflows/services_unit_tests.yml index 4696e81..493e8f6 100644 --- a/.github/workflows/services_unit_tests.yml +++ b/.github/workflows/services_unit_tests.yml @@ -3,13 +3,13 @@ on: branches: [develop, main] paths: - 'unicorn_contracts/**' - - 'unicorn_properties/**' + - 'unicorn_approvals/**' - 'unicorn_web/**' pull_request: branches: [develop, main] paths: - 'unicorn_contracts/**' - - 'unicorn_properties/**' + - 'unicorn_approvals/**' - 'unicorn_web/**' jobs: @@ -18,10 +18,10 @@ jobs: with: service_directory: unicorn_contracts - unicorn_properties: + unicorn_approvals: uses: ./.github/workflows/reusable_unit_tests.yml with: - service_directory: unicorn_properties + service_directory: unicorn_approvals unicorn_web: uses: ./.github/workflows/reusable_unit_tests.yml diff --git a/README.md b/README.md index 945edc9..144797e 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,38 @@ -AWS Serverless Developer Experience Workshop Reference Architecture +[![Build & Test Workflow](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-python/actions/workflows/build.yml/badge.svg)](https://github.com/aws-samples/aws-serverless-developer-experience-workshop-python/actions/workflows/build.yml) # AWS Serverless Developer Experience workshop reference architecture (Python) -This repository contains the reference architecture for the AWS Serverless Developer Experience workshop. +AWS Serverless Developer Experience Workshop Reference Architecture + +This repository contains the Python reference architecture for the AWS Serverless Developer Experience workshop. -The AWS Serverless Developer Experience workshop provides you with an immersive experience as a serverless developer. The goal of this workshop is to provide you with hands-on experience building a serverless solution using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**. +The AWS Serverless Developer Experience Workshop is a comprehensive, hands-on training program designed to equip developers with practical serverless development skills using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**. -Along the way, you will learn about principals of distributed event-driven architectures, messaging patterns, orchestration, and observability and how to apply them in code. You will explore exciting open-source tools, the core features of Powertools for AWS Lambda, and simplified CI/CD deployments supported by AWS SAM Pipelines. +The workshop employs a practical, code-centric approach, emphasizing direct implementation and real-world scenario exploration to ensure you develop serverless development skills across several critical areas including distributed event-driven architectures, messaging patterns, orchestration, and observability. You will explore open-source tools, [Powertools for AWS](https://powertools.aws.dev/), and simplified CI/CD deployments with AWS SAM Pipelines. By the end, you will be familiar with serverless developer workflows, microservice composition using AWS SAM, serverless development best practices, and applied event-driven architectures. -At the end of this workshop, you will be familiar with Serverless developer workflows and microservice composition using AWS SAM, Serverless development best practices, and applied event-driven architectures. +The 6-8 hour workshop assumes your practical development skills in Python, TypeScript, Java, or .NET, and familiarity with [Amazon API Gateway](https://aws.amazon.com/apigateway/), [AWS Lambda](https://aws.amazon.com/lambda/), [Amazon EventBridge](https://aws.amazon.com/eventbridge/), [AWS Step Functions](https://aws.amazon.com/step-functions/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). ## Introducing the Unicorn Properties architecture ![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/architecture.png) -Our use case is based on a real estate company called **Unicorn Properties**. - -As a real estate agency, **Unicorn Properties** needs to manage the publishing of new property listings and sale contracts linked to individual properties, and provide a way for their customers to view approved property listings. - -To support their needs, Unicorn Properties have adopted a serverless, event-driven approach to designing their architecture. This architecture is centred around two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Properties Services). - -The **Unicorn Contracts** service (namespace: `Unicorn.Contracts`) is a simplified service that manages the contractual relationship between a seller of a property and Unicorn Properties. Contracts are drawn up that define the property for sale, the terms and conditions that Unicorn Properties sets, and how much it will cost the seller to engage the services of the agency. +Real estate company **Unicorn Properties** needs to manage publishing of new property listings and sale contracts linked to individual properties, and provide a way for customers to view approved listings. They adopted a serverless, event-driven architecture with two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Approvals Services). -The **Unicorn Web** (namespace: `Unicorn.Web`) manages the details of a property listing to be published on the Unicorn Properties website. Every property listing has an address, a sale price, a description of the property, and some photos that members of the public can look at to get them interested in purchasing the property. Only properties that have been approved for publication can be made visible to the public. +**Unicorn Contracts** (using the `Unicorn.Contracts` namespace) service manages contractual relationships between property sellers and Unicorn Approvals, defining properties for sale, terms, and engagement costs. -The **Unicorn Properties** service (namespace: `Unicorn.Properties`) approves a property listings. This service implements a workflow that checks for the existence of a contract, makes sure that the content and the images are safe to publish, and finally checks that the contract has been approved. We don’t want to publish a property until we have an approved contract! +**Unicorn Approvals** (using the `Unicorn.Approvals` namespace) service approves property listings by implementing a workflow that checks for contract existence, content and image safety, and contract approval before publishing. -Have a go at building this architecture yourself! Head over to the [Serverless Developer Experience Workshop](https://catalog.workshops.aws/serverless-developer-experience) for more details. +**Unicorn Web** (using the `Unicorn.Web` namespace) manages property listing details (address, sale price, description, photos) to be published on the website, with only approved listings visible to the public. ## Credits -Throughout this workshop we wanted to introduce you to some Open Source tools that can help you build serverless applications. This is not an exhaustive list, just a small selection of what we will be using in the workshop. +This workshop introduces you to some open-source tools that can help you build serverless applications. This is not an exhaustive list, but a small selection of what you will be using in the workshop. Many thanks to all the AWS teams and community builders who have contributed to this list: -| Tools | Description | Download / Installation Instructions | -| --------------------- | ----------- | --------------------------------------- | -| cfn-lint | Validate AWS CloudFormation yaml/json templates against the AWS CloudFormation Resource Specification and additional checks. | https://github.com/aws-cloudformation/cfn-lint | -| cfn-lint-serverless | Compilation of rules to validate infrastructure-as-code templates against recommended practices for serverless applications. | https://github.com/awslabs/serverless-rules | -| @mhlabs/iam-policies-cli| CLI for generating AWS IAM policy documents or SAM policy templates based on the JSON definition used in the AWS Policy Generator. | https://github.com/mhlabs/iam-policies-cli | -| @mhlabs/evb-cli | Pattern generator and debugging tool for Amazon EventBridge | https://github.com/mhlabs/evb-cli | +| Tools | Description | Download / Installation Instructions | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| cfn-lint | Validate AWS CloudFormation yaml/json templates against the AWS CloudFormation Resource Specification and additional checks. | https://github.com/aws-cloudformation/cfn-lint | +| cfn-lint-serverless | Compilation of rules to validate infrastructure-as-code templates against recommended practices for serverless applications. | https://github.com/awslabs/serverless-rules | +| @mhlabs/iam-policies-cli | CLI for generating AWS IAM policy documents or SAM policy templates based on the JSON definition used in the AWS Policy Generator. | https://github.com/mhlabs/iam-policies-cli | +| @mhlabs/evb-cli | Pattern generator and debugging tool for Amazon EventBridge | https://github.com/mhlabs/evb-cli | diff --git a/docs/architecture.png b/docs/architecture.png index 1e741f2..903363b 100644 Binary files a/docs/architecture.png and b/docs/architecture.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..97cced0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "aws-serverless-developer-experience-workshop-python" +version = "0.1.0" +description = "Add your description here" +requires-python = ">=3.12" +dependencies = [] diff --git a/unicorn_properties/.gitignore b/unicorn_approvals/.gitignore similarity index 100% rename from unicorn_properties/.gitignore rename to unicorn_approvals/.gitignore diff --git a/unicorn_properties/Makefile b/unicorn_approvals/Makefile similarity index 100% rename from unicorn_properties/Makefile rename to unicorn_approvals/Makefile diff --git a/unicorn_approvals/README.md b/unicorn_approvals/README.md new file mode 100644 index 0000000..008a8ad --- /dev/null +++ b/unicorn_approvals/README.md @@ -0,0 +1,15 @@ +# Developing Unicorn Approvals + +![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-approvals.png) + +## Architecture overview + +**Unicorn Approvals** uses an AWS Step Functions state machine to approve property listings for Unicorn Web. The workflow checks for contract information, description sentiment and safe images, and verifies the contract is approved before approving the listing. It publishes the result via the `PublicationEvaluationCompleted` event. + +A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approvals service maintains a local copy of contract status by consuming the ContractStatusChanged event. + +The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property Approval Sync function handles these events and passes the task token back to the state machine based on the contract state. + +If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag. + +**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. diff --git a/unicorn_properties/__init__.py b/unicorn_approvals/__init__.py similarity index 100% rename from unicorn_properties/__init__.py rename to unicorn_approvals/__init__.py diff --git a/unicorn_properties/integration/PublicationEvaluationCompleted.json b/unicorn_approvals/integration/PublicationEvaluationCompleted.json similarity index 100% rename from unicorn_properties/integration/PublicationEvaluationCompleted.json rename to unicorn_approvals/integration/PublicationEvaluationCompleted.json diff --git a/unicorn_properties/integration/event-schemas.yaml b/unicorn_approvals/integration/event-schemas.yaml similarity index 95% rename from unicorn_properties/integration/event-schemas.yaml rename to unicorn_approvals/integration/event-schemas.yaml index adde88d..2a05fff 100644 --- a/unicorn_properties/integration/event-schemas.yaml +++ b/unicorn_approvals/integration/event-schemas.yaml @@ -18,7 +18,7 @@ Resources: Properties: Description: 'Event schemas for Unicorn Properties' RegistryName: - Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}-${Stage}" + Fn::Sub: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}-${Stage}" EventRegistryPolicy: Type: AWS::EventSchemas::RegistryPolicy @@ -52,7 +52,7 @@ Resources: RegistryName: Fn::GetAtt: EventRegistry.RegistryName SchemaName: - Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}@PublicationEvaluationCompleted' + Fn::Sub: '{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}@PublicationEvaluationCompleted' Description: 'The schema for when a property evaluation is completed' Content: Fn::Sub: | diff --git a/unicorn_properties/integration/subscriber-policies.yaml b/unicorn_approvals/integration/subscriber-policies.yaml similarity index 89% rename from unicorn_properties/integration/subscriber-policies.yaml rename to unicorn_approvals/integration/subscriber-policies.yaml index 265c6d4..d41211b 100644 --- a/unicorn_properties/integration/subscriber-policies.yaml +++ b/unicorn_approvals/integration/subscriber-policies.yaml @@ -20,7 +20,7 @@ Resources: Type: AWS::Events::EventBusPolicy Properties: EventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBus}}" StatementId: Fn::Sub: "OnlyRulesForPropertiesServiceEvents-${Stage}" Statement: @@ -40,12 +40,12 @@ Resources: - Fn::Sub: - arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${eventBusName}/* - eventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBus}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBus}}" Condition: StringEqualsIfExists: "events:creatorAccount": "${aws:PrincipalAccount}" StringEquals: "events:source": - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" "Null": "events:source": "false" diff --git a/unicorn_properties/integration/subscriptions.yaml b/unicorn_approvals/integration/subscriptions.yaml similarity index 89% rename from unicorn_properties/integration/subscriptions.yaml rename to unicorn_approvals/integration/subscriptions.yaml index b908185..90bce06 100644 --- a/unicorn_properties/integration/subscriptions.yaml +++ b/unicorn_approvals/integration/subscriptions.yaml @@ -19,7 +19,7 @@ Resources: DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: unicorn.properties-ContractStatusChanged + Name: unicorn.approvals-ContractStatusChanged Description: Contract Status Changed subscription EventBusName: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornContractsEventBusArn}}" @@ -32,7 +32,7 @@ Resources: Targets: - Id: SendEventTo Arn: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" RoleArn: Fn::GetAtt: [ UnicornPropertiesSubscriptionRole, Arn ] @@ -42,7 +42,7 @@ Resources: DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: unicorn.properties-PublicationApprovalRequested + Name: unicorn.approvals-PublicationApprovalRequested Description: Publication evaluation completed subscription EventBusName: Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornWebEventBusArn}}" @@ -55,13 +55,13 @@ Resources: Targets: - Id: SendEventTo Arn: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" RoleArn: Fn::GetAtt: [ UnicornPropertiesSubscriptionRole, Arn ] # This IAM role allows EventBridge to assume the permissions necessary to send events - # from the publishing event bus, to the subscribing event bus (UnicornPropertiesEventBusArn) + # from the publishing event bus, to the subscribing event bus (UnicornApprovalsEventBusArn) UnicornPropertiesSubscriptionRole: Type: AWS::IAM::Role DeletionPolicy: Delete @@ -81,7 +81,7 @@ Resources: - Effect: Allow Action: events:PutEvents Resource: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" Outputs: ContractStatusChangedSubscription: diff --git a/unicorn_approvals/pyproject.toml b/unicorn_approvals/pyproject.toml new file mode 100644 index 0000000..81739cd --- /dev/null +++ b/unicorn_approvals/pyproject.toml @@ -0,0 +1,37 @@ +[project] +name = "approvals_service" +version = "0.2.0" +description = "Unicorn Properties Property Service" +authors = [ + {name = "Amazon Web Services"} +] +readme = "README.md" +requires-python = ">=3.12" + +dependencies = [ + "aws-lambda-powertools[tracer]>=3.18.0", + "aws-xray-sdk>=2.14.0", + "boto3>=1.40.2", +] + +[project.optional-dependencies] +dev = [ + "aws-lambda-powertools[all]>=3.18.0", + "requests>=2.32.3", + "moto[dynamodb,events,sqs]>=5.1.9", + "importlib-metadata>=8.4.0", + "pyyaml>=6.0.2", + "arnparse>=0.0.2", + "pytest>=8.4.1", + "ruff>=0.12.7", +] + +[tool.setuptools] +package-dir = {"approvals_service" = "src"} +packages = ["approvals_service"] + +[tool.pytest.ini_options] +minversion = "7.0" +addopts = "-ra -vv -W ignore::UserWarning" +testpaths = ["tests/unit", "tests/integration"] +pythonpath = ["."] diff --git a/unicorn_properties/pytest.ini b/unicorn_approvals/pytest.ini similarity index 100% rename from unicorn_properties/pytest.ini rename to unicorn_approvals/pytest.ini diff --git a/unicorn_properties/ruff.toml b/unicorn_approvals/ruff.toml similarity index 100% rename from unicorn_properties/ruff.toml rename to unicorn_approvals/ruff.toml diff --git a/unicorn_properties/samconfig.toml b/unicorn_approvals/samconfig.toml similarity index 87% rename from unicorn_properties/samconfig.toml rename to unicorn_approvals/samconfig.toml index 80abe0b..e8b9e83 100644 --- a/unicorn_properties/samconfig.toml +++ b/unicorn_approvals/samconfig.toml @@ -1,8 +1,8 @@ version = 0.1 [default.global.parameters] -stack_name = "uni-prop-local-properties" -s3_prefix = "uni-prop-local-properties" +stack_name = "uni-prop-local-approvals" +s3_prefix = "uni-prop-local-approvals" resolve_s3 = true resolve_image_repositories = true diff --git a/unicorn_approvals/src/README.md b/unicorn_approvals/src/README.md new file mode 100644 index 0000000..78cbc75 --- /dev/null +++ b/unicorn_approvals/src/README.md @@ -0,0 +1,15 @@ +# Developing Unicorn Approvals + +![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-approvals.png) + +## Architecture overview + +**Unicorn Approvals** uses an AWS Step Functions state machine to approve property listings for Unicorn Web. The workflow checks for contract information, description sentiment and safe images, and verifies the contract is approved before approving the listing. It publishes the result via the `PublicationEvaluationCompleted` event. + +A Unicorn Properties agent initiates the workflow by requesting to approve a listing, generating a `PublicationApprovalRequested` event with property information. To decouple from the Contracts Service, the Approvals service maintains a local copy of contract status by consuming the ContractStatusChanged event. + +The workflow checks the contract state. If the contract is in the WaitForContractApproval state, it updates the contract status for the property with its task token, triggering a DynamoDB stream event. The Property Approval Sync function handles these events and passes the task token back to the state machine based on the contract state. + +If the workflow completes successfully, it emits a PublicationEvaluationCompleted event with an **approved** or **declined** evaluation result, which Unicorn Web listens to update its publication flag. + +**Note:** Upon deleting the CloudFormation stack for this service, check if the `ApprovalStateMachine` StepFunction doesn't have any executions in `RUNNING` state. If there are, cancel those execution prior to deleting the CloudFormation stack. \ No newline at end of file diff --git a/unicorn_properties/src/__init__.py b/unicorn_approvals/src/__init__.py similarity index 100% rename from unicorn_properties/src/__init__.py rename to unicorn_approvals/src/__init__.py diff --git a/unicorn_properties/src/properties_service/__init__.py b/unicorn_approvals/src/approvals_service/__init__.py similarity index 100% rename from unicorn_properties/src/properties_service/__init__.py rename to unicorn_approvals/src/approvals_service/__init__.py diff --git a/unicorn_properties/src/properties_service/contract_status_changed_event_handler.py b/unicorn_approvals/src/approvals_service/contract_status_changed_event_handler.py similarity index 100% rename from unicorn_properties/src/properties_service/contract_status_changed_event_handler.py rename to unicorn_approvals/src/approvals_service/contract_status_changed_event_handler.py diff --git a/unicorn_properties/src/properties_service/exceptions.py b/unicorn_approvals/src/approvals_service/exceptions.py similarity index 100% rename from unicorn_properties/src/properties_service/exceptions.py rename to unicorn_approvals/src/approvals_service/exceptions.py diff --git a/unicorn_properties/src/properties_service/properties_approval_sync_function.py b/unicorn_approvals/src/approvals_service/properties_approval_sync_function.py similarity index 100% rename from unicorn_properties/src/properties_service/properties_approval_sync_function.py rename to unicorn_approvals/src/approvals_service/properties_approval_sync_function.py diff --git a/unicorn_properties/src/properties_service/wait_for_contract_approval_function.py b/unicorn_approvals/src/approvals_service/wait_for_contract_approval_function.py similarity index 97% rename from unicorn_properties/src/properties_service/wait_for_contract_approval_function.py rename to unicorn_approvals/src/approvals_service/wait_for_contract_approval_function.py index 07ad5b6..0af233e 100644 --- a/unicorn_properties/src/properties_service/wait_for_contract_approval_function.py +++ b/unicorn_approvals/src/approvals_service/wait_for_contract_approval_function.py @@ -10,7 +10,7 @@ from aws_lambda_powertools.event_handler.exceptions import InternalServerError from botocore.exceptions import ClientError -from properties_service.exceptions import ContractStatusNotFoundException +from approvals_service.exceptions import ContractStatusNotFoundException # Initialise Environment variables diff --git a/unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/AWSEvent.py b/unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/AWSEvent.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/AWSEvent.py rename to unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/AWSEvent.py diff --git a/unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.py b/unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.py rename to unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/ContractStatusChanged.py diff --git a/unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/__init__.py b/unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/__init__.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/__init__.py rename to unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/__init__.py diff --git a/unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/marshaller.py b/unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/marshaller.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_contracts/contractstatuschanged/marshaller.py rename to unicorn_approvals/src/schema/unicorn_contracts/contractstatuschanged/marshaller.py diff --git a/unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/AWSEvent.py b/unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/AWSEvent.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/AWSEvent.py rename to unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/AWSEvent.py diff --git a/unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/PublicationApprovalRequested.py b/unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/PublicationApprovalRequested.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/PublicationApprovalRequested.py rename to unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/PublicationApprovalRequested.py diff --git a/unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/__init__.py b/unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/__init__.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/__init__.py rename to unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/__init__.py diff --git a/unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/marshaller.py b/unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/marshaller.py similarity index 100% rename from unicorn_properties/src/schema/unicorn_web/publicationapprovalrequested/marshaller.py rename to unicorn_approvals/src/schema/unicorn_web/publicationapprovalrequested/marshaller.py diff --git a/unicorn_properties/state_machine/property_approval.asl.yaml b/unicorn_approvals/state_machine/property_approval.asl.yaml similarity index 100% rename from unicorn_properties/state_machine/property_approval.asl.yaml rename to unicorn_approvals/state_machine/property_approval.asl.yaml diff --git a/unicorn_properties/template.yaml b/unicorn_approvals/template.yaml similarity index 80% rename from unicorn_properties/template.yaml rename to unicorn_approvals/template.yaml index ab5b2a3..da29cb0 100644 --- a/unicorn_properties/template.yaml +++ b/unicorn_approvals/template.yaml @@ -4,7 +4,7 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: - AWS::Serverless-2016-10-31 Description: > - Unicorn Properties Service. Validate the content, images and contract of property listings. + Unicorn Approvals Service. Validate the content, images and contract of property listings. Metadata: cfn-lint: @@ -16,7 +16,6 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 Parameters: Stage: @@ -53,41 +52,41 @@ Globals: Environment: Variables: CONTRACT_STATUS_TABLE: !Ref ContractStatusTable - EVENT_BUS: !Ref UnicornPropertiesEventBus - SERVICE_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + EVENT_BUS: !Ref UnicornApprovalsEventBus + SERVICE_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_LOGGER_CASE: PascalCase - POWERTOOLS_SERVICE_NAME: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + POWERTOOLS_SERVICE_NAME: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_TRACE_DISABLED: "false" # Explicitly disables tracing, default POWERTOOLS_LOGGER_LOG_EVENT: !If [IsProd, "false", "true"] # Logs incoming event, default POWERTOOLS_LOGGER_SAMPLE_RATE: !If [IsProd, "0.1", "0"] # Debug log sampling percentage, default - POWERTOOLS_METRICS_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + POWERTOOLS_METRICS_NAMESPACE: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" POWERTOOLS_LOG_LEVEL: INFO # Log level for Logger (INFO, DEBUG, etc.), default LOG_LEVEL: INFO # Log level for Logger Tags: stage: !Ref Stage project: !FindInMap [Constants, ProjectName, Value] - namespace: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + namespace: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" Resources: #### SSM PARAMETERS # Services share their event bus name and arn - UnicornPropertiesEventBusNameParam: + UnicornApprovalsEventBusNameParam: Type: AWS::SSM::Parameter DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: Type: String - Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBus - Value: !GetAtt UnicornPropertiesEventBus.Name + Name: !Sub /uni-prop/${Stage}/UnicornApprovalsEventBus + Value: !GetAtt UnicornApprovalsEventBus.Name - UnicornPropertiesEventBusArnParam: + UnicornApprovalsEventBusArnParam: Type: AWS::SSM::Parameter DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: Type: String - Name: !Sub /uni-prop/${Stage}/UnicornPropertiesEventBusArn - Value: !GetAtt UnicornPropertiesEventBus.Arn + Name: !Sub /uni-prop/${Stage}/UnicornApprovalsEventBusArn + Value: !GetAtt UnicornApprovalsEventBus.Arn #### LAMBDA FUNCTIONS # Listens to ContractStatusChanged events from EventBridge @@ -95,7 +94,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: src/ - Handler: properties_service.contract_status_changed_event_handler.lambda_handler + Handler: publication_manager_service.contract_status_changed_event_handler.lambda_handler Policies: - DynamoDBWritePolicy: TableName: !Ref ContractStatusTable @@ -105,8 +104,8 @@ Resources: StatusChangedEvent: Type: EventBridgeRule Properties: - RuleName: unicorn.properties-ContractStatusChanged - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + RuleName: unicorn.approvals-ContractStatusChanged + EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" @@ -116,12 +115,12 @@ Resources: MaximumRetryAttempts: 5 MaximumEventAgeInSeconds: 900 DeadLetterConfig: - Arn: !GetAtt PropertiesEventBusRuleDLQ.Arn + Arn: !GetAtt ApprovalsEventBusRuleDLQ.Arn EventInvokeConfig: DestinationConfig: OnFailure: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn # Log group for the ContractStatusChangedHandlerFunction ContractStatusChangedHandlerFunctionLogGroup: @@ -137,7 +136,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: src/ - Handler: properties_service.properties_approval_sync_function.lambda_handler + Handler: publication_manager_service.properties_approval_sync_function.lambda_handler Policies: - DynamoDBReadPolicy: TableName: !Ref ContractStatusTable @@ -146,7 +145,7 @@ Resources: StreamName: !Select [3, !Split ["/", !GetAtt ContractStatusTable.StreamArn]] - SQSSendMessagePolicy: - QueueName: !GetAtt PropertiesServiceDLQ.QueueName + QueueName: !GetAtt ApprovalsServiceDLQ.QueueName - Statement: - Effect: Allow Action: @@ -163,12 +162,12 @@ Resources: MaximumRetryAttempts: 3 DestinationConfig: OnFailure: - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn EventInvokeConfig: DestinationConfig: OnFailure: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn # Log group for the PropertiesApprovalSyncFunction PropertiesApprovalSyncFunctionLogGroup: @@ -184,7 +183,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: src/ - Handler: properties_service.wait_for_contract_approval_function.lambda_handler + Handler: publication_manager_service.wait_for_contract_approval_function.lambda_handler Policies: - DynamoDBCrudPolicy: TableName: !Ref ContractStatusTable @@ -219,7 +218,7 @@ Resources: - S3ReadPolicy: BucketName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/ImagesBucket}}" - EventBridgePutEventsPolicy: - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + EventBusName: !GetAtt UnicornApprovalsEventBus.Name - Statement: - Effect: Allow Action: @@ -243,8 +242,8 @@ Resources: PublicationApprovalRequestedEvent: Type: EventBridgeRule Properties: - RuleName: unicorn.properties-PublicationApprovalRequested - EventBusName: !GetAtt UnicornPropertiesEventBus.Name + RuleName: unicorn.approvals-PublicationApprovalRequested + EventBusName: !GetAtt UnicornApprovalsEventBus.Name Pattern: source: - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" @@ -255,13 +254,13 @@ Resources: MaximumEventAgeInSeconds: 900 DeadLetterConfig: Type: SQS - Destination: !GetAtt PropertiesServiceDLQ.Arn + Destination: !GetAtt ApprovalsServiceDLQ.Arn DefinitionSubstitutions: WaitForContractApprovalArn: !GetAtt WaitForContractApprovalFunction.Arn TableName: !Ref ContractStatusTable ImageUploadBucketName: !Sub "{{resolve:ssm:/uni-prop/${Stage}/ImagesBucket}}" - EventBusName: !GetAtt UnicornPropertiesEventBus.Name - ServiceName: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + EventBusName: !GetAtt UnicornApprovalsEventBus.Name + ServiceName: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Store ApprovalStateMachineLogGroup workflow execution logs ApprovalStateMachineLogGroup: @@ -274,7 +273,7 @@ Resources: #### DEAD LETTER QUEUES # Store EventBridge events that failed to be DELIVERED to ContractStatusChangedHandlerFunction - PropertiesEventBusRuleDLQ: + ApprovalsEventBusRuleDLQ: Type: AWS::SQS::Queue UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -285,12 +284,12 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage - # Store failed INVOCATIONS to each Lambda function in Unicorn Properties Service - PropertiesServiceDLQ: + # Store failed INVOCATIONS to each Lambda function in Unicorn Approvals Service + ApprovalsServiceDLQ: Type: AWS::SQS::Queue UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -301,7 +300,7 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage @@ -324,62 +323,62 @@ Resources: - Key: project Value: !FindInMap [Constants, ProjectName, Value] - Key: namespace - Value: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + Value: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - Key: stage Value: !Ref Stage #### EVENT BUS - # Event bus for Unicorn Properties Service, used to publish and consume events - UnicornPropertiesEventBus: + # Event bus for Unicorn Approvals Service, used to publish and consume events + UnicornApprovalsEventBus: Type: AWS::Events::EventBus DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: !Sub UnicornPropertiesBus-${Stage} + Name: !Sub UnicornApprovalsBus-${Stage} - # Event bus policy to restrict who can publish events (should only be services from UnicornPropertiesNamespace) - UnicornPropertiesEventsBusPublishPolicy: + # Event bus policy to restrict who can publish events (should only be services from UnicornApprovalsNamespace) + UnicornApprovalsEventsBusPublishPolicy: Type: AWS::Events::EventBusPolicy DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - EventBusName: !Ref UnicornPropertiesEventBus - StatementId: !Sub OnlyPropertiesServiceCanPublishToEventBus-${Stage} + EventBusName: !Ref UnicornApprovalsEventBus + StatementId: !Sub OnlyApprovalsServiceCanPublishToEventBus-${Stage} Statement: Effect: Allow Principal: AWS: - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" Action: events:PutEvents - Resource: !GetAtt UnicornPropertiesEventBus.Arn + Resource: !GetAtt UnicornApprovalsEventBus.Arn Condition: StringEquals: events:source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" # Catchall rule used for development purposes. Logs all events matching any of the services to CloudWatch Logs - UnicornPropertiesCatchAllRule: + UnicornApprovalsCatchAllRule: Type: AWS::Events::Rule DeletionPolicy: Delete UpdateReplacePolicy: Delete Properties: - Name: properties.catchall + Name: approvals.catchall Description: Catchall rule used for development purposes. - EventBusName: !Ref UnicornPropertiesEventBus + EventBusName: !Ref UnicornApprovalsEventBus EventPattern: account: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: - - Arn: !GetAtt UnicornPropertiesCatchAllLogGroup.Arn - Id: !Sub UnicornPropertiesCatchAllLogGroupTarget-${Stage} + - Arn: !GetAtt UnicornApprovalsCatchAllLogGroup.Arn + Id: !Sub UnicornApprovalsCatchAllLogGroupTarget-${Stage} # CloudWatch log group used to catch all events - UnicornPropertiesCatchAllLogGroup: + UnicornApprovalsCatchAllLogGroup: Type: AWS::Logs::LogGroup UpdateReplacePolicy: Delete DeletionPolicy: Delete @@ -387,7 +386,7 @@ Resources: LogGroupName: !Sub - "/aws/events/${Stage}/${NS}-catchall" - Stage: !Ref Stage - NS: "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + NS: "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" RetentionInDays: !FindInMap [LogsRetentionPeriodMap, !Ref Stage, Days] # Permissions to allow EventBridge to send logs to CloudWatch @@ -413,14 +412,14 @@ Resources: "logs:PutLogEvents" ], "Resource": [ - "${UnicornPropertiesCatchAllLogGroup.Arn}" + "${UnicornApprovalsCatchAllLogGroup.Arn}" ] } ] } #### CLOUDFORMATION NESTED STACKS - # CloudFormation Stack with the Properties Service Event Registry and Schemas + # CloudFormation Stack with the Approvals Service Event Registry and Schemas EventSchemasStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete @@ -430,25 +429,25 @@ Resources: Parameters: Stage: !Ref Stage - # CloudFormation Stack with the Cross-service EventBus policy for Properties Service + # CloudFormation Stack with the Cross-service EventBus policy for Approvals Service SubscriberPoliciesStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete DeletionPolicy: Delete DependsOn: - - UnicornPropertiesEventBusNameParam + - UnicornApprovalsEventBusNameParam Properties: Location: "integration/subscriber-policies.yaml" Parameters: Stage: !Ref Stage - # CloudFormation Stack with the Cross-service EventBus Rules for Properties Service + # CloudFormation Stack with the Cross-service EventBus Rules for Approvals Service SubscriptionsStack: Type: AWS::Serverless::Application UpdateReplacePolicy: Delete DeletionPolicy: Delete DependsOn: - - UnicornPropertiesEventBusArnParam + - UnicornApprovalsEventBusArnParam Properties: Location: "integration/subscriptions.yaml" Parameters: @@ -483,13 +482,13 @@ Outputs: Value: !Ref ApprovalStateMachine #### EVENT BRIDGE OUTPUTS - UnicornPropertiesEventBusName: - Value: !GetAtt UnicornPropertiesEventBus.Name + UnicornApprovalsEventBusName: + Value: !GetAtt UnicornApprovalsEventBus.Name #### CLOUDWATCH LOGS OUTPUTS - UnicornPropertiesCatchAllLogGroupArn: + UnicornApprovalsCatchAllLogGroupArn: Description: Log all events on the service's EventBridge Bus - Value: !GetAtt UnicornPropertiesCatchAllLogGroup.Arn + Value: !GetAtt UnicornApprovalsCatchAllLogGroup.Arn ApprovalStateMachineLogGroupName: Value: !Ref ApprovalStateMachineLogGroup diff --git a/unicorn_properties/tests/__init__.py b/unicorn_approvals/tests/__init__.py similarity index 100% rename from unicorn_properties/tests/__init__.py rename to unicorn_approvals/tests/__init__.py diff --git a/unicorn_properties/tests/unit/events/ddb_stream_events/contract_status_changed_draft.json b/unicorn_approvals/tests/events/ddb_stream_events/contract_status_changed_draft.json similarity index 100% rename from unicorn_properties/tests/unit/events/ddb_stream_events/contract_status_changed_draft.json rename to unicorn_approvals/tests/events/ddb_stream_events/contract_status_changed_draft.json diff --git a/unicorn_properties/tests/unit/events/ddb_stream_events/sfn_check_exists.json b/unicorn_approvals/tests/events/ddb_stream_events/sfn_check_exists.json similarity index 100% rename from unicorn_properties/tests/unit/events/ddb_stream_events/sfn_check_exists.json rename to unicorn_approvals/tests/events/ddb_stream_events/sfn_check_exists.json diff --git a/unicorn_properties/tests/unit/events/ddb_stream_events/sfn_wait_approval.json b/unicorn_approvals/tests/events/ddb_stream_events/sfn_wait_approval.json similarity index 100% rename from unicorn_properties/tests/unit/events/ddb_stream_events/sfn_wait_approval.json rename to unicorn_approvals/tests/events/ddb_stream_events/sfn_wait_approval.json diff --git a/unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_waiting_for_approval.json b/unicorn_approvals/tests/events/ddb_stream_events/status_approved_waiting_for_approval.json similarity index 100% rename from unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_waiting_for_approval.json rename to unicorn_approvals/tests/events/ddb_stream_events/status_approved_waiting_for_approval.json diff --git a/unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_with_no_workflow.json b/unicorn_approvals/tests/events/ddb_stream_events/status_approved_with_no_workflow.json similarity index 100% rename from unicorn_properties/tests/unit/events/ddb_stream_events/status_approved_with_no_workflow.json rename to unicorn_approvals/tests/events/ddb_stream_events/status_approved_with_no_workflow.json diff --git a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed.json b/unicorn_approvals/tests/events/eventbridge/contract_status_changed.json similarity index 100% rename from unicorn_properties/tests/unit/events/eventbridge/contract_status_changed.json rename to unicorn_approvals/tests/events/eventbridge/contract_status_changed.json diff --git a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_approved.json b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_approved.json similarity index 85% rename from unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_approved.json rename to unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_approved.json index 776aee1..a484d18 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_approved.json +++ b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_approved.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_updated_on\": \"10/08/2022 19:56:30\", \"contract_id\": \"f2bedc80-3dc8-4544-9140-9b606d71a6ee\", \"property_id\": \"usa/anytown/main-street/111\", \"contract_status\": \"APPROVED\" }" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_draft.json b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_draft.json similarity index 85% rename from unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_draft.json rename to unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_draft.json index a50374f..0da247a 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_1_draft.json +++ b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_1_draft.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_updated_on\": \"10/08/2022 19:56:30\", \"contract_id\": \"f2bedc80-3dc8-4544-9140-9b606d71a6ee\", \"property_id\": \"usa/anytown/main-street/111\", \"contract_status\": \"DRAFT\" }" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_approved.json b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_approved.json similarity index 85% rename from unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_approved.json rename to unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_approved.json index 99936c7..2b6f023 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_approved.json +++ b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_approved.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_updated_on\": \"10/08/2022 19:56:30\", \"contract_id\": \"9183453b-d284-4466-a2d9-f00b1d569ad7\", \"property_id\": \"usa/anytown/main-street/222\", \"contract_status\": \"APPROVED\" }" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_draft.json b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_draft.json similarity index 85% rename from unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_draft.json rename to unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_draft.json index e98b1b3..c2fe23d 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/contract_status_changed_event_contract_2_draft.json +++ b/unicorn_approvals/tests/events/eventbridge/contract_status_changed_event_contract_2_draft.json @@ -2,7 +2,7 @@ { "DetailType": "ContractStatusChanged", "Source": "unicorn.contracts", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{ \"contract_updated_on\": \"10/08/2022 19:56:30\", \"contract_id\": \"9183453b-d284-4466-a2d9-f00b1d569ad7\", \"property_id\": \"usa/anytown/main-street/222\", \"contract_status\": \"DRAFT\" }" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event.json similarity index 100% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event.json diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_all_good.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_all_good.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_all_good.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_all_good.json index 6d05077..ff30e2c 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_all_good.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_all_good.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/222\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":222},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_description.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_description.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_description.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_description.json index ce594c1..14fc8c4 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_description.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_description.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This is a property for goblins. The property has the worst quality and is atrocious when it comes to design. The property is not clean whatsoever, and will make any property owner have buyers' remorse as soon the property is bought. Keep away from this property as much as possible!\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_images.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_images.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_images.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_images.json index eb9c5ab..3e72041 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_inappropriate_images.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_inappropriate_images.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\",\"property_images/prop1_interior4-bad.jpg\"]}" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_non_existing_contract.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_non_existing_contract.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_non_existing_contract.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_non_existing_contract.json index 7066219..d5f137d 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_non_existing_contract.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_non_existing_contract.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/333\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":333},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_pause_workflow.json b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_pause_workflow.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_pause_workflow.json rename to unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_pause_workflow.json index 8f804ab..ee5206a 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_approval_requested_event_pause_workflow.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_approval_requested_event_pause_workflow.json @@ -2,7 +2,7 @@ { "DetailType": "PublicationApprovalRequested", "Source": "unicorn.web", - "EventBusName": "UnicornPropertiesBus-local", + "EventBusName": "UnicornApprovalsBus-local", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"address\":{\"country\":\"USA\",\"city\":\"Anytown\",\"street\":\"Main Street\",\"number\":111},\"description\":\"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\",\"contract\":\"sale\",\"listprice\":200,\"currency\":\"SPL\",\"images\":[\"property_images/prop1_exterior1.jpg\",\"property_images/prop1_interior1.jpg\",\"property_images/prop1_interior2.jpg\",\"property_images/prop1_interior3.jpg\"]}" } ] diff --git a/unicorn_properties/tests/unit/events/eventbridge/publication_evaluation_completed_event.json b/unicorn_approvals/tests/events/eventbridge/publication_evaluation_completed_event.json similarity index 92% rename from unicorn_properties/tests/unit/events/eventbridge/publication_evaluation_completed_event.json rename to unicorn_approvals/tests/events/eventbridge/publication_evaluation_completed_event.json index b96c3a6..aeeabc0 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/publication_evaluation_completed_event.json +++ b/unicorn_approvals/tests/events/eventbridge/publication_evaluation_completed_event.json @@ -2,7 +2,7 @@ "version": "0", "id": "f849f683-76e1-1c84-669d-544a9828dfef", "detail-type": "PublicationEvaluationCompleted", - "source": "unicorn.properties", + "source": "unicorn.approvals", "account": "123456789012", "time": "2022-08-16T06:33:05Z", "region": "ap-southeast-2", diff --git a/unicorn_properties/tests/unit/events/eventbridge/put_event_property_approval_requested.json b/unicorn_approvals/tests/events/eventbridge/put_event_property_approval_requested.json similarity index 94% rename from unicorn_properties/tests/unit/events/eventbridge/put_event_property_approval_requested.json rename to unicorn_approvals/tests/events/eventbridge/put_event_property_approval_requested.json index b3d9d71..068a030 100644 --- a/unicorn_properties/tests/unit/events/eventbridge/put_event_property_approval_requested.json +++ b/unicorn_approvals/tests/events/eventbridge/put_event_property_approval_requested.json @@ -3,6 +3,6 @@ "Source": "unicorn.web", "Detail": "{ \"property_id\": \"usa/anytown/main-street/111\", \"country\": \"USA\", \"city\": \"Anytown\", \"street\": \"Main Street\", \"number\": 111, \"description\": \"This classic Anytown estate comes with a covetable lake view. The romantic and comfortable backyard is the perfect setting for unicorn get-togethers. The open concept Main Stable is fully equipped with all the desired amenities. Second floor features 6 straw bales including large Rainbow Suite with private training pool terrace and Jr Sparkles Suite.\", \"contract\": \"sale\", \"listprice\": 200, \"currency\": \"SPL\", \"images\": [ \"prop1_exterior1.jpg\", \"prop1_interior1.jpg\", \"prop1_interior2.jpg\", \"prop1_interior3.jpg\", \"prop1_interior4-bad.jpg\" ] }", "DetailType": "PublicationApprovalRequested", - "EventBusName": "UnicornPropertiesBus-local" + "EventBusName": "UnicornApprovalsBus-local" } ] diff --git a/unicorn_properties/tests/unit/events/lambda/content_integrity_validator_function_success.json b/unicorn_approvals/tests/events/lambda/content_integrity_validator_function_success.json similarity index 100% rename from unicorn_properties/tests/unit/events/lambda/content_integrity_validator_function_success.json rename to unicorn_approvals/tests/events/lambda/content_integrity_validator_function_success.json diff --git a/unicorn_properties/tests/unit/events/lambda/contract_status_checker.json b/unicorn_approvals/tests/events/lambda/contract_status_checker.json similarity index 100% rename from unicorn_properties/tests/unit/events/lambda/contract_status_checker.json rename to unicorn_approvals/tests/events/lambda/contract_status_checker.json diff --git a/unicorn_properties/tests/unit/events/lambda/wait_for_contract_approval_function.json b/unicorn_approvals/tests/events/lambda/wait_for_contract_approval_function.json similarity index 100% rename from unicorn_properties/tests/unit/events/lambda/wait_for_contract_approval_function.json rename to unicorn_approvals/tests/events/lambda/wait_for_contract_approval_function.json diff --git a/unicorn_properties/tests/unit/__init__.py b/unicorn_approvals/tests/unit/__init__.py similarity index 100% rename from unicorn_properties/tests/unit/__init__.py rename to unicorn_approvals/tests/unit/__init__.py diff --git a/unicorn_properties/tests/unit/conftest.py b/unicorn_approvals/tests/unit/conftest.py similarity index 100% rename from unicorn_properties/tests/unit/conftest.py rename to unicorn_approvals/tests/unit/conftest.py diff --git a/unicorn_properties/tests/unit/helper.py b/unicorn_approvals/tests/unit/helper.py similarity index 90% rename from unicorn_properties/tests/unit/helper.py rename to unicorn_approvals/tests/unit/helper.py index cebe719..0b6367d 100644 --- a/unicorn_properties/tests/unit/helper.py +++ b/unicorn_approvals/tests/unit/helper.py @@ -6,7 +6,7 @@ TABLE_NAME = "table1" EVENTBUS_NAME = "test-eventbridge" -EVENTS_DIR = Path(__file__).parent / "events" +EVENTS_DIR = Path(__file__).parent.parent / "events" def load_event(filename): @@ -18,12 +18,12 @@ def return_env_vars_dict(k={}): "AWS_DEFAULT_REGION": "ap-southeast-2", "CONTRACT_STATUS_TABLE": TABLE_NAME, "EVENT_BUS": EVENTBUS_NAME, - "SERVICE_NAMESPACE": "unicorn.properties", - "POWERTOOLS_SERVICE_NAME": "unicorn.properties", + "SERVICE_NAMESPACE": "unicorn.approvals", + "POWERTOOLS_SERVICE_NAME": "unicorn.approvals", "POWERTOOLS_TRACE_DISABLED": "true", "POWERTOOLS_LOGGER_LOG_EVENT": "true", "POWERTOOLS_LOGGER_SAMPLE_RATE": "0.1", - "POWERTOOLS_METRICS_NAMESPACE": "unicorn.properties", + "POWERTOOLS_METRICS_NAMESPACE": "unicorn.approvals", "LOG_LEVEL": "INFO", } d.update(k) diff --git a/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py b/unicorn_approvals/tests/unit/test_contract_status_changed_event_handler.py similarity index 90% rename from unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py rename to unicorn_approvals/tests/unit/test_contract_status_changed_event_handler.py index 0d14e86..451883a 100644 --- a/unicorn_properties/tests/unit/test_contract_status_changed_event_handler.py +++ b/unicorn_approvals/tests/unit/test_contract_status_changed_event_handler.py @@ -14,7 +14,7 @@ def test_contract_status_changed_event_handler(dynamodb, lambda_context): eventbridge_event = load_event("eventbridge/contract_status_changed") - from properties_service import contract_status_changed_event_handler + from approvals_service import contract_status_changed_event_handler # Reload is required to prevent function setup reuse from another test reload(contract_status_changed_event_handler) @@ -30,7 +30,7 @@ def test_contract_status_changed_event_handler(dynamodb, lambda_context): def test_missing_property_id(dynamodb, lambda_context): eventbridge_event = {"detail": {}} - from properties_service import contract_status_changed_event_handler + from approvals_service import contract_status_changed_event_handler # Reload is required to prevent function setup reuse from another test reload(contract_status_changed_event_handler) diff --git a/unicorn_properties/tests/unit/test_properties_approval_sync_function.py b/unicorn_approvals/tests/unit/test_properties_approval_sync_function.py similarity index 89% rename from unicorn_properties/tests/unit/test_properties_approval_sync_function.py rename to unicorn_approvals/tests/unit/test_properties_approval_sync_function.py index 2f8a18e..6d12769 100644 --- a/unicorn_properties/tests/unit/test_properties_approval_sync_function.py +++ b/unicorn_approvals/tests/unit/test_properties_approval_sync_function.py @@ -12,7 +12,7 @@ def test_handle_status_changed_draft(stepfunction, lambda_context): ddbstream_event = load_event("ddb_stream_events/contract_status_changed_draft") - from properties_service import properties_approval_sync_function + from approvals_service import properties_approval_sync_function reload(properties_approval_sync_function) @@ -27,7 +27,7 @@ def test_handle_status_changed_approved(caplog, stepfunction, lambda_context): pass # ddbstream_event = load_event('ddb_stream_events/status_approved_waiting_for_approval') - # from properties_service import properties_approval_sync_function + # from publication_manager_service import properties_approval_sync_function # reload(properties_approval_sync_function) # ret = properties_approval_sync_function.lambda_handler(ddbstream_event, lambda_context) diff --git a/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py b/unicorn_approvals/tests/unit/test_wait_for_contract_approval_function.py similarity index 94% rename from unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py rename to unicorn_approvals/tests/unit/test_wait_for_contract_approval_function.py index a6ae95e..bd7db05 100644 --- a/unicorn_properties/tests/unit/test_wait_for_contract_approval_function.py +++ b/unicorn_approvals/tests/unit/test_wait_for_contract_approval_function.py @@ -12,7 +12,7 @@ def test_handle_wait_for_contract_approval_function(dynamodb, lambda_context): stepfunctions_event = load_event("lambda/wait_for_contract_approval_function") - from properties_service import wait_for_contract_approval_function + from approvals_service import wait_for_contract_approval_function reload(wait_for_contract_approval_function) diff --git a/unicorn_properties/uv.lock b/unicorn_approvals/uv.lock similarity index 74% rename from unicorn_properties/uv.lock rename to unicorn_approvals/uv.lock index 2f36876..15725a7 100644 --- a/unicorn_properties/uv.lock +++ b/unicorn_approvals/uv.lock @@ -11,6 +11,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "approvals-service" +version = "0.2.0" +source = { virtual = "." } +dependencies = [ + { name = "aws-lambda-powertools", extra = ["tracer"] }, + { name = "aws-xray-sdk" }, + { name = "boto3" }, +] + +[package.optional-dependencies] +dev = [ + { name = "arnparse" }, + { name = "aws-lambda-powertools", extra = ["all"] }, + { name = "importlib-metadata" }, + { name = "moto", extra = ["dynamodb", "events"] }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "arnparse", marker = "extra == 'dev'", specifier = ">=0.0.2" }, + { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.18.0" }, + { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.18.0" }, + { name = "aws-xray-sdk", specifier = ">=2.14.0" }, + { name = "boto3", specifier = ">=1.40.2" }, + { name = "importlib-metadata", marker = "extra == 'dev'", specifier = ">=8.4.0" }, + { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.1.9" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" }, + { name = "pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.2" }, + { name = "requests", marker = "extra == 'dev'", specifier = ">=2.32.3" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.7" }, +] +provides-extras = ["dev"] + [[package]] name = "arnparse" version = "0.0.2" @@ -31,7 +69,7 @@ wheels = [ [[package]] name = "aws-encryption-sdk" -version = "4.0.1" +version = "4.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -39,22 +77,22 @@ dependencies = [ { name = "cryptography" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/ff/fefbcc1cee829f3ab188dbcb5069862f61b64ed82a6205314f1ab7bb90e6/aws-encryption-sdk-4.0.1.tar.gz", hash = "sha256:7320dc4cf8d8d5a9b4c88a343be93835da18756e05308d3536554be0ca2889a5", size = 260219, upload-time = "2025-03-27T17:24:54.669Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/85/d2cc3ccb44cbd325347fe895cc449e1e3ad8097e68e33763b9fdf6ff88df/aws_encryption_sdk-4.0.2.tar.gz", hash = "sha256:911a900980732e509b86e0443fe3bdcee480760a460e0f702f360565a20f3888", size = 260341, upload-time = "2025-07-02T17:09:36.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/a5/82956e2111b169c644633212da2a5f84dd9d953b9dd146ccfccfb8a46290/aws_encryption_sdk-4.0.1-py2.py3-none-any.whl", hash = "sha256:5c2ca9a207e1732542a1370ac7efd630ab6e04d05f98e68badf20927eb95ed1d", size = 99127, upload-time = "2025-03-27T17:24:50.903Z" }, + { url = "https://files.pythonhosted.org/packages/1f/89/f4bec3d892f339927524824b3baf41391559118f1f0b4e162c208e15157e/aws_encryption_sdk-4.0.2-py2.py3-none-any.whl", hash = "sha256:73d9aadc3b10927148f3e057e51e0c15f0e68431df6d3ef45d8af83fefe7156f", size = 99130, upload-time = "2025-07-02T17:09:35.116Z" }, ] [[package]] name = "aws-lambda-powertools" -version = "3.13.0" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/2b/068efd467c0866e2272c5de7525ddb02ff4e694f71245c8d2a83d4948f23/aws_lambda_powertools-3.13.0.tar.gz", hash = "sha256:99dc11ac6eb81564f599fdd85ba79069f7740ae3481c99bca2cee8abb7c95543", size = 672664, upload-time = "2025-05-20T07:35:30.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d9/5ba420bbe1aef011559e035a1d1ef82a6d30500610116e7feb2f2b5dcda7/aws_lambda_powertools-3.18.0.tar.gz", hash = "sha256:74f484b03dfb733769828bf8e9f33ac427cd57c477a9ceae2dc19f643051e3c3", size = 686276, upload-time = "2025-07-29T08:22:50.901Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/cd/2241ff877528c66ee11ea636684c4242ceeadb6459a33b08507a40151414/aws_lambda_powertools-3.13.0-py3-none-any.whl", hash = "sha256:9df045f4c3ff944176655813dbff8c1160e056babf5e6d71d4e18c0003818f2e", size = 802546, upload-time = "2025-05-20T07:35:27.767Z" }, + { url = "https://files.pythonhosted.org/packages/42/b1/ca638f5ce0f4d1c4c75412342c08d124142facf89104f9d3659e9c395c63/aws_lambda_powertools-3.18.0-py3-none-any.whl", hash = "sha256:5afb230abf4e64bce00d35e858a4e83ae99c667f648c1f4d246e4bb022715df8", size = 828005, upload-time = "2025-07-29T08:22:48.807Z" }, ] [package.optional-dependencies] @@ -85,39 +123,39 @@ wheels = [ [[package]] name = "boto3" -version = "1.38.25" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/af/404827da46c67359e6d2a63b0f4fadd5b6150551d25c405b3bd480c19319/boto3-1.38.25.tar.gz", hash = "sha256:85c1556a110896f68de8573a9b4757c81071448dbf6ffc1074941bfc8a43195e", size = 111819, upload-time = "2025-05-28T19:26:49.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/c0/9ceff05d2243f169765ae9db08fa6f085d026af71a778cd083dc972f0f2b/boto3-1.40.2.tar.gz", hash = "sha256:2dfbc214fdbf94abfd61eec687ea39089d05af43bb00be792c76f3a6c1393f7b", size = 111826, upload-time = "2025-08-04T19:31:51.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/27/b2ea9d2494b1d35c3de23485bf6b0a7b8539dea965349c8829a1fd84d08e/boto3-1.38.25-py3-none-any.whl", hash = "sha256:2f2cd517dd31d33ace0eefe567dc903fdf74221513e32f1e9445bdfac7554db7", size = 139938, upload-time = "2025-05-28T19:26:44.902Z" }, + { url = "https://files.pythonhosted.org/packages/f7/66/01bccaaebcd1365ce1334be042765e49ccf23787887afb8e43c6d4bc2f6e/boto3-1.40.2-py3-none-any.whl", hash = "sha256:3d99325ee874190e8f3bfd38823987327c826cdfbab943420851bdb7684d727c", size = 139882, upload-time = "2025-08-04T19:31:50.493Z" }, ] [[package]] name = "botocore" -version = "1.38.25" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/00/bed5bf325d1fdb98d6212d106bd44cd0bbe65563e3a2c3f87b64cc4d75c2/botocore-1.38.25.tar.gz", hash = "sha256:8c73e97d9662a6c92be33dab66cd1e2b59797154c7ec379ce3bb5d6779d9d78c", size = 13914295, upload-time = "2025-05-28T19:26:35.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/e7d68381042a6d50510c8d4629f39922ce27ff32f45baf852ba6534342c5/botocore-1.40.2.tar.gz", hash = "sha256:77c4710bf37b28e897833b5b1f47d6a83e45a29985cd01a560dfdb8b6ad524e5", size = 14284599, upload-time = "2025-08-04T19:31:42.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/ae/3b52634df58cbd506ad804315a1c979ea06942cf88b591dcd671f45adf63/botocore-1.38.25-py3-none-any.whl", hash = "sha256:5a960bd990a11cdb78865e908a58ed712d87d9b419f55ad21c99d7d21f0d6726", size = 13574906, upload-time = "2025-05-28T19:26:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" }, ] [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] @@ -199,37 +237,37 @@ wheels = [ [[package]] name = "cryptography" -version = "45.0.3" +version = "45.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/1f/9fa001e74a1993a9cadd2333bb889e50c66327b8594ac538ab8a04f915b7/cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899", size = 744738, upload-time = "2025-05-25T14:17:24.777Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b2/2345dc595998caa6f68adf84e8f8b50d18e9fc4638d32b22ea8daedd4b7a/cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71", size = 7056239, upload-time = "2025-05-25T14:16:12.22Z" }, - { url = "https://files.pythonhosted.org/packages/71/3d/ac361649a0bfffc105e2298b720d8b862330a767dab27c06adc2ddbef96a/cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b", size = 4205541, upload-time = "2025-05-25T14:16:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/70/3e/c02a043750494d5c445f769e9c9f67e550d65060e0bfce52d91c1362693d/cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f", size = 4433275, upload-time = "2025-05-25T14:16:16.421Z" }, - { url = "https://files.pythonhosted.org/packages/40/7a/9af0bfd48784e80eef3eb6fd6fde96fe706b4fc156751ce1b2b965dada70/cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942", size = 4209173, upload-time = "2025-05-25T14:16:18.163Z" }, - { url = "https://files.pythonhosted.org/packages/31/5f/d6f8753c8708912df52e67969e80ef70b8e8897306cd9eb8b98201f8c184/cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9", size = 3898150, upload-time = "2025-05-25T14:16:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/8b/50/f256ab79c671fb066e47336706dc398c3b1e125f952e07d54ce82cf4011a/cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56", size = 4466473, upload-time = "2025-05-25T14:16:22.605Z" }, - { url = "https://files.pythonhosted.org/packages/62/e7/312428336bb2df0848d0768ab5a062e11a32d18139447a76dfc19ada8eed/cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca", size = 4211890, upload-time = "2025-05-25T14:16:24.738Z" }, - { url = "https://files.pythonhosted.org/packages/e7/53/8a130e22c1e432b3c14896ec5eb7ac01fb53c6737e1d705df7e0efb647c6/cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1", size = 4466300, upload-time = "2025-05-25T14:16:26.768Z" }, - { url = "https://files.pythonhosted.org/packages/ba/75/6bb6579688ef805fd16a053005fce93944cdade465fc92ef32bbc5c40681/cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578", size = 4332483, upload-time = "2025-05-25T14:16:28.316Z" }, - { url = "https://files.pythonhosted.org/packages/2f/11/2538f4e1ce05c6c4f81f43c1ef2bd6de7ae5e24ee284460ff6c77e42ca77/cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497", size = 4573714, upload-time = "2025-05-25T14:16:30.474Z" }, - { url = "https://files.pythonhosted.org/packages/f5/bb/e86e9cf07f73a98d84a4084e8fd420b0e82330a901d9cac8149f994c3417/cryptography-45.0.3-cp311-abi3-win32.whl", hash = "sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710", size = 2934752, upload-time = "2025-05-25T14:16:32.204Z" }, - { url = "https://files.pythonhosted.org/packages/c7/75/063bc9ddc3d1c73e959054f1fc091b79572e716ef74d6caaa56e945b4af9/cryptography-45.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490", size = 3412465, upload-time = "2025-05-25T14:16:33.888Z" }, - { url = "https://files.pythonhosted.org/packages/71/9b/04ead6015229a9396890d7654ee35ef630860fb42dc9ff9ec27f72157952/cryptography-45.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06", size = 7031892, upload-time = "2025-05-25T14:16:36.214Z" }, - { url = "https://files.pythonhosted.org/packages/46/c7/c7d05d0e133a09fc677b8a87953815c522697bdf025e5cac13ba419e7240/cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57", size = 4196181, upload-time = "2025-05-25T14:16:37.934Z" }, - { url = "https://files.pythonhosted.org/packages/08/7a/6ad3aa796b18a683657cef930a986fac0045417e2dc428fd336cfc45ba52/cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716", size = 4423370, upload-time = "2025-05-25T14:16:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/4f/58/ec1461bfcb393525f597ac6a10a63938d18775b7803324072974b41a926b/cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8", size = 4197839, upload-time = "2025-05-25T14:16:41.322Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3d/5185b117c32ad4f40846f579369a80e710d6146c2baa8ce09d01612750db/cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc", size = 3886324, upload-time = "2025-05-25T14:16:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/67/85/caba91a57d291a2ad46e74016d1f83ac294f08128b26e2a81e9b4f2d2555/cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342", size = 4450447, upload-time = "2025-05-25T14:16:44.759Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d1/164e3c9d559133a38279215c712b8ba38e77735d3412f37711b9f8f6f7e0/cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b", size = 4200576, upload-time = "2025-05-25T14:16:46.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/7a/e002d5ce624ed46dfc32abe1deff32190f3ac47ede911789ee936f5a4255/cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782", size = 4450308, upload-time = "2025-05-25T14:16:48.228Z" }, - { url = "https://files.pythonhosted.org/packages/87/ad/3fbff9c28cf09b0a71e98af57d74f3662dea4a174b12acc493de00ea3f28/cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65", size = 4325125, upload-time = "2025-05-25T14:16:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b4/51417d0cc01802304c1984d76e9592f15e4801abd44ef7ba657060520bf0/cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b", size = 4560038, upload-time = "2025-05-25T14:16:51.398Z" }, - { url = "https://files.pythonhosted.org/packages/80/38/d572f6482d45789a7202fb87d052deb7a7b136bf17473ebff33536727a2c/cryptography-45.0.3-cp37-abi3-win32.whl", hash = "sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab", size = 2924070, upload-time = "2025-05-25T14:16:53.472Z" }, - { url = "https://files.pythonhosted.org/packages/91/5a/61f39c0ff4443651cc64e626fa97ad3099249152039952be8f344d6b0c86/cryptography-45.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2", size = 3395005, upload-time = "2025-05-25T14:16:55.134Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, ] [[package]] @@ -358,7 +396,7 @@ wheels = [ [[package]] name = "moto" -version = "5.1.5" +version = "5.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -371,9 +409,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/9f/5cacf53cbd26b4a77c817fd4fbec2ed38139ba2cfcd842c279aa19ee161f/moto-5.1.5.tar.gz", hash = "sha256:42b362ea9a16181e8e7b615ac212c294b882f020e9ae02f01230f167926df84e", size = 6893155, upload-time = "2025-05-24T12:19:55.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/62/767e0f65066489b50580a25182025fec19825cbed499ae2044da1e779ed6/moto-5.1.9.tar.gz", hash = "sha256:0c4f0387b06b5d24c0ce90f8f89f31a565cc05789189c5d59b5df02594f2e371", size = 7041662, upload-time = "2025-07-28T19:24:48.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/0f/72a420e972a9e782d08843ced85d29f04c8470c66fc84ecb1c8a7e7abcf8/moto-5.1.5-py3-none-any.whl", hash = "sha256:866ae85eb5efe11a78f991127531878fd7f49177eb4a6680f47060430eb8932d", size = 4992353, upload-time = "2025-05-24T12:19:53.435Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/fd37926e3fd597df879d7c360f1641850023d30b1d67ba6686f4e33ab1fb/moto-5.1.9-py3-none-any.whl", hash = "sha256:e9ba7e4764a6088ccc34e3cc846ae719861ca202409fa865573de40a3e805b9b", size = 5216109, upload-time = "2025-07-28T19:24:45.997Z" }, ] [package.optional-dependencies] @@ -412,44 +450,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] -[[package]] -name = "properties-service" -version = "0.2.0" -source = { virtual = "." } -dependencies = [ - { name = "aws-lambda-powertools", extra = ["tracer"] }, - { name = "aws-xray-sdk" }, - { name = "boto3" }, -] - -[package.optional-dependencies] -dev = [ - { name = "arnparse" }, - { name = "aws-lambda-powertools", extra = ["all"] }, - { name = "importlib-metadata" }, - { name = "moto", extra = ["dynamodb", "events"] }, - { name = "pytest" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "arnparse", marker = "extra == 'dev'", specifier = ">=0.0.2" }, - { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.9.0" }, - { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.9.0" }, - { name = "aws-xray-sdk", specifier = ">=2.14.0" }, - { name = "boto3", specifier = ">=1.37.23" }, - { name = "importlib-metadata", marker = "extra == 'dev'", specifier = ">=8.4.0" }, - { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.0.14" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" }, - { name = "pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.2" }, - { name = "requests", marker = "extra == 'dev'", specifier = ">=2.32.3" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.7" }, -] -provides-extras = ["dev"] - [[package]] name = "py-partiql-parser" version = "0.6.1" @@ -470,7 +470,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.5" +version = "2.11.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -478,9 +478,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] @@ -527,31 +527,41 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.9.1" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -568,24 +578,27 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] name = "pywin32" -version = "310" +version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, - { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, - { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload-time = "2025-03-17T00:56:04.383Z" }, - { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload-time = "2025-03-17T00:56:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] [[package]] @@ -616,7 +629,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -624,9 +637,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] @@ -645,39 +658,39 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/53/ae4857030d59286924a8bdb30d213d6ff22d8f0957e738d0289990091dd8/ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", size = 4186707, upload-time = "2025-05-22T19:19:34.363Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/14/f2326676197bab099e2a24473158c21656fbf6a207c65f596ae15acb32b9/ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", size = 10229049, upload-time = "2025-05-22T19:18:45.516Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f3/bff7c92dd66c959e711688b2e0768e486bbca46b2f35ac319bb6cce04447/ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", size = 11053601, upload-time = "2025-05-22T19:18:49.269Z" }, - { url = "https://files.pythonhosted.org/packages/e2/38/8e1a3efd0ef9d8259346f986b77de0f62c7a5ff4a76563b6b39b68f793b9/ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", size = 10367421, upload-time = "2025-05-22T19:18:51.754Z" }, - { url = "https://files.pythonhosted.org/packages/b4/50/557ad9dd4fb9d0bf524ec83a090a3932d284d1a8b48b5906b13b72800e5f/ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", size = 10581980, upload-time = "2025-05-22T19:18:54.011Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b2/e2ed82d6e2739ece94f1bdbbd1d81b712d3cdaf69f0a1d1f1a116b33f9ad/ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", size = 10089241, upload-time = "2025-05-22T19:18:56.041Z" }, - { url = "https://files.pythonhosted.org/packages/3d/9f/b4539f037a5302c450d7c695c82f80e98e48d0d667ecc250e6bdeb49b5c3/ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac", size = 11699398, upload-time = "2025-05-22T19:18:58.248Z" }, - { url = "https://files.pythonhosted.org/packages/61/fb/32e029d2c0b17df65e6eaa5ce7aea5fbeaed22dddd9fcfbbf5fe37c6e44e/ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", size = 12427955, upload-time = "2025-05-22T19:19:00.981Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e3/160488dbb11f18c8121cfd588e38095ba779ae208292765972f7732bfd95/ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", size = 12069803, upload-time = "2025-05-22T19:19:03.258Z" }, - { url = "https://files.pythonhosted.org/packages/ff/16/3b006a875f84b3d0bff24bef26b8b3591454903f6f754b3f0a318589dcc3/ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", size = 11242630, upload-time = "2025-05-22T19:19:05.871Z" }, - { url = "https://files.pythonhosted.org/packages/65/0d/0338bb8ac0b97175c2d533e9c8cdc127166de7eb16d028a43c5ab9e75abd/ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", size = 11507310, upload-time = "2025-05-22T19:19:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bf/d7130eb26174ce9b02348b9f86d5874eafbf9f68e5152e15e8e0a392e4a3/ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", size = 10441144, upload-time = "2025-05-22T19:19:13.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f3/4be2453b258c092ff7b1761987cf0749e70ca1340cd1bfb4def08a70e8d8/ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", size = 10081987, upload-time = "2025-05-22T19:19:15.821Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6e/dfa4d2030c5b5c13db158219f2ec67bf333e8a7748dccf34cfa2a6ab9ebc/ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", size = 11073922, upload-time = "2025-05-22T19:19:18.104Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f4/f7b0b0c3d32b593a20ed8010fa2c1a01f2ce91e79dda6119fcc51d26c67b/ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", size = 11568537, upload-time = "2025-05-22T19:19:20.889Z" }, - { url = "https://files.pythonhosted.org/packages/d2/46/0e892064d0adc18bcc81deed9aaa9942a27fd2cd9b1b7791111ce468c25f/ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", size = 10536492, upload-time = "2025-05-22T19:19:23.642Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d9/232e79459850b9f327e9f1dc9c047a2a38a6f9689e1ec30024841fc4416c/ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", size = 11612562, upload-time = "2025-05-22T19:19:27.013Z" }, - { url = "https://files.pythonhosted.org/packages/ce/eb/09c132cff3cc30b2e7244191dcce69437352d6d6709c0adf374f3e6f476e/ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", size = 10735951, upload-time = "2025-05-22T19:19:30.043Z" }, +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, ] [[package]] name = "s3transfer" -version = "0.13.0" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, ] [[package]] @@ -691,11 +704,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.13.2" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] @@ -712,11 +725,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] @@ -784,9 +797,9 @@ wheels = [ [[package]] name = "zipp" -version = "3.22.0" +version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] diff --git a/unicorn_contracts/README.md b/unicorn_contracts/README.md index 1cfad3b..61fdd99 100644 --- a/unicorn_contracts/README.md +++ b/unicorn_contracts/README.md @@ -4,13 +4,22 @@ ## Architecture overview -Unicorn Contract manages the contractual relationship between the customers and the Unicorn Properties agency. It's primary function is to allow Unicorn Properties agents to create a new contract for a property listing, and to have the contract approved once it's ready. +The **Unicorn Contracts** service manages contractual relationships between customers and Unicorn Properties agency. The service handles standard terms and conditions, property service rates, fees, and additional services. -The architecture is fairly straight forward. An API exposes the create contract and update contract methods. This information is recorded in a Amazon DynamoDB table which will contain all latest information about the contract and it's status. +Each property can have only one active contract. Properties use their address as a unique identifier instead of a GUID, which correlates across services. -Each time a new contract is created or updated, Unicorn Contracts publishes a `ContractStatusChanged` event to Amazon EventBridge signalling changes to the contract status. These events are consumed by **Unicorn Properties**, so it can track changes to contracts, without needing to take a direct dependency on Unicorn Contracts and it's database. +For example: `usa/anytown/main-street/111`. -Here is an example of an event that is published to EventBridge: +The contract workflow operates as follows: + +1. Agents submit contract creation/update commands through the Contracts API +1. The API sends requests to Amazon SQS +1. A Contracts function processes the queue messages and updates Amazon DynamoDB +1. DynamoDB Streams captures contract changes +1. Amazon EventBridge Pipes transforms the DynamoDB records into ContractStatusChanged events +1. Unicorn Approvals consumes these events to track contract changes without direct database dependencies + +An example of `ContractStatusChanged` event: ```json { diff --git a/unicorn_contracts/pyproject.toml b/unicorn_contracts/pyproject.toml index 8cb837c..a4007d4 100644 --- a/unicorn_contracts/pyproject.toml +++ b/unicorn_contracts/pyproject.toml @@ -9,24 +9,30 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ - "aws-lambda-powertools[tracer]>=3.9.0", + "aws-lambda-powertools[tracer]>=3.18.0", "aws-xray-sdk>=2.14.0", - "boto3>=1.37.23", + "boto3>=1.40.2", ] [project.optional-dependencies] dev = [ - "aws-lambda-powertools[all]>=3.9.0", + "aws-lambda-powertools[all]>=3.18.0", "requests>=2.32.3", - "moto[dynamodb,events,sqs]>=5.0.14", + "moto[dynamodb,events,sqs]>=5.1.9", "importlib-metadata>=8.4.0", "pyyaml>=6.0.2", "arnparse>=0.0.2", - "pytest>=8.3.4", - "ruff>=0.9.7", + "pytest>=8.4.1", + "ruff>=0.12.7", "tomli>=2.2.1", ] [tool.setuptools] package-dir = {"contracts_service" = "src"} packages = ["contracts_service"] + +[tool.pytest.ini_options] +minversion = "7.0" +addopts = "-ra -vv -W ignore::UserWarning" +testpaths = ["tests/unit", "tests/integration"] +pythonpath = ["."] \ No newline at end of file diff --git a/unicorn_contracts/template.yaml b/unicorn_contracts/template.yaml index 33b2b88..c5732ed 100644 --- a/unicorn_contracts/template.yaml +++ b/unicorn_contracts/template.yaml @@ -1,6 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -AWSTemplateFormatVersion: 2010-09-09 +AWSTemplateFormatVersion: "2010-09-09" Transform: - AWS::Serverless-2016-10-31 Description: > @@ -15,9 +15,7 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 - - E3002 # Rule disabled due to using cfn-lint-serverless rules v0.3 - + Parameters: Stage: Type: String @@ -293,7 +291,7 @@ Resources: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: diff --git a/unicorn_contracts/uv.lock b/unicorn_contracts/uv.lock index 18ac214..4a802fb 100644 --- a/unicorn_contracts/uv.lock +++ b/unicorn_contracts/uv.lock @@ -46,15 +46,15 @@ wheels = [ [[package]] name = "aws-lambda-powertools" -version = "3.13.0" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/2b/068efd467c0866e2272c5de7525ddb02ff4e694f71245c8d2a83d4948f23/aws_lambda_powertools-3.13.0.tar.gz", hash = "sha256:99dc11ac6eb81564f599fdd85ba79069f7740ae3481c99bca2cee8abb7c95543", size = 672664, upload-time = "2025-05-20T07:35:30.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d9/5ba420bbe1aef011559e035a1d1ef82a6d30500610116e7feb2f2b5dcda7/aws_lambda_powertools-3.18.0.tar.gz", hash = "sha256:74f484b03dfb733769828bf8e9f33ac427cd57c477a9ceae2dc19f643051e3c3", size = 686276, upload-time = "2025-07-29T08:22:50.901Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/cd/2241ff877528c66ee11ea636684c4242ceeadb6459a33b08507a40151414/aws_lambda_powertools-3.13.0-py3-none-any.whl", hash = "sha256:9df045f4c3ff944176655813dbff8c1160e056babf5e6d71d4e18c0003818f2e", size = 802546, upload-time = "2025-05-20T07:35:27.767Z" }, + { url = "https://files.pythonhosted.org/packages/42/b1/ca638f5ce0f4d1c4c75412342c08d124142facf89104f9d3659e9c395c63/aws_lambda_powertools-3.18.0-py3-none-any.whl", hash = "sha256:5afb230abf4e64bce00d35e858a4e83ae99c667f648c1f4d246e4bb022715df8", size = 828005, upload-time = "2025-07-29T08:22:48.807Z" }, ] [package.optional-dependencies] @@ -85,30 +85,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.38.24" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/7d/cdd55376fe9b9102a843649cbd9cba38d49bfd570a89042c090550b23bf5/boto3-1.38.24.tar.gz", hash = "sha256:abdb8c760543e9c22026320e62e2934762b0c4ac4f42e8ea2a756f2d489b3135", size = 111854, upload-time = "2025-05-27T21:26:22.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/c0/9ceff05d2243f169765ae9db08fa6f085d026af71a778cd083dc972f0f2b/boto3-1.40.2.tar.gz", hash = "sha256:2dfbc214fdbf94abfd61eec687ea39089d05af43bb00be792c76f3a6c1393f7b", size = 111826, upload-time = "2025-08-04T19:31:51.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/cc/78cf9f63bfa84d3f0ac4d5a527a3d141ede40554fd4718ec2634dee08683/boto3-1.38.24-py3-none-any.whl", hash = "sha256:1f95ec3ac88ae6381fa0409e4c2ad0a41f0caf5fd6d8ef45a9525406a3f58b18", size = 139938, upload-time = "2025-05-27T21:26:18.601Z" }, + { url = "https://files.pythonhosted.org/packages/f7/66/01bccaaebcd1365ce1334be042765e49ccf23787887afb8e43c6d4bc2f6e/boto3-1.40.2-py3-none-any.whl", hash = "sha256:3d99325ee874190e8f3bfd38823987327c826cdfbab943420851bdb7684d727c", size = 139882, upload-time = "2025-08-04T19:31:50.493Z" }, ] [[package]] name = "botocore" -version = "1.38.24" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/1b/1e38f24245e1b0461470176335bc0a443050459e9e64a0d881244a0a8a5e/botocore-1.38.24.tar.gz", hash = "sha256:43563d5c2dfd56ebbcd9e25f482fc45000bfaec5966b26c77b331bd340c46376", size = 13909191, upload-time = "2025-05-27T21:26:08.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/e7d68381042a6d50510c8d4629f39922ce27ff32f45baf852ba6534342c5/botocore-1.40.2.tar.gz", hash = "sha256:77c4710bf37b28e897833b5b1f47d6a83e45a29985cd01a560dfdb8b6ad524e5", size = 14284599, upload-time = "2025-08-04T19:31:42.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/58/197221be8faf51ae4fb72c227601db468ef7981c107efbff27d794445942/botocore-1.38.24-py3-none-any.whl", hash = "sha256:5901667b96d3a8603479879ab097560216cdc4c2918d433fc6509555d0ada29c", size = 13570245, upload-time = "2025-05-27T21:26:04.669Z" }, + { url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" }, ] [[package]] @@ -223,16 +223,16 @@ dev = [ [package.metadata] requires-dist = [ { name = "arnparse", marker = "extra == 'dev'", specifier = ">=0.0.2" }, - { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.9.0" }, - { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.9.0" }, + { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.18.0" }, + { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.18.0" }, { name = "aws-xray-sdk", specifier = ">=2.14.0" }, - { name = "boto3", specifier = ">=1.37.23" }, + { name = "boto3", specifier = ">=1.40.2" }, { name = "importlib-metadata", marker = "extra == 'dev'", specifier = ">=8.4.0" }, - { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.0.14" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" }, + { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.1.9" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" }, { name = "pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.2" }, { name = "requests", marker = "extra == 'dev'", specifier = ">=2.32.3" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.7" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.7" }, { name = "tomli", marker = "extra == 'dev'", specifier = ">=2.2.1" }, ] provides-extras = ["dev"] @@ -398,7 +398,7 @@ wheels = [ [[package]] name = "moto" -version = "5.1.5" +version = "5.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -411,9 +411,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/9f/5cacf53cbd26b4a77c817fd4fbec2ed38139ba2cfcd842c279aa19ee161f/moto-5.1.5.tar.gz", hash = "sha256:42b362ea9a16181e8e7b615ac212c294b882f020e9ae02f01230f167926df84e", size = 6893155, upload-time = "2025-05-24T12:19:55.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/62/767e0f65066489b50580a25182025fec19825cbed499ae2044da1e779ed6/moto-5.1.9.tar.gz", hash = "sha256:0c4f0387b06b5d24c0ce90f8f89f31a565cc05789189c5d59b5df02594f2e371", size = 7041662, upload-time = "2025-07-28T19:24:48.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/0f/72a420e972a9e782d08843ced85d29f04c8470c66fc84ecb1c8a7e7abcf8/moto-5.1.5-py3-none-any.whl", hash = "sha256:866ae85eb5efe11a78f991127531878fd7f49177eb4a6680f47060430eb8932d", size = 4992353, upload-time = "2025-05-24T12:19:53.435Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/fd37926e3fd597df879d7c360f1641850023d30b1d67ba6686f4e33ab1fb/moto-5.1.9-py3-none-any.whl", hash = "sha256:e9ba7e4764a6088ccc34e3cc846ae719861ca202409fa865573de40a3e805b9b", size = 5216109, upload-time = "2025-07-28T19:24:45.997Z" }, ] [package.optional-dependencies] @@ -541,19 +541,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -647,27 +657,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/53/ae4857030d59286924a8bdb30d213d6ff22d8f0957e738d0289990091dd8/ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", size = 4186707, upload-time = "2025-05-22T19:19:34.363Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/14/f2326676197bab099e2a24473158c21656fbf6a207c65f596ae15acb32b9/ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", size = 10229049, upload-time = "2025-05-22T19:18:45.516Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f3/bff7c92dd66c959e711688b2e0768e486bbca46b2f35ac319bb6cce04447/ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", size = 11053601, upload-time = "2025-05-22T19:18:49.269Z" }, - { url = "https://files.pythonhosted.org/packages/e2/38/8e1a3efd0ef9d8259346f986b77de0f62c7a5ff4a76563b6b39b68f793b9/ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", size = 10367421, upload-time = "2025-05-22T19:18:51.754Z" }, - { url = "https://files.pythonhosted.org/packages/b4/50/557ad9dd4fb9d0bf524ec83a090a3932d284d1a8b48b5906b13b72800e5f/ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", size = 10581980, upload-time = "2025-05-22T19:18:54.011Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b2/e2ed82d6e2739ece94f1bdbbd1d81b712d3cdaf69f0a1d1f1a116b33f9ad/ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", size = 10089241, upload-time = "2025-05-22T19:18:56.041Z" }, - { url = "https://files.pythonhosted.org/packages/3d/9f/b4539f037a5302c450d7c695c82f80e98e48d0d667ecc250e6bdeb49b5c3/ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac", size = 11699398, upload-time = "2025-05-22T19:18:58.248Z" }, - { url = "https://files.pythonhosted.org/packages/61/fb/32e029d2c0b17df65e6eaa5ce7aea5fbeaed22dddd9fcfbbf5fe37c6e44e/ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", size = 12427955, upload-time = "2025-05-22T19:19:00.981Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e3/160488dbb11f18c8121cfd588e38095ba779ae208292765972f7732bfd95/ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", size = 12069803, upload-time = "2025-05-22T19:19:03.258Z" }, - { url = "https://files.pythonhosted.org/packages/ff/16/3b006a875f84b3d0bff24bef26b8b3591454903f6f754b3f0a318589dcc3/ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", size = 11242630, upload-time = "2025-05-22T19:19:05.871Z" }, - { url = "https://files.pythonhosted.org/packages/65/0d/0338bb8ac0b97175c2d533e9c8cdc127166de7eb16d028a43c5ab9e75abd/ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", size = 11507310, upload-time = "2025-05-22T19:19:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bf/d7130eb26174ce9b02348b9f86d5874eafbf9f68e5152e15e8e0a392e4a3/ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", size = 10441144, upload-time = "2025-05-22T19:19:13.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f3/4be2453b258c092ff7b1761987cf0749e70ca1340cd1bfb4def08a70e8d8/ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", size = 10081987, upload-time = "2025-05-22T19:19:15.821Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6e/dfa4d2030c5b5c13db158219f2ec67bf333e8a7748dccf34cfa2a6ab9ebc/ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", size = 11073922, upload-time = "2025-05-22T19:19:18.104Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f4/f7b0b0c3d32b593a20ed8010fa2c1a01f2ce91e79dda6119fcc51d26c67b/ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", size = 11568537, upload-time = "2025-05-22T19:19:20.889Z" }, - { url = "https://files.pythonhosted.org/packages/d2/46/0e892064d0adc18bcc81deed9aaa9942a27fd2cd9b1b7791111ce468c25f/ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", size = 10536492, upload-time = "2025-05-22T19:19:23.642Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d9/232e79459850b9f327e9f1dc9c047a2a38a6f9689e1ec30024841fc4416c/ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", size = 11612562, upload-time = "2025-05-22T19:19:27.013Z" }, - { url = "https://files.pythonhosted.org/packages/ce/eb/09c132cff3cc30b2e7244191dcce69437352d6d6709c0adf374f3e6f476e/ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", size = 10735951, upload-time = "2025-05-22T19:19:30.043Z" }, +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, ] [[package]] diff --git a/unicorn_properties/README.md b/unicorn_properties/README.md deleted file mode 100644 index ad4ebce..0000000 --- a/unicorn_properties/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Developing Unicorn Properties - -![Properties Approval Architecture](https://static.us-east-1.prod.workshops.aws/public/f273b5fc-17cd-406b-9e63-1d331b00589d/static/images/architecture-properties.png) - -## Architecture overview - -Unicorn Properties is primarily responsible for approving property listings for Unicorn Web. - -A core component of Unicorn Properties is the approvals workflow. The approvals workflow is implemented using an AWS Step Functions state machine. At a high level, the workflow will: - -* Check whether or not it has any contract information for the property it needs to approve. If there is no contract information, the approval process cannot be completed. -* Ensure the sentiment of the property description is positive and that there no unsafe images. All checks must pass for the listing to be made public. -* Ensure that the contract is in an APPROVED state before it can approve the listing. This accounts for a situation where the property listings are created before the contract has been signed and the services for Unicorn Properties are paid for. -* Publish the result of the workflow via the `PublicationEvaluationCompleted` event. - -The workflow is initiated by a request made by an Unicorn Properties **agent** to have the property approved for publication. Once they have created a property listing (added property details and photos), they initiate the request in Unicorn Web, which generates a `PublicationApprovalRequested` event. This event contains the property information which the workflow processes. - -In order process the approvals workflow successfully, the properties service needs to know the current status of a contract. To remain fully decoupled from the **Contracts Service**, it maintains a local copy of contract status by consuming the `ContractStatusChanged` event. This is eliminates the need for the Contracts service to expose an API that gives other services access to its database, and allows the Properties service to function autonomously. - -When the workflow is paused to check to see whether or not the contract is in an approved state, the `WaitForContractApproval` state will update a contract status for a specified property with its task token. This initiates a stream event on the DynamoDB table. The Property approvals sync function handles DynamoDB stream events. It determines whether or not to pass AWS Step Function task token back to the state machine based on the contract state. - -If workflow is completed successfully, it will emit a `PublicationEvaluationCompleted` event, with an evaluation result of `APPROVED` or `DECLINED`. This is what the Property Web will listen to in order to make the list available for publication. diff --git a/unicorn_properties/pyproject.toml b/unicorn_properties/pyproject.toml deleted file mode 100644 index c3346df..0000000 --- a/unicorn_properties/pyproject.toml +++ /dev/null @@ -1,31 +0,0 @@ -[project] -name = "properties_service" -version = "0.2.0" -description = "Unicorn Properties Property Service" -authors = [ - {name = "Amazon Web Services"} -] -readme = "README.md" -requires-python = ">=3.12" - -dependencies = [ - "aws-lambda-powertools[tracer]>=3.9.0", - "aws-xray-sdk>=2.14.0", - "boto3>=1.37.23", -] - -[project.optional-dependencies] -dev = [ - "aws-lambda-powertools[all]>=3.9.0", - "requests>=2.32.3", - "moto[dynamodb,events,sqs]>=5.0.14", - "importlib-metadata>=8.4.0", - "pyyaml>=6.0.2", - "arnparse>=0.0.2", - "pytest>=8.3.4", - "ruff>=0.9.7", -] - -[tool.setuptools] -package-dir = {"properties_service" = "src"} -packages = ["properties_service"] diff --git a/unicorn_properties/src/README.md b/unicorn_properties/src/README.md deleted file mode 100644 index f4b82a2..0000000 --- a/unicorn_properties/src/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# ContractStatusChanged - -*Automatically generated by the [Amazon Event Schemas](https://aws.amazon.com/)* - -## Requirements - -1. Python 36+ -2. six 1.12.0 -3. regex 2019.11.1 - -## Install Dependencies -### pip users - -Create and update it in current project's **requirements.txt**: - -``` -six == 1.12.0 -regex == 2019.11.1 -``` - -Run Command: - -```sh -pip3 install -r requirements.txt -``` diff --git a/unicorn_shared/Makefile b/unicorn_shared/Makefile index ffc3833..53074ee 100644 --- a/unicorn_shared/Makefile +++ b/unicorn_shared/Makefile @@ -32,3 +32,6 @@ delete-images: ## Deletes all shared images stacks --stack-name "uni-prop-$$env-images"; \ fi; \ done + +list-parameters: ## Lists all parameters in the Unicorn Properties namespace + aws ssm get-parameters-by-path --path "/uni-prop" --recursive --with-decryption --query 'Parameters[*].[Name,Value,Type]' --output table \ No newline at end of file diff --git a/unicorn_shared/uni-prop-namespaces.yaml b/unicorn_shared/uni-prop-namespaces.yaml index 55602fb..7df02f4 100644 --- a/unicorn_shared/uni-prop-namespaces.yaml +++ b/unicorn_shared/uni-prop-namespaces.yaml @@ -14,12 +14,12 @@ Resources: Name: !Sub /uni-prop/UnicornContractsNamespace Value: "unicorn.contracts" - UnicornPropertiesNamespaceParam: + UnicornApprovalsNamespaceParam: Type: AWS::SSM::Parameter Properties: Type: String - Name: !Sub /uni-prop/UnicornPropertiesNamespace - Value: "unicorn.properties" + Name: !Sub /uni-prop/UnicornApprovalsNamespace + Value: "unicorn.approvals" UnicornWebNamespaceParam: Type: AWS::SSM::Parameter @@ -35,9 +35,9 @@ Outputs: Description: Unicorn Contracts namespace parameter Value: !Ref UnicornContractsNamespaceParam - UnicornPropertiesNamespace: + UnicornApprovalsNamespace: Description: Unicorn Properties namespace parameter - Value: !Ref UnicornPropertiesNamespaceParam + Value: !Ref UnicornApprovalsNamespaceParam UnicornWebNamespace: Description: Unicorn Web namespace parameter @@ -49,7 +49,7 @@ Outputs: UnicornPropertiesNamespaceValue: Description: Unicorn Properties namespace parameter value - Value: !GetAtt UnicornPropertiesNamespaceParam.Value + Value: !GetAtt UnicornApprovalsNamespaceParam.Value UnicornWebNamespaceValue: Description: Unicorn Web namespace parameter value diff --git a/unicorn_web/README.md b/unicorn_web/README.md index 4cec4a0..38e17ea 100644 --- a/unicorn_web/README.md +++ b/unicorn_web/README.md @@ -4,15 +4,27 @@ ## Architecture Overview -Unicorn Web is primarily responsible for allowing customers to search and view property listings. It also supports ability for agents to request approval for specific property. Those approval requests are sent to Property service for validation, before Properties table is updated with approval evaluation results. +Unicorn Web lets customers search for and view property listings. The Web API also allows Unicorn Properties agents to request approval for specific properties that they want to publish so they may be returned in customer searches results. These requests are sent to the Unicorn Approvals service for validation. -A core component of Unicorn Web are the Lambda functions which are responsible with completing API Gateway requests to: +Lambda functions handle API Gateway requests to: -- search approved property listings -This function interacts with DynamoDB table to retrieve property listings marked as `APPROVED`. The API Gateway implementation and lambda code support multiple types of search patterns, and allow searching by city, street, or house number. +- Search approved property listings: The **Search function** retrieves property listings marked as APPROVED from the DynamoDB table using multiple search patterns. -- request approval of property listing -This function sends an event to EventBridge requesting an approval for a property listing specified in the payload sent from client +- Request property listing approval: The **Request Approval function** sends an EventBridge event requesting approval for a property listing specified in the payload. -- publication approved function -There is also a lambda function responsible for receiving any "Approval Evaluation Completed" events from EventBridge. This function writes the evaluation result to DynamoDB table. +- Process approved listings: The **Publication Evaluation Event Handler function** processes `PublicationEvaluationCompleted` events from the Unicorn Approvals service and writes the evaluation result to the DynamoDB table. + +### Testing the APIs + +```bash +export API=`aws cloudformation describe-stacks --stack-name uni-prop-local-web --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" --output text` + +curl --location --request POST "${API}request_approval" \ +--header 'Content-Type: application/json' \ +--data-raw '{"PropertyId": "usa/anytown/main-street/111"}' + + +curl -X POST ${API_URL}request_approval \ + -H 'Content-Type: application/json' \ + -d '{"PropertyId":"usa/anytown/main-street/111"}' | jq +``` diff --git a/unicorn_web/integration/subscriptions.yaml b/unicorn_web/integration/subscriptions.yaml index b8f6cd7..874c409 100644 --- a/unicorn_web/integration/subscriptions.yaml +++ b/unicorn_web/integration/subscriptions.yaml @@ -20,10 +20,10 @@ Resources: Name: unicorn.web-PublicationEvaluationCompleted Description: PublicationEvaluationCompleted subscription EventBusName: - Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornPropertiesEventBusArn}}" + Fn::Sub: "{{resolve:ssm:/uni-prop/${Stage}/UnicornApprovalsEventBusArn}}" EventPattern: source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" detail-type: - PublicationEvaluationCompleted State: ENABLED diff --git a/unicorn_web/pyproject.toml b/unicorn_web/pyproject.toml index dd1d532..c1c00c5 100644 --- a/unicorn_web/pyproject.toml +++ b/unicorn_web/pyproject.toml @@ -9,22 +9,22 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ - "aws-lambda-powertools[tracer]>=3.9.0", + "aws-lambda-powertools[tracer]>=3.18.0", "aws-xray-sdk>=2.14.0", - "boto3>=1.37.23", + "boto3>=1.40.2", "requests>=2.32.3", "crhelper>=2.0.11", ] [project.optional-dependencies] dev = [ - "aws-lambda-powertools[all]>=3.9.0", - "moto[dynamodb,events,sqs]>=5.0.14", + "aws-lambda-powertools[all]>=3.18.0", + "moto[dynamodb,events,sqs]>=5.1.9", "importlib-metadata>=8.4.0", "pyyaml>=6.0.2", "arnparse>=0.0.2", - "pytest>=8.3.4", - "ruff>=0.9.7", + "pytest>=8.4.1", + "ruff>=0.12.7", ] [tool.setuptools] diff --git a/unicorn_web/src/approvals_service/__init__.py b/unicorn_web/src/publication_manager_service/__init__.py similarity index 100% rename from unicorn_web/src/approvals_service/__init__.py rename to unicorn_web/src/publication_manager_service/__init__.py diff --git a/unicorn_web/src/approvals_service/publication_approved_event_handler.py b/unicorn_web/src/publication_manager_service/publication_approved_event_handler.py similarity index 98% rename from unicorn_web/src/approvals_service/publication_approved_event_handler.py rename to unicorn_web/src/publication_manager_service/publication_approved_event_handler.py index e555d74..2c252bb 100644 --- a/unicorn_web/src/approvals_service/publication_approved_event_handler.py +++ b/unicorn_web/src/publication_manager_service/publication_approved_event_handler.py @@ -11,13 +11,12 @@ from aws_lambda_powertools.metrics import Metrics, MetricUnit from aws_lambda_powertools.event_handler.exceptions import InternalServerError -from schema.unicorn_properties.publicationevaluationcompleted import ( +from schema.unicorn_approvals.publicationevaluationcompleted import ( AWSEvent, Marshaller, PublicationEvaluationCompleted, ) - # Initialise Environment variables if (SERVICE_NAMESPACE := os.environ.get("SERVICE_NAMESPACE")) is None: raise InternalServerError("SERVICE_NAMESPACE environment variable is undefined") diff --git a/unicorn_web/src/approvals_service/request_approval_function.py b/unicorn_web/src/publication_manager_service/request_approval_function.py similarity index 100% rename from unicorn_web/src/approvals_service/request_approval_function.py rename to unicorn_web/src/publication_manager_service/request_approval_function.py diff --git a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.py b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.py similarity index 97% rename from unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.py rename to unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.py index b2fc54a..2cab608 100644 --- a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/AWSEvent.py +++ b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/AWSEvent.py @@ -4,7 +4,7 @@ import six from enum import Enum -from schema.unicorn_properties.publicationevaluationcompleted.PublicationEvaluationCompleted import ( +from schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted import ( PublicationEvaluationCompleted, ) # noqa: F401,E501 diff --git a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.py b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.py similarity index 100% rename from unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/PublicationEvaluationCompleted.py rename to unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/PublicationEvaluationCompleted.py diff --git a/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/__init__.py b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/__init__.py new file mode 100644 index 0000000..93bdbe6 --- /dev/null +++ b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/__init__.py @@ -0,0 +1,9 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from schema.unicorn_approvals.publicationevaluationcompleted.marshaller import Marshaller +from schema.unicorn_approvals.publicationevaluationcompleted.AWSEvent import AWSEvent +from schema.unicorn_approvals.publicationevaluationcompleted.PublicationEvaluationCompleted import ( + PublicationEvaluationCompleted, +) diff --git a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/marshaller.py b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/marshaller.py similarity index 96% rename from unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/marshaller.py rename to unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/marshaller.py index 9a42d94..0181a81 100644 --- a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/marshaller.py +++ b/unicorn_web/src/schema/unicorn_approvals/publicationevaluationcompleted/marshaller.py @@ -1,7 +1,7 @@ import datetime import re import six -import schema.unicorn_properties.publicationevaluationcompleted +import schema.unicorn_approvals.publicationevaluationcompleted class Marshaller: @@ -59,7 +59,7 @@ def unmarshall(cls, data, typeName): if typeName in cls.NATIVE_TYPES_MAPPING: typeName = cls.NATIVE_TYPES_MAPPING[typeName] else: - typeName = getattr(schema.unicorn_properties.publicationevaluationcompleted, typeName) + typeName = getattr(schema.unicorn_approvals.publicationevaluationcompleted, typeName) if typeName in cls.PRIMITIVE_TYPES: return cls.__unmarshall_primitive(data, typeName) diff --git a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/__init__.py b/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/__init__.py deleted file mode 100644 index 9e73411..0000000 --- a/unicorn_web/src/schema/unicorn_properties/publicationevaluationcompleted/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding: utf-8 - -from __future__ import absolute_import - -from schema.unicorn_properties.publicationevaluationcompleted.marshaller import Marshaller -from schema.unicorn_properties.publicationevaluationcompleted.AWSEvent import AWSEvent -from schema.unicorn_properties.publicationevaluationcompleted.PublicationEvaluationCompleted import ( - PublicationEvaluationCompleted, -) diff --git a/unicorn_web/template.yaml b/unicorn_web/template.yaml index 9e2211a..8e713e6 100644 --- a/unicorn_web/template.yaml +++ b/unicorn_web/template.yaml @@ -15,7 +15,6 @@ Metadata: - WS2001 # Rule disabled because check does not support !ToJsonString transform - ES1001 # Rule disabled because our Lambda functions don't need DestinationConfig.OnFailure - W3002 # Rule disabled as nested templates are being packaged - - E3030 # Rule disabled due to using cfn-lint-serverless rules v0.3 Parameters: Stage: @@ -111,7 +110,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: src/ - Handler: approvals_service.request_approval_function.lambda_handler + Handler: publication_manager_service.request_approval_function.lambda_handler Policies: - EventBridgePutEventsPolicy: EventBusName: !Ref UnicornWebEventBus @@ -141,7 +140,7 @@ Resources: Type: AWS::Serverless::Function Properties: CodeUri: src/ - Handler: approvals_service.publication_approved_event_handler.lambda_handler + Handler: publication_manager_service.publication_approved_event_handler.lambda_handler Policies: - DynamoDBCrudPolicy: TableName: !Ref WebTable @@ -157,7 +156,7 @@ Resources: EventBusName: !Ref UnicornWebEventBus Pattern: source: - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" detail-type: - PublicationEvaluationCompleted @@ -362,7 +361,7 @@ Resources: - !Ref AWS::AccountId source: - "{{resolve:ssm:/uni-prop/UnicornContractsNamespace}}" - - "{{resolve:ssm:/uni-prop/UnicornPropertiesNamespace}}" + - "{{resolve:ssm:/uni-prop/UnicornApprovalsNamespace}}" - "{{resolve:ssm:/uni-prop/UnicornWebNamespace}}" State: ENABLED #You may want to disable this rule in production Targets: @@ -462,7 +461,7 @@ Outputs: Description: "GET request to list all properties in a given street" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/search/{country}/{city}/{street}" ApiPropertyApproval: - Description: "POST request to add a property to the database" + Description: "POST request approval to allow property to be searchable" Value: !Sub "https://${UnicornWebApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${Stage}/request_approval" ApiPropertyDetails: Description: "GET request to get the full details of a single property" diff --git a/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json b/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json index 6b715f6..25e8b56 100644 --- a/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json +++ b/unicorn_web/tests/events/eventbridge/put_event_publication_evaluation_completed.json @@ -1,6 +1,6 @@ [ { - "Source": "unicorn.properties", + "Source": "unicorn.approvals", "Detail": "{\"property_id\":\"usa/anytown/main-street/111\",\"evaluation_result\": \"APPROVED\"}", "DetailType": "PublicationEvaluationCompleted", "EventBusName": "UnicornWebBus-local" diff --git a/unicorn_web/tests/unit/events/property_approved.json b/unicorn_web/tests/unit/events/property_approved.json index 9047063..2541251 100644 --- a/unicorn_web/tests/unit/events/property_approved.json +++ b/unicorn_web/tests/unit/events/property_approved.json @@ -2,7 +2,7 @@ "version": "0", "id": "a1b2c3d4-5678-90ab-cdef-EXAMPLEaaaaa", "detail-type": "PublicationEvaluationCompleted", - "source": "unicorn.properties", + "source": "unicorn.approvals", "account": "111122223333", "time": "2022-12-01T01:01:01Z", "region": "ap-southeast-1", diff --git a/unicorn_web/tests/unit/test_publication_approved_event_handler.py b/unicorn_web/tests/unit/test_publication_approved_event_handler.py index 755520c..caecd16 100644 --- a/unicorn_web/tests/unit/test_publication_approved_event_handler.py +++ b/unicorn_web/tests/unit/test_publication_approved_event_handler.py @@ -24,7 +24,7 @@ # eventbridge_event = load_event('events/property_approved.json') # property_id = eventbridge_event['detail']['property_id'] -# import approvals_service.publication_approved_event_handler as app +# import publication_manager_service.publication_approved_event_handler as app # reload(app) # Reload is required to prevent function setup reuse from another test # create_ddb_table_property_web(dynamodb) diff --git a/unicorn_web/tests/unit/test_request_approval_function.py b/unicorn_web/tests/unit/test_request_approval_function.py index 8cdd023..d9365bf 100644 --- a/unicorn_web/tests/unit/test_request_approval_function.py +++ b/unicorn_web/tests/unit/test_request_approval_function.py @@ -22,7 +22,7 @@ def test_valid_event(dynamodb, eventbridge, sqs, lambda_context): event = sqs_event([{"body": payload, "attributes": {"HttpMethod": "POST"}}]) # Loading function here so that mocking works correctly. - from approvals_service import request_approval_function + from publication_manager_service import request_approval_function # Reload is required to prevent function setup reuse from another test reload(request_approval_function) @@ -58,7 +58,7 @@ def test_valid_event(dynamodb, eventbridge, sqs, lambda_context): # apigw_event = load_event('events/request_approval_bad_input.json') # # Loading function here so that mocking works correctly. -# import approvals_service.request_approval_function as app +# import publication_manager_service.request_approval_function as app # # Reload is required to prevent function setup reuse from another test # reload(app) @@ -79,7 +79,7 @@ def test_valid_event(dynamodb, eventbridge, sqs, lambda_context): # apigw_event = load_event('events/request_invalid_property_id.json') # # Loading function here so that mocking works correctly. -# import approvals_service.request_approval_function as app +# import publication_manager_service.request_approval_function as app # # Reload is required to prevent function setup reuse from another test # reload(app) @@ -100,7 +100,7 @@ def test_valid_event(dynamodb, eventbridge, sqs, lambda_context): # apigw_event = load_event('events/request_already_approved.json') # # Loading function here so that mocking works correctly. -# import approvals_service.request_approval_function as app +# import publication_manager_service.request_approval_function as app # # Reload is required to prevent function setup reuse from another test # reload(app) @@ -121,7 +121,7 @@ def test_valid_event(dynamodb, eventbridge, sqs, lambda_context): # apigw_event = load_event('events/request_non_existent_property.json') # # Loading function here so that mocking works correctly. -# import approvals_service.request_approval_function as app +# import publication_manager_service.request_approval_function as app # # Reload is required to prevent function setup reuse from another test # reload(app) diff --git a/unicorn_web/uv.lock b/unicorn_web/uv.lock index a1e76c8..25aa393 100644 --- a/unicorn_web/uv.lock +++ b/unicorn_web/uv.lock @@ -46,15 +46,15 @@ wheels = [ [[package]] name = "aws-lambda-powertools" -version = "3.13.0" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/2b/068efd467c0866e2272c5de7525ddb02ff4e694f71245c8d2a83d4948f23/aws_lambda_powertools-3.13.0.tar.gz", hash = "sha256:99dc11ac6eb81564f599fdd85ba79069f7740ae3481c99bca2cee8abb7c95543", size = 672664, upload-time = "2025-05-20T07:35:30.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d9/5ba420bbe1aef011559e035a1d1ef82a6d30500610116e7feb2f2b5dcda7/aws_lambda_powertools-3.18.0.tar.gz", hash = "sha256:74f484b03dfb733769828bf8e9f33ac427cd57c477a9ceae2dc19f643051e3c3", size = 686276, upload-time = "2025-07-29T08:22:50.901Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/cd/2241ff877528c66ee11ea636684c4242ceeadb6459a33b08507a40151414/aws_lambda_powertools-3.13.0-py3-none-any.whl", hash = "sha256:9df045f4c3ff944176655813dbff8c1160e056babf5e6d71d4e18c0003818f2e", size = 802546, upload-time = "2025-05-20T07:35:27.767Z" }, + { url = "https://files.pythonhosted.org/packages/42/b1/ca638f5ce0f4d1c4c75412342c08d124142facf89104f9d3659e9c395c63/aws_lambda_powertools-3.18.0-py3-none-any.whl", hash = "sha256:5afb230abf4e64bce00d35e858a4e83ae99c667f648c1f4d246e4bb022715df8", size = 828005, upload-time = "2025-07-29T08:22:48.807Z" }, ] [package.optional-dependencies] @@ -85,30 +85,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.38.25" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/af/404827da46c67359e6d2a63b0f4fadd5b6150551d25c405b3bd480c19319/boto3-1.38.25.tar.gz", hash = "sha256:85c1556a110896f68de8573a9b4757c81071448dbf6ffc1074941bfc8a43195e", size = 111819, upload-time = "2025-05-28T19:26:49.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/c0/9ceff05d2243f169765ae9db08fa6f085d026af71a778cd083dc972f0f2b/boto3-1.40.2.tar.gz", hash = "sha256:2dfbc214fdbf94abfd61eec687ea39089d05af43bb00be792c76f3a6c1393f7b", size = 111826, upload-time = "2025-08-04T19:31:51.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/27/b2ea9d2494b1d35c3de23485bf6b0a7b8539dea965349c8829a1fd84d08e/boto3-1.38.25-py3-none-any.whl", hash = "sha256:2f2cd517dd31d33ace0eefe567dc903fdf74221513e32f1e9445bdfac7554db7", size = 139938, upload-time = "2025-05-28T19:26:44.902Z" }, + { url = "https://files.pythonhosted.org/packages/f7/66/01bccaaebcd1365ce1334be042765e49ccf23787887afb8e43c6d4bc2f6e/boto3-1.40.2-py3-none-any.whl", hash = "sha256:3d99325ee874190e8f3bfd38823987327c826cdfbab943420851bdb7684d727c", size = 139882, upload-time = "2025-08-04T19:31:50.493Z" }, ] [[package]] name = "botocore" -version = "1.38.25" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/00/bed5bf325d1fdb98d6212d106bd44cd0bbe65563e3a2c3f87b64cc4d75c2/botocore-1.38.25.tar.gz", hash = "sha256:8c73e97d9662a6c92be33dab66cd1e2b59797154c7ec379ce3bb5d6779d9d78c", size = 13914295, upload-time = "2025-05-28T19:26:35.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/e7d68381042a6d50510c8d4629f39922ce27ff32f45baf852ba6534342c5/botocore-1.40.2.tar.gz", hash = "sha256:77c4710bf37b28e897833b5b1f47d6a83e45a29985cd01a560dfdb8b6ad524e5", size = 14284599, upload-time = "2025-08-04T19:31:42.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/ae/3b52634df58cbd506ad804315a1c979ea06942cf88b591dcd671f45adf63/botocore-1.38.25-py3-none-any.whl", hash = "sha256:5a960bd990a11cdb78865e908a58ed712d87d9b419f55ad21c99d7d21f0d6726", size = 13574906, upload-time = "2025-05-28T19:26:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" }, ] [[package]] @@ -367,7 +367,7 @@ wheels = [ [[package]] name = "moto" -version = "5.1.5" +version = "5.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -380,9 +380,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/9f/5cacf53cbd26b4a77c817fd4fbec2ed38139ba2cfcd842c279aa19ee161f/moto-5.1.5.tar.gz", hash = "sha256:42b362ea9a16181e8e7b615ac212c294b882f020e9ae02f01230f167926df84e", size = 6893155, upload-time = "2025-05-24T12:19:55.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/62/767e0f65066489b50580a25182025fec19825cbed499ae2044da1e779ed6/moto-5.1.9.tar.gz", hash = "sha256:0c4f0387b06b5d24c0ce90f8f89f31a565cc05789189c5d59b5df02594f2e371", size = 7041662, upload-time = "2025-07-28T19:24:48.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/0f/72a420e972a9e782d08843ced85d29f04c8470c66fc84ecb1c8a7e7abcf8/moto-5.1.5-py3-none-any.whl", hash = "sha256:866ae85eb5efe11a78f991127531878fd7f49177eb4a6680f47060430eb8932d", size = 4992353, upload-time = "2025-05-24T12:19:53.435Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/fd37926e3fd597df879d7c360f1641850023d30b1d67ba6686f4e33ab1fb/moto-5.1.9-py3-none-any.whl", hash = "sha256:e9ba7e4764a6088ccc34e3cc846ae719861ca202409fa865573de40a3e805b9b", size = 5216109, upload-time = "2025-07-28T19:24:45.997Z" }, ] [package.optional-dependencies] @@ -510,19 +510,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] @@ -616,27 +626,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/53/ae4857030d59286924a8bdb30d213d6ff22d8f0957e738d0289990091dd8/ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", size = 4186707, upload-time = "2025-05-22T19:19:34.363Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/14/f2326676197bab099e2a24473158c21656fbf6a207c65f596ae15acb32b9/ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", size = 10229049, upload-time = "2025-05-22T19:18:45.516Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f3/bff7c92dd66c959e711688b2e0768e486bbca46b2f35ac319bb6cce04447/ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", size = 11053601, upload-time = "2025-05-22T19:18:49.269Z" }, - { url = "https://files.pythonhosted.org/packages/e2/38/8e1a3efd0ef9d8259346f986b77de0f62c7a5ff4a76563b6b39b68f793b9/ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", size = 10367421, upload-time = "2025-05-22T19:18:51.754Z" }, - { url = "https://files.pythonhosted.org/packages/b4/50/557ad9dd4fb9d0bf524ec83a090a3932d284d1a8b48b5906b13b72800e5f/ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", size = 10581980, upload-time = "2025-05-22T19:18:54.011Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b2/e2ed82d6e2739ece94f1bdbbd1d81b712d3cdaf69f0a1d1f1a116b33f9ad/ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", size = 10089241, upload-time = "2025-05-22T19:18:56.041Z" }, - { url = "https://files.pythonhosted.org/packages/3d/9f/b4539f037a5302c450d7c695c82f80e98e48d0d667ecc250e6bdeb49b5c3/ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac", size = 11699398, upload-time = "2025-05-22T19:18:58.248Z" }, - { url = "https://files.pythonhosted.org/packages/61/fb/32e029d2c0b17df65e6eaa5ce7aea5fbeaed22dddd9fcfbbf5fe37c6e44e/ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", size = 12427955, upload-time = "2025-05-22T19:19:00.981Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e3/160488dbb11f18c8121cfd588e38095ba779ae208292765972f7732bfd95/ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", size = 12069803, upload-time = "2025-05-22T19:19:03.258Z" }, - { url = "https://files.pythonhosted.org/packages/ff/16/3b006a875f84b3d0bff24bef26b8b3591454903f6f754b3f0a318589dcc3/ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", size = 11242630, upload-time = "2025-05-22T19:19:05.871Z" }, - { url = "https://files.pythonhosted.org/packages/65/0d/0338bb8ac0b97175c2d533e9c8cdc127166de7eb16d028a43c5ab9e75abd/ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", size = 11507310, upload-time = "2025-05-22T19:19:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bf/d7130eb26174ce9b02348b9f86d5874eafbf9f68e5152e15e8e0a392e4a3/ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", size = 10441144, upload-time = "2025-05-22T19:19:13.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f3/4be2453b258c092ff7b1761987cf0749e70ca1340cd1bfb4def08a70e8d8/ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", size = 10081987, upload-time = "2025-05-22T19:19:15.821Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6e/dfa4d2030c5b5c13db158219f2ec67bf333e8a7748dccf34cfa2a6ab9ebc/ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", size = 11073922, upload-time = "2025-05-22T19:19:18.104Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f4/f7b0b0c3d32b593a20ed8010fa2c1a01f2ce91e79dda6119fcc51d26c67b/ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", size = 11568537, upload-time = "2025-05-22T19:19:20.889Z" }, - { url = "https://files.pythonhosted.org/packages/d2/46/0e892064d0adc18bcc81deed9aaa9942a27fd2cd9b1b7791111ce468c25f/ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", size = 10536492, upload-time = "2025-05-22T19:19:23.642Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d9/232e79459850b9f327e9f1dc9c047a2a38a6f9689e1ec30024841fc4416c/ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", size = 11612562, upload-time = "2025-05-22T19:19:27.013Z" }, - { url = "https://files.pythonhosted.org/packages/ce/eb/09c132cff3cc30b2e7244191dcce69437352d6d6709c0adf374f3e6f476e/ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", size = 10735951, upload-time = "2025-05-22T19:19:30.043Z" }, +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, ] [[package]] @@ -716,17 +726,17 @@ dev = [ [package.metadata] requires-dist = [ { name = "arnparse", marker = "extra == 'dev'", specifier = ">=0.0.2" }, - { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.9.0" }, - { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.9.0" }, + { name = "aws-lambda-powertools", extras = ["all"], marker = "extra == 'dev'", specifier = ">=3.18.0" }, + { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.18.0" }, { name = "aws-xray-sdk", specifier = ">=2.14.0" }, - { name = "boto3", specifier = ">=1.37.23" }, + { name = "boto3", specifier = ">=1.40.2" }, { name = "crhelper", specifier = ">=2.0.11" }, { name = "importlib-metadata", marker = "extra == 'dev'", specifier = ">=8.4.0" }, - { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.0.14" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" }, + { name = "moto", extras = ["dynamodb", "events", "sqs"], marker = "extra == 'dev'", specifier = ">=5.1.9" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" }, { name = "pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.2" }, { name = "requests", specifier = ">=2.32.3" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.7" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.7" }, ] provides-extras = ["dev"]